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,66 @@
1
+ import { useState } from "react";
2
+ import { Button } from "../button";
3
+ import Tabs from "./Tabs";
4
+
5
+ export default {
6
+ title: "Components/Tabs",
7
+ component: Tabs,
8
+ };
9
+
10
+ export const Basic = () => {
11
+ const tabs = [
12
+ {
13
+ id: "bulbasaur",
14
+ title: "Bulbasaur",
15
+ content: (
16
+ <>
17
+ <p>
18
+ There is a plant seed on its back right from the day this Pokémon is born. The seed
19
+ slowly grows larger. There is a plant seed on its back right from the day this Pokémon
20
+ is born. The seed slowly grows larger. There is a plant seed on its back right from the
21
+ day this Pokémon is born. The seed slowly grows larger.
22
+ </p>
23
+ </>
24
+ ),
25
+ },
26
+ {
27
+ id: "charmander",
28
+ title: "Charmander",
29
+ content: (
30
+ <>
31
+ <p>
32
+ It has a preference for hot things. When it rains, steam is said to spout from the tip
33
+ of its tail. It has a preference for hot things. When it rains, steam is said to spout
34
+ from the tip of its tail. It has a preference for hot things. When it rains, steam is
35
+ said to spout from the tip of its tail.
36
+ </p>
37
+ </>
38
+ ),
39
+ },
40
+ {
41
+ id: "squirtle",
42
+ title: "Squirtle",
43
+ content: (
44
+ <>
45
+ <p>
46
+ When it retracts its long neck into its shell, it squirts out water with vigorous force.
47
+ When it retracts its long neck into its shell, it squirts out water with vigorous force.
48
+ When it retracts its long neck into its shell, it squirts out water with vigorous force.
49
+ </p>
50
+ </>
51
+ ),
52
+ },
53
+ ];
54
+ const [id, setId] = useState<string | number>("bulbasaur");
55
+ return (
56
+ <div className="flex flex-col gap-2">
57
+ <Tabs tabs={tabs} stickyTabs={true} value={id} onChange={setId}>
58
+ <Button icon variant="text" color="gray">
59
+ <i className="fe-sidebar-collapse"></i>
60
+ </Button>
61
+ </Tabs>
62
+ <Tabs tabs={tabs} value={id} onChange={setId} />
63
+ <Tabs tabs={tabs} fullWidth={true} value={id} onChange={setId} />
64
+ </div>
65
+ );
66
+ };
@@ -0,0 +1,80 @@
1
+ import { ReactNode } from "react";
2
+ import clsx from "clsx";
3
+
4
+ /* TODO: fix stickyTabs with overflow: hidden */
5
+
6
+ export interface Tab {
7
+ id: string | number;
8
+ title: ReactNode;
9
+ content?: ReactNode;
10
+ }
11
+
12
+ interface Props {
13
+ tabs?: Tab[];
14
+ value: number | string;
15
+ /**
16
+ * Will make tabs stick to the top of the container when overflowing
17
+ */
18
+ stickyTabs?: boolean;
19
+ /**
20
+ * Tabs will take the maximum width and divide equally
21
+ */
22
+ fullWidth?: boolean;
23
+ onChange: (id: number | string) => void;
24
+
25
+ children?: ReactNode;
26
+
27
+ className?: string;
28
+ contentClassName?: string;
29
+ listClassName?: string;
30
+ }
31
+
32
+ export default function Tabs({
33
+ className,
34
+ listClassName,
35
+ contentClassName,
36
+ tabs = [],
37
+ value,
38
+ onChange,
39
+ fullWidth = false,
40
+ stickyTabs = false,
41
+ children,
42
+ }: Props) {
43
+ const content = tabs.find((t) => t.id === value)?.content;
44
+ return (
45
+ <div className={clsx("ll-tabs md:rounded-sm shadow bg-gray-2 overflow-hidden", className)}>
46
+ <div
47
+ role="tablist"
48
+ className={clsx("tabs-list flex", stickyTabs && "sticky z-[1] top-0", listClassName)}
49
+ >
50
+ {tabs.map(({ title, id }) => {
51
+ return (
52
+ <div
53
+ key={id}
54
+ className={clsx(
55
+ "tabs-list-item border-t-4 hover:text-gray-7 transition-all",
56
+ fullWidth && "text-center flex-1",
57
+ value === id
58
+ ? "border-t-yellow-3 bg-gray-0 text-gray-7"
59
+ : "border-t-transparent text-gray-6",
60
+ )}
61
+ >
62
+ <button
63
+ className="px-2 py-1 focus-visible:outline-offset-0 w-full"
64
+ onClick={(event) => {
65
+ event.stopPropagation();
66
+ event.preventDefault();
67
+ onChange(id);
68
+ }}
69
+ >
70
+ {title}
71
+ </button>
72
+ </div>
73
+ );
74
+ })}
75
+ {children && <div className="extra ml-auto mr-2 flex items-center">{children}</div>}
76
+ </div>
77
+ <div className={clsx("p-2 bg-gray-0 shadow", contentClassName)}>{content}</div>
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,17 @@
1
+ import { ReactNode, isValidElement } from "react";
2
+ import { TooltipOptions } from "./interface";
3
+ import { Tooltip, TooltipContent, TooltipTrigger } from ".";
4
+
5
+ interface Props extends TooltipOptions {
6
+ children: ReactNode;
7
+ content: ReactNode;
8
+ }
9
+
10
+ export default function SimpleTooltip({ content, children, contentClassName, ...options }: Props) {
11
+ return (
12
+ <Tooltip {...options}>
13
+ <TooltipContent className={contentClassName}>{content}</TooltipContent>
14
+ <TooltipTrigger asChild={isValidElement(children)}>{children}</TooltipTrigger>
15
+ </Tooltip>
16
+ );
17
+ }
@@ -0,0 +1,129 @@
1
+ import { HTMLProps, forwardRef } from "react";
2
+ import { useDrag } from "@use-gesture/react";
3
+ import { useSpring, animated } from "@react-spring/web";
4
+ import Tooltip from "./Tooltip";
5
+ import SimpleTooltip from "./SimpleTooltip";
6
+
7
+ export default {
8
+ title: "Components/Tooltip",
9
+ component: Tooltip,
10
+ };
11
+
12
+ const Box = forwardRef<HTMLDivElement, HTMLProps<HTMLDivElement>>((props, ref) => (
13
+ <div
14
+ ref={ref}
15
+ className="shadow-lg w-[100px] h-[100px] bg-gray-2 flex-center"
16
+ style={{
17
+ margin: "100px",
18
+ }}
19
+ {...props}
20
+ ></div>
21
+ ));
22
+
23
+ export const Basic = () => {
24
+ return (
25
+ <div>
26
+ <p>
27
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Distinctio neque dolorum soluta
28
+ suscipit,&nbsp;
29
+ <SimpleTooltip content="infos" placement="bottom">
30
+ dessous
31
+ </SimpleTooltip>
32
+ &nbsp;aut unde provident? Optio ipsum provident unde! Nulla, dignissimos recusandae. Eveniet
33
+ ut quam voluptatum accusantium aspernatur?
34
+ </p>
35
+ <p>
36
+ Lorem ipsum dolor sit amet consectetur sit amet consectetur sit amet consectetur sit amet
37
+ consectetur sit amet consectetursit amet consectetur adipisicing elit. Distinctio neque
38
+ dolorum soluta suscipit,&nbsp;
39
+ <SimpleTooltip content="infos" placement="top">
40
+ top
41
+ </SimpleTooltip>
42
+ &nbsp; aut unde provident? Optio ipsum provident unde! Nulla, dignissimos recusandae.
43
+ Eveniet ut quam voluptatum accusantium aspernatur?
44
+ </p>
45
+ <p>
46
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Distinctio neque dolorum soluta
47
+ suscipit, sit amet consectetur&nbsp;
48
+ <SimpleTooltip content="infos" placement="left">
49
+ gauche
50
+ </SimpleTooltip>
51
+ &nbsp; aut unde provident? Optio ipsum provident unde! Nulla, dignissimos recusandae.
52
+ Eveniet ut quam voluptatum accusantium aspernatur?
53
+ </p>
54
+ <p>
55
+ Lorem ipsum dolor sit amet consectetur sit amet consectetur sit amet consectetur adipisicing
56
+ elit. Distinctio neque dolorum soluta suscipit, &nbsp;
57
+ <SimpleTooltip content="infos" placement="right">
58
+ droite
59
+ </SimpleTooltip>
60
+ &nbsp; aut unde provident? Optio ipsum provident unde! Nulla, dignissimos recusandae.
61
+ Eveniet ut quam voluptatum accusantium aspernatur?
62
+ </p>
63
+
64
+ <div className="grid grid-cols-repeat-fill-300 gap-8">
65
+ <SimpleTooltip color="yellow" open={true} placement="right-start" content="infos">
66
+ <Box />
67
+ </SimpleTooltip>
68
+ <SimpleTooltip open={true} placement="right" content="infos">
69
+ <Box />
70
+ </SimpleTooltip>
71
+ <SimpleTooltip open={true} placement="right-end" content="infos">
72
+ <Box />
73
+ </SimpleTooltip>
74
+ <SimpleTooltip color="yellow" open={true} placement="left-start" content="infos">
75
+ <Box />
76
+ </SimpleTooltip>
77
+ <SimpleTooltip color="gray" open={true} placement="left" content="infos">
78
+ <Box />
79
+ </SimpleTooltip>
80
+ <SimpleTooltip color="orange" open={true} placement="left-end" content="infos">
81
+ <Box />
82
+ </SimpleTooltip>
83
+ <SimpleTooltip color="red" open={true} placement="top-start" content="infos">
84
+ <Box />
85
+ </SimpleTooltip>
86
+ <SimpleTooltip color="blue" open={true} placement="top" content="infos">
87
+ <Box />
88
+ </SimpleTooltip>
89
+ <SimpleTooltip color="green" open={true} placement="top-end" content="infos">
90
+ <Box />
91
+ </SimpleTooltip>
92
+ <SimpleTooltip color="red" open={true} placement="bottom-start" content="infos">
93
+ <Box />
94
+ </SimpleTooltip>
95
+ <SimpleTooltip open={true} placement="bottom" content="infos">
96
+ <Box />
97
+ </SimpleTooltip>
98
+ <SimpleTooltip open={true} placement="bottom-end" content="infos">
99
+ <Box />
100
+ </SimpleTooltip>
101
+ </div>
102
+ </div>
103
+ );
104
+ };
105
+
106
+ export const Draggable = () => {
107
+ const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }));
108
+ const bind = useDrag(({ event, offset: [x, y] }) => {
109
+ event.preventDefault();
110
+ api.start({ x, y, immediate: true });
111
+ });
112
+ return (
113
+ <div className="container">
114
+ <SimpleTooltip content="infos" placement="top" open={true} color="yellow">
115
+ <animated.div
116
+ {...bind()}
117
+ className="shadow-lg w-[100px] h-[100px] bg-gray-2 flex-center"
118
+ style={{
119
+ x,
120
+ y,
121
+ cursor: "pointer",
122
+ }}
123
+ >
124
+ <span>Box</span>
125
+ </animated.div>
126
+ </SimpleTooltip>
127
+ </div>
128
+ );
129
+ };
@@ -0,0 +1,18 @@
1
+ import { cleanup, fireEvent, render } from "@testing-library/react";
2
+ import { afterEach, describe, it } from "vitest";
3
+ import { SimpleTooltip } from ".";
4
+
5
+ afterEach(cleanup);
6
+
7
+ describe("<Tooltip />", () => {
8
+ it("show the popup", async () => {
9
+ const { getByRole, findByText } = render(
10
+ <SimpleTooltip openDelay={1} content="details">
11
+ tooltip
12
+ </SimpleTooltip>,
13
+ );
14
+
15
+ fireEvent.mouseEnter(getByRole("button"));
16
+ findByText("details");
17
+ });
18
+ });
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from "react";
2
+ import { TooltipOptions } from "./interface";
3
+ import useTooltip from "./useTooltip";
4
+ import { TooltipContext } from "./useTooltipContext";
5
+
6
+ interface Props extends TooltipOptions {
7
+ children: ReactNode;
8
+ }
9
+
10
+ export default function Tooltip({ children, ...options }: Props) {
11
+ const tooltip = useTooltip(options);
12
+ return <TooltipContext.Provider value={tooltip}>{children}</TooltipContext.Provider>;
13
+ }
@@ -0,0 +1,41 @@
1
+ import { ComponentProps, forwardRef } from "react";
2
+ import useTooltipContext from "./useTooltipContext";
3
+ import { FloatingPortal, useMergeRefs } from "@floating-ui/react";
4
+ import clsx from "clsx";
5
+ import { computeArrowStyle } from "../dialog/util";
6
+ import { Dialog } from "../dialog";
7
+
8
+ const TooltipContent = forwardRef<HTMLDivElement, ComponentProps<"div">>(
9
+ ({ style, children, className, ...props }, propRef) => {
10
+ const context = useTooltipContext();
11
+ const ref = useMergeRefs([context.refs.setFloating, propRef]);
12
+ if (!context.open) {
13
+ return null;
14
+ }
15
+ return (
16
+ <FloatingPortal>
17
+ <div
18
+ ref={ref}
19
+ className={clsx("z-tooltip", context.middlewareData.hide?.referenceHidden && "invisible")}
20
+ style={{ ...context.floatingStyles, ...style }}
21
+ {...context.getFloatingProps(props)}
22
+ >
23
+ <Dialog
24
+ placement={context.placement}
25
+ className={clsx("animate-fade-in px-2 py-1 max-w-80 text-sm", className)}
26
+ color={context.color}
27
+ >
28
+ {children}
29
+ <div
30
+ ref={context.arrowRef}
31
+ style={computeArrowStyle(context)}
32
+ className="p8n-arrow"
33
+ ></div>
34
+ </Dialog>
35
+ </div>
36
+ </FloatingPortal>
37
+ );
38
+ },
39
+ );
40
+
41
+ export default TooltipContent;
@@ -0,0 +1,36 @@
1
+ import { HTMLProps, cloneElement, forwardRef, isValidElement } from "react";
2
+ import useTooltipContext from "./useTooltipContext";
3
+ import { useMergeRefs } from "@floating-ui/react";
4
+
5
+ interface Props extends HTMLProps<HTMLElement> {
6
+ /* allows the user to pass any element as the anchor instead of <button> */
7
+ asChild?: boolean;
8
+ }
9
+
10
+ const TooltipTrigger = forwardRef<HTMLElement, Props>(
11
+ ({ children, asChild = false, ...props }, propRef) => {
12
+ const context = useTooltipContext();
13
+ const childrenRef = (children as any).ref;
14
+ const ref = useMergeRefs([context.refs.setReference, propRef, childrenRef]);
15
+
16
+ if (asChild && isValidElement(children)) {
17
+ return cloneElement(
18
+ children,
19
+ context.getReferenceProps({
20
+ ref,
21
+ ...props,
22
+ ...children.props,
23
+ "data-state": context.open ? "open" : "closed",
24
+ }),
25
+ );
26
+ }
27
+
28
+ return (
29
+ <abbr ref={ref} {...context.getReferenceProps(props)}>
30
+ {children}
31
+ </abbr>
32
+ );
33
+ },
34
+ );
35
+
36
+ export default TooltipTrigger;
@@ -0,0 +1,6 @@
1
+ export { default as Tooltip } from "./Tooltip";
2
+ export { default as TooltipContent } from "./TooltipContent";
3
+ export { default as TooltipTrigger } from "./TooltipTrigger";
4
+ export { default as useTooltip } from "./useTooltip";
5
+ export { default as useTooltipContext, TooltipContext } from "./useTooltipContext";
6
+ export { default as SimpleTooltip } from "./SimpleTooltip";
@@ -0,0 +1,15 @@
1
+ import { Placement } from "@floating-ui/react";
2
+ import { ThemeColor } from "~/types.d";
3
+
4
+ export interface TooltipOptions {
5
+ initialOpen?: boolean;
6
+ placement?: Placement;
7
+ open?: boolean;
8
+ onOpen?: (open: boolean) => void;
9
+ openDelay?: number;
10
+ closeDelay?: number;
11
+ color?: ThemeColor;
12
+ contentClassName?: string;
13
+ }
14
+
15
+ export { ThemeColor };
@@ -0,0 +1,87 @@
1
+ import { useMemo, useRef, useState } from "react";
2
+ import { TooltipOptions } from "./interface";
3
+ import {
4
+ arrow,
5
+ autoUpdate,
6
+ flip,
7
+ hide,
8
+ offset,
9
+ shift,
10
+ useDismiss,
11
+ useFloating,
12
+ useFocus,
13
+ useHover,
14
+ useInteractions,
15
+ useRole,
16
+ } from "@floating-ui/react";
17
+
18
+ const arrowWidth = 16;
19
+
20
+ export default function useTooltip({
21
+ initialOpen = false,
22
+ placement = "top",
23
+ open: controlledOpen,
24
+ onOpen: setControlledOpen,
25
+ openDelay = 500,
26
+ closeDelay = 500,
27
+ color = "yellow",
28
+ }: TooltipOptions = {}) {
29
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(initialOpen);
30
+
31
+ const isUncontrolled = controlledOpen === null || controlledOpen === undefined;
32
+
33
+ const open = isUncontrolled ? uncontrolledOpen : controlledOpen;
34
+ const setOpen = isUncontrolled ? setUncontrolledOpen : setControlledOpen;
35
+
36
+ const arrowRef = useRef<HTMLDivElement>(null);
37
+
38
+ const offsetVal = arrowWidth / 2;
39
+
40
+ const data = useFloating({
41
+ placement,
42
+ open,
43
+ onOpenChange: setOpen,
44
+ whileElementsMounted: autoUpdate,
45
+ middleware: [
46
+ offset(offsetVal),
47
+ flip(),
48
+ shift({ padding: 5 }),
49
+ arrow({
50
+ element: arrowRef,
51
+ padding: 12,
52
+ }),
53
+ hide({
54
+ padding: 20,
55
+ }),
56
+ ],
57
+ });
58
+ const { context } = data;
59
+
60
+ const hover = useHover(context, {
61
+ move: false,
62
+ enabled: isUncontrolled,
63
+ delay: {
64
+ open: openDelay || 1,
65
+ close: closeDelay,
66
+ },
67
+ });
68
+ const focus = useFocus(context, {
69
+ enabled: isUncontrolled,
70
+ });
71
+ const dismiss = useDismiss(context);
72
+ const role = useRole(context, { role: "tooltip" });
73
+
74
+ const interactions = useInteractions([hover, focus, dismiss, role]);
75
+
76
+ return useMemo(
77
+ () => ({
78
+ open,
79
+ setOpen,
80
+ ...interactions,
81
+ ...data,
82
+ arrowRef,
83
+ color,
84
+ }),
85
+ [open, setOpen, color, interactions, data],
86
+ );
87
+ }
@@ -0,0 +1,16 @@
1
+ import { createContext, useContext } from "react";
2
+ import useTooltip from "./useTooltip";
3
+
4
+ type ContextType = ReturnType<typeof useTooltip> | null;
5
+
6
+ export const TooltipContext = createContext<ContextType>(null);
7
+
8
+ export default function useTooltipContext() {
9
+ const context = useContext(TooltipContext);
10
+
11
+ if (context === null) {
12
+ throw new Error("Tooltip components must be wrapped in <Tooltip />");
13
+ }
14
+
15
+ return context;
16
+ }
package/src/env.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import "react";
2
+
3
+ type Merge<T, U> = Omit<T, keyof U> & U;
4
+ type PropsWithAs<P, T extends React.ElementType> = P & { as?: T };
5
+ export type PolymorphicPropsWithRef<P, T extends React.ElementType> = Merge<
6
+ T extends keyof JSX.IntrinsicElements
7
+ ? React.PropsWithRef<JSX.IntrinsicElements[T]>
8
+ : React.ComponentPropsWithRef<T>,
9
+ PropsWithAs<P, T>
10
+ >;
11
+
12
+ declare global {
13
+ interface GlobalEventHandlersEventMap {
14
+ "maplibre-contextmenu": CustomEvent;
15
+ }
16
+ }
17
+
18
+ declare module "react" {
19
+ interface CSSProperties {
20
+ [key: `--${string}`]: string | number;
21
+ }
22
+
23
+ // eslint-disable-next-line @typescript-eslint/ban-types
24
+ function forwardRef<T, P = {}>(
25
+ render: (props: P, ref: React.Ref<T>) => React.ReactElement | null,
26
+ ): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
27
+ }
@@ -0,0 +1,13 @@
1
+ export { useNotifications } from "./useNotifications";
2
+ export { useRipple } from "./useRipple";
3
+ export { useIsMounted } from "./useIsMounted";
4
+ export { useFetch } from "./useFetch";
5
+ export { useCombinedRefs } from "./useCombinedRefs";
6
+ export { useIsClosing } from "./useIsClosing";
7
+ export { usePrevious } from "./usePrevious";
8
+ export { useEventCallback } from "./useEventCallback";
9
+ export { useDebounce, useStateDebounce, useRefDebounce } from "./useDebounce";
10
+ export { useCopyToClipboard } from "./useCopyToClipboard";
11
+ export { useEventListener } from "./useEventListener";
12
+ export { useOnClickOutside } from "./useOnClickOutside";
13
+ export { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
@@ -0,0 +1,42 @@
1
+ import { createElement, useEffect, useRef } from "react";
2
+
3
+ import { render } from "@testing-library/react";
4
+
5
+ import { useCombinedRefs } from "./useCombinedRefs";
6
+ import { describe, expect, it, vi } from "vitest";
7
+
8
+ describe("useCombinedRefs", () => {
9
+ it("should correctly combine refs into one ref", async () => {
10
+ const cbRef = vi.fn();
11
+ const refs: { a: null | HTMLDivElement; b: null | HTMLDivElement } = { a: null, b: null };
12
+
13
+ const Test = () => {
14
+ const refA = useRef<HTMLDivElement>(null);
15
+ const refB = useRef<HTMLDivElement>(null);
16
+ const combinedRef = useCombinedRefs(refA, refB, cbRef, undefined);
17
+ useEffect(() => {
18
+ refs.a = refA.current;
19
+ refs.b = refB.current;
20
+ return () => {
21
+ refs.a = refA.current;
22
+ refs.b = refB.current;
23
+ };
24
+ });
25
+ return <div ref={combinedRef} data-testid="div"></div>;
26
+ };
27
+
28
+ const { getByTestId, rerender } = render(createElement(Test));
29
+
30
+ const div = getByTestId("div");
31
+
32
+ expect(refs.a).toBe(div);
33
+ expect(refs.b).toBe(div);
34
+ expect(cbRef).toHaveBeenCalledWith(div);
35
+
36
+ rerender(<div />);
37
+
38
+ expect(cbRef).toHaveBeenCalledWith(null);
39
+ expect(refs.a).toBe(null);
40
+ expect(refs.b).toBe(null);
41
+ });
42
+ });
@@ -0,0 +1,21 @@
1
+ import { Ref, useCallback } from "react";
2
+
3
+ export const useCombinedRefs = <T extends any>(...refs: (Ref<T> | undefined)[]): Ref<T> =>
4
+ useCallback(
5
+ (element: T) =>
6
+ refs.forEach((ref) => {
7
+ if (!ref) {
8
+ return;
9
+ }
10
+
11
+ // Ref can have two types - a function or an object. We treat each case.
12
+ if (typeof ref === "function") {
13
+ return ref(element);
14
+ }
15
+
16
+ // As per https://github.com/facebook/react/issues/13029
17
+ // it should be fine to set current this way.
18
+ (ref as any).current = element;
19
+ }),
20
+ refs,
21
+ );
@@ -0,0 +1,28 @@
1
+ import { useCallback, useState } from "react";
2
+
3
+ type CopiedValue = string | null;
4
+
5
+ type CopyFn = (text: string) => Promise<boolean>;
6
+
7
+ export function useCopyToClipboard(): [CopiedValue, CopyFn] {
8
+ const [copiedText, setCopiedText] = useState<CopiedValue>(null);
9
+
10
+ const copy: CopyFn = useCallback(async (text) => {
11
+ if (!navigator?.clipboard) {
12
+ console.warn("Clipboard not supported");
13
+ return false;
14
+ }
15
+
16
+ try {
17
+ await navigator.clipboard.writeText(text);
18
+ setCopiedText(text);
19
+ return true;
20
+ } catch (error) {
21
+ console.warn("Copy failed", error);
22
+ setCopiedText(null);
23
+ return false;
24
+ }
25
+ }, []);
26
+
27
+ return [copiedText, copy];
28
+ }