Skip to content

Commit a18703d

Browse files
committed
new changes to improve collapsible component #528
1 parent eac4bb3 commit a18703d

File tree

5 files changed

+51
-73
lines changed

5 files changed

+51
-73
lines changed

src/components/ui/Collapsible/Collapsible.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ export type CollapsibleProps = {
2525
open?: boolean;
2626
title?: string;
2727
trigger?: ReactNode;
28-
items: { content: any }[];
28+
items: { content: string | ReactNode }[];
2929
disabled?: boolean;
30-
defaultOpen?: { content: any };
30+
defaultOpen?: { content: string | ReactNode };
3131
onOpenChange?: Dispatch<SetStateAction<boolean>>;
3232
} & PropsWithChildren;
3333

@@ -37,7 +37,7 @@ const Collapsible = ({ children, items, ...props }: CollapsibleProps) => {
3737

3838

3939
// Disable or enable collapse
40-
const disabled = props.disabled;
40+
const disabled = props.disabled ?? false;
4141

4242
// Title for the component
4343
const title = props.title;
@@ -51,15 +51,16 @@ const Collapsible = ({ children, items, ...props }: CollapsibleProps) => {
5151
<CollapsibleRoot
5252
open={props.open ?? open}
5353
onOpenChange={props.onOpenChange ?? onOpenChange}
54+
disabled={disabled}
5455
>
5556
<CollapsibleHeader title={title}>
5657
{/* Button */}
57-
{!disabled && (
58+
5859
<CollapsibleTrigger asChild >
5960
{props.trigger && props.trigger}
6061

6162
</CollapsibleTrigger>
62-
)}
63+
6364
</CollapsibleHeader>
6465

6566
{/* Conditonal Loop */}
@@ -77,11 +78,9 @@ const Collapsible = ({ children, items, ...props }: CollapsibleProps) => {
7778
{/* Collapsable Content */}
7879
<CollapsibleContent state={props.open ?? open}>
7980
{items.map((item, index) => (
80-
<>
81-
{item != defaultOpen && (
82-
<CollapsibleItem key={index}>{item.content}</CollapsibleItem>
83-
)}
84-
</>
81+
item !== defaultOpen && (
82+
<CollapsibleItem key={index}>{item.content}</CollapsibleItem>
83+
)
8584
))}
8685
</CollapsibleContent>
8786
</>

src/components/ui/Collapsible/fragments/CollapsibleRoot.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ export type CollapsibleRootProps = {
1414
open: boolean;
1515
onOpenChange: Dispatch<SetStateAction<boolean>>;
1616
className?: string
17+
disabled?: boolean
1718
};
1819

19-
const CollapsibleRoot = ({children,className="", customRootClass, open, onOpenChange}: CollapsibleRootProps) => {
20+
const CollapsibleRoot = ({children,className="",disabled, customRootClass, open, onOpenChange}: CollapsibleRootProps) => {
2021

2122
const rootClass = customClassSwitcher(customRootClass,COMPONENT_NAME)
2223

@@ -25,7 +26,8 @@ const CollapsibleRoot = ({children,className="", customRootClass, open, onOpenCh
2526
value={{
2627
rootClass,
2728
open,
28-
onOpenChange
29+
onOpenChange,
30+
disabled
2931
}}
3032
><div className={clsx(`${rootClass}-root`,className)}>
3133
{children}</div></CollapsibleContext.Provider>

src/components/ui/Collapsible/fragments/CollapsibleTrigger.tsx

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,34 @@ type CollapsibleTriggerProps = {
4747

4848

4949
const CollapsibleTrigger = ({children, asChild=false}: CollapsibleTriggerProps) => {
50-
const {rootClass,open,onOpenChange} = useContext(CollapsibleContext)
51-
const toggleCollapse = () => onOpenChange && onOpenChange((p) => (!p))
50+
const {rootClass,open,onOpenChange,disabled} = useContext(CollapsibleContext)
51+
const toggleCollapse = () => onOpenChange && !disabled && onOpenChange((p) => (!p))
5252

53-
53+
5454
return (
55-
<div className={clsx(`${rootClass}-trigger`)} onClick={toggleCollapse}>
56-
{asChild? children: <ButtonPrimitive>
57-
{open ? <CollapseIcon /> : <ExpandIcon />}
58-
</ButtonPrimitive>}
59-
60-
55+
<div
56+
className={clsx(`${rootClass}-trigger`)}
57+
role="button"
58+
tabIndex={0}
59+
onKeyDown={(e) => {
60+
if (e.key === "Enter" || e.key === " ") {
61+
e.preventDefault();
62+
toggleCollapse();
63+
}
64+
}}
65+
aria-expanded={open}
66+
onClick={
67+
68+
toggleCollapse
69+
}
70+
>
71+
{asChild ? (
72+
children
73+
) : (
74+
<ButtonPrimitive>
75+
{open ? <CollapseIcon /> : <ExpandIcon />}
76+
</ButtonPrimitive>
77+
)}
6178
</div>
6279
);
6380
}

src/components/ui/Collapsible/stories/Collapsible.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export const ExternalTrigger = () => {
5959
title="Quote"
6060
items={Items}
6161
defaultOpen={Items[0]}
62+
disabled={true}
6263
trigger={
6364
<Button style={{ margin: "0" }}>
6465
{open ? "CLOSE" : "OPEN"}

src/components/ui/Collapsible/tests/Collapsible.test.tsx

Lines changed: 11 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,17 @@ describe("Collapsible Component", () => {
4141
});
4242

4343
it("disables collapsible when disabled prop is true", () => {
44-
const { getByText } = render(
45-
<Collapsible disabled={true} items={[{ content: "Item 1" }]} />
46-
);
47-
expect(getByText("Item 1")).toBeInTheDocument();
44+
const { getByText, queryByText } = render(
45+
<Collapsible
46+
disabled={true}
47+
trigger={<button>Toggle</button>}
48+
items={[{ content: "Item 1" }]}
49+
/>
50+
);
51+
const trigger = getByText("Toggle");
52+
const initialState = queryByText("Item 1");
53+
fireEvent.click(trigger);
54+
expect(queryByText("Item 1")).toBe(initialState); // Content state should not change
4855
});
4956

5057
it("renders default open content when provided", () => {
@@ -58,55 +65,7 @@ describe("Collapsible Component", () => {
5865
});
5966

6067

61-
describe("Collapsible Component", () => {
62-
it("renders without crashing", () => {
63-
const { getByText } = render(
64-
<Collapsible items={[{ content: "Item 1" }]} />
65-
);
66-
expect(getByText("Item 1")).toBeInTheDocument();
67-
});
6868

69-
it("renders title when provided", () => {
70-
const { getByText } = render(
71-
<Collapsible title="Test Title" items={[{ content: "Item 1" }]} />
72-
);
73-
expect(getByText("Test Title")).toBeInTheDocument();
74-
});
75-
76-
it("renders trigger when provided", () => {
77-
const { getByText } = render(
78-
<Collapsible trigger={<button>Toggle</button>} items={[{ content: "Item 1" }]} />
79-
);
80-
expect(getByText("Toggle")).toBeInTheDocument();
81-
});
82-
83-
it("toggles content visibility on trigger click", () => {
84-
const { getByText, queryByText } = render(
85-
<Collapsible trigger={<button>Toggle</button>} items={[{ content: "Item 1" }]} />
86-
);
87-
const trigger = getByText("Toggle");
88-
fireEvent.click(trigger);
89-
expect(queryByText("Item 1")).not.toBeInTheDocument();
90-
fireEvent.click(trigger);
91-
expect(queryByText("Item 1")).toBeInTheDocument();
92-
});
93-
94-
it("disables collapsible when disabled prop is true", () => {
95-
const { getByText } = render(
96-
<Collapsible disabled={true} items={[{ content: "Item 1" }]} />
97-
);
98-
expect(getByText("Item 1")).toBeInTheDocument();
99-
});
100-
101-
it("renders default open content when provided", () => {
102-
const { getByText } = render(
103-
<Collapsible defaultOpen={{ content: "Default Item" }} items={[{ content: "Item 1" }]} />
104-
);
105-
expect(getByText("Default Item")).toBeInTheDocument();
106-
});
107-
108-
109-
});
11069

11170
describe("CollapsibleHeader Component", () => {
11271
it("renders title", () => {

0 commit comments

Comments
 (0)