refactor(message): ts

This commit is contained in:
07akioni 2021-01-16 02:11:39 +08:00
parent b08ac196bf
commit b9b273b1ca
14 changed files with 373 additions and 266 deletions

View File

@ -1,4 +1,4 @@
import { computed, h } from 'vue'
import { computed, h, defineComponent, inject, VNodeChild } from 'vue'
import {
InfoIcon,
SuccessIcon,
@ -12,9 +12,9 @@ import {
NBaseClose
} from '../../_base'
import { render, createKey } from '../../_utils'
import { useTheme } from '../../_mixins'
import { messageLight } from '../styles'
import props from './message-props'
import { ThemePropsReactive, useTheme } from '../../_mixins'
import { messageLight, MessageTheme } from '../styles'
import { messageProps, MessageType } from './message-props'
import style from './styles/index.cssr.js'
const iconMap = {
@ -25,11 +25,21 @@ const iconMap = {
loading: NBaseLoading
}
export default {
export default defineComponent({
name: 'Message',
props,
props: messageProps,
setup (props) {
const themeRef = useTheme('Message', 'Message', style, messageLight, props)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const themeProps = inject<ThemePropsReactive<MessageTheme>>(
'NMessageProvider'
)!
const themeRef = useTheme(
'Message',
'Message',
style,
messageLight,
themeProps
)
return {
handleClose () {
props.onClose()
@ -140,9 +150,9 @@ export default {
]
)
}
}
})
function createIconVNode (icon, type) {
function createIconVNode (icon: () => VNodeChild, type: MessageType) {
if (typeof icon === 'function') {
return icon()
} else {

View File

@ -1,27 +1,27 @@
import { h, defineComponent } from 'vue'
import { h, defineComponent, ref, onMounted, PropType } from 'vue'
import { NFadeInExpandTransition } from '../../_base'
import NMessage from './Message.js'
import props from './message-props'
import { messageProps } from './message-props'
export default defineComponent({
name: 'MessageEnvironment',
props: {
...props,
...messageProps,
duration: {
type: Number,
default: 3000
},
onClose: {
type: Function,
default: undefined
},
onAfterLeave: {
type: Function,
default: undefined
},
internalKey: {
type: String,
required: true
},
// private
onInternalAfterLeave: {
type: Function,
type: Function as PropType<(key: string) => void>,
default: undefined
},
// deprecated
@ -34,43 +34,51 @@ export default defineComponent({
default: undefined
}
},
data () {
return {
// internal state
timerId: null,
show: true
}
},
mounted () {
if (this.duration) {
this.timerId = window.setTimeout(this.hide, this.duration)
}
},
methods: {
hide () {
const { timerId, onHide } = this
this.show = false
setup (props) {
const timerIdRef = ref<number | null>(null)
const showRef = ref<boolean>(true)
onMounted(() => {
const { duration } = props
if (duration) {
timerIdRef.value = window.setTimeout(hide, duration)
}
})
function hide () {
const { value: timerId } = timerIdRef
const { onHide } = props
showRef.value = false
if (timerId) {
window.clearTimeout(timerId)
}
// deprecated
if (onHide) onHide()
},
handleClose () {
const { onClose } = this
}
function handleClose () {
const { onClose } = props
if (onClose) onClose()
this.hide()
},
handleAfterLeave () {
const { onAfterLeave, onInternalAfterLeave, onAfterHide } = this
hide()
}
function handleAfterLeave () {
const {
onAfterLeave,
onInternalAfterLeave,
onAfterHide,
internalKey
} = props
if (onAfterLeave) onAfterLeave()
if (onInternalAfterLeave) onInternalAfterLeave(this._.vnode.key)
if (onInternalAfterLeave) onInternalAfterLeave(internalKey)
// deprecated
if (onAfterHide) onAfterHide()
},
}
// deprecated
deactivate () {
this.hide()
function deactivate () {
hide()
}
return {
show: showRef,
handleClose,
handleAfterLeave,
deactivate
}
},
render () {

View File

@ -1,91 +0,0 @@
import { Fragment, ref, h, reactive, Teleport, defineComponent } from 'vue'
import { createId } from 'seemly'
import { omit } from '../../_utils'
import MessageEnvironment from './MessageEnvironment.js'
export default defineComponent({
name: 'MessageProvider',
provide () {
return {
message: {
info: this.info,
success: this.success,
warning: this.warning,
error: this.error,
loading: this.loading
}
}
},
props: {
to: {
type: [String, Object],
default: undefined
}
},
setup () {
const messageListRef = ref([])
return {
messageList: messageListRef
}
},
methods: {
create (content, options = {}) {
const key = createId()
const messageReactive = reactive({
...options,
content,
key,
destroy: () => {
this.$refs[`n-message-${key}`].hide()
}
})
this.messageList.push(messageReactive)
return messageReactive
},
...['info', 'success', 'warning', 'error', 'loading'].reduce(
(api, type) => {
api[type] = function (content, options) {
return this.create(content, { ...options, type })
}
return api
},
{}
),
handleAfterLeave (key) {
const { messageList } = this
messageList.splice(
messageList.findIndex((message) => message.key === key),
1
)
}
},
render () {
return h(Fragment, null, [
this.messageList.length
? h(
Teleport,
{
to: this.to ?? 'body'
},
[
h(
'div',
{
class: 'n-message-container',
key: 'n-message-container'
},
this.messageList.map((message) =>
h(MessageEnvironment, {
ref: `n-message-${message.key}`,
...omit(message, ['destroy']),
onInternalAfterLeave: this.handleAfterLeave
})
)
)
]
)
: null,
this.$slots.default()
])
}
})

View File

@ -0,0 +1,136 @@
import {
Fragment,
ref,
h,
reactive,
Teleport,
defineComponent,
provide,
VNodeChild
} from 'vue'
import { createId } from 'seemly'
import { omit } from '../../_utils'
import { ThemePropsReactive, useTheme } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import MessageEnvironment from './MessageEnvironment.js'
import { MessageType } from './message-props'
import { MessageTheme } from '../styles'
interface MessageOptions {
duration?: number
closable?: boolean
icon?: () => VNodeChild
onClose?: () => void
}
interface MessageInjection {
info(content: string, options: MessageOptions): void
success(content: string, options: MessageOptions): void
warning(content: string, options: MessageOptions): void
error(content: string, options: MessageOptions): void
loading(content: string, options: MessageOptions): void
}
interface MessageReactive {
content?: string
duration?: number
closable?: boolean
icon?: () => VNodeChild
onClose?: () => void
destroy: () => void
}
interface PrivateMessageReactive extends MessageReactive {
key: string
}
interface PrivateMessageRef extends MessageReactive {
key: string
hide: () => void
}
export default defineComponent({
name: 'MessageProvider',
props: {
...(useTheme.props as ThemeProps<MessageTheme>),
to: {
type: [String, Object],
default: undefined
}
},
setup (props) {
const messageListRef = ref<PrivateMessageReactive[]>([])
const messageRefs = ref<{ [key: string]: PrivateMessageRef }>({})
const api: MessageInjection = ([
'info',
'success',
'warning',
'error',
'loading'
] as MessageType[]).reduce((api, type) => {
api[type] = function (content: string, options: MessageOptions) {
return create(content, { ...options, type })
}
return api
}, {} as MessageInjection)
provide<ThemePropsReactive<MessageTheme>>('NMessageProvider', props)
provide<MessageInjection>('message', api)
function create (content: string, options = {}) {
const key = createId()
const messageReactive = reactive({
...options,
content,
key,
destroy: () => {
messageRefs.value[`n-message-${key}`].hide()
}
})
messageListRef.value.push(messageReactive)
return messageReactive
}
function handleAfterLeave (key: string) {
messageListRef.value.splice(
messageListRef.value.findIndex((message) => message.key === key),
1
)
}
return {
messageRefs,
messageList: messageListRef,
handleAfterLeave
}
},
render () {
const { default: defaultSlot } = this.$slots
return h(Fragment, null, [
this.messageList.length
? h(
Teleport,
{
to: this.to ?? 'body'
},
[
h(
'div',
{
class: 'n-message-container',
key: 'n-message-container'
},
this.messageList.map((message) => {
return h(MessageEnvironment, {
ref: ((inst: PrivateMessageRef) => {
this.messageRefs[`n-message-${message.key}`] = inst
}) as () => void,
internalKey: message.key,
onInternalAfterLeave: this.handleAfterLeave,
...omit(message, ['destroy'], undefined)
})
})
)
]
)
: null,
defaultSlot && defaultSlot()
])
}
})

View File

@ -1,24 +0,0 @@
export default {
icon: {
type: [String, Function],
default: undefined
},
type: {
type: String,
default: 'default'
},
content: {
validator () {
return true
},
default: undefined
},
closable: {
type: Boolean,
default: false
},
onClose: {
type: Function,
default: () => {}
}
}

View File

@ -0,0 +1,26 @@
import { PropType, VNodeChild } from 'vue'
export type MessageType = 'info' | 'success' | 'warning' | 'error' | 'loading'
export const messageProps = {
icon: {
type: Function as PropType<() => VNodeChild>,
default: undefined
},
type: {
type: String as PropType<MessageType>,
default: 'info'
},
content: {
type: [String, Number, Function] as PropType<string | (() => VNodeChild)>,
default: undefined
},
closable: {
type: Boolean,
default: false
},
onClose: {
type: Function,
default: undefined
}
} as const

View File

@ -25,23 +25,29 @@ import fadeInHeightExpand from '../../../_styles/transitions/fade-in-height-expa
// --close-color-hover
// --border-radius
export default c([
cB('message-wrapper', `
cB(
'message-wrapper',
`
margin: var(--margin);
z-index: 0;
transform-origin: top center;
`, [
fadeInHeightExpand({
overflow: 'visible',
originalTransition: 'transform .3s var(--bezier)',
enterToProps: {
transform: 'scale(1)'
},
leaveToProps: {
transform: 'scale(0.85)'
}
})
]),
cB('message', `
`,
[
fadeInHeightExpand({
overflow: 'visible',
originalTransition: 'transform .3s var(--bezier)',
enterToProps: {
transform: 'scale(1)'
},
leaveToProps: {
transform: 'scale(0.85)'
}
})
]
),
cB(
'message',
`
box-sizing: border-box;
display: flex;
align-items: center;
@ -60,44 +66,65 @@ export default c([
color: var(--text-color);
background-color: var(--color);
box-shadow: var(--box-shadow);
`, [
cE('content', `
`,
[
cE(
'content',
`
display: inline-block;
line-height: var(--line-height);
font-size: var(--font-size);
`),
cE('icon', `
`
),
cE(
'icon',
`
position: relative;
margin-right: var(--icon-margin);
height: var(--icon-size);
width: var(--icon-size);
font-size: var(--icon-size);
flex-shrink: 0;
`, [
['info', 'success', 'warning', 'error', 'loading'].map(type => cM(`${type}-type`, [
c('> *', `
`,
[
['info', 'success', 'warning', 'error', 'loading'].map((type) =>
cM(`${type}-type`, [
c(
'> *',
`
color: var(--icon-color-${type});
transition: color .3s var(--bezier);
`)
])),
c('> *', {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0
}, [
iconSwitchTransition()
])
]),
cE('close', `
`
)
])
),
c(
'> *',
{
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0
},
[iconSwitchTransition()]
)
]
),
cE(
'close',
`
font-size: var(--close-size);
margin: var(--close-margin);
transition: color .3s var(--bezier);
flex-shrink: 0;
`)
]),
cB('message-container', `
`
)
]
),
cB(
'message-container',
`
z-index: 6000;
position: fixed;
top: 12px;
@ -108,5 +135,6 @@ export default c([
display: flex;
flex-direction: column;
align-items: center;
`)
`
)
])

View File

@ -1,7 +1,8 @@
import commonVariables from './_common'
import { commonDark } from '../../_styles/new-common'
import type { MessageTheme } from './light'
export default {
const messageDark: MessageTheme = {
name: 'Message',
common: commonDark,
self (vars) {
@ -58,7 +59,10 @@ export default {
closeColorHoverLoading: closeColorHoverOverlay,
closeColorPressedLoading: closeColorPressedOverlay,
lineHeight,
borderRadius
borderRadius,
loadingColor: primaryColor
}
}
}
export default messageDark

View File

@ -1,2 +0,0 @@
export { default as messageDark } from './dark.js'
export { default as messageLight } from './light.js'

View File

@ -0,0 +1,3 @@
export { default as messageDark } from './dark'
export { default as messageLight } from './light'
export type { MessageTheme, MessageThemeVars } from './light'

View File

@ -1,65 +0,0 @@
import commonVariables from './_common'
import { commonLight } from '../../_styles/new-common'
export default {
name: 'Message',
common: commonLight,
self (vars) {
const {
textColor2,
closeColor,
closeColorHover,
closeColorPressed,
infoColor,
successColor,
errorColor,
warningColor,
popoverColor,
boxShadow2,
primaryColor,
lineHeight,
borderRadius
} = vars
return {
...commonVariables,
textColorInfo: textColor2,
textColorSuccess: textColor2,
textColorError: textColor2,
textColorWarning: textColor2,
textColorLoading: textColor2,
colorInfo: popoverColor,
colorSuccess: popoverColor,
colorError: popoverColor,
colorWarning: popoverColor,
colorLoading: popoverColor,
boxShadowInfo: boxShadow2,
boxShadowSuccess: boxShadow2,
boxShadowError: boxShadow2,
boxShadowWarning: boxShadow2,
boxShadowLoading: boxShadow2,
iconColorInfo: infoColor,
iconColorSuccess: successColor,
iconColorWarning: warningColor,
iconColorError: errorColor,
iconColorLoading: primaryColor,
closeColorInfo: closeColor,
closeColorHoverInfo: closeColorHover,
closeColorPressedInfo: closeColorPressed,
closeColorSuccess: closeColor,
closeColorHoverSuccess: closeColorHover,
closeColorPressedSuccess: closeColorPressed,
closeColorError: closeColor,
closeColorHoverError: closeColorHover,
closeColorPressedError: closeColorPressed,
closeColorWarning: closeColor,
closeColorHoverWarning: closeColorHover,
closeColorPressedWarning: closeColorPressed,
closeColorLoading: closeColor,
closeColorHoverLoading: closeColorHover,
closeColorPressedLoading: closeColorPressed,
loadingColor: primaryColor,
lineHeight,
borderRadius
}
}
}

View File

@ -0,0 +1,74 @@
import commonVariables from './_common'
import { commonLight } from '../../_styles/new-common'
import type { ThemeCommonVars } from '../../_styles/new-common'
import { Theme } from '../../_mixins/use-theme'
const self = (vars: ThemeCommonVars) => {
const {
textColor2,
closeColor,
closeColorHover,
closeColorPressed,
infoColor,
successColor,
errorColor,
warningColor,
popoverColor,
boxShadow2,
primaryColor,
lineHeight,
borderRadius
} = vars
return {
...commonVariables,
textColorInfo: textColor2,
textColorSuccess: textColor2,
textColorError: textColor2,
textColorWarning: textColor2,
textColorLoading: textColor2,
colorInfo: popoverColor,
colorSuccess: popoverColor,
colorError: popoverColor,
colorWarning: popoverColor,
colorLoading: popoverColor,
boxShadowInfo: boxShadow2,
boxShadowSuccess: boxShadow2,
boxShadowError: boxShadow2,
boxShadowWarning: boxShadow2,
boxShadowLoading: boxShadow2,
iconColorInfo: infoColor,
iconColorSuccess: successColor,
iconColorWarning: warningColor,
iconColorError: errorColor,
iconColorLoading: primaryColor,
closeColorInfo: closeColor,
closeColorHoverInfo: closeColorHover,
closeColorPressedInfo: closeColorPressed,
closeColorSuccess: closeColor,
closeColorHoverSuccess: closeColorHover,
closeColorPressedSuccess: closeColorPressed,
closeColorError: closeColor,
closeColorHoverError: closeColorHover,
closeColorPressedError: closeColorPressed,
closeColorWarning: closeColor,
closeColorHoverWarning: closeColorHover,
closeColorPressedWarning: closeColorPressed,
closeColorLoading: closeColor,
closeColorHoverLoading: closeColorHover,
closeColorPressedLoading: closeColorPressed,
loadingColor: primaryColor,
lineHeight,
borderRadius
}
}
export type MessageThemeVars = ReturnType<typeof self>
const messageLight: Theme<MessageThemeVars> = {
name: 'Message',
common: commonLight,
self
}
export default messageLight
export type MessageTheme = typeof messageLight