Skip to content

Commit

Permalink
feat: 更新chip组件
Browse files Browse the repository at this point in the history
  • Loading branch information
busy-mango committed Sep 25, 2024
1 parent e402f2d commit d917832
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 44 deletions.
4 changes: 4 additions & 0 deletions docs/components/data-view/chip.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@

<code src='@examples/chip/basic' />

### 基本用法

<code src='@examples/chip/group' />

## API

* 通用属性-`Input`
Expand Down
17 changes: 17 additions & 0 deletions examples/chip/group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IChipGroup, IFlex } from '@/components';

const App: React.FC = () => (
<IFlex gap={8}>
<IChipGroup
chips={[
{ value: 'Angular' },
{ value: 'jQuery' },
{ value: 'Polymer' },
{ value: 'React' },
{ value: 'Vue.js' },
]}
/>
</IFlex>
);

export default App;
104 changes: 72 additions & 32 deletions src/components/widgets/chip/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef } from 'react';
import { Fragment, useRef } from 'react';
import classNames from 'classnames';
import { AnimatePresence, motion } from 'framer-motion';

Expand All @@ -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 }
) => (
<ISVGWrap {...others}>
<AnimatePresence>{isLoading ? <ISpinner /> : icon}</AnimatePresence>
</ISVGWrap>
);

const iSuffixRender: IChipSuffixRender = ({
icon,
close,
onClose,
...others
}) => (
<AnimatePresence>
{close && (
<ISVGWrap whileHover={{ scale: 1.12 }} onClick={onClose} {...others}>
{isTrue(close) ? <ISignLine type="cross" /> : close}
</ISVGWrap>
)}
</AnimatePresence>
);

export const IChip: ReactCFC<IChipProps> = (props) => {
const {
icon,
Expand All @@ -29,19 +59,26 @@ export const IChip: ReactCFC<IChipProps> = (props) => {
size = 'medium',
disabled = false,
variant = 'filled',
render,
onKeyDown,
onClose,
...others
} = props;

const target = useRef<HTMLSpanElement>(null);

const iClose = useMemoFunc(
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
iPropagation(event);
onClose?.(event);
}
);
const states: IChipState = {
clickable,
disabled,
isLoading,
variant,
size,
};

const iClose = useMemoFunc((event: React.UIEvent<HTMLElement>) => {
iPropagation(event);
onClose?.(event);
});

return (
<motion.span
Expand All @@ -61,31 +98,34 @@ export const IChip: ReactCFC<IChipProps> = (props) => {
{...others}
>
{clickable && <IWave target={target} />}
<AnimatePresence>
{isLoading ? (
<ISVGWrap className={styles.icon}>
<ISpinner />
</ISVGWrap>
) : (
icon && <ISVGWrap className={styles.icon}>{icon}</ISVGWrap>
)}
</AnimatePresence>
{(render?.prefix ?? iPrefixRender)(
{ icon, close, onClose: iClose, className: styles.icon },
states
)}
{children}
<AnimatePresence>
{close && (
<ISVGWrap
className={styles.close}
whileHover={{
scale: 1.12,
}}
onClick={iClose}
>
{isTrue(close) ? <ISignLine type="cross" /> : close}
</ISVGWrap>
)}
</AnimatePresence>
{(render?.suffix ?? iSuffixRender)(
{ icon, close, onClose: iClose, className: styles.close },
states
)}
</motion.span>
);
};

export const IChipGroup: React.FC<IChipGroupProps> = () => <IFlex>{''}</IFlex>;
export const IChipGroup: React.FC<IChipGroupProps> = (props) => {
const { value, chips, variant, icon, onChange, onChipsChange } = props;

const [iChips, iChipsChange] = useControlState({
value: chips,
onChange: onChipsChange,
});

return (
<Fragment>
{iChips?.map(({ value, label, ...others }, index) => (
<IChip key={value ?? index} {...others}>
{label ?? value?.toString()}
</IChip>
))}
</Fragment>
);
};
61 changes: 49 additions & 12 deletions src/components/widgets/chip/models/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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' */
Expand All @@ -38,16 +30,61 @@ export interface IChipProps extends HTMLMotionProps<'span'> {
isLoading?: boolean;
}

export type IChipCloseFunc = (event: React.UIEvent<HTMLElement>) => 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;
}
1 change: 1 addition & 0 deletions src/components/widgets/spinners/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const ISpinner: React.FC<ISpinnerProps> = ({
repeat: Infinity,
ease: 'linear',
},
...animate,
}}
className={className}
exit={{ x: 0, y: 0 }}
Expand Down

0 comments on commit d917832

Please sign in to comment.