Skip to content

Commit 281ff39

Browse files
committed
feat: add component accordion
1 parent bd03d32 commit 281ff39

File tree

16 files changed

+531
-0
lines changed

16 files changed

+531
-0
lines changed

packages/ui-variants/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './shared';
22

33
export * from './types';
44

5+
export * from './variants/accordion';
56
export * from './variants/badge';
67
export * from './variants/button';
78
export * from './variants/button-group';
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { tv } from 'tailwind-variants';
2+
3+
export const accordionVariants = tv({
4+
defaultVariants: {
5+
size: 'md'
6+
},
7+
slots: {
8+
content: [`overflow-hidden data-[state=open]:animate-accordion-down data-[state=closed]:animate-accordion-up`],
9+
header: 'flex',
10+
item: 'border-b',
11+
root: '',
12+
trigger: [
13+
`flex-1 flex items-center justify-start font-medium transition-all duration-200 bg-transparent`,
14+
`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:ring-primary`,
15+
`hover:underline [&[data-state=open]>.trigger-icon]:rotate-180`
16+
],
17+
triggerIcon: `trigger-icon ml-auto shrink-0 text-muted-foreground transition-transform duration-200`,
18+
triggerLeadingIcon: `shrink-0`
19+
},
20+
variants: {
21+
size: {
22+
'2xl': {
23+
content: 'data-[state=open]:pb-6',
24+
root: 'text-2xl',
25+
trigger: 'py-6 gap-6'
26+
},
27+
lg: {
28+
content: 'data-[state=open]:pb-4.5',
29+
root: 'text-base',
30+
trigger: 'py-4.5 gap-4.5'
31+
},
32+
md: {
33+
content: 'data-[state=open]:pb-4',
34+
root: 'text-sm',
35+
trigger: 'py-4 gap-4'
36+
},
37+
sm: {
38+
content: 'data-[state=open]:pb-3.5',
39+
root: 'text-xs',
40+
trigger: 'py-3.5 gap-3.5'
41+
},
42+
xl: {
43+
content: 'data-[state=open]:pb-5',
44+
root: 'text-lg',
45+
trigger: 'py-5 gap-5'
46+
},
47+
xs: {
48+
content: 'data-[state=open]:pb-3',
49+
root: 'text-2xs',
50+
trigger: 'py-3 gap-3'
51+
}
52+
}
53+
}
54+
});
55+
56+
export type AccordionSlots = keyof typeof accordionVariants.slots;

packages/ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"lint": "eslint . --max-warnings 0"
1212
},
1313
"dependencies": {
14+
"@radix-ui/react-accordion": "^1.2.11",
1415
"@radix-ui/react-compose-refs": "1.1.2",
1516
"@radix-ui/react-scroll-area": "^1.2.9",
1617
"@radix-ui/react-separator": "^1.1.7",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export { default as Accordion } from './source/Accordion';
2+
export { default as AccordionContent } from './source/AccordionContent';
3+
export { default as AccordionHeader } from './source/AccordionHeader';
4+
export { default as AccordionItem } from './source/AccordionItem';
5+
export { default as AccordionRoot } from './source/AccordionRoot';
6+
export { default as AccordionTrigger } from './source/AccordionTrigger';
7+
8+
export * from './types';
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use client';
2+
3+
import type { Root } from '@radix-ui/react-accordion';
4+
import { accordionVariants, cn } from '@soybean-react-ui/variants';
5+
import { forwardRef } from 'react';
6+
7+
import type { AccordionProps } from '../types';
8+
9+
import AccordionContent from './AccordionContent';
10+
import AccordionHeader from './AccordionHeader';
11+
import AccordionItem from './AccordionItem';
12+
import AccordionRoot from './AccordionRoot';
13+
import AccordionTrigger from './AccordionTrigger';
14+
15+
const Accordion = forwardRef<React.ElementRef<typeof Root>, AccordionProps>((props, ref) => {
16+
const { className, classNames, items, size, triggerIcon, triggerLeading, triggerTrailing, ...rest } = props;
17+
18+
const { root } = accordionVariants({ size });
19+
20+
const mergedCls = cn(root(), className);
21+
22+
return (
23+
<AccordionRoot
24+
className={mergedCls}
25+
ref={ref}
26+
{...rest}
27+
>
28+
{items.map(item => (
29+
<AccordionItem
30+
className={classNames?.item}
31+
disabled={item.disabled}
32+
key={item.value}
33+
value={item.value}
34+
>
35+
<AccordionHeader className={classNames?.header}>
36+
<AccordionTrigger
37+
className={classNames?.trigger}
38+
classNames={classNames}
39+
icon={triggerIcon}
40+
leading={item.leading || triggerLeading}
41+
size={size}
42+
trailing={item.trailing || triggerTrailing}
43+
>
44+
{item.title}
45+
</AccordionTrigger>
46+
</AccordionHeader>
47+
48+
<AccordionContent
49+
className={classNames?.content}
50+
size={size}
51+
>
52+
{item.children}
53+
</AccordionContent>
54+
</AccordionItem>
55+
))}
56+
</AccordionRoot>
57+
);
58+
});
59+
Accordion.displayName = 'Accordion';
60+
61+
export default Accordion;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Content } from '@radix-ui/react-accordion';
2+
import { accordionVariants, cn } from '@soybean-react-ui/variants';
3+
import { forwardRef } from 'react';
4+
5+
import type { AccordionContentProps } from '../types';
6+
7+
const AccordionContent = forwardRef<React.ElementRef<typeof Content>, AccordionContentProps>((props, ref) => {
8+
const { className, size, ...rest } = props;
9+
10+
const { content } = accordionVariants({ size });
11+
12+
const mergedCls = cn(content(), className);
13+
14+
return (
15+
<Content
16+
className={mergedCls}
17+
ref={ref}
18+
{...rest}
19+
/>
20+
);
21+
});
22+
AccordionContent.displayName = Content.displayName;
23+
24+
export default AccordionContent;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Header } from '@radix-ui/react-accordion';
2+
import { accordionVariants, cn } from '@soybean-react-ui/variants';
3+
import { forwardRef } from 'react';
4+
5+
import type { AccordionHeaderProps } from '../types';
6+
7+
const AccordionHeader = forwardRef<React.ElementRef<typeof Header>, AccordionHeaderProps>((props, ref) => {
8+
const { className, ...rest } = props;
9+
10+
const { header } = accordionVariants();
11+
12+
const mergedCls = cn(header(), className);
13+
14+
return (
15+
<Header
16+
className={mergedCls}
17+
ref={ref}
18+
{...rest}
19+
/>
20+
);
21+
});
22+
AccordionHeader.displayName = Header.displayName;
23+
24+
export default AccordionHeader;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Item } from '@radix-ui/react-accordion';
2+
import { accordionVariants, cn } from '@soybean-react-ui/variants';
3+
import { forwardRef } from 'react';
4+
5+
import type { AccordionItemProps } from '../types';
6+
7+
const AccordionItem = forwardRef<React.ElementRef<typeof Item>, AccordionItemProps>((props, ref) => {
8+
const { className, ...rest } = props;
9+
10+
const { item } = accordionVariants();
11+
12+
const mergedCls = cn(item(), className);
13+
14+
return (
15+
<Item
16+
className={mergedCls}
17+
ref={ref}
18+
{...rest}
19+
/>
20+
);
21+
});
22+
AccordionItem.displayName = Item.displayName;
23+
24+
export default AccordionItem;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Root } from '@radix-ui/react-accordion';
2+
import { accordionVariants, cn } from '@soybean-react-ui/variants';
3+
import { forwardRef } from 'react';
4+
5+
import type { AccordionRootProps } from '../types';
6+
7+
const AccordionRoot = forwardRef<React.ElementRef<typeof Root>, AccordionRootProps>((props, ref) => {
8+
const { className, size, ...rest } = props;
9+
10+
const { root } = accordionVariants({ size });
11+
12+
const mergedCls = cn(root(), className);
13+
14+
return (
15+
<Root
16+
className={mergedCls}
17+
ref={ref}
18+
{...rest}
19+
/>
20+
);
21+
});
22+
AccordionRoot.displayName = Root.displayName;
23+
24+
export default AccordionRoot;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Trigger } from '@radix-ui/react-accordion';
2+
import { Slot } from '@radix-ui/react-slot';
3+
import { accordionVariants, cn } from '@soybean-react-ui/variants';
4+
import { ChevronDown } from 'lucide-react';
5+
import { forwardRef } from 'react';
6+
7+
import type { AccordionTriggerProps } from '../types';
8+
9+
const AccordionTrigger = forwardRef<React.ElementRef<typeof Trigger>, AccordionTriggerProps>((props, ref) => {
10+
const { children, className, classNames, icon, leading, size, trailing, ...rest } = props;
11+
12+
const { trigger, triggerIcon, triggerLeadingIcon } = accordionVariants({ size });
13+
14+
const mergedCls = cn(trigger(), className);
15+
16+
const leadingIcon = cn(triggerLeadingIcon(), classNames?.triggerLeadingIcon);
17+
18+
const iconCls = cn(triggerIcon(), classNames?.triggerIcon);
19+
20+
return (
21+
<Trigger
22+
className={mergedCls}
23+
ref={ref}
24+
{...rest}
25+
>
26+
<Slot className={leadingIcon}>{leading}</Slot>
27+
28+
{children}
29+
30+
{trailing}
31+
32+
<Slot className={iconCls}>{icon || <ChevronDown />}</Slot>
33+
</Trigger>
34+
);
35+
});
36+
AccordionTrigger.displayName = Trigger.displayName;
37+
38+
export default AccordionTrigger;

0 commit comments

Comments
 (0)