Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export default {
'pages/Tooltip/Tooltip',
'pages/ActionSheet/ActionSheet',
'pages/ToastPage/ToastPage',
'pages/UseToast/UseToast',
'pages/Dialog/Dialog',
'pages/Team/Team',
'pages/Share/Share',
Expand Down
18 changes: 16 additions & 2 deletions src/components/Toast/Toast.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,26 @@
border-radius: $hui-toast-border-radius;
transform: $hui-toast-position-transform-x;

.text {
@include line-clamp(2);
.content-text {
display: flex;
align-items: center;

.text {
@include line-clamp(2);
}
}

.icon {
margin-bottom: $hui-toast-icon-position-top;
}
}

.row-toast {
flex-direction: unset;

.icon {
margin-right: $hui-toast-icon-position-top;
margin-bottom: 0;
}
}
}
58 changes: 44 additions & 14 deletions src/components/Toast/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,34 @@ import cx from 'classnames'

import HuiIcon from '../Icon/Icon'
import { HIconType } from '../Icon/type'
import { ToastTypeProp, ToastTypeEnum } from './constants'
import {
ToastTypeProp,
ToastTypeEnum,
AlignType,
getIconSize,
} from './constants'

export interface HuiToastProps {
/** 轻提示内容 */
title: string
/** 是否有遮罩层 */
mask: boolean
mask?: boolean
/** 轻提示类型: 成功(success)失败(fail)警告(warning)纯文本(text)自定义(custom) */
type: ToastTypeProp
/** 轻提示可见 */
visible: boolean
/** 轻提示持续显示的时间 */
duration: number
/** 轻提示自定义icon */
duration?: number
/** 轻提示自定义icon, type !== text时,一定要传,要不不显示 Icon */
icon: HIconType
/** icon 的颜色 */
iconColor?: string
/** 接口调用结束的回调函数 */
success?: () => void
/** toast 布局方向 */
align?: AlignType
/** 自定义提示的内容 */
customIcon?: React.ReactNode
}
interface State {
_visible: boolean
Expand Down Expand Up @@ -52,6 +65,7 @@ export default class Index extends React.Component<HuiToastProps, State> {
this.setState({
_visible: false,
})
this.props.success && this.props.success()
this.clearTimer()
}
}
Expand Down Expand Up @@ -87,23 +101,39 @@ export default class Index extends React.Component<HuiToastProps, State> {
}

// eslint-disable-next-line class-methods-use-this
getIconName(type: ToastTypeProp, customIcon: HIconType): HIconType {
return DefaultIconName.get(type) || customIcon
getIconName(type: ToastTypeProp, iconFont: HIconType): HIconType {
return DefaultIconName.get(type) || iconFont
}

render(): JSX.Element | null {
const { type, title, icon } = this.props
const {
type,
title,
icon,
align = 'column',
customIcon,
iconColor,
} = this.props
return this.state._visible ? (
<View className={cx('hui-toast-box', { mask: this.props.mask })}>
<View className='toast'>
{type !== ToastTypeEnum.TEXT && (
<View className='icon'>
{(type as ToastTypeEnum) !== ToastTypeEnum.TEXT && (
<HuiIcon name={this.getIconName(type, icon)} size={36} />
<View className={cx('toast', align === 'row' && 'row-toast')}>
{customIcon || (
<React.Fragment>
{type && type !== ToastTypeEnum.TEXT && (
<View className='icon'>
<HuiIcon
color={iconColor}
name={this.getIconName(type, icon)}
size={getIconSize(align)}
/>
</View>
)}
</View>

<View className='content-text'>
<Text className='text'>{title}</Text>
</View>
</React.Fragment>
)}
<Text className='text'>{title}</Text>
</View>
</View>
) : null
Expand Down
8 changes: 8 additions & 0 deletions src/components/Toast/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,11 @@ export enum ToastTypeEnum {
TEXT = 'text',
CUSTOM = 'custom',
}

/** toast 布局方向 */
export type AlignType = 'row' | 'column'

/** 获取icon的展示尺寸 */
export function getIconSize(layout: AlignType): number {
return layout === 'row' ? 20 : 36
}
80 changes: 80 additions & 0 deletions src/components/UseToast/UseToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react'
import { View } from '@tarojs/components'

import {
UseHandleModalPromise,
useHandleModalPromise,
} from '../../hook/useModal'
import HuiToast, { HuiToastProps } from '../Toast'
import { HIconType } from '../Icon/type'

/** 调用 toast 需要传的参数 */
export type UseToastProps = Omit<HuiToastProps, 'visible' | 'icon'> & {
icon?: HIconType
}

/** useModel 整合后的数据 */
interface ToastProps {
config: UseToastProps
visible: boolean
onClose: () => void
}

const classPrefix = 'h-toast'
const UseToast = (props: ToastProps): React.ReactElement | boolean => {
const { config = {}, visible, onClose } = props
const {
icon,
iconColor,
title,
type,
duration,
align,
success,
customIcon,
mask,
} = config as UseToastProps

const handleSuccess = () => {
onClose()
success && success()
}
return (
<View className={classPrefix}>
<HuiToast
type={type}
title={title}
icon={icon}
iconColor={iconColor}
duration={duration}
align={align}
mask={mask}
customIcon={customIcon}
visible={visible}
success={handleSuccess}
/>
</View>
)
}

/**
*
* todo: 使用方式
*
* import { useToast } from '@/components/HToast'
*import { type } from '../Badge/index';
import { HIconType } from '@/components/Icon/type';

* const {node: Toast, show: showToast } = useToast()
*
* {Toast} 挂载在组件上
*
* showToast({ title: '测试' }) 方法内调用即可
*/
export const useToast = (
props?: object,
): UseHandleModalPromise<UseToastProps> =>
useHandleModalPromise(UseToast as React.FC, {
value: 'config',
props,
})
5 changes: 5 additions & 0 deletions src/components/UseToast/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { useToast as useHuiToast } from './UseToast'
import type { UseToastProps as UseHuiToastProps } from './UseToast'

export type { UseHuiToastProps }
export default useHuiToast
153 changes: 153 additions & 0 deletions src/hook/useModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React, { ReactNode, useCallback, useMemo, useState } from 'react'

type ModalResponse = any | undefined

interface PropsControl {
value: ModalResponse
visible: boolean
onModalOk: (vle?: ModalResponse) => void
onCancel: () => void
mergeProps?: object
}

export type ActionType = 'cancel' | 'ok'

interface PickerValue {
type: ActionType
value?: ModalResponse
}

export type ShowModalType<T> = (
val?: T,
otherProps?: object,
) => Promise<PickerValue>

interface MapProps {
value?: string
visible?: string
onModalOk?: string
onCancel?: string
onClose?: string
props?: object
}

type useHandleModal = <T>(
Component: React.FC,
mapProps: MapProps,
) => UseHandleModalPromise<T>

export interface UseHandleModalPromise<T> {
node: ReactNode
show: ShowModalType<T>
props: PropsControl
}

export const useHandleModalPromise: useHandleModal = <T,>(
Component,
mapProps: MapProps,
) => {
const [callback, setCallback] = useState<(PickerValue) => void>(() => {})
const [visible, setVisible] = useState(false)
const [value, setValue] = useState<ModalResponse>()
const [mergeProps, setMergeProps] = useState<any>()

const { beforeFn, restProps } = useMemo(() => {
const { beforeOk, ...rest } = mergeProps ?? {}

return {
beforeFn: beforeOk,
restProps: rest,
}
}, [mergeProps])

const showModal: ShowModalType<T> = useCallback((val, otherProps) => {
setValue(val)
setMergeProps({ ...otherProps })
setVisible(true)
return new Promise((resolve) => {
setCallback(() => resolve)
})
}, [])

const onCancel = useCallback(() => {
setVisible(false)
callback?.({
type: 'cancel',
})
}, [callback])

const onClose = useCallback(() => {
setVisible(false)
callback?.({
type: 'close',
})
}, [callback])

const onModalOk = useCallback(
async (e) => {
let res = true
if (beforeFn) {
try {
await beforeFn()
} catch (error) {
res = false
}
}
if (res) {
setVisible(false)
callback?.({
type: 'ok',
value: e,
})
}
},
[beforeFn, callback],
)

const propsControl = useMemo(
() => ({
value,
visible,
onModalOk,
onCancel,
onClose,
mergeProps,
}),
[value, visible, onModalOk, onCancel, onClose, mergeProps],
)

const componentProps = useMemo(() => {
const propsValue = mapProps.value || 'value'
const propsVisible = mapProps.visible || 'visible'
const propsOnModalOk = mapProps.onModalOk || 'onModalOk'
const propsOnCancel = mapProps.onCancel || 'onCancel'
const propsOnClose = mapProps.onClose || 'onClose'
return {
[propsValue]: propsControl.value ?? mapProps?.props?.[propsValue],
[propsVisible]: propsControl.visible ?? mapProps?.props?.[propsVisible],
[propsOnModalOk]:
propsControl.onModalOk ?? mapProps?.props?.[propsOnModalOk],
[propsOnCancel]:
propsControl.onCancel ?? mapProps?.props?.[propsOnCancel],
[propsOnClose]: propsControl.onClose ?? mapProps?.props?.[propsOnClose],
}
}, [
mapProps.onCancel,
mapProps.onClose,
mapProps.onModalOk,
mapProps?.props,
mapProps.value,
mapProps.visible,
propsControl.onCancel,
propsControl.onClose,
propsControl.onModalOk,
propsControl.value,
propsControl.visible,
])

return {
node: <Component {...componentProps} {...restProps} />,
show: showModal,
props: propsControl,
}
}
1 change: 1 addition & 0 deletions src/pages/Index/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ const NAV_LIST: {
icon: '009-checkcircle',
items: [
{ title: '轻提示Toast', url: router.ToastPage.url },
{ title: '轻提示UseToast(new)', url: router.UseToast.url },
{ title: '加载Loading', url: router.Loader.url },
{ title: '文字提示Tooltip', url: router.Tooltip.url },
{ title: '对话框Dialog', url: router.Dialog.url },
Expand Down
Loading