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,35 @@
1
+ import { ComponentPropsWithRef, forwardRef, useRef } from "react";
2
+ import { type ThemeColor } from "~/types.d";
3
+ import { useCombinedRefs } from "~/hooks";
4
+ import clsx from "clsx";
5
+
6
+ interface Props extends ComponentPropsWithRef<"input"> {
7
+ disabled?: boolean;
8
+ color?: ThemeColor;
9
+ }
10
+
11
+ const Toggle = forwardRef<HTMLInputElement, Props>(
12
+ ({ color = "yellow", disabled = false, checked, className, children, ...rest }, ref) => {
13
+ const inputRef = useRef<HTMLInputElement>(null);
14
+ const combinedRef = useCombinedRefs(inputRef, ref);
15
+ return (
16
+ <label className={clsx("cursor-pointer flex items-center", disabled && "disabled")}>
17
+ <input
18
+ data-color={color}
19
+ ref={combinedRef}
20
+ disabled={disabled}
21
+ type="checkbox"
22
+ className={clsx(
23
+ "p8n-input-checkbox p8n-input-toggle ll-toggle w-[30px] h-[18px] box-border appearance-none rounded-xl inline-block relative mr-1 bg-gray-1 checked:bg-gray-0 after:absolute after:top-[3px] after:left-[3px] after:rounded-full after:w-[12px] after:h-[12px] after:bg-gray-2 after:outline after:outline-1 after:outline-gray-3 checked:after:bg-current checked:after:translate-x-[12px] checked:after:outline-current after:transition-all",
24
+ className,
25
+ )}
26
+ checked={checked}
27
+ {...rest}
28
+ />
29
+ {children}
30
+ </label>
31
+ );
32
+ },
33
+ );
34
+
35
+ export default Toggle;
@@ -0,0 +1,4 @@
1
+ export { default as Input, inputConfig } from "./Input";
2
+ export { default as Checkbox } from "./Checkbox";
3
+ export { default as RadioGroup } from "./RadioGroup";
4
+ export { default as Toggle } from "./Toggle";
@@ -0,0 +1,227 @@
1
+ import { Meta } from "@storybook/react";
2
+ import { ChangeEvent, useState } from "react";
3
+ import InputField from "./InputField";
4
+ import { Toggle, Checkbox, RadioGroup } from "../input";
5
+
6
+ const meta = {
7
+ title: "Components/InputField",
8
+ component: InputField,
9
+ } satisfies Meta<typeof InputField>;
10
+ export default meta;
11
+
12
+ export const Playbook = () => {
13
+ const [value, setValue] = useState("");
14
+ const [label, setLabel] = useState("Your label");
15
+ const [hint, setHint] = useState("Any hint related to input field");
16
+ const [placeholder, setPlaceholder] = useState("Ex: Fernando");
17
+ const [help, setHelp] = useState("Help message into the bottom");
18
+ const [warning, setWarning] = useState("");
19
+ const [error, setError] = useState("");
20
+
21
+ return (
22
+ <>
23
+ <div className="shadow-md rounded-2xl p-12 mb-12">
24
+ <InputField
25
+ label={label}
26
+ hint={hint}
27
+ placeholder={placeholder}
28
+ help={help}
29
+ value={value}
30
+ warning={warning}
31
+ error={error}
32
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}
33
+ />
34
+ </div>
35
+ <div className="grid grid-cols-2 gap-4">
36
+ <InputField
37
+ label="Label"
38
+ placeholder="What is your name ?"
39
+ value={label}
40
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setLabel(e.target.value)}
41
+ />
42
+ <InputField
43
+ label="Hint"
44
+ placeholder="One hint ?"
45
+ value={hint}
46
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setHint(e.target.value)}
47
+ />
48
+ <InputField
49
+ label="Placeholder"
50
+ value={placeholder}
51
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setPlaceholder(e.target.value)}
52
+ />
53
+ <InputField
54
+ label="Help"
55
+ value={help}
56
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setHelp(e.target.value)}
57
+ />
58
+ <InputField
59
+ label="Warning"
60
+ placeholder="warning message"
61
+ value={warning}
62
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setWarning(e.target.value)}
63
+ />
64
+ <InputField
65
+ label="Error"
66
+ placeholder="Error message"
67
+ value={error}
68
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setError(e.target.value)}
69
+ />
70
+ </div>
71
+ </>
72
+ );
73
+ };
74
+
75
+ export const Context = () => {
76
+ const [name, setName] = useState("");
77
+ const [gender, setGender] = useState<string | null>("female");
78
+ const [isAgree, setIsAgree] = useState(true);
79
+ const [isEnabled, setIsEnabled] = useState(true);
80
+
81
+ return (
82
+ <div className="flex flex-col gap-4">
83
+ <InputField
84
+ label="What is your name"
85
+ hint="Any hint related to input field"
86
+ placeholder="Ex: Fernando"
87
+ help="Help text for your input field"
88
+ value={name}
89
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setName(e.target.value)}
90
+ />
91
+ <InputField
92
+ label="What is your name"
93
+ hint="Any hint related to input field"
94
+ placeholder="Ex: Fernando"
95
+ help="Help text for your input field"
96
+ error="Input is required"
97
+ value={name}
98
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setName(e.target.value)}
99
+ />
100
+ <InputField
101
+ label="What is your name"
102
+ hint="Any hint related to input field"
103
+ placeholder="Ex: Fernando"
104
+ help="Help text for your input field"
105
+ warning="Only your firstname"
106
+ value={name}
107
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setName(e.target.value)}
108
+ />
109
+ <InputField
110
+ value={gender}
111
+ onChange={(value: string | null) => {
112
+ setGender(value);
113
+ }}
114
+ label="What is your gender"
115
+ hint="You don't have to answer"
116
+ as={RadioGroup}
117
+ options={[
118
+ { label: "Male", value: "male" },
119
+ { label: "Female", value: "female" },
120
+ { label: "I don't want to answer", value: "undefined" },
121
+ ]}
122
+ help="You still have to check a box"
123
+ />
124
+ <InputField
125
+ value={gender}
126
+ onChange={(value: string | null) => {
127
+ setGender(value);
128
+ }}
129
+ label="What is your gender"
130
+ hint="You don't have to answer"
131
+ as={RadioGroup}
132
+ error="This field is required"
133
+ options={[
134
+ { label: "Male", value: "male" },
135
+ { label: "Female", value: "female" },
136
+ { label: "I don't want to answer", value: "undefined" },
137
+ ]}
138
+ help="You still have to check a box"
139
+ />
140
+
141
+ <InputField
142
+ value={gender}
143
+ onChange={(value: string | null) => {
144
+ setGender(value);
145
+ }}
146
+ label="What is your gender"
147
+ hint="You don't have to answer"
148
+ as={RadioGroup}
149
+ warning="Make your choice"
150
+ options={[
151
+ { label: "Male", value: "male" },
152
+ { label: "Female", value: "female" },
153
+ { label: "I don't want to answer", value: "undefined" },
154
+ ]}
155
+ help="You still have to check a box"
156
+ />
157
+ <InputField
158
+ checked={isAgree}
159
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setIsAgree(e.target.checked)}
160
+ label="Gender"
161
+ hint="One hint"
162
+ as={Checkbox}
163
+ >
164
+ I agree
165
+ </InputField>
166
+ <InputField
167
+ checked={isAgree}
168
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setIsAgree(e.target.checked)}
169
+ label="Gender"
170
+ hint="One hint"
171
+ warning="Make your choice"
172
+ as={Checkbox}
173
+ >
174
+ I agree
175
+ </InputField>
176
+ <InputField
177
+ checked={isAgree}
178
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setIsAgree(e.target.checked)}
179
+ label="Gender"
180
+ hint="One hint"
181
+ error="This field is required"
182
+ as={Checkbox}
183
+ >
184
+ I agree
185
+ </InputField>
186
+
187
+ <InputField
188
+ checked={isEnabled}
189
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setIsEnabled(e.target.checked)}
190
+ label="Label"
191
+ hint=""
192
+ as={Toggle}
193
+ >
194
+ Enabled
195
+ </InputField>
196
+ <InputField
197
+ checked={isEnabled}
198
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setIsEnabled(e.target.checked)}
199
+ label="Label"
200
+ hint="One hint"
201
+ warning="Make your choice"
202
+ as={Toggle}
203
+ >
204
+ Enabled
205
+ </InputField>
206
+ <InputField
207
+ checked={isEnabled}
208
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setIsEnabled(e.target.checked)}
209
+ label="Label"
210
+ hint="One hint"
211
+ error="This field is required"
212
+ as={Toggle}
213
+ >
214
+ Enabled
215
+ </InputField>
216
+ <pre>
217
+ name: {name}
218
+ <br />
219
+ gender: {gender}
220
+ <br />
221
+ agree: {isAgree ? "True" : "False"}
222
+ <br />
223
+ enabled: {isEnabled ? "True" : "False"}
224
+ </pre>
225
+ </div>
226
+ );
227
+ };
@@ -0,0 +1,62 @@
1
+ import { ElementType, ForwardedRef, ReactNode, forwardRef, useId } from "react";
2
+ import { Input } from "../input";
3
+ import { type ThemeColor } from "~/types.d";
4
+ import { type PolymorphicPropsWithRef } from "~/env.d";
5
+
6
+ interface InputFieldOwnProps {
7
+ label?: ReactNode;
8
+ hint?: ReactNode;
9
+ help?: ReactNode;
10
+ error?: ReactNode | boolean;
11
+ warning?: ReactNode | boolean;
12
+ }
13
+
14
+ const defaultElement = Input;
15
+
16
+ type Props<E extends ElementType> = PolymorphicPropsWithRef<InputFieldOwnProps, E>;
17
+
18
+ const InputFieldBase = <E extends ElementType = typeof defaultElement>(
19
+ { label, hint, help, error, warning, as, ...rest }: Props<E>,
20
+ ref: ForwardedRef<Element>,
21
+ ) => {
22
+ const id = useId();
23
+ const Element: ElementType = as || defaultElement;
24
+
25
+ const labelElement = label && <span className="font-bold">{label}</span>;
26
+ const hintElement = hint && <span className="ml-auto text-gray-6 text-sm">{hint}</span>;
27
+ const errorElement = error && typeof error !== "boolean" && (
28
+ <span className="text-red-4 font-medium">
29
+ <i className="fe-circle-exclamation"></i>
30
+ <span>{error}</span>
31
+ </span>
32
+ );
33
+ const warningElement = warning && typeof warning !== "boolean" && (
34
+ <span className="text-orange-4 font-medium">
35
+ <i className="fe-circle-exclamation"></i>
36
+ <span>{warning}</span>
37
+ </span>
38
+ );
39
+
40
+ const color: ThemeColor = error ? "red" : warning ? "orange" : "yellow";
41
+
42
+ return (
43
+ <div>
44
+ {label || hint ? (
45
+ <label className="flex items-end mb-1" htmlFor={id}>
46
+ {labelElement}
47
+ {hintElement}
48
+ </label>
49
+ ) : (
50
+ <label htmlFor={id} className="invisible"></label>
51
+ )}
52
+ <Element ref={ref} id={id} color={color} {...rest} />
53
+ <div className="text-gray-6 text-sm mt-1">{errorElement || warningElement || help}</div>
54
+ </div>
55
+ );
56
+ };
57
+ InputFieldBase.displayName = "InputField";
58
+
59
+ const InputField: <E extends ElementType = typeof defaultElement>(props: Props<E>) => ReactNode =
60
+ forwardRef(InputFieldBase);
61
+
62
+ export default InputField;
@@ -0,0 +1,37 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import Loader from "./Loader";
3
+
4
+ const meta = {
5
+ title: "Components/Loader",
6
+ component: Loader,
7
+ argTypes: {
8
+ color: {
9
+ control: {
10
+ type: "select",
11
+ },
12
+ options: ["yellow", "gray", "red", "orange", "green", "blue"],
13
+ },
14
+ },
15
+ } satisfies Meta<typeof Loader>;
16
+ export default meta;
17
+
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Playbook: Story = {
21
+ args: {
22
+ size: "medium",
23
+ color: "yellow",
24
+ },
25
+ };
26
+
27
+ export const Context = () => (
28
+ <div className="flex gap-2 flex-col">
29
+ <Loader size="small" />
30
+ <br />
31
+ <Loader size="medium" color="yellow" />
32
+ <br />
33
+ <Loader size="large" color="red" />
34
+ <br />
35
+ <Loader size="medium" color="gray" />
36
+ </div>
37
+ );
@@ -0,0 +1,54 @@
1
+ import { CSSProperties, ComponentPropsWithoutRef, useId } from "react";
2
+ import { ThemeColor } from "~/types.d";
3
+ import clsx from "clsx";
4
+ import { colorVariants } from "~/lib/tailwindVariants";
5
+
6
+ interface Props extends ComponentPropsWithoutRef<"svg"> {
7
+ size?: "small" | "medium" | "large";
8
+ color?: ThemeColor;
9
+ }
10
+
11
+ const sizeConfig = {
12
+ small: "w-6 h-6 text-base",
13
+ medium: "w-8 h-8 text-[2rem]",
14
+ large: "w-12 h-12 text-[3rem]",
15
+ };
16
+
17
+ const trackStyle: CSSProperties = {
18
+ strokeLinecap: "round",
19
+ strokeWidth: 2,
20
+ };
21
+ const circleStyle: CSSProperties = {
22
+ strokeLinecap: "round",
23
+ strokeWidth: 2,
24
+
25
+ /**
26
+ * $total-length: 43.699; // total length of path, calculated by getTotalLength() in JS
27
+ * $looping-percent: 40;
28
+ * $looping-length: math.div($looping-percent * $total-length, 100) = 17.464;
29
+ * stroke-dasharray: #{$looping-length * 1px}, #{($total-length - $looping-length) * 1px};
30
+ */
31
+ strokeDasharray: "17.45px, 26.21px",
32
+ };
33
+
34
+ export default function Loader({ size = "medium", color = "blue", className, ...rest }: Props) {
35
+ const id = useId();
36
+ return (
37
+ <svg
38
+ xmlns="http://www.w3.org/2000/svg"
39
+ className={clsx("inline-block", sizeConfig[size], colorVariants[color].text, className)}
40
+ viewBox="0 0 16 16"
41
+ {...rest}
42
+ >
43
+ <defs>
44
+ <circle id={id} cx="8" cy="8" r="7" />
45
+ </defs>
46
+ <use href={`#${id}`} className="opacity-25 fill-none stroke-current" style={trackStyle} />
47
+ <use
48
+ href={`#${id}`}
49
+ className="fill-none stroke-current animate-loader-stroke"
50
+ style={circleStyle}
51
+ />
52
+ </svg>
53
+ );
54
+ }
@@ -0,0 +1,58 @@
1
+ import { ComponentProps } from "react";
2
+ import { useModalContext } from ".";
3
+ import clsx from "clsx";
4
+ import { Button } from "../button";
5
+ import Scroll from "../scroll/Scroll";
6
+
7
+ interface ModalHeaderProps extends ComponentProps<"h2"> {
8
+ closeButton?: boolean;
9
+ }
10
+ export function ModalHeader({
11
+ children,
12
+ className,
13
+ closeButton = true,
14
+ ...props
15
+ }: ModalHeaderProps) {
16
+ const { setOpen } = useModalContext();
17
+
18
+ return (
19
+ <>
20
+ {closeButton && (
21
+ <div className="p-1 float-right">
22
+ <Button icon variant="text" color="gray" onClick={() => setOpen(false)}>
23
+ <i className="fe-cancel"></i>
24
+ </Button>
25
+ </div>
26
+ )}
27
+ <header className={clsx("flex items-center px-2 pt-2", className)} {...props}>
28
+ <h4>{children}</h4>
29
+ </header>
30
+ </>
31
+ );
32
+ }
33
+
34
+ interface ModalDescriptionProps extends ComponentProps<"div"> {
35
+ height?: number;
36
+ }
37
+ export function ModalDescription({ children, className, height, ...props }: ModalDescriptionProps) {
38
+ if (!height) {
39
+ return (
40
+ <div className={clsx("p-2", className)} {...props}>
41
+ {children}
42
+ </div>
43
+ );
44
+ }
45
+ return (
46
+ <div className={clsx("py-2", className)} {...props}>
47
+ <Scroll style={{ height: `${height}px` }}>{children}</Scroll>
48
+ </div>
49
+ );
50
+ }
51
+
52
+ export function ModalFooter({ children, className, ...props }: ComponentProps<"footer">) {
53
+ return (
54
+ <footer className={clsx("relative z-10 px-2 pb-2", className)} {...props}>
55
+ {children}
56
+ </footer>
57
+ );
58
+ }
@@ -0,0 +1,113 @@
1
+ import { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Button } from "../button";
4
+
5
+ import { Modal, ModalTrigger, ModalContent, ModalDescription, ModalHeader, ModalFooter } from ".";
6
+
7
+ const meta = {
8
+ title: "Components/Modal",
9
+ component: Modal,
10
+ } satisfies Meta<typeof Modal>;
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof meta>;
14
+
15
+ export const Basic: Story = {
16
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
17
+ render: ({ children, ...args }) => {
18
+ return (
19
+ <Modal {...args}>
20
+ <ModalTrigger>open Modal</ModalTrigger>
21
+ <ModalContent>
22
+ <ModalHeader>Header</ModalHeader>
23
+ <ModalDescription>Content</ModalDescription>
24
+ </ModalContent>
25
+ </Modal>
26
+ );
27
+ },
28
+ args: {
29
+ initialOpen: false,
30
+ color: "default",
31
+ },
32
+ };
33
+
34
+ export const Context = () => {
35
+ const [isOpen, setIsOpen] = useState(false);
36
+ return (
37
+ <Modal
38
+ open={isOpen}
39
+ onOpen={(e) => {
40
+ console.log("setIsOpen", e);
41
+ setIsOpen(e);
42
+ }}
43
+ >
44
+ <ModalTrigger>open Modal</ModalTrigger>
45
+ <ModalContent>
46
+ <ModalHeader>Header</ModalHeader>
47
+ <ModalDescription>Content</ModalDescription>
48
+ <ModalFooter>
49
+ <div className="flex justify-between">
50
+ <Button variant="text" color="gray" onClick={() => setIsOpen(false)}>
51
+ Cancel
52
+ </Button>
53
+ <Button color="yellow" onClick={() => setIsOpen(false)}>
54
+ Accept
55
+ </Button>
56
+ </div>
57
+ </ModalFooter>
58
+ </ModalContent>
59
+ </Modal>
60
+ );
61
+ };
62
+
63
+ export const ScrollableModal = () => {
64
+ const [isOpen, setIsOpen] = useState(false);
65
+ return (
66
+ <Modal
67
+ open={isOpen}
68
+ onOpen={(e) => {
69
+ console.log("setIsOpen", e);
70
+ setIsOpen(e);
71
+ }}
72
+ >
73
+ <ModalTrigger>open scrollable modal</ModalTrigger>
74
+ <ModalContent>
75
+ <ModalHeader>Header</ModalHeader>
76
+ <ModalDescription height={160}>
77
+ <div className="px-4 text-justify">
78
+ <p>
79
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
80
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
81
+ Deleniti iure amet qui vero, blanditiis quos?
82
+ </p>
83
+ <p>
84
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
85
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
86
+ Deleniti iure amet qui vero, blanditiis quos?
87
+ </p>
88
+ <p>
89
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
90
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
91
+ Deleniti iure amet qui vero, blanditiis quos?
92
+ </p>
93
+ <p>
94
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
95
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
96
+ Deleniti iure amet qui vero, blanditiis quos?
97
+ </p>
98
+ </div>
99
+ </ModalDescription>
100
+ <ModalFooter>
101
+ <div className="flex justify-between">
102
+ <Button variant="text" color="gray" onClick={() => setIsOpen(false)}>
103
+ Cancel
104
+ </Button>
105
+ <Button color="yellow" onClick={() => setIsOpen(false)}>
106
+ Accept
107
+ </Button>
108
+ </div>
109
+ </ModalFooter>
110
+ </ModalContent>
111
+ </Modal>
112
+ );
113
+ };
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from "react";
2
+ import { ModalOptions } from "./interface";
3
+ import useModal from "./useModal";
4
+ import { ModalContext } from "./useModalContext";
5
+
6
+ interface Props extends ModalOptions {
7
+ children?: ReactNode;
8
+ }
9
+
10
+ export default function Modal({ children, ...options }: Props) {
11
+ const modal = useModal(options);
12
+ return <ModalContext.Provider value={modal}>{children}</ModalContext.Provider>;
13
+ }
@@ -0,0 +1,46 @@
1
+ import {
2
+ FloatingFocusManager,
3
+ FloatingOverlay,
4
+ FloatingPortal,
5
+ useMergeRefs,
6
+ } from "@floating-ui/react";
7
+ import { ComponentProps, forwardRef } from "react";
8
+ import { useModalContext } from ".";
9
+ import clsx from "clsx";
10
+ import { Dialog } from "../dialog";
11
+
12
+ const ModalContent = forwardRef<HTMLDivElement, ComponentProps<"div">>(
13
+ ({ style, children, ...props }, propRef) => {
14
+ const context = useModalContext();
15
+ const floatingContext = context.context;
16
+
17
+ const ref = useMergeRefs([context.refs.setFloating, propRef]);
18
+ if (!context.open) {
19
+ return null;
20
+ }
21
+
22
+ return (
23
+ <FloatingPortal>
24
+ <FloatingOverlay
25
+ className={clsx(["flex-center bg-gray-7/40 z-overlay", "animate-fade-in-opacity"])}
26
+ lockScroll
27
+ >
28
+ <FloatingFocusManager context={floatingContext}>
29
+ <Dialog
30
+ className={clsx("w-96 max-w-full", `border-${context.color}-2`, "animate-fade-in")}
31
+ ref={ref}
32
+ aria-labelledby={context.labelId}
33
+ aria-describedby={context.descriptionId}
34
+ style={style}
35
+ {...context.getFloatingProps(props)}
36
+ >
37
+ {children}
38
+ </Dialog>
39
+ </FloatingFocusManager>
40
+ </FloatingOverlay>
41
+ </FloatingPortal>
42
+ );
43
+ },
44
+ );
45
+
46
+ export default ModalContent;