Skip to content

Commit f06eeba

Browse files
authored
Feat. Users can freely customize the toggle icon. (#89)
* Refactor. Custom Toggle * refactor. compound components * feat. Added conditional logic to the custom toggle.
1 parent 5db5219 commit f06eeba

File tree

4 files changed

+108
-13
lines changed

4 files changed

+108
-13
lines changed

packages/core/src/lib/components/toggle.tsx

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,79 @@ import RichText from "./internal/rich-text";
77

88
type ToggleProps = {
99
children?: React.ReactNode;
10+
isOpen?: boolean;
11+
onChangeOpen?: (open: boolean) => void;
1012
} & ToggleArgs;
1113

12-
const Toggle: React.FC<ToggleProps> = ({ children, ...props }) => {
14+
const Toggle: React.FC<ToggleProps> & { Icon: typeof ToggleIcon } = ({
15+
children,
16+
onChangeOpen,
17+
...props
18+
}) => {
1319
const {
1420
toggle: { color, rich_text: texts },
1521
} = props;
1622

1723
const [open, setOpen] = useState(false);
1824

19-
const toggleOpen = useCallback(() => setOpen((prevOpen) => !prevOpen), []);
25+
let iconElement: React.ReactNode = <DefaultToggleIcon open={open} />;
26+
const otherChildren: React.ReactNode[] = [];
27+
28+
React.Children.forEach(children, (child) => {
29+
if (React.isValidElement(child) && child.type === Toggle.Icon) {
30+
iconElement = child.props.children;
31+
} else {
32+
otherChildren.push(child);
33+
}
34+
});
35+
36+
const toggleOpen = useCallback(() => {
37+
setOpen((prevOpen) => {
38+
if (onChangeOpen) {
39+
onChangeOpen(!prevOpen);
40+
}
41+
42+
return !prevOpen;
43+
});
44+
}, [setOpen, onChangeOpen]);
2045

2146
return (
2247
<div
2348
className={`notion-block notion-toggle ${getColorCss(color)} ${open ? "notion-toggle-open" : ""}`}
2449
aria-expanded={open}
2550
>
2651
<div className="notion-toggle-content">
27-
<button onClick={toggleOpen} className="notion-toggle-button">
28-
<div
29-
className={`notion-toggle-button-arrow-box ${open ? "notion-toggle-button-arrow-box-opened" : ""}`}
30-
>
31-
<div className="notion-toggle-button-arrow"></div>
32-
</div>
52+
<button onClick={toggleOpen} className="notion-toggle-button">
53+
{iconElement}
3354
</button>
3455
<p>
3556
<RichText props={texts} />
3657
</p>
3758
</div>
3859

39-
{children}
60+
{otherChildren}
4061
</div>
4162
);
4263
};
4364

65+
type DefaultToggleIconProps = {
66+
open: boolean;
67+
};
68+
69+
const DefaultToggleIcon = ({ open }: DefaultToggleIconProps) => {
70+
return (
71+
<div
72+
className={`notion-toggle-button-arrow-box ${open ? "notion-toggle-button-arrow-box-opened" : ""}`}
73+
>
74+
<div className="notion-toggle-button-arrow"/>
75+
</div>
76+
);
77+
};
78+
79+
const ToggleIcon: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
80+
<>{children}</>
81+
);
82+
83+
Toggle.Icon = ToggleIcon;
84+
4485
export default Toggle;

packages/core/src/lib/types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ export type TodoArgs = {
101101
checked: boolean;
102102
rich_text: TextArgs[];
103103
};
104-
customElement?: React.ReactNode;
105104
} & ContextedBlock;
106105

107106
export type ToggleArgs = {
@@ -110,7 +109,6 @@ export type ToggleArgs = {
110109
color: string;
111110
rich_text: TextArgs[];
112111
};
113-
customElement?: React.ReactNode;
114112
} & ContextedBlock;
115113

116114
export type QuoteArgs = {

packages/story/src/stories/todo/todo.stories.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { Meta, StoryObj } from "@storybook/react";
22
import Component from "../../lib/Notion";
33
import json from "./todo.json";
4-
import { Todo, TodoArgs } from "@notionpresso/react";
4+
import { Todo } from "@notionpresso/react";
5+
import { TodoArgs } from "@notionpresso/react";
56

67
const blocks = json.blocks as any;
78

packages/story/src/stories/toggle/toggle.stories.tsx

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import type { Meta, StoryObj } from "@storybook/react";
22
import Component from "../../lib/Notion";
33
import json from "./toggle.json";
44

5+
import { Toggle } from "@notionpresso/react";
6+
import type { ToggleArgs } from "@notionpresso/react";
7+
import { useState } from "react";
8+
59
const blocks = json.blocks as any;
610

711
const meta: Meta<typeof Component> = {
@@ -12,9 +16,60 @@ const meta: Meta<typeof Component> = {
1216
export default meta;
1317
type Story = StoryObj<typeof Component>;
1418

15-
export const Toggle: Story = {
19+
export const ToggleStory: Story = {
20+
name: "Toggle",
1621
args: {
1722
title: "Toggle",
1823
blocks: blocks,
1924
},
2025
};
26+
27+
type ToggleProps = ToggleArgs & {
28+
children?: React.ReactNode;
29+
};
30+
31+
const CustomToggle = ({ children, ...props }: ToggleProps) => {
32+
const [isOpen, setIsOpen] = useState(false);
33+
34+
const handleChangeOpen = (open: boolean) => {
35+
setIsOpen(open);
36+
};
37+
38+
return (
39+
<Toggle {...props} onChangeOpen={handleChangeOpen}>
40+
<Toggle.Icon>
41+
{isOpen ? (
42+
<div
43+
style={{
44+
width: "10px",
45+
height: "3px",
46+
cursor: "pointer",
47+
backgroundColor: "orange",
48+
}}
49+
/>
50+
) : (
51+
<div
52+
style={{
53+
width: "3px",
54+
height: "10px",
55+
cursor: "pointer",
56+
backgroundColor: "orange",
57+
}}
58+
/>
59+
)}
60+
</Toggle.Icon>
61+
{children}
62+
</Toggle>
63+
);
64+
};
65+
66+
export const CustomToggleStory: Story = {
67+
name: "Custom Toggle",
68+
args: {
69+
title: "Custom Toggle",
70+
blocks: blocks,
71+
custom: {
72+
toggle: CustomToggle,
73+
},
74+
},
75+
};

0 commit comments

Comments
 (0)