diff --git a/docs/components/data-view/chip.mdx b/docs/components/data-view/chip.mdx index f0b4821a..473e3b10 100644 --- a/docs/components/data-view/chip.mdx +++ b/docs/components/data-view/chip.mdx @@ -18,6 +18,10 @@ +### 基本用法 + + + ## API * 通用属性-`Input` diff --git a/examples/chip/group.tsx b/examples/chip/group.tsx new file mode 100644 index 00000000..06c5e5fe --- /dev/null +++ b/examples/chip/group.tsx @@ -0,0 +1,17 @@ +import { IChipGroup, IFlex } from '@/components'; + +const App: React.FC = () => ( + + + +); + +export default App; diff --git a/src/components/widgets/chip/index.tsx b/src/components/widgets/chip/index.tsx index 2d73bc5c..c2dce6f9 100644 --- a/src/components/widgets/chip/index.tsx +++ b/src/components/widgets/chip/index.tsx @@ -1,4 +1,4 @@ -import { useRef } from 'react'; +import { Fragment, useRef } from 'react'; import classNames from 'classnames'; import { AnimatePresence, motion } from 'framer-motion'; @@ -8,15 +8,45 @@ import { useMemoFunc } from '@/hooks'; import type { ReactCFC } from '@/models'; import { iEscapeEvent, iPropagation } from '@/utils'; -import { IFlex } from '../flex'; +import { useControlState } from '../control'; import { ISignLine } from '../sign'; import { ISpinner } from '../spinners'; import { ISVGWrap } from '../svg-wrap'; import { IWave } from '../wave'; -import type { IChipGroupProps, IChipProps } from './models'; +import type { + IChipGroupProps, + IChipPrefixRender, + IChipProps, + IChipState, + IChipSuffixRender, +} from './models'; import * as styles from './index.scss'; +const iPrefixRender: IChipPrefixRender = ( + { icon, close, ...others }, + { isLoading } +) => ( + + {isLoading ? : icon} + +); + +const iSuffixRender: IChipSuffixRender = ({ + icon, + close, + onClose, + ...others +}) => ( + + {close && ( + + {isTrue(close) ? : close} + + )} + +); + export const IChip: ReactCFC = (props) => { const { icon, @@ -29,6 +59,7 @@ export const IChip: ReactCFC = (props) => { size = 'medium', disabled = false, variant = 'filled', + render, onKeyDown, onClose, ...others @@ -36,12 +67,18 @@ export const IChip: ReactCFC = (props) => { const target = useRef(null); - const iClose = useMemoFunc( - (event: React.MouseEvent) => { - iPropagation(event); - onClose?.(event); - } - ); + const states: IChipState = { + clickable, + disabled, + isLoading, + variant, + size, + }; + + const iClose = useMemoFunc((event: React.UIEvent) => { + iPropagation(event); + onClose?.(event); + }); return ( = (props) => { {...others} > {clickable && } - - {isLoading ? ( - - - - ) : ( - icon && {icon} - )} - + {(render?.prefix ?? iPrefixRender)( + { icon, close, onClose: iClose, className: styles.icon }, + states + )} {children} - - {close && ( - - {isTrue(close) ? : close} - - )} - + {(render?.suffix ?? iSuffixRender)( + { icon, close, onClose: iClose, className: styles.close }, + states + )} ); }; -export const IChipGroup: React.FC = () => {''}; +export const IChipGroup: React.FC = (props) => { + const { value, chips, variant, icon, onChange, onChipsChange } = props; + + const [iChips, iChipsChange] = useControlState({ + value: chips, + onChange: onChipsChange, + }); + + return ( + + {iChips?.map(({ value, label, ...others }, index) => ( + + {label ?? value?.toString()} + + ))} + + ); +}; diff --git a/src/components/widgets/chip/models/index.tsx b/src/components/widgets/chip/models/index.tsx index 03b9702f..cd5775b3 100644 --- a/src/components/widgets/chip/models/index.tsx +++ b/src/components/widgets/chip/models/index.tsx @@ -5,12 +5,12 @@ import type { HTMLMotionProps } from 'framer-motion'; -import type { ReactValue, ReactValueChangeFunc } from '@/models'; +import type { ReactRender, ReactValue, ReactValueChangeFunc } from '@/models'; import type { ControlUISize } from '../../control'; +import type { ISVGWrapProps } from '../../svg-wrap'; -export interface IChipProps extends HTMLMotionProps<'span'> { - close?: React.ReactNode; +export interface IChipState { /** * If `true`, the chip will appear clickable, and will raise when pressed, * even if the onClick prop is not defined. @@ -22,14 +22,6 @@ export interface IChipProps extends HTMLMotionProps<'span'> { clickable?: boolean; /** @default false */ disabled?: boolean; - - /** Icon element */ - icon?: React.ReactNode; - /** - * Callback fired when the delete icon is clicked. - * If set, the delete icon will be shown. - */ - onClose?: (event: React.UIEvent) => void; /** @default 'medium' */ size?: ControlUISize; /** @default 'outlined' */ @@ -38,16 +30,61 @@ export interface IChipProps extends HTMLMotionProps<'span'> { isLoading?: boolean; } +export type IChipCloseFunc = (event: React.UIEvent) => void; + +export type IChipPrefixRender = ReactRender< + ISVGWrapProps & { + icon?: React.ReactNode; + close?: React.ReactNode; + onClose: IChipCloseFunc; + }, + IChipState +>; + +export type IChipSuffixRender = ReactRender< + ISVGWrapProps & { + icon?: React.ReactNode; + close?: React.ReactNode; + onClose: IChipCloseFunc; + }, + IChipState +>; + +export interface IChipRenders { + prefix?: IChipPrefixRender; + suffix?: IChipSuffixRender; +} + +export interface IChipProps extends IChipState, HTMLMotionProps<'span'> { + /** Prefix icon element */ + icon?: React.ReactNode; + /** Close icon element */ + close?: React.ReactNode; + render?: IChipRenders; + /** + * Callback fired when the delete icon is clicked. + * If set, the delete icon will be shown. + */ + onClose?: IChipCloseFunc; +} + export type IChipConfig = Pick< IChipProps, 'isLoading' | 'variant' | 'size' | 'icon' | 'disabled' | 'clickable' | 'close' > & { value?: React.Key; + label?: React.ReactNode; }; -export interface IChipGroupProps { +export interface IChipGroupProps + extends Pick< + IChipConfig, + 'variant' | 'size' | 'icon' | 'disabled' | 'clickable' | 'close' + > { chips?: IChipConfig[]; value?: ReactValue; + editable?: boolean; + creatable?: boolean; onChange?: ReactValueChangeFunc; onChipsChange?: (current: IChipConfig, chips?: IChipConfig[]) => void; } diff --git a/src/components/widgets/spinners/index.tsx b/src/components/widgets/spinners/index.tsx index 8d461370..e782f78f 100644 --- a/src/components/widgets/spinners/index.tsx +++ b/src/components/widgets/spinners/index.tsx @@ -20,6 +20,7 @@ export const ISpinner: React.FC = ({ repeat: Infinity, ease: 'linear', }, + ...animate, }} className={className} exit={{ x: 0, y: 0 }}