@usefui/components 1.5.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 (104) hide show
  1. package/CHANGELOG.md +233 -0
  2. package/LICENSE +21 -0
  3. package/README.md +0 -0
  4. package/babel.config.js +12 -0
  5. package/dist/index.d.mts +1299 -0
  6. package/dist/index.d.ts +1299 -0
  7. package/dist/index.js +3701 -0
  8. package/dist/index.mjs +3586 -0
  9. package/package.json +44 -0
  10. package/src/__tests__/Accordion.test.tsx +106 -0
  11. package/src/__tests__/Avatar.test.tsx +89 -0
  12. package/src/__tests__/Badge.test.tsx +58 -0
  13. package/src/__tests__/Button.test.tsx +88 -0
  14. package/src/__tests__/Checkbox.test.tsx +106 -0
  15. package/src/__tests__/Collapsible.test.tsx +79 -0
  16. package/src/__tests__/Dialog.test.tsx +109 -0
  17. package/src/__tests__/Dropdown.test.tsx +159 -0
  18. package/src/__tests__/Field.test.tsx +100 -0
  19. package/src/__tests__/OTPField.test.tsx +199 -0
  20. package/src/__tests__/Overlay.test.tsx +70 -0
  21. package/src/__tests__/Page.test.tsx +98 -0
  22. package/src/__tests__/Portal.test.tsx +28 -0
  23. package/src/__tests__/Sheet.test.tsx +125 -0
  24. package/src/__tests__/Switch.test.tsx +90 -0
  25. package/src/__tests__/Tabs.test.tsx +129 -0
  26. package/src/__tests__/Toggle.test.tsx +67 -0
  27. package/src/__tests__/Toolbar.test.tsx +147 -0
  28. package/src/__tests__/Tooltip.test.tsx +88 -0
  29. package/src/accordion/Accordion.stories.tsx +89 -0
  30. package/src/accordion/hooks/index.tsx +39 -0
  31. package/src/accordion/index.tsx +170 -0
  32. package/src/avatar/Avatar.stories.tsx +62 -0
  33. package/src/avatar/index.tsx +90 -0
  34. package/src/avatar/styles/index.ts +79 -0
  35. package/src/badge/Badge.stories.tsx +60 -0
  36. package/src/badge/index.tsx +58 -0
  37. package/src/badge/styles/index.ts +109 -0
  38. package/src/button/Button.stories.tsx +47 -0
  39. package/src/button/index.tsx +79 -0
  40. package/src/button/styles/index.ts +180 -0
  41. package/src/checkbox/Checkbox.stories.tsx +100 -0
  42. package/src/checkbox/hooks/index.tsx +40 -0
  43. package/src/checkbox/index.tsx +147 -0
  44. package/src/checkbox/styles/index.ts +139 -0
  45. package/src/collapsible/Collapsible.stories.tsx +95 -0
  46. package/src/collapsible/hooks/index.tsx +50 -0
  47. package/src/collapsible/index.tsx +137 -0
  48. package/src/dialog/Dialog.stories.tsx +73 -0
  49. package/src/dialog/hooks/index.tsx +35 -0
  50. package/src/dialog/index.tsx +221 -0
  51. package/src/dialog/styles/index.ts +72 -0
  52. package/src/divider/index.ts +10 -0
  53. package/src/dropdown/Dropdown.stories.tsx +100 -0
  54. package/src/dropdown/hooks/index.tsx +64 -0
  55. package/src/dropdown/index.tsx +316 -0
  56. package/src/dropdown/styles/index.ts +90 -0
  57. package/src/field/Field.stories.tsx +146 -0
  58. package/src/field/hooks/index.tsx +28 -0
  59. package/src/field/index.tsx +183 -0
  60. package/src/field/styles/index.ts +166 -0
  61. package/src/index.ts +33 -0
  62. package/src/otp-field/OTPField.stories.tsx +50 -0
  63. package/src/otp-field/hooks/index.tsx +13 -0
  64. package/src/otp-field/index.tsx +234 -0
  65. package/src/otp-field/styles/index.ts +33 -0
  66. package/src/otp-field/types/index.ts +23 -0
  67. package/src/overlay/Overlay.stories.tsx +59 -0
  68. package/src/overlay/index.tsx +58 -0
  69. package/src/overlay/styles/index.ts +26 -0
  70. package/src/page/Page.stories.tsx +85 -0
  71. package/src/page/index.tsx +265 -0
  72. package/src/page/styles/index.ts +59 -0
  73. package/src/portal/Portal.stories.tsx +27 -0
  74. package/src/portal/index.tsx +36 -0
  75. package/src/scrollarea/Scrollarea.stories.tsx +99 -0
  76. package/src/scrollarea/index.tsx +27 -0
  77. package/src/scrollarea/styles/index.ts +71 -0
  78. package/src/sheet/Sheet.stories.tsx +86 -0
  79. package/src/sheet/hooks/index.tsx +47 -0
  80. package/src/sheet/index.tsx +190 -0
  81. package/src/sheet/styles/index.ts +69 -0
  82. package/src/switch/Switch.stories.tsx +96 -0
  83. package/src/switch/hooks/index.tsx +33 -0
  84. package/src/switch/index.tsx +122 -0
  85. package/src/switch/styles/index.ts +118 -0
  86. package/src/table/index.tsx +138 -0
  87. package/src/table/styles/index.ts +48 -0
  88. package/src/tabs/Tabs.stories.tsx +87 -0
  89. package/src/tabs/hooks/index.tsx +35 -0
  90. package/src/tabs/index.tsx +161 -0
  91. package/src/tabs/styles/index.ts +9 -0
  92. package/src/toggle/Toggle.stories.tsx +118 -0
  93. package/src/toggle/index.tsx +55 -0
  94. package/src/toggle/styles/index.ts +0 -0
  95. package/src/toolbar/Toolbar.stories.tsx +89 -0
  96. package/src/toolbar/hooks/index.tsx +35 -0
  97. package/src/toolbar/index.tsx +243 -0
  98. package/src/toolbar/styles/index.ts +129 -0
  99. package/src/tooltip/Tooltip.stories.tsx +60 -0
  100. package/src/tooltip/index.tsx +177 -0
  101. package/src/tooltip/styles/index.ts +38 -0
  102. package/src/utils/index.ts +2 -0
  103. package/tsconfig.json +18 -0
  104. package/vitest.config.ts +16 -0
@@ -0,0 +1,221 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { useDialog, DialogProvider } from "./hooks";
5
+ import { useDisabledScroll } from "@usefui/hooks";
6
+ import {
7
+ Overlay,
8
+ IOverlayProperties,
9
+ Button,
10
+ IButtonProperties,
11
+ ScrollArea,
12
+ IScrollAreaProperties,
13
+ } from "..";
14
+ import { DialogWrapper, Menu } from "./styles";
15
+ import { applyDataState } from "../utils";
16
+ import {
17
+ IComponentStyling,
18
+ ComponentSizeEnum,
19
+ IComponentSize,
20
+ } from "../../../../types";
21
+
22
+ export interface IDialogItemProperties
23
+ extends IComponentStyling,
24
+ IComponentSize,
25
+ IScrollAreaProperties,
26
+ React.ComponentProps<"dialog"> {
27
+ lock?: boolean;
28
+ }
29
+
30
+ /**
31
+ * Dialog are used displays a modal window to the user on top of the current layer.
32
+ *
33
+ * **Best practices:**
34
+ *
35
+ * - Ensure that the dialog is visible and accessible to all users, including those using assistive technologies.
36
+ * - Use keyboard shortcuts to provide an alternative way of interacting with the dialog.
37
+ * - Ensure that the dialog is responsive and adapts to different screen sizes and orientations.
38
+ *
39
+ * @param {IDialogItemProperties} props - The props for the Dialog component.
40
+ * @param {boolean} props.raw - Define whether the component is styled or not.
41
+ * @param {ComponentSizeEnum} props.sizing - The size of the component.
42
+ * @param {boolean} props.open - Whether the dialog is open or not.
43
+ * @param {ReactNode} props.children - The content to be rendered inside the dialog.
44
+ * @returns {ReactElement} The Dialog component.
45
+ */
46
+ const Dialog = (props: IDialogItemProperties) => {
47
+ const {
48
+ raw,
49
+ sizing = ComponentSizeEnum.Medium,
50
+ open = false,
51
+ lock = true,
52
+ children,
53
+ ...restProps
54
+ } = props;
55
+ const { states, methods } = useDialog();
56
+ const { getDialogId, toggleDialog } = methods;
57
+
58
+ const triggerId = getDialogId && getDialogId("trigger");
59
+ const contentId = getDialogId && getDialogId("content");
60
+
61
+ React.useEffect(() => {
62
+ if (open && toggleDialog) toggleDialog();
63
+ // eslint-disable-next-line react-hooks/exhaustive-deps
64
+ }, []);
65
+
66
+ if (lock) useDisabledScroll(Boolean(states.open));
67
+
68
+ return (
69
+ <>
70
+ {states.open && (
71
+ <ScrollArea
72
+ as={DialogWrapper}
73
+ role="dialog"
74
+ tabIndex={-1}
75
+ id={String(contentId)}
76
+ open={Boolean(states.open)}
77
+ aria-labelledby={String(triggerId)}
78
+ data-state={applyDataState(Boolean(states.open))}
79
+ data-size={sizing}
80
+ data-raw={Boolean(raw)}
81
+ {...restProps}
82
+ >
83
+ {children}
84
+ </ScrollArea>
85
+ )}
86
+ </>
87
+ );
88
+ };
89
+ Dialog.displayName = "Dialog";
90
+
91
+ const DialogRoot = ({ children }: React.ComponentProps<"div">) => {
92
+ return <DialogProvider>{children}</DialogProvider>;
93
+ };
94
+ DialogRoot.displayName = "Dialog.Root";
95
+
96
+ const DialogOverlay = (props: IOverlayProperties) => {
97
+ const { closeOnInteract = false, onClick, ...restProps } = props;
98
+ const { states, methods } = useDialog();
99
+ const { toggleDialog } = methods;
100
+
101
+ const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
102
+ if (closeOnInteract && toggleDialog) toggleDialog();
103
+ if (onClick) onClick(event);
104
+ };
105
+
106
+ return (
107
+ <Overlay
108
+ visible={Boolean(states.open)}
109
+ closeOnInteract={closeOnInteract}
110
+ onClick={handleClick}
111
+ {...restProps}
112
+ />
113
+ );
114
+ };
115
+ DialogOverlay.displayName = "Dialog.Overlay";
116
+
117
+ /**
118
+ * Dialog.Trigger is used to trigger the render of the associated Dialog component.
119
+ *
120
+ * **Best practices:**
121
+ *
122
+ * - Use a clear and descriptive title for the trigger that accurately conveys the content of the associated Dialog.
123
+ * - Ensure that the trigger can be operated using only the keyboard.
124
+ * - Ensure that the focus is properly managed when the trigger is activated.
125
+ *
126
+ * @param {IButtonProperties} props - The props for the Dialog.Trigger component.
127
+ * @returns {ReactElement} The Dialog.Trigger component.
128
+ */
129
+ const DialogTrigger = (props: IButtonProperties) => {
130
+ const { onClick, children, ...restProps } = props;
131
+ const { states, methods } = useDialog();
132
+ const { getDialogId, toggleDialog } = methods;
133
+
134
+ const triggerId = getDialogId && getDialogId("trigger");
135
+ const contentId = getDialogId && getDialogId("content");
136
+
137
+ const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
138
+ if (toggleDialog) toggleDialog();
139
+ if (onClick) onClick(event);
140
+ };
141
+
142
+ return (
143
+ <Button
144
+ id={String(triggerId)}
145
+ onClick={handleClick}
146
+ aria-controls={String(contentId)}
147
+ aria-expanded={Boolean(states.open)}
148
+ data-state={applyDataState(Boolean(states.open))}
149
+ {...restProps}
150
+ >
151
+ {children}
152
+ </Button>
153
+ );
154
+ };
155
+ DialogTrigger.displayName = "Dialog.Trigger";
156
+
157
+ const DialogMenu = (props: IDialogItemProperties) => {
158
+ const { raw, children, ...restProps } = props;
159
+
160
+ return (
161
+ <Menu data-raw={Boolean(raw)} {...restProps}>
162
+ {children}
163
+ </Menu>
164
+ );
165
+ };
166
+ DialogMenu.displayName = "Dialog.Menu";
167
+
168
+ /**
169
+ * Dialog.Control is used to trigger action inside the associated Dialog component.
170
+ *
171
+ * **Best practices:**
172
+ *
173
+ * - Use a clear and descriptive title for the trigger that accurately conveys the content of the associated action.
174
+ * - Ensure that the trigger can be operated using only the keyboard.
175
+ * - Ensure that the focus is properly managed when the trigger is activated.
176
+ *
177
+ * @param {IButtonProperties} props - The props for the Dialog.Control component.
178
+ * @returns {ReactElement} The Dialog.Control component.
179
+ */
180
+ const DialogControl = (props: IButtonProperties) => {
181
+ const { onClick, children, ...restProps } = props;
182
+ const { states, methods } = useDialog();
183
+ const { getDialogId, toggleDialog } = methods;
184
+
185
+ const innerControlId = getDialogId && getDialogId("inner-control");
186
+ const contentId = getDialogId && getDialogId("content");
187
+
188
+ const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
189
+ if (toggleDialog) toggleDialog();
190
+ if (onClick) onClick(event);
191
+ };
192
+
193
+ return (
194
+ <Button
195
+ id={String(innerControlId)}
196
+ onClick={handleClick}
197
+ aria-controls={String(contentId)}
198
+ aria-expanded={Boolean(states.open)}
199
+ data-state={applyDataState(Boolean(states.open))}
200
+ {...restProps}
201
+ >
202
+ {children}
203
+ </Button>
204
+ );
205
+ };
206
+ DialogControl.displayName = "Dialog.Control";
207
+
208
+ Dialog.Root = DialogRoot;
209
+ Dialog.Trigger = DialogTrigger;
210
+ Dialog.Control = DialogControl;
211
+ Dialog.Menu = DialogMenu;
212
+ Dialog.Overlay = DialogOverlay;
213
+
214
+ export {
215
+ Dialog,
216
+ DialogRoot,
217
+ DialogTrigger,
218
+ DialogControl,
219
+ DialogMenu,
220
+ DialogOverlay,
221
+ };
@@ -0,0 +1,72 @@
1
+ import styled, { css } from "styled-components";
2
+
3
+ const DialogDefaultStyles = css`
4
+ position: fixed;
5
+ top: 15dvh;
6
+ padding: var(--measurement-medium-60);
7
+ width: 100%;
8
+
9
+ border: var(--measurement-small-10) solid var(--font-color-alpha-10);
10
+ background-color: var(--body-color);
11
+ border-radius: var(--measurement-medium-30);
12
+
13
+ transition: all ease-in-out 0.2s;
14
+ z-index: var(--depth-default-100);
15
+ `;
16
+ const DialogSizeStyles = css`
17
+ --base-size: var(--measurement-medium-60);
18
+ --computed-size: calc(100% - (var(--base-size) * 2));
19
+ --max-height: 75dvh;
20
+
21
+ &[data-size="small"] {
22
+ max-width: calc(var(--measurement-large-90) * 1.33);
23
+ max-height: var(--max-height);
24
+ }
25
+ &[data-size="medium"] {
26
+ max-width: calc(var(--measurement-large-90) * 2.66);
27
+ max-height: var(--max-height);
28
+ }
29
+
30
+ &[data-size="large"] {
31
+ top: var(--base-size);
32
+ max-width: var(--computed-size);
33
+ height: var(--computed-size);
34
+ padding: var(--measurement-medium-80);
35
+ }
36
+ `;
37
+
38
+ export const Menu = styled.menu<any>`
39
+ margin: 0;
40
+ padding: 0;
41
+
42
+ &[data-raw="false"] {
43
+ display: flex;
44
+ justify-content: flex-end;
45
+ gap: var(--measurement-medium-30);
46
+ }
47
+ `;
48
+ export const DialogWrapper = styled.dialog<any>`
49
+ @keyframes slide-in {
50
+ 0% {
51
+ opacity: 0;
52
+ transform: translateY(var(--measurement-medium-30));
53
+ }
54
+ 100% {
55
+ opacity: 1;
56
+ transform: translateY(0);
57
+ }
58
+ }
59
+
60
+ border: none;
61
+ box-shadow: none;
62
+ z-index: 100;
63
+
64
+ &[data-raw="false"] {
65
+ ${DialogDefaultStyles}
66
+ ${DialogSizeStyles}
67
+
68
+ animation-duration: 0.2s;
69
+ animation-name: slide-in;
70
+ animation-fill-mode: backwards;
71
+ }
72
+ `;
@@ -0,0 +1,10 @@
1
+ "use client";
2
+
3
+ import styled from "styled-components";
4
+
5
+ export const Divider = styled.hr`
6
+ height: var(--measurement-small-10);
7
+ margin: var(--measurement-medium-30) 0;
8
+ background: var(--font-color-alpha-10);
9
+ border: none;
10
+ `;
@@ -0,0 +1,100 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+
4
+ import { DropdownMenu } from "..";
5
+ import { ComponentVariantEnum, ComponentSizeEnum } from "../../../../types";
6
+
7
+ // Duplicated doc: The JSDoc content isn't rendering on Storybook.
8
+
9
+ /**
10
+ * Dropdown are used to expand and collapse list of actions.
11
+ *
12
+ * **Best practices:**
13
+ *
14
+ * - Use semantic HTML elements to structure the dropdown menu content.
15
+ * - Ensure that the dropdown menu can be opened and closed using the keyboard.
16
+ * - Ensure that the dropdown menu is visibly focused when opened using the keyboard.
17
+ * - Ensure that the dropdown menu is dismissed when the user clicks outside of it or presses the Esc key.
18
+ *
19
+ */
20
+ const meta = {
21
+ title: "Components/DropdownMenu",
22
+ component: DropdownMenu,
23
+ tags: ["autodocs"],
24
+ decorators: [
25
+ (Story) => (
26
+ <div className="m-medium-30">
27
+ <Story />
28
+ </div>
29
+ ),
30
+ ],
31
+ } satisfies Meta<typeof DropdownMenu>;
32
+ export default meta;
33
+
34
+ type Story = StoryObj<typeof meta>;
35
+ export const Default: Story = {
36
+ args: {
37
+ raw: false,
38
+ container: "",
39
+ defaultOpen: false,
40
+ radio: false,
41
+ disabled: false,
42
+ },
43
+ argTypes: {
44
+ variant: {
45
+ options: [
46
+ ComponentVariantEnum.Primary,
47
+ ComponentVariantEnum.Secondary,
48
+ ComponentVariantEnum.Tertiary,
49
+ ComponentVariantEnum.Mono,
50
+ ComponentVariantEnum.Border,
51
+ ComponentVariantEnum.Ghost,
52
+ ],
53
+ control: { type: "radio" },
54
+ },
55
+ sizing: {
56
+ options: [
57
+ ComponentSizeEnum.Small,
58
+ ComponentSizeEnum.Medium,
59
+ ComponentSizeEnum.Large,
60
+ ],
61
+ control: { type: "radio" },
62
+ },
63
+ },
64
+ render: ({ ...args }) => (
65
+ <DropdownMenu.Root>
66
+ <DropdownMenu.Trigger>🐻‍❄️</DropdownMenu.Trigger>
67
+ <DropdownMenu>
68
+ <DropdownMenu.Content>
69
+ {["🐻", "🐻‍❄️", "🦊", "🐱", "🐶"].map((item) => (
70
+ <DropdownMenu.Item key={item}>{item}</DropdownMenu.Item>
71
+ ))}
72
+ </DropdownMenu.Content>
73
+ </DropdownMenu>
74
+ </DropdownMenu.Root>
75
+ ),
76
+ };
77
+ export const DefaultOpen: Story = {
78
+ render: ({ ...args }) => (
79
+ <DropdownMenu.Root>
80
+ <DropdownMenu.Trigger>🐻‍❄️</DropdownMenu.Trigger>
81
+ <DropdownMenu>
82
+ <DropdownMenu.Content defaultOpen>
83
+ <DropdownMenu.Item>🐻🐻‍❄️🦊🐱🐶</DropdownMenu.Item>
84
+ </DropdownMenu.Content>
85
+ </DropdownMenu>
86
+ </DropdownMenu.Root>
87
+ ),
88
+ };
89
+ export const RadioItem: Story = {
90
+ render: ({ ...args }) => (
91
+ <DropdownMenu.Root>
92
+ <DropdownMenu.Trigger>🐻‍❄️</DropdownMenu.Trigger>
93
+ <DropdownMenu>
94
+ <DropdownMenu.Content>
95
+ <DropdownMenu.Item radio>🐻🐻‍❄️🦊🐱🐶</DropdownMenu.Item>
96
+ </DropdownMenu.Content>
97
+ </DropdownMenu>
98
+ </DropdownMenu.Root>
99
+ ),
100
+ };
@@ -0,0 +1,64 @@
1
+ import React from "react";
2
+ import { IReactChildren, IComponentAPI } from "../../../../../types";
3
+
4
+ const DEFAULT_API = {
5
+ id: "",
6
+ states: {},
7
+ methods: {},
8
+ };
9
+ const DEFAULT_POSITIONS = {
10
+ top: 0,
11
+ right: 0,
12
+ bottom: 0,
13
+ left: 0,
14
+ };
15
+ const DEFAULT_DIMENSIONS = {
16
+ width: 0,
17
+ height: 0,
18
+ };
19
+
20
+ const DropdownMenuContext = React.createContext<IComponentAPI>(DEFAULT_API);
21
+ export const useDropdownMenu = () => React.useContext(DropdownMenuContext);
22
+
23
+ export const DropdownMenuProvider = ({
24
+ children,
25
+ }: IReactChildren): JSX.Element => {
26
+ const context = useDropdownMenuProvider();
27
+
28
+ return (
29
+ <DropdownMenuContext.Provider value={context}>
30
+ {children}
31
+ </DropdownMenuContext.Provider>
32
+ );
33
+ };
34
+
35
+ function useDropdownMenuProvider(): IComponentAPI {
36
+ const [open, setOpen] = React.useState<boolean>(false);
37
+
38
+ const [contentProps, setContentProps] = React.useState({
39
+ ...DEFAULT_POSITIONS,
40
+ ...DEFAULT_DIMENSIONS,
41
+ });
42
+ const [triggerProps, setTriggerProps] = React.useState({
43
+ ...DEFAULT_POSITIONS,
44
+ ...DEFAULT_DIMENSIONS,
45
+ });
46
+
47
+ const triggerId = React.useId();
48
+ const dropdownId = React.useId();
49
+ const composedId = `${triggerId}|${dropdownId}`;
50
+
51
+ return {
52
+ id: composedId,
53
+ states: {
54
+ open,
55
+ contentProps,
56
+ triggerProps,
57
+ },
58
+ methods: {
59
+ toggleOpen: (): boolean | void => setOpen(!open),
60
+ setContentProps,
61
+ setTriggerProps,
62
+ },
63
+ };
64
+ }