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,182 @@
1
+ import { Meta } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Button } from "../button";
4
+ import { Checkbox, Input } from "../input";
5
+ import { Select } from "../select";
6
+
7
+ const meta = {
8
+ title: "Components/QuickSettings",
9
+ decorators: (Story) => (
10
+ <div className="grid grid-cols-1 gap-8" style={{ width: "350px" }}>
11
+ <Story />
12
+ </div>
13
+ ),
14
+ } satisfies Meta;
15
+ export default meta;
16
+
17
+ const profileOptions = [
18
+ { value: "car", label: "Voiture" },
19
+ { value: "pedestrian", label: "Piéton" },
20
+ ];
21
+
22
+ const directionOptions = [
23
+ { value: "departure", label: "Départ" },
24
+ { value: "arrival", label: "Arrivée" },
25
+ ];
26
+
27
+ // const quicksettingConfig = {
28
+ // setting: "flex items-center justify-between first:text-gray-6 first:text-sm",
29
+ // };
30
+
31
+ export const Basic = () => {
32
+ const [profile, setProfile] = useState<string | null>("car");
33
+ const [direction, setDirection] = useState<string | null>("departure");
34
+ return (
35
+ <div className="grid grid-cols-1 gap-2">
36
+ <div>
37
+ <div className="p8n-setting">
38
+ <div>Coordonnées</div>
39
+ <div>
40
+ <span className="text-gray-6 text-sm">lon/lat </span>
41
+ 6.497886, 46.091857
42
+ </div>
43
+ </div>
44
+ <div className="p8n-setting">
45
+ <div>Altitude</div>
46
+ <div>
47
+ 500 <span className="text-gray-6 text-sm">m</span>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ <div className="actions flex gap-2">
52
+ <Button variant="text" color="gray" size="large" className="flex-1 min-w-0">
53
+ <span className="flex-center w-full">
54
+ <i className="fe-isochrone text-2xl"></i>
55
+ </span>
56
+ </Button>
57
+ <Button variant="text" color="gray" size="large" className="flex-1 min-w-0">
58
+ <span className="flex-center w-full">
59
+ <i className="fe-route text-2xl"></i>
60
+ </span>
61
+ </Button>
62
+ <Button variant="text" color="gray" size="large" className="flex-1 min-w-0">
63
+ <span className="flex-center w-full">RAW</span>
64
+ </Button>
65
+ </div>
66
+
67
+ <div className="actions flex gap-2">
68
+ <Button variant="text" color="gray" size="large" className="flex-1 min-w-0">
69
+ <span className="flex-center w-full">
70
+ <i className="fe-stopwatch"></i>
71
+ isochrone
72
+ </span>
73
+ </Button>
74
+ <Button variant="text" color="gray" size="large" className="flex-1 min-w-0">
75
+ <span className="flex-center w-full">
76
+ <i className="fe-ruler"></i>
77
+ isodistance
78
+ </span>
79
+ </Button>
80
+ </div>
81
+
82
+ <div className="ll-steps-extra">
83
+ <Button variant="ghost" color="gray">
84
+ <span
85
+ className="ll-marker"
86
+ style={{ "--marker-color": "#c0c0c0", "--marker-size": "34px" }}
87
+ >
88
+ <span className="marker">
89
+ <span className="ovale"></span>
90
+ <i className="fe-plus"></i>
91
+ </span>
92
+ <span className="target"></span>
93
+ </span>
94
+ <span>Ajouter un point</span>
95
+ </Button>
96
+
97
+ <Button icon variant="text" color="gray">
98
+ <i className="fe-sliders"></i>
99
+ </Button>
100
+ </div>
101
+
102
+ <div className="p8n-setting">
103
+ <div>Temps</div>
104
+ <Input suffix="min" defaultValue="0" />
105
+ </div>
106
+ <div className="p8n-setting">
107
+ <div>Distance</div>
108
+ <div>
109
+ <Input suffix="km" />
110
+ </div>
111
+ </div>
112
+
113
+ <div className="p8n-setting">
114
+ <div>Mode de transport</div>
115
+ <div>
116
+ <Select
117
+ variant="ghost"
118
+ options={profileOptions}
119
+ value={profile}
120
+ onChange={(o) => {
121
+ // @ts-ignore
122
+ setProfile(o.target.value);
123
+ }}
124
+ ></Select>
125
+ </div>
126
+ </div>
127
+ <div className="p8n-setting">
128
+ <div>Sens de parcours</div>
129
+ <Select
130
+ variant="ghost"
131
+ options={directionOptions}
132
+ value={direction}
133
+ onChange={(o) => {
134
+ // @ts-ignore
135
+ setDirection(o.target.value);
136
+ }}
137
+ ></Select>
138
+ </div>
139
+ <div className="p8n-setting multiple">
140
+ <div>Passages autorisés</div>
141
+ <div className="">
142
+ <Checkbox checked={true}>Péages</Checkbox>
143
+ <Checkbox checked={false}>Ponts</Checkbox>
144
+ </div>
145
+ </div>
146
+
147
+ <div className="p8n-setting multiple">
148
+ <div>Éviter</div>
149
+ <div className="">
150
+ <Checkbox checked={true}>
151
+ <span>Sections à péage</span>
152
+ </Checkbox>
153
+ <Checkbox checked={false}>
154
+ <span>Ponts</span>
155
+ </Checkbox>
156
+ <Checkbox checked={false}>
157
+ <span>Tunnels</span>
158
+ </Checkbox>
159
+ </div>
160
+ </div>
161
+
162
+ <div className="p8n-separator"></div>
163
+
164
+ <div>
165
+ <div className="p8n-setting">
166
+ <div>Distance</div>
167
+ <div>
168
+ 325 <span className="text-gray-6 text-sm">km</span>
169
+ </div>
170
+ </div>
171
+ <div className="p8n-setting">
172
+ <div>Durée</div>
173
+ <div>3h 25min</div>
174
+ </div>
175
+ </div>
176
+
177
+ <div className="text-right">
178
+ <Button>Calculer</Button>
179
+ </div>
180
+ </div>
181
+ );
182
+ };
@@ -0,0 +1,40 @@
1
+ html {
2
+ --sidebar-extra-height: 200px;
3
+ --sidebar-menu-width: 200px;
4
+ }
5
+
6
+ html,
7
+ body,
8
+ #storybook-root,
9
+ #app {
10
+ margin: 0;
11
+ padding: 0;
12
+ width: 100%;
13
+ height: 100%;
14
+ }
15
+
16
+ #app {
17
+ display: flex;
18
+ flex-direction: column;
19
+ }
20
+
21
+ #principal {
22
+ flex: 1;
23
+ display: flex;
24
+ }
25
+
26
+ #content {
27
+ flex: 1;
28
+ }
29
+
30
+ #menu-col {
31
+ border-right: 1px solid rgb(var(--color-gray-2));
32
+ flex: 0 0 var(--sidebar-menu-width);
33
+ position: relative;
34
+ }
35
+
36
+ #extra-row {
37
+ border-top: 1px solid rgb(var(--color-gray-2));
38
+ flex: 0 0 var(--sidebar-extra-height);
39
+ position: relative;
40
+ }
@@ -0,0 +1,30 @@
1
+ import { Meta } from "@storybook/react";
2
+ import ResizeArea from "./ResizeArea";
3
+ import "./ResizeArea.stories.css";
4
+
5
+ const meta = {
6
+ title: "Components/ResizeArea",
7
+ component: ResizeArea,
8
+ parameters: {
9
+ layout: "fullscreen",
10
+ },
11
+ } satisfies Meta<typeof ResizeArea>;
12
+ export default meta;
13
+
14
+ export const Basic = () => {
15
+ return (
16
+ <div id="app">
17
+ <div id="principal">
18
+ <div id="menu-col">
19
+ <ResizeArea name="menu" position="right" />
20
+ <div>Menu</div>
21
+ </div>
22
+ <div id="content">Content</div>
23
+ </div>
24
+ <div id="extra-row">
25
+ <ResizeArea name="extra" position="top" />
26
+ <div>Extra row</div>
27
+ </div>
28
+ </div>
29
+ );
30
+ };
@@ -0,0 +1,79 @@
1
+ import { useCallback, useEffect } from "react";
2
+ import clsx from "clsx";
3
+
4
+ interface Props {
5
+ name: string;
6
+ position: "top" | "bottom" | "left" | "right";
7
+ initialValue?: number;
8
+ }
9
+
10
+ export default function ResizeArea({ name, position, initialValue }: Props) {
11
+ useEffect(() => {
12
+ const cssVarName = `--sidebar-${name}-${["top", "bottom"].includes(position) ? "height" : "width"}`;
13
+ if (initialValue) {
14
+ document.documentElement.style.setProperty(cssVarName, `${initialValue}px`);
15
+ }
16
+ // we want to set the initial value, we don't want this value to be updated after
17
+ // eslint-disable-next-line react-hooks/exhaustive-deps
18
+ }, []);
19
+
20
+ useEffect(() => {
21
+ const cssVarName = `--sidebar-${name}-${["top", "bottom"].includes(position) ? "height" : "width"}`;
22
+ return () => {
23
+ // some cleanup
24
+ document.documentElement.style.removeProperty(cssVarName);
25
+ };
26
+ }, [name, position]);
27
+
28
+ const handlePointerMove = useCallback(
29
+ (e: MouseEvent) => {
30
+ const cssVarName = `--sidebar-${name}-${["top", "bottom"].includes(position) ? "height" : "width"}`;
31
+ let cssVarValue;
32
+ switch (position) {
33
+ case "top":
34
+ cssVarValue = document.documentElement.clientHeight - e.pageY;
35
+ break;
36
+ case "right":
37
+ cssVarValue = e.pageX;
38
+ break;
39
+ case "left":
40
+ cssVarValue = document.documentElement.clientWidth - e.pageX;
41
+ break;
42
+ case "bottom":
43
+ cssVarValue = e.pageY;
44
+ break;
45
+ }
46
+ document.documentElement.style.setProperty(cssVarName, `${cssVarValue}px`);
47
+ return () => {
48
+ document.documentElement.style.removeProperty(cssVarName);
49
+ };
50
+ },
51
+ [name, position],
52
+ );
53
+
54
+ const handlePointerUp = useCallback(() => {
55
+ const handler = handlePointerMove;
56
+ document.removeEventListener("pointermove", handler);
57
+ document.documentElement.classList.remove("cursor-row-resize", "cursor-col-resize");
58
+
59
+ return () => {
60
+ document.removeEventListener("pointermove", handler);
61
+ };
62
+ }, [handlePointerMove]);
63
+
64
+ function handlePointerDown() {
65
+ document.addEventListener("pointerup", handlePointerUp, { once: true });
66
+ document.addEventListener("pointermove", handlePointerMove);
67
+ if (["top", "bottom"].includes(position)) {
68
+ document.documentElement.classList.add(`cursor-row-resize`);
69
+ } else {
70
+ document.documentElement.classList.add(`cursor-col-resize`);
71
+ }
72
+ }
73
+
74
+ return (
75
+ <div className={clsx(["p8n-resize-area", position])}>
76
+ <button className="area-button" type="button" onPointerDown={handlePointerDown}></button>
77
+ </div>
78
+ );
79
+ }
@@ -0,0 +1,81 @@
1
+ import Scroll from "./Scroll";
2
+
3
+ export default {
4
+ component: Scroll,
5
+ title: "Components/Scroll",
6
+ };
7
+
8
+ export const Basic = () => {
9
+ return (
10
+ <div className="flex flex-col gap-2">
11
+ <Scroll className="border border-gray-2" style={{ height: "160px", width: "400px" }}>
12
+ <div className="px-4 text-justify">
13
+ <p>
14
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
15
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
16
+ Deleniti iure amet qui vero, blanditiis quos?
17
+ </p>
18
+ <p>
19
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
20
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
21
+ Deleniti iure amet qui vero, blanditiis quos?
22
+ </p>
23
+ <p>
24
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
25
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
26
+ Deleniti iure amet qui vero, blanditiis quos?
27
+ </p>
28
+ <p>
29
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
30
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
31
+ Deleniti iure amet qui vero, blanditiis quos?
32
+ </p>
33
+ </div>
34
+ </Scroll>
35
+ <Scroll horizontal className="border border-gray-2">
36
+ <div className="p-4 flex flex-nowrap">
37
+ <p className="pr-7" style={{ minWidth: 400 }}>
38
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
39
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
40
+ Deleniti iure amet qui vero, blanditiis quos?
41
+ </p>
42
+ <p className="pr-7" style={{ minWidth: 400 }}>
43
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
44
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
45
+ Deleniti iure amet qui vero, blanditiis quos?
46
+ </p>
47
+ <p className="pr-7" style={{ minWidth: 400 }}>
48
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
49
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
50
+ Deleniti iure amet qui vero, blanditiis quos?
51
+ </p>
52
+ <p className="pr-7" style={{ minWidth: 400 }}>
53
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
54
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
55
+ Deleniti iure amet qui vero, blanditiis quos?
56
+ </p>
57
+ <p className="pr-7" style={{ minWidth: 400 }}>
58
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
59
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
60
+ Deleniti iure amet qui vero, blanditiis quos?
61
+ </p>
62
+ <p className="pr-7" style={{ minWidth: 400 }}>
63
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
64
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
65
+ Deleniti iure amet qui vero, blanditiis quos?
66
+ </p>
67
+ <p className="pr-7" style={{ minWidth: 400 }}>
68
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
69
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
70
+ Deleniti iure amet qui vero, blanditiis quos?
71
+ </p>
72
+ <p style={{ minWidth: 400 }}>
73
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium unde, blanditiis
74
+ rem accusamus obcaecati enim amet, voluptatibus nemo facilis illum aut itaque in?
75
+ Deleniti iure amet qui vero, blanditiis quos?
76
+ </p>
77
+ </div>
78
+ </Scroll>
79
+ </div>
80
+ );
81
+ };
@@ -0,0 +1,111 @@
1
+ import { ComponentPropsWithoutRef, useEffect, useRef, useState } from "react";
2
+ import clsx from "clsx";
3
+
4
+ const TOLERANCE = 5;
5
+
6
+ interface Props extends ComponentPropsWithoutRef<"div"> {
7
+ horizontal?: boolean;
8
+ }
9
+
10
+ export const scrollVariants = {
11
+ vertical: {
12
+ inner: "overflow-y-auto h-full",
13
+ shadow: "h-1 left-0 right-0",
14
+ shadowStart: "top-0 bg-gradient-to-b from-gray-8/10",
15
+ shadowEnd: "bottom-0 bg-gradient-to-t from-gray-8/10",
16
+ child: "",
17
+ },
18
+ horizontal: {
19
+ inner: "overflow-x-auto",
20
+ shadow: "w-1 top-0 bottom-0",
21
+ shadowStart: "left-0 bg-gradient-to-r from-gray-8/10",
22
+ shadowEnd: "right-0 bg-gradient-to-l from-gray-8/10",
23
+ child: "inline-block",
24
+ },
25
+ };
26
+
27
+ export default function Scroll({ horizontal = false, className, children, ...rest }: Props) {
28
+ const [showStartShadow, setShowStartShadow] = useState(false);
29
+ const [showEndShadow, setShowEndShadow] = useState(false);
30
+ const scrollChildRef = useRef<HTMLDivElement>(null);
31
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
32
+
33
+ const position = horizontal ? "horizontal" : "vertical";
34
+
35
+ const setShadows = (container: HTMLDivElement | null, child: HTMLDivElement | null) => {
36
+ if (!container || !child) {
37
+ return;
38
+ }
39
+
40
+ const containerRect = container.getBoundingClientRect();
41
+
42
+ const childRect = child.getBoundingClientRect();
43
+
44
+ const isOnStartEdge = horizontal
45
+ ? containerRect.left - childRect.left < TOLERANCE
46
+ : containerRect.top - childRect.top < TOLERANCE;
47
+
48
+ const isOnEndEdge = horizontal
49
+ ? childRect.right - containerRect.right < TOLERANCE
50
+ : childRect.bottom - containerRect.bottom < TOLERANCE;
51
+
52
+ setShowStartShadow((previousShowStartShadow) =>
53
+ isOnStartEdge === previousShowStartShadow
54
+ ? !previousShowStartShadow
55
+ : previousShowStartShadow,
56
+ );
57
+
58
+ setShowEndShadow((previousShowEndShadow) =>
59
+ isOnEndEdge === previousShowEndShadow ? !previousShowEndShadow : previousShowEndShadow,
60
+ );
61
+ };
62
+
63
+ useEffect(() => {
64
+ const resizeObserver = new ResizeObserver(() => {
65
+ setShadows(scrollContainerRef.current, scrollChildRef.current);
66
+ });
67
+ resizeObserver.observe(scrollChildRef.current!);
68
+ resizeObserver.observe(scrollContainerRef.current!);
69
+
70
+ return () => {
71
+ resizeObserver.disconnect();
72
+ };
73
+ });
74
+
75
+ function handleScroll(event: React.UIEvent<HTMLDivElement, UIEvent>) {
76
+ if (!scrollChildRef?.current) {
77
+ return;
78
+ }
79
+ setShadows(event.currentTarget, scrollChildRef.current);
80
+ }
81
+
82
+ return (
83
+ <div className={clsx("ll-scroll-zone", "relative", className)} {...rest}>
84
+ <div
85
+ className={clsx(
86
+ "scroll-shadow z-[1] absolute opacity-0 transition-all",
87
+ scrollVariants[position].shadow,
88
+ scrollVariants[position].shadowStart,
89
+ showStartShadow && "opacity-100",
90
+ )}
91
+ ></div>
92
+ <div
93
+ className={clsx(
94
+ "scroll-shadow z-[1] absolute opacity-0 transition-all",
95
+ scrollVariants[position].shadow,
96
+ scrollVariants[position].shadowEnd,
97
+ showEndShadow && "opacity-100",
98
+ )}
99
+ ></div>
100
+ <div
101
+ className={scrollVariants[position].inner}
102
+ ref={scrollContainerRef}
103
+ onScroll={handleScroll}
104
+ >
105
+ <div className={scrollVariants[position].child} ref={scrollChildRef}>
106
+ {children}
107
+ </div>
108
+ </div>
109
+ </div>
110
+ );
111
+ }