Skip to content

Commit 08afb32

Browse files
committed
feat: add component chip
1 parent 5f6016c commit 08afb32

File tree

9 files changed

+303
-0
lines changed

9 files changed

+303
-0
lines changed

packages/ui-variants/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export * from './variants/button-group';
1313
export * from './variants/card';
1414
export * from './variants/carousel';
1515
export * from './variants/checkbox';
16+
export * from './variants/chip';
1617
export * from './variants/divider';
1718
export * from './variants/label';
1819
export * from './variants/scroll-area';
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { tv } from 'tailwind-variants';
2+
import type { VariantProps } from 'tailwind-variants';
3+
4+
export const chipVariants = tv({
5+
defaultVariants: {
6+
color: 'primary',
7+
position: 'top-right',
8+
size: 'md'
9+
},
10+
slots: {
11+
content: `absolute flex justify-center items-center whitespace-nowrap rounded-full transform`,
12+
root: 'relative'
13+
},
14+
variants: {
15+
color: {
16+
accent: {
17+
content: 'bg-accent text-accent-foreground'
18+
},
19+
carbon: {
20+
content: 'bg-carbon text-carbon-foreground'
21+
},
22+
destructive: {
23+
content: 'bg-destructive text-destructive-foreground'
24+
},
25+
info: {
26+
content: 'bg-info text-info-foreground'
27+
},
28+
primary: {
29+
content: 'bg-primary text-primary-foreground'
30+
},
31+
secondary: {
32+
content: 'bg-secondary text-secondary-foreground'
33+
},
34+
success: {
35+
content: 'bg-success text-success-foreground'
36+
},
37+
warning: {
38+
content: 'bg-warning text-warning-foreground'
39+
}
40+
},
41+
position: {
42+
'bottom-left': {
43+
content: 'left-0 bottom-0 -translate-x-1/2 translate-y-1/2'
44+
},
45+
'bottom-right': {
46+
content: 'right-0 bottom-0 translate-x-1/2 translate-y-1/2'
47+
},
48+
'top-left': {
49+
content: 'left-0 top-0 -translate-x-1/2 -translate-y-1/2'
50+
},
51+
'top-right': {
52+
content: 'right-0 top-0 translate-x-1/2 -translate-y-1/2'
53+
}
54+
},
55+
size: {
56+
'2xl': {
57+
content: `min-h-5 px-2.5 text-base leading-relaxed`
58+
},
59+
lg: {
60+
content: `min-h-3.5 px-1.75 text-xs leading-relaxed`
61+
},
62+
md: {
63+
content: `min-h-3 px-1.5 text-2xs leading-relaxed`
64+
},
65+
sm: {
66+
content: `min-h-2.5 px-1.25 text-3xs leading-relaxed`
67+
},
68+
xl: {
69+
content: `min-h-4 px-2 text-sm leading-relaxed`
70+
},
71+
xs: {
72+
content: `min-h-2 px-1 text-4xs leading-relaxed`
73+
}
74+
}
75+
}
76+
});
77+
78+
type ChipVariants = VariantProps<typeof chipVariants>;
79+
80+
export type ChipPosition = NonNullable<ChipVariants['position']>;
81+
82+
export type ChipSlots = keyof typeof chipVariants.slots;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export { default as Chip } from './source/Chip';
2+
export { default as ChipContent } from './source/ChipContent';
3+
export { default as ChipRoot } from './source/ChipRoot';
4+
5+
export * from './types';
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { forwardRef } from 'react';
2+
3+
import type { ChipProps } from '../types';
4+
5+
import ChipContent from './ChipContent';
6+
import ChipRoot from './ChipRoot';
7+
8+
const Chip = forwardRef<HTMLDivElement, ChipProps>((props, ref) => {
9+
const { children, className, classNames, color, content, open = true, position, size, ...rest } = props;
10+
11+
return (
12+
<ChipRoot
13+
className={className || classNames?.root}
14+
ref={ref}
15+
{...rest}
16+
>
17+
{children}
18+
19+
{open && (
20+
<ChipContent
21+
className={classNames?.content}
22+
color={color}
23+
position={position}
24+
size={size}
25+
>
26+
{content}
27+
</ChipContent>
28+
)}
29+
</ChipRoot>
30+
);
31+
});
32+
33+
Chip.displayName = 'Chip';
34+
35+
export default Chip;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { chipVariants, cn } from '@soybean-react-ui/variants';
2+
import { forwardRef } from 'react';
3+
4+
import type { ChipContentProps } from '../types';
5+
6+
const ChipContent = forwardRef<HTMLSpanElement, ChipContentProps>((props, ref) => {
7+
const { className, color, position, size, ...rest } = props;
8+
9+
const { content } = chipVariants({ color, position, size });
10+
11+
const mergedCls = cn(content(), className);
12+
13+
return (
14+
<span
15+
className={mergedCls}
16+
ref={ref}
17+
{...rest}
18+
/>
19+
);
20+
});
21+
22+
ChipContent.displayName = 'ChipContent';
23+
24+
export default ChipContent;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { chipVariants, cn } from '@soybean-react-ui/variants';
2+
import { forwardRef } from 'react';
3+
4+
import type { ChipRootProps } from '../types';
5+
6+
const ChipRoot = forwardRef<HTMLDivElement, ChipRootProps>((props, ref) => {
7+
const { className, ...rest } = props;
8+
9+
const { root } = chipVariants();
10+
11+
const mergedCls = cn(root(), className);
12+
13+
return (
14+
<div
15+
className={mergedCls}
16+
ref={ref}
17+
{...rest}
18+
/>
19+
);
20+
});
21+
22+
ChipRoot.displayName = 'ChipRoot';
23+
24+
export default ChipRoot;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { ChipPosition, ChipSlots, ClassValue, ThemeColor, ThemeSize } from '@soybean-react-ui/variants';
2+
3+
import type { BaseComponentProps } from '../../types';
4+
5+
export type ChipRootProps = BaseComponentProps<'div'>;
6+
7+
export interface ChipContentProps extends BaseComponentProps<'span'> {
8+
color?: ThemeColor;
9+
position?: ChipPosition;
10+
size?: ThemeSize;
11+
}
12+
13+
export type ChipUi = Partial<Record<ChipSlots, ClassValue>>;
14+
15+
export interface ChipProps
16+
extends Omit<ChipRootProps, 'color' | 'content'>,
17+
Pick<ChipContentProps, 'color' | 'position' | 'size'> {
18+
classNames?: ChipUi;
19+
content?: React.ReactNode;
20+
open?: boolean;
21+
}
22+
23+
export { ChipPosition };

packages/ui/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export * from './components/carousel';
2020

2121
export * from './components/checkbox';
2222

23+
export * from './components/chip';
24+
2325
export * from './components/divider';
2426

2527
export * from './components/label';
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import type { ChipPosition, ThemeColor, ThemeSize } from 'soybean-react-ui';
2+
import { Button, Card, Chip } from 'soybean-react-ui';
3+
4+
const colors: ThemeColor[] = ['primary', 'destructive', 'success', 'warning', 'info', 'carbon', 'secondary', 'accent'];
5+
6+
const positions: ChipPosition[] = ['top-right', 'bottom-right', 'top-left', 'bottom-left'];
7+
8+
const sizes: ThemeSize[] = ['xs', 'sm', 'md', 'lg', 'xl', '2xl'];
9+
10+
const ChipPage = () => {
11+
return (
12+
<div className="flex-c gap-4">
13+
<Card
14+
split
15+
title="Color"
16+
>
17+
<div className="flex gap-3">
18+
{colors.map(color => (
19+
<div key={color}>
20+
<Chip color={color}>
21+
<Button variant="dashed">{color}</Button>
22+
</Chip>
23+
</div>
24+
))}
25+
</div>
26+
</Card>
27+
28+
<Card
29+
split
30+
title="Color With Text"
31+
>
32+
<div className="flex gap-4">
33+
{colors.map(color => (
34+
<div key={color}>
35+
<Chip
36+
color={color}
37+
content="99+"
38+
>
39+
<Button variant="dashed">{color}</Button>
40+
</Chip>
41+
</div>
42+
))}
43+
</div>
44+
</Card>
45+
46+
<Card
47+
split
48+
title="Positions"
49+
>
50+
<div className="flex gap-3">
51+
{positions.map(position => (
52+
<div key={position}>
53+
<Chip position={position}>
54+
<Button
55+
className="w-30"
56+
variant="dashed"
57+
>
58+
{position}
59+
</Button>
60+
</Chip>
61+
</div>
62+
))}
63+
</div>
64+
</Card>
65+
66+
<Card
67+
split
68+
title="Size"
69+
>
70+
<div className="flex gap-3">
71+
{sizes.map(size => (
72+
<div key={size}>
73+
<Chip size={size}>
74+
<Button
75+
size={size}
76+
variant="soft"
77+
>
78+
{size}
79+
</Button>
80+
</Chip>
81+
</div>
82+
))}
83+
</div>
84+
85+
<div className="flex mt-6 gap-3">
86+
{sizes.map(size => (
87+
<div key={size}>
88+
<Chip
89+
content="99+"
90+
size={size}
91+
>
92+
<Button
93+
size={size}
94+
variant="soft"
95+
>
96+
{size}
97+
</Button>
98+
</Chip>
99+
</div>
100+
))}
101+
</div>
102+
</Card>
103+
</div>
104+
);
105+
};
106+
107+
export default ChipPage;

0 commit comments

Comments
 (0)