pentatrion-design 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/.storybook/main.ts +33 -0
  2. package/.storybook/manager.ts +8 -0
  3. package/.storybook/preview-head.html +2 -0
  4. package/.storybook/preview.ts +26 -0
  5. package/.storybook/public/bg-map.jpg +0 -0
  6. package/.storybook/public/favicon-32x32.png +0 -0
  7. package/.storybook/public/favicon.svg +28 -0
  8. package/.storybook/public/movies.json +2153 -0
  9. package/.storybook/public/population.json +1178 -0
  10. package/.storybook/public/town-74.geojson +8995 -0
  11. package/.storybook/public/town-74.json +4062 -0
  12. package/.storybook/theme.ts +14 -0
  13. package/Makefile +14 -0
  14. package/README.md +40 -0
  15. package/TODO.md +5 -0
  16. package/dist/index.d.ts +1110 -0
  17. package/dist/index.js +3707 -0
  18. package/dist/redux/index.d.ts +43 -0
  19. package/dist/redux/index.js +583 -0
  20. package/dist/types.d-DlHa_IaW.d.ts +14 -0
  21. package/images/logo-shadow.svg +173 -0
  22. package/images/logo.svg +76 -0
  23. package/package.json +85 -0
  24. package/postcss.config.js +7 -0
  25. package/src/components/autocomplete/Autocomplete.stories.tsx +89 -0
  26. package/src/components/autocomplete/Autocomplete.tsx +316 -0
  27. package/src/components/autocomplete/AutocompleteOption.tsx +35 -0
  28. package/src/components/autocomplete/LazyAutocomplete.tsx +130 -0
  29. package/src/components/autocomplete/SimpleAutocomplete.tsx +74 -0
  30. package/src/components/autocomplete/index.ts +6 -0
  31. package/src/components/autocomplete/useAutocompleteContext.ts +20 -0
  32. package/src/components/autocomplete/util.ts +19 -0
  33. package/src/components/badge/Badge.stories.tsx +72 -0
  34. package/src/components/badge/Badge.tsx +70 -0
  35. package/src/components/button/Button.stories.tsx +189 -0
  36. package/src/components/button/Button.test.tsx +20 -0
  37. package/src/components/button/Button.tsx +200 -0
  38. package/src/components/button/ButtonGroup.stories.tsx +46 -0
  39. package/src/components/button/ButtonGroup.tsx +29 -0
  40. package/src/components/button/index.ts +2 -0
  41. package/src/components/card/Card.stories.tsx +21 -0
  42. package/src/components/card/config.ts +4 -0
  43. package/src/components/card/index.ts +1 -0
  44. package/src/components/code/Code.tsx +15 -0
  45. package/src/components/context-menu/ContextMenu.stories.tsx +22 -0
  46. package/src/components/context-menu/ContextMenu.tsx +175 -0
  47. package/src/components/context-menu/ContextMenuItem.tsx +34 -0
  48. package/src/components/context-menu/index.ts +2 -0
  49. package/src/components/dialog/Dialog.stories.tsx +270 -0
  50. package/src/components/dialog/Dialog.tsx +46 -0
  51. package/src/components/dialog/index.ts +1 -0
  52. package/src/components/dialog/util.ts +29 -0
  53. package/src/components/dropdown-menu/DropdownMenu.stories.tsx +19 -0
  54. package/src/components/dropdown-menu/DropdownMenu.tsx +12 -0
  55. package/src/components/dropdown-menu/DropdownMenuContent.tsx +46 -0
  56. package/src/components/dropdown-menu/DropdownMenuItem.tsx +33 -0
  57. package/src/components/dropdown-menu/DropdownMenuTrigger.tsx +42 -0
  58. package/src/components/dropdown-menu/index.ts +7 -0
  59. package/src/components/dropdown-menu/interface.d.ts +13 -0
  60. package/src/components/dropdown-menu/useDropdownMenu.tsx +115 -0
  61. package/src/components/dropdown-menu/useDropdownMenuContext.ts +16 -0
  62. package/src/components/dropdown-menu-nested/DropdownMenuNested.stories.tsx +33 -0
  63. package/src/components/dropdown-menu-nested/DropdownMenuNested.tsx +19 -0
  64. package/src/components/dropdown-menu-nested/MenuContext.ts +15 -0
  65. package/src/components/dropdown-menu-nested/MenuItem.tsx +43 -0
  66. package/src/components/dropdown-menu-nested/MenuItemWithChildren.tsx +204 -0
  67. package/src/components/dropdown-menu-nested/index.ts +3 -0
  68. package/src/components/flash/Flash.stories.tsx +34 -0
  69. package/src/components/flash/Flash.tsx +24 -0
  70. package/src/components/highlight/Highlight.stories.tsx +128 -0
  71. package/src/components/highlight/Highlight.tsx +29 -0
  72. package/src/components/href/Href.stories.tsx +55 -0
  73. package/src/components/href/Href.tsx +18 -0
  74. package/src/components/input/Checkbox.stories.tsx +52 -0
  75. package/src/components/input/Checkbox.tsx +41 -0
  76. package/src/components/input/Input.stories.tsx +103 -0
  77. package/src/components/input/Input.test.tsx +10 -0
  78. package/src/components/input/Input.tsx +60 -0
  79. package/src/components/input/RadioGroup.stories.tsx +76 -0
  80. package/src/components/input/RadioGroup.tsx +84 -0
  81. package/src/components/input/Toggle.stories.tsx +29 -0
  82. package/src/components/input/Toggle.tsx +35 -0
  83. package/src/components/input/index.ts +4 -0
  84. package/src/components/input-field/InputField.stories.tsx +227 -0
  85. package/src/components/input-field/InputField.tsx +62 -0
  86. package/src/components/loader/Loader.stories.tsx +37 -0
  87. package/src/components/loader/Loader.tsx +54 -0
  88. package/src/components/modal/Components.tsx +58 -0
  89. package/src/components/modal/Modal.stories.tsx +113 -0
  90. package/src/components/modal/Modal.tsx +13 -0
  91. package/src/components/modal/ModalContent.tsx +46 -0
  92. package/src/components/modal/ModalTrigger.tsx +41 -0
  93. package/src/components/modal/index.ts +7 -0
  94. package/src/components/modal/interface.d.ts +10 -0
  95. package/src/components/modal/useModal.ts +48 -0
  96. package/src/components/modal/useModalContext.ts +16 -0
  97. package/src/components/notification/Notification.stories.tsx +51 -0
  98. package/src/components/notification/NotificationsContext.ts +79 -0
  99. package/src/components/notification/NotificationsProvider.tsx +58 -0
  100. package/src/components/notification/index.ts +2 -0
  101. package/src/components/popover/Components.tsx +45 -0
  102. package/src/components/popover/Popover.stories.tsx +46 -0
  103. package/src/components/popover/Popover.test.tsx +21 -0
  104. package/src/components/popover/Popover.tsx +12 -0
  105. package/src/components/popover/PopoverContent.tsx +46 -0
  106. package/src/components/popover/PopoverTrigger.tsx +42 -0
  107. package/src/components/popover/index.ts +7 -0
  108. package/src/components/popover/interface.d.ts +13 -0
  109. package/src/components/popover/usePopover.tsx +80 -0
  110. package/src/components/popover/usePopoverContext.ts +16 -0
  111. package/src/components/portal/Portal.tsx +12 -0
  112. package/src/components/quick-settings/QuickSettings.stories.tsx +182 -0
  113. package/src/components/resize-area/ResizeArea.stories.css +40 -0
  114. package/src/components/resize-area/ResizeArea.stories.tsx +30 -0
  115. package/src/components/resize-area/ResizeArea.tsx +79 -0
  116. package/src/components/scroll/Scroll.stories.tsx +81 -0
  117. package/src/components/scroll/Scroll.tsx +111 -0
  118. package/src/components/select/Select.stories.tsx +248 -0
  119. package/src/components/select/Select.tsx +362 -0
  120. package/src/components/select/SelectOption.tsx +29 -0
  121. package/src/components/select/SelectSelection.tsx +13 -0
  122. package/src/components/select/index.ts +5 -0
  123. package/src/components/select/interface.d.ts +36 -0
  124. package/src/components/select/useSelectContext.ts +19 -0
  125. package/src/components/select/util.ts +12 -0
  126. package/src/components/snack/Snack.stories.tsx +33 -0
  127. package/src/components/snack/Snack.tsx +47 -0
  128. package/src/components/sortable/Sortable.stories.tsx +98 -0
  129. package/src/components/sortable/Sortable.tsx +37 -0
  130. package/src/components/sortable/index.ts +2 -0
  131. package/src/components/sortable/types.d.ts +12 -0
  132. package/src/components/steps/Step.tsx +41 -0
  133. package/src/components/steps/Steps.stories.tsx +141 -0
  134. package/src/components/steps/Steps.tsx +49 -0
  135. package/src/components/steps/index.ts +3 -0
  136. package/src/components/steps/util.ts +3 -0
  137. package/src/components/table/Table.stories.tsx +36 -0
  138. package/src/components/table/Table.tsx +13 -0
  139. package/src/components/table/TableBody.tsx +9 -0
  140. package/src/components/table/TableCell.tsx +16 -0
  141. package/src/components/table/TableFooter.tsx +9 -0
  142. package/src/components/table/TableHeader.tsx +14 -0
  143. package/src/components/table/TableHeaderCell.tsx +14 -0
  144. package/src/components/table/TableRow.tsx +14 -0
  145. package/src/components/table/index.ts +7 -0
  146. package/src/components/tabs/Tabs.stories.tsx +66 -0
  147. package/src/components/tabs/Tabs.tsx +80 -0
  148. package/src/components/tooltip/SimpleTooltip.tsx +17 -0
  149. package/src/components/tooltip/Tooltip.stories.tsx +129 -0
  150. package/src/components/tooltip/Tooltip.test.tsx +18 -0
  151. package/src/components/tooltip/Tooltip.tsx +13 -0
  152. package/src/components/tooltip/TooltipContent.tsx +41 -0
  153. package/src/components/tooltip/TooltipTrigger.tsx +36 -0
  154. package/src/components/tooltip/index.ts +6 -0
  155. package/src/components/tooltip/interface.d.ts +15 -0
  156. package/src/components/tooltip/useTooltip.ts +87 -0
  157. package/src/components/tooltip/useTooltipContext.ts +16 -0
  158. package/src/env.d.ts +27 -0
  159. package/src/hooks/index.ts +13 -0
  160. package/src/hooks/useCombinedRefs.test.tsx +42 -0
  161. package/src/hooks/useCombinedRefs.ts +21 -0
  162. package/src/hooks/useCopyToClipboard.ts +28 -0
  163. package/src/hooks/useDebounce.ts +59 -0
  164. package/src/hooks/useEventCallback.ts +13 -0
  165. package/src/hooks/useEventListener.ts +86 -0
  166. package/src/hooks/useFetch.tsx +21 -0
  167. package/src/hooks/useIsClosing.ts +25 -0
  168. package/src/hooks/useIsMounted.tsx +16 -0
  169. package/src/hooks/useIsomorphicLayoutEffect.ts +4 -0
  170. package/src/hooks/useNotifications.tsx +13 -0
  171. package/src/hooks/useOnClickOutside.ts +35 -0
  172. package/src/hooks/usePrevious.ts +9 -0
  173. package/src/hooks/useRipple.test.tsx +52 -0
  174. package/src/hooks/useRipple.tsx +79 -0
  175. package/src/index.css +14 -0
  176. package/src/index.ts +39 -0
  177. package/src/lib/arrUtil.ts +8 -0
  178. package/src/lib/error.ts +18 -0
  179. package/src/lib/fetch.test.ts +107 -0
  180. package/src/lib/fetch.ts +115 -0
  181. package/src/lib/index.ts +3 -0
  182. package/src/lib/tailwindVariants.ts +35 -0
  183. package/src/redux/index.ts +1 -0
  184. package/src/redux/notification/components/NotificationConsumer.tsx +52 -0
  185. package/src/redux/notification/errorCatcherMiddleware.ts +17 -0
  186. package/src/redux/notification/hooks/useNotification.ts +26 -0
  187. package/src/redux/notification/index.ts +5 -0
  188. package/src/redux/notification/notificationSlice.ts +68 -0
  189. package/src/styles/Animation.stories.tsx +43 -0
  190. package/src/styles/Shadow.stories.tsx +43 -0
  191. package/src/styles/Themes.stories.tsx +31 -0
  192. package/src/styles/Typography.stories.tsx +101 -0
  193. package/src/tailwind/base.css +188 -0
  194. package/src/tailwind/components-input-outline.css +62 -0
  195. package/src/tailwind/components-resize-area.css +112 -0
  196. package/src/tailwind/components-step.css +161 -0
  197. package/src/tailwind/components.css +51 -0
  198. package/src/tailwind/keyframes.ts +69 -0
  199. package/src/tailwind/pentatrionTw.ts +147 -0
  200. package/src/tailwind/utilities-dialog.css +103 -0
  201. package/src/tailwind/utilities.css +3 -0
  202. package/src/types.d.ts +12 -0
  203. package/tailwind.config.js +18 -0
  204. package/tests/README.md +32 -0
  205. package/tests/setup.ts +1 -0
  206. package/tsconfig.json +14 -0
  207. package/tsup.config.ts +23 -0
  208. package/vite.config.ts +24 -0
@@ -0,0 +1,70 @@
1
+ import { ReactNode } from "react";
2
+ import { ThemeColor } from "~/types.d";
3
+ import clsx from "clsx";
4
+ import { buttonVariants } from "../button/Button";
5
+ import Href from "../href/Href";
6
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../tooltip";
7
+ interface Props {
8
+ children: ReactNode;
9
+ className?: string;
10
+ tooltip?: string;
11
+ url?: string;
12
+ color?: ThemeColor;
13
+ onClick?: () => void;
14
+ onRemove?: () => void;
15
+ }
16
+
17
+ export default function Badge({
18
+ children,
19
+ className,
20
+ onRemove,
21
+ onClick,
22
+ tooltip,
23
+ url,
24
+ color = "yellow",
25
+ }: Props) {
26
+ const badge = (
27
+ <span
28
+ className={clsx(
29
+ "ll-badge",
30
+ "text-xs rounded-full w-fit shadow overflow-hidden inline-flex items-stretch",
31
+ buttonVariants.variant.contained(color),
32
+ // badgeVariants.color[color],
33
+ // `variant-solid-${color}`,
34
+ // `text-${color}-4`,
35
+ className,
36
+ )}
37
+ >
38
+ {onClick ? (
39
+ <button className="px-2" onClick={onClick}>
40
+ {children}
41
+ </button>
42
+ ) : (
43
+ <span className="px-2">{children}</span>
44
+ )}
45
+ {onRemove && (
46
+ <button className="remove rounded-r-full pr-1 pl-0.5" onClick={onRemove}>
47
+ <i className="fe-cancel"></i>
48
+ </button>
49
+ )}
50
+ </span>
51
+ );
52
+ const wrappedBadge = url ? (
53
+ <Href href={url} ghost={true}>
54
+ {badge}
55
+ </Href>
56
+ ) : (
57
+ badge
58
+ );
59
+
60
+ if (!tooltip) {
61
+ return wrappedBadge;
62
+ }
63
+
64
+ return (
65
+ <Tooltip>
66
+ <TooltipContent>{tooltip}</TooltipContent>
67
+ <TooltipTrigger asChild={true}>{wrappedBadge}</TooltipTrigger>
68
+ </Tooltip>
69
+ );
70
+ }
@@ -0,0 +1,189 @@
1
+ import type { Meta, StoryObj, ReactRenderer } from "@storybook/react";
2
+ import { PartialStoryFn } from "@storybook/types";
3
+
4
+ import { Table, TableBody, TableCell, TableHeader, TableHeaderCell, TableRow } from "../table";
5
+
6
+ import { Button } from "../button";
7
+
8
+ const meta = {
9
+ title: "Components/Button",
10
+ component: Button,
11
+ argTypes: {
12
+ variant: {
13
+ control: {
14
+ type: "select",
15
+ },
16
+ options: ["contained", "light", "outlined", "text", "ghost"],
17
+ },
18
+ size: {
19
+ control: {
20
+ type: "select",
21
+ },
22
+ options: ["small", "medium", "large"],
23
+ },
24
+ color: {
25
+ control: {
26
+ type: "select",
27
+ },
28
+ options: ["yellow", "gray", "red", "orange", "green", "blue"],
29
+ },
30
+ },
31
+ parameters: {
32
+ layout: "fullscreen",
33
+ },
34
+ decorators: [
35
+ (Story) => (
36
+ <div className="bg-gray-1 p-4">
37
+ <Story />
38
+ </div>
39
+ ),
40
+ ] as ((story: PartialStoryFn<ReactRenderer, any>) => JSX.Element)[],
41
+ } satisfies Meta<typeof Button>;
42
+ export default meta;
43
+
44
+ type Story = StoryObj<typeof meta>;
45
+
46
+ export const Basic: Story = {
47
+ args: {
48
+ variant: "contained",
49
+ size: "medium",
50
+ color: "yellow",
51
+ children: "My button",
52
+ loading: undefined,
53
+ disabled: false,
54
+ fullWidth: false,
55
+ selected: false,
56
+ withRipple: false,
57
+ },
58
+ };
59
+
60
+ const variants = ["contained", "light", "outlined", "text", "ghost"] as const;
61
+ const colors = ["yellow", "gray", "red", "orange", "green", "blue"] as const;
62
+
63
+ export const Context = () => {
64
+ return (
65
+ <div>
66
+ hello
67
+ <a href="#">link</a>
68
+ world
69
+ <button>normal button</button>
70
+ <span tabIndex={0}>tab</span>
71
+ index
72
+ <h3 className="mt-8">Sizes</h3>
73
+ <div className="flex gap-2 items-center">
74
+ <Button size="small">small</Button>
75
+ <Button size="medium">medium</Button>
76
+ <Button size="large">large</Button>
77
+ </div>
78
+ <h3 className="mt-8">Variants</h3>
79
+ <div className="flex gap-2">
80
+ <Button variant="contained">solid</Button>
81
+ <Button variant="light">light</Button>
82
+ <Button variant="outlined">outlined</Button>
83
+ <Button variant="text">text</Button>
84
+ <Button variant="ghost">ghost</Button>
85
+ </div>
86
+ <h3 className="mt-8">States</h3>
87
+ <div className="flex gap-2 items-center">
88
+ <Button disabled={true}>disabled</Button>
89
+ <Button loading={false}>isLoading false</Button>
90
+ <Button loading={true}>isLoading true</Button>
91
+ </div>
92
+ <h3 className="mt-8">Default state</h3>
93
+ <Table>
94
+ <TableHeader>
95
+ <TableRow>
96
+ <TableHeaderCell>
97
+ <></>
98
+ </TableHeaderCell>
99
+ {colors.map((color) => (
100
+ <TableHeaderCell key={color}>{color}</TableHeaderCell>
101
+ ))}
102
+ </TableRow>
103
+ </TableHeader>
104
+ <TableBody>
105
+ {variants.map((variant) => (
106
+ <TableRow key={variant}>
107
+ <TableCell>{variant}</TableCell>
108
+ {colors.map((color) => (
109
+ <TableCell key={color} label={color}>
110
+ <div className="flex gap-2">
111
+ <Button variant={variant} color={color}>
112
+ Lorem
113
+ </Button>
114
+ </div>
115
+ </TableCell>
116
+ ))}
117
+ </TableRow>
118
+ ))}
119
+ </TableBody>
120
+ </Table>
121
+ <h3 className="mt-8">Only Selected state</h3>
122
+ <Table>
123
+ <TableHeader>
124
+ <TableRow>
125
+ <TableHeaderCell>
126
+ <></>
127
+ </TableHeaderCell>
128
+ {colors.map((color) => (
129
+ <TableHeaderCell key={color}>{color}</TableHeaderCell>
130
+ ))}
131
+ </TableRow>
132
+ </TableHeader>
133
+ <TableBody>
134
+ {variants.map((variant) => (
135
+ <TableRow key={variant}>
136
+ <TableCell>{variant}</TableCell>
137
+ {colors.map((color) => (
138
+ <TableCell key={color} label={color}>
139
+ <div className="flex gap-2">
140
+ <Button selected variant={variant} color={color}>
141
+ Lorem
142
+ </Button>
143
+ </div>
144
+ </TableCell>
145
+ ))}
146
+ </TableRow>
147
+ ))}
148
+ </TableBody>
149
+ </Table>
150
+ <h3 className="mt-8">Icons</h3>
151
+ <div className="flex items-center gap-4 mb-4">
152
+ <Button icon variant="contained" color="gray">
153
+ <i className="fe-cancel"></i>
154
+ </Button>
155
+ <Button icon variant="outlined" color="gray">
156
+ <i className="fe-cancel"></i>
157
+ </Button>
158
+ <Button icon variant="text" color="gray">
159
+ <i className="fe-cancel"></i>
160
+ </Button>
161
+ <Button icon variant="ghost" color="gray">
162
+ <i className="fe-cancel"></i>
163
+ </Button>
164
+ <Button variant="contained" color="gray" icon>
165
+ <i className="fe-cancel"></i>
166
+ <span>Cancel</span>
167
+ </Button>
168
+ </div>
169
+ <div className="flex items-center gap-4 mb-4">
170
+ <Button icon variant="contained">
171
+ <i className="fe-cancel"></i>
172
+ </Button>
173
+ <Button icon variant="outlined">
174
+ <i className="fe-cancel"></i>
175
+ </Button>
176
+ <Button icon variant="text">
177
+ <i className="fe-cancel"></i>
178
+ </Button>
179
+ <Button icon variant="ghost">
180
+ <i className="fe-cancel"></i>
181
+ </Button>
182
+ <Button variant="contained" icon>
183
+ <i className="fe-cancel"></i>
184
+ <span>Cancel</span>
185
+ </Button>
186
+ </div>
187
+ </div>
188
+ );
189
+ };
@@ -0,0 +1,20 @@
1
+ import { cleanup, fireEvent, render } from "@testing-library/react";
2
+ import { describe, it, expect, vi, afterEach } from "vitest";
3
+ import { Button } from ".";
4
+
5
+ afterEach(cleanup);
6
+
7
+ describe("<Button />", () => {
8
+ it("forwards className", () => {
9
+ const { getByRole } = render(<Button className="custom" />);
10
+ expect(getByRole("button")).toHaveClass("custom");
11
+ });
12
+
13
+ it("handle click", () => {
14
+ const mockOnClick = vi.fn();
15
+ const { getByRole } = render(<Button onClick={mockOnClick}>Click Me</Button>);
16
+
17
+ fireEvent.click(getByRole("button"));
18
+ expect(mockOnClick).toHaveBeenCalledTimes(1);
19
+ });
20
+ });
@@ -0,0 +1,200 @@
1
+ import { ComponentPropsWithRef, forwardRef, useImperativeHandle, useRef } from "react";
2
+ import clsx from "clsx";
3
+ import { ThemeColor } from "~/types.d";
4
+ import Loader from "../loader/Loader";
5
+ import { useRipple } from "~/hooks";
6
+
7
+ export interface ButtonProps extends ComponentPropsWithRef<"button"> {
8
+ withRipple?: boolean;
9
+
10
+ variant?: "contained" | "light" | "outlined" | "text" | "ghost";
11
+
12
+ size?: "small" | "medium" | "large" | "custom";
13
+
14
+ color?: ThemeColor;
15
+
16
+ children?: React.ReactNode;
17
+
18
+ /**
19
+ * undefined: hidden
20
+ * false: invisible
21
+ * true: visible
22
+ */
23
+ loading?: boolean;
24
+
25
+ disabled?: boolean;
26
+
27
+ fullWidth?: boolean;
28
+
29
+ focusable?: boolean;
30
+
31
+ /**
32
+ * For a selected item inside a group.
33
+ */
34
+ selected?: boolean;
35
+
36
+ icon?: boolean;
37
+ }
38
+
39
+ export const buttonVariants = {
40
+ size(icon: boolean, size: "small" | "medium" | "large" | "custom") {
41
+ if (size === "custom") {
42
+ return false;
43
+ }
44
+ switch (size) {
45
+ case "small":
46
+ return clsx(!icon && "px-2", "text-sm h-6");
47
+ case "medium":
48
+ return !icon && "px-4 h-8";
49
+ case "large":
50
+ return !icon && "px-8 h-12";
51
+ }
52
+ },
53
+ variant: {
54
+ contained(color: ThemeColor) {
55
+ return clsx(
56
+ "shadow hover:shadow-md focus:shadow-md active-full:shadow-lg outline-offset-0",
57
+ {
58
+ yellow:
59
+ "text-yellow-text bg-yellow-3 hover:bg-yellow-4 active-full:bg-yellow-5 focus-visible:outline-yellow-5",
60
+ gray: "text-gray-text bg-gray-3 hover:bg-gray-4 active-full:bg-gray-5 focus-visible:outline-gray-5",
61
+ red: "text-red-text bg-red-3 hover:bg-red-4 active-full:bg-red-5 focus-visible:outline-red-5",
62
+ orange:
63
+ "text-orange-text bg-orange-3 hover:bg-orange-4 active-full:bg-orange-5 focus-visible:outline-orange-5",
64
+ green:
65
+ "text-green-text bg-green-3 hover:bg-green-4 active-full:bg-green-5 focus-visible:outline-green-5",
66
+ blue: "text-blue-text bg-blue-3 hover:bg-blue-4 active-full:bg-blue-5 focus-visible:outline-blue-5",
67
+ }[color],
68
+ );
69
+ },
70
+ light(color: ThemeColor) {
71
+ return clsx(
72
+ "shadow hover:shadow-md focus:shadow-md active-full:shadow-lg outline-offset-0 bg-gray-0",
73
+ {
74
+ yellow:
75
+ "text-gray-text hover:text-yellow-text hover:bg-yellow-3 active-full:bg-yellow-4 dark:active-full:bg-yellow-4/50 focus-visible:outline-yellow-4",
76
+ gray: "text-gray-text hover:text-gray-text hover:bg-gray-3 active-full:bg-gray-4 dark:active-full:bg-gray-4/50 focus-visible:outline-gray-4",
77
+ red: "text-gray-text hover:text-red-text hover:bg-red-3 active-full:bg-red-4 dark:active-full:bg-red-4/50 focus-visible:outline-red-4",
78
+ orange:
79
+ "text-gray-text hover:text-orange-text hover:bg-orange-3 active-full:bg-orange-4 dark:active-full:bg-orange-4/50 focus-visible:outline-orange-4",
80
+ green:
81
+ "text-gray-text hover:text-green-text hover:bg-green-3 active-full:bg-green-4 dark:active-full:bg-green-4/50 focus-visible:outline-green-4",
82
+ blue: "text-gray-text hover:text-blue-text hover:bg-blue-3 active-full:bg-blue-4 dark:active-full:bg-blue-4/50 focus-visible:outline-blue-4",
83
+ }[color],
84
+ );
85
+ },
86
+ outlined(color: ThemeColor) {
87
+ return clsx(
88
+ "bg-gray-0 text-gray-7 outline outline-2 outline-offset-[-2px] focus-visible:ring-2 active-full:shadow-md",
89
+ {
90
+ yellow:
91
+ "hover:bg-yellow-1/50 active-full:bg-yellow-2/50 outline-yellow-5 focus-visible:ring-yellow-4",
92
+ gray: "hover:bg-gray-1 active-full:bg-gray-2 outline-gray-5 focus-visible:ring-gray-4",
93
+ red: "hover:bg-red-1/50 active-full:bg-red-2/50 outline-red-5 focus-visible:ring-red-4",
94
+ orange:
95
+ "hover:bg-orange-1/50 active-full:bg-orange-2/50 outline-orange-5 focus-visible:ring-orange-4",
96
+ green:
97
+ "hover:bg-green-1/50 active-full:bg-green-2/50 outline-green-5 focus-visible:ring-green-4",
98
+ blue: "hover:bg-blue-1/50 active-full:bg-blue-2/50 outline-blue-5 focus-visible:ring-blue-4",
99
+ }[color],
100
+ );
101
+ },
102
+ text(color: ThemeColor) {
103
+ return clsx(
104
+ "bg-transparent active-full:shadow-sm outline-offset-0",
105
+ {
106
+ yellow:
107
+ "hover:bg-yellow-1/50 active-full:bg-yellow-2/25 text-yellow-4 hover:text-yellow-5",
108
+ gray: "hover:bg-gray-1 active-full:bg-gray-2 text-gray-5 hover:text-gray-6 active-full:text-gray-7",
109
+ red: "hover:bg-red-1/50 active-full:bg-red-2/50 text-red-4 hover:text-red-5",
110
+ orange:
111
+ "hover:bg-orange-1/50 active-full:bg-orange-2/50 text-orange-4 hover:text-orange-5",
112
+ green: "hover:bg-green-1/50 active-full:bg-green-2/50 text-green-4 hover:text-green-5",
113
+ blue: "hover:bg-blue-1/50 active-full:bg-blue-2/50 text-blue-4 hover:text-blue-5",
114
+ }[color],
115
+ );
116
+ },
117
+ ghost(color: ThemeColor) {
118
+ return clsx(
119
+ "bg-transparent outline-offset-0",
120
+ {
121
+ yellow: "text-yellow-4 hover:text-yellow-5",
122
+ gray: "text-gray-5 hover:text-gray-6 active-full:text-gray-7",
123
+ red: "text-red-4 hover:text-red-5",
124
+ orange: "text-orange-4 hover:text-orange-5",
125
+ green: "text-green-4 hover:text-green-5",
126
+ blue: "text-blue-4 hover:text-blue-5",
127
+ }[color],
128
+ );
129
+ },
130
+ },
131
+ };
132
+
133
+ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
134
+ (
135
+ {
136
+ withRipple = true,
137
+ variant = "contained",
138
+ loading,
139
+ color = "yellow",
140
+ size = "medium",
141
+ focusable = true,
142
+ fullWidth,
143
+ className,
144
+ disabled,
145
+ children,
146
+ selected = false,
147
+ icon = false,
148
+ ...props
149
+ },
150
+ ref,
151
+ ) => {
152
+ const inputRef = useRef<HTMLButtonElement>(null);
153
+
154
+ useImperativeHandle<HTMLButtonElement | null, HTMLButtonElement | null>(
155
+ ref,
156
+ () => inputRef.current,
157
+ );
158
+
159
+ const notClickable = loading || disabled;
160
+
161
+ const ripples = useRipple(inputRef);
162
+
163
+ // check if overflow-clip-margin: 1px; is needed ?
164
+ return (
165
+ <button
166
+ tabIndex={focusable ? 0 : -1}
167
+ role="button"
168
+ ref={inputRef}
169
+ className={clsx(
170
+ "rounded-2xl cursor-pointer relative overflow-clip focus-visible:outline focus-visible:outline-2 no-underline border-0 inline-flex items-center transition-color-shadow duration-300 leading-5",
171
+ className,
172
+ buttonVariants.size(icon, size),
173
+ buttonVariants.variant[variant](color),
174
+ icon && "justify-center min-w-8 h-8 [&_i]:w-8 [&_:last-child:not(i,img)]:pr-4",
175
+ fullWidth && "w-full",
176
+ selected && "active",
177
+ notClickable && "disabled",
178
+ )}
179
+ data-variant={variant}
180
+ disabled={disabled}
181
+ aria-busy={loading}
182
+ {...props}
183
+ >
184
+ {!notClickable && withRipple && ripples}
185
+ {children}
186
+ {loading !== undefined && (
187
+ <span className="">
188
+ <Loader
189
+ color={color}
190
+ size="small"
191
+ className={clsx("ml-2 -mr-2", !loading && "invisible")}
192
+ />
193
+ </span>
194
+ )}
195
+ </button>
196
+ );
197
+ },
198
+ );
199
+
200
+ export default Button;
@@ -0,0 +1,46 @@
1
+ import { ThemeColor } from "~/types.d";
2
+ import { Meta } from "@storybook/react";
3
+ import { useState } from "react";
4
+
5
+ import { Button, ButtonGroup } from "../button";
6
+
7
+ const meta = {
8
+ title: "Components/ButtonGroup",
9
+ component: ButtonGroup,
10
+ } satisfies Meta<typeof ButtonGroup>;
11
+ export default meta;
12
+
13
+ const buttonLabels = ["one", "two", "three"];
14
+ const colors: ThemeColor[] = ["yellow", "gray", "blue", "red"];
15
+ const variants = ["contained", "light", "outlined", "text"] as const;
16
+ const directions = ["horizontal", "vertical"] as const;
17
+ export const Context = () => {
18
+ const [val, setVal] = useState("one");
19
+ return (
20
+ <div className="grid grid-cols-1 gap-8">
21
+ {directions.map((direction) => (
22
+ <div className="flex flex-col gap-4">
23
+ {variants.map((variant) => (
24
+ <div key={variant} className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-2">
25
+ {colors.map((color) => (
26
+ <ButtonGroup key={color} direction={direction}>
27
+ {buttonLabels.map((label) => (
28
+ <Button
29
+ variant={variant}
30
+ color={color}
31
+ key={label}
32
+ selected={val === label}
33
+ onClick={() => setVal(label)}
34
+ >
35
+ {label}
36
+ </Button>
37
+ ))}
38
+ </ButtonGroup>
39
+ ))}
40
+ </div>
41
+ ))}
42
+ </div>
43
+ ))}
44
+ </div>
45
+ );
46
+ };
@@ -0,0 +1,29 @@
1
+ import { ComponentProps, ReactNode } from "react";
2
+ import clsx from "clsx";
3
+
4
+ interface Props extends ComponentProps<"div"> {
5
+ children: ReactNode;
6
+ direction?: "horizontal" | "vertical";
7
+ }
8
+
9
+ const buttonGroupVariants = {
10
+ horizontal:
11
+ "[&_:not(:first-child)]:rounded-bl-none [&_:not(:last-child)]:rounded-tr-none [&_[data-variant='outlined']]:-mx-[1px]",
12
+ vertical:
13
+ "flex flex-col [&_:not(:first-child)]:rounded-tr-none [&_:not(:last-child)]:rounded-bl-none [&_[data-variant='outlined']]:-my-[1px]",
14
+ };
15
+
16
+ export default function ButtonGroup({ children, className, direction = "horizontal" }: Props) {
17
+ return (
18
+ <div
19
+ className={clsx(
20
+ "ll-button-group [&_:not(:first-child)]:rounded-tl-none [&_:not(:last-child)]:rounded-br-none",
21
+ buttonGroupVariants[direction],
22
+ `direction-${direction}`,
23
+ className,
24
+ )}
25
+ >
26
+ {children}
27
+ </div>
28
+ );
29
+ }
@@ -0,0 +1,2 @@
1
+ export { default as Button, type ButtonProps, buttonVariants } from "./Button";
2
+ export { default as ButtonGroup } from "./ButtonGroup";
@@ -0,0 +1,21 @@
1
+ import { Meta } from "@storybook/react";
2
+ import { cardConfig } from "./config";
3
+
4
+ const meta = {
5
+ title: "Components/Card",
6
+ } satisfies Meta;
7
+ export default meta;
8
+
9
+ export const Basic = () => {
10
+ return <div className={cardConfig.item}>Hello world</div>;
11
+ };
12
+
13
+ export const Multiple = () => {
14
+ return (
15
+ <div className={cardConfig.group}>
16
+ <div className={cardConfig.item}>Card 1</div>
17
+ <div className={cardConfig.item}>Card 2</div>
18
+ <div className={cardConfig.item}>Card 3</div>
19
+ </div>
20
+ );
21
+ };
@@ -0,0 +1,4 @@
1
+ export const cardConfig = {
2
+ item: "p-2 bg-gray-1 rounded-2xl",
3
+ group: "grid grid-cols-2 gap-4",
4
+ };
@@ -0,0 +1 @@
1
+ export { cardConfig } from "./config";
@@ -0,0 +1,15 @@
1
+ import clsx from "clsx";
2
+ import { ReactNode } from "react";
3
+
4
+ interface Props {
5
+ children: ReactNode;
6
+ className?: string;
7
+ }
8
+
9
+ export default function Code({ children, className }: Props) {
10
+ return (
11
+ <code className={clsx("px-1 rounded m-[1px] bg-gray-1 border border-gray-2", className)}>
12
+ {children}
13
+ </code>
14
+ );
15
+ }
@@ -0,0 +1,22 @@
1
+ import { Meta } from "@storybook/react";
2
+ import { ContextMenu, ContextMenuItem } from ".";
3
+ import { action } from "@storybook/addon-actions";
4
+
5
+ const onChangeAction = action("onChange");
6
+
7
+ const meta = {
8
+ title: "Components/ContextMenu",
9
+ } satisfies Meta;
10
+ export default meta;
11
+
12
+ export const Basic = () => {
13
+ return (
14
+ <ContextMenu>
15
+ <ContextMenuItem label="Back" onClick={onChangeAction} />
16
+ <ContextMenuItem label="Forward" />
17
+ <ContextMenuItem label="Reload" disabled />
18
+ <ContextMenuItem label="Save As..." />
19
+ <ContextMenuItem label="Print" />
20
+ </ContextMenu>
21
+ );
22
+ };