@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.
- package/CHANGELOG.md +233 -0
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/babel.config.js +12 -0
- package/dist/index.d.mts +1299 -0
- package/dist/index.d.ts +1299 -0
- package/dist/index.js +3701 -0
- package/dist/index.mjs +3586 -0
- package/package.json +44 -0
- package/src/__tests__/Accordion.test.tsx +106 -0
- package/src/__tests__/Avatar.test.tsx +89 -0
- package/src/__tests__/Badge.test.tsx +58 -0
- package/src/__tests__/Button.test.tsx +88 -0
- package/src/__tests__/Checkbox.test.tsx +106 -0
- package/src/__tests__/Collapsible.test.tsx +79 -0
- package/src/__tests__/Dialog.test.tsx +109 -0
- package/src/__tests__/Dropdown.test.tsx +159 -0
- package/src/__tests__/Field.test.tsx +100 -0
- package/src/__tests__/OTPField.test.tsx +199 -0
- package/src/__tests__/Overlay.test.tsx +70 -0
- package/src/__tests__/Page.test.tsx +98 -0
- package/src/__tests__/Portal.test.tsx +28 -0
- package/src/__tests__/Sheet.test.tsx +125 -0
- package/src/__tests__/Switch.test.tsx +90 -0
- package/src/__tests__/Tabs.test.tsx +129 -0
- package/src/__tests__/Toggle.test.tsx +67 -0
- package/src/__tests__/Toolbar.test.tsx +147 -0
- package/src/__tests__/Tooltip.test.tsx +88 -0
- package/src/accordion/Accordion.stories.tsx +89 -0
- package/src/accordion/hooks/index.tsx +39 -0
- package/src/accordion/index.tsx +170 -0
- package/src/avatar/Avatar.stories.tsx +62 -0
- package/src/avatar/index.tsx +90 -0
- package/src/avatar/styles/index.ts +79 -0
- package/src/badge/Badge.stories.tsx +60 -0
- package/src/badge/index.tsx +58 -0
- package/src/badge/styles/index.ts +109 -0
- package/src/button/Button.stories.tsx +47 -0
- package/src/button/index.tsx +79 -0
- package/src/button/styles/index.ts +180 -0
- package/src/checkbox/Checkbox.stories.tsx +100 -0
- package/src/checkbox/hooks/index.tsx +40 -0
- package/src/checkbox/index.tsx +147 -0
- package/src/checkbox/styles/index.ts +139 -0
- package/src/collapsible/Collapsible.stories.tsx +95 -0
- package/src/collapsible/hooks/index.tsx +50 -0
- package/src/collapsible/index.tsx +137 -0
- package/src/dialog/Dialog.stories.tsx +73 -0
- package/src/dialog/hooks/index.tsx +35 -0
- package/src/dialog/index.tsx +221 -0
- package/src/dialog/styles/index.ts +72 -0
- package/src/divider/index.ts +10 -0
- package/src/dropdown/Dropdown.stories.tsx +100 -0
- package/src/dropdown/hooks/index.tsx +64 -0
- package/src/dropdown/index.tsx +316 -0
- package/src/dropdown/styles/index.ts +90 -0
- package/src/field/Field.stories.tsx +146 -0
- package/src/field/hooks/index.tsx +28 -0
- package/src/field/index.tsx +183 -0
- package/src/field/styles/index.ts +166 -0
- package/src/index.ts +33 -0
- package/src/otp-field/OTPField.stories.tsx +50 -0
- package/src/otp-field/hooks/index.tsx +13 -0
- package/src/otp-field/index.tsx +234 -0
- package/src/otp-field/styles/index.ts +33 -0
- package/src/otp-field/types/index.ts +23 -0
- package/src/overlay/Overlay.stories.tsx +59 -0
- package/src/overlay/index.tsx +58 -0
- package/src/overlay/styles/index.ts +26 -0
- package/src/page/Page.stories.tsx +85 -0
- package/src/page/index.tsx +265 -0
- package/src/page/styles/index.ts +59 -0
- package/src/portal/Portal.stories.tsx +27 -0
- package/src/portal/index.tsx +36 -0
- package/src/scrollarea/Scrollarea.stories.tsx +99 -0
- package/src/scrollarea/index.tsx +27 -0
- package/src/scrollarea/styles/index.ts +71 -0
- package/src/sheet/Sheet.stories.tsx +86 -0
- package/src/sheet/hooks/index.tsx +47 -0
- package/src/sheet/index.tsx +190 -0
- package/src/sheet/styles/index.ts +69 -0
- package/src/switch/Switch.stories.tsx +96 -0
- package/src/switch/hooks/index.tsx +33 -0
- package/src/switch/index.tsx +122 -0
- package/src/switch/styles/index.ts +118 -0
- package/src/table/index.tsx +138 -0
- package/src/table/styles/index.ts +48 -0
- package/src/tabs/Tabs.stories.tsx +87 -0
- package/src/tabs/hooks/index.tsx +35 -0
- package/src/tabs/index.tsx +161 -0
- package/src/tabs/styles/index.ts +9 -0
- package/src/toggle/Toggle.stories.tsx +118 -0
- package/src/toggle/index.tsx +55 -0
- package/src/toggle/styles/index.ts +0 -0
- package/src/toolbar/Toolbar.stories.tsx +89 -0
- package/src/toolbar/hooks/index.tsx +35 -0
- package/src/toolbar/index.tsx +243 -0
- package/src/toolbar/styles/index.ts +129 -0
- package/src/tooltip/Tooltip.stories.tsx +60 -0
- package/src/tooltip/index.tsx +177 -0
- package/src/tooltip/styles/index.ts +38 -0
- package/src/utils/index.ts +2 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +16 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { ScrollArea } from "..";
|
|
4
|
+
|
|
5
|
+
// Duplicated doc: The JSDoc content isn't rendering on Storybook.
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Add native scroll functionality to custom components.
|
|
9
|
+
*/
|
|
10
|
+
const meta = {
|
|
11
|
+
title: "Components/ScrollArea",
|
|
12
|
+
component: ScrollArea,
|
|
13
|
+
tags: ["autodocs"],
|
|
14
|
+
} satisfies Meta<typeof ScrollArea>;
|
|
15
|
+
export default meta;
|
|
16
|
+
|
|
17
|
+
type Story = StoryObj<typeof meta>;
|
|
18
|
+
export const Default: Story = {
|
|
19
|
+
args: {
|
|
20
|
+
scrollbar: false,
|
|
21
|
+
},
|
|
22
|
+
render: ({ ...args }) => {
|
|
23
|
+
return (
|
|
24
|
+
<ScrollArea
|
|
25
|
+
className="h-100 w-100 flex align-center justify-center p-large-10"
|
|
26
|
+
style={{ maxWidth: 1024, margin: "0 auto" }}
|
|
27
|
+
{...args}
|
|
28
|
+
>
|
|
29
|
+
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Dicta autem
|
|
30
|
+
incidunt voluptatum dignissimos, aperiam nostrum, explicabo adipisci
|
|
31
|
+
ipsum magnam iste id accusamus, sed inventore ex eaque? Vitae iusto odit
|
|
32
|
+
temporibus. Lorem ipsum dolor sit, amet consectetur adipisicing elit.
|
|
33
|
+
Dicta autem incidunt voluptatum dignissimos, aperiam nostrum, explicabo
|
|
34
|
+
adipisci ipsum magnam iste id accusamus, sed inventore ex eaque? Vitae
|
|
35
|
+
iusto odit temporibus. Lorem ipsum dolor sit, amet consectetur
|
|
36
|
+
adipisicing elit. Dicta autem incidunt voluptatum dignissimos, aperiam
|
|
37
|
+
nostrum, explicabo adipisci ipsum magnam iste id accusamus, sed
|
|
38
|
+
inventore ex eaque? Vitae iusto odit temporibus. Lorem ipsum dolor sit,
|
|
39
|
+
amet consectetur adipisicing elit. Dicta autem incidunt voluptatum
|
|
40
|
+
dignissimos, aperiam nostrum, explicabo adipisci ipsum magnam iste id
|
|
41
|
+
accusamus, sed inventore ex eaque? Vitae iusto odit temporibus. Lorem
|
|
42
|
+
ipsum dolor sit, amet consectetur adipisicing elit. Dicta autem incidunt
|
|
43
|
+
voluptatum dignissimos, aperiam nostrum, explicabo adipisci ipsum magnam
|
|
44
|
+
iste id accusamus, sed inventore ex eaque? Vitae iusto odit temporibus.
|
|
45
|
+
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Dicta autem
|
|
46
|
+
incidunt voluptatum dignissimos, aperiam nostrum, explicabo adipisci
|
|
47
|
+
ipsum magnam iste id accusamus, sed inventore ex eaque? Vitae iusto odit
|
|
48
|
+
temporibus. Lorem ipsum dolor sit, amet consectetur adipisicing elit.
|
|
49
|
+
Dicta autem incidunt voluptatum dignissimos, aperiam nostrum, explicabo
|
|
50
|
+
adipisci ipsum magnam iste id accusamus, sed inventore ex eaque? Vitae
|
|
51
|
+
iusto odit temporibus. Lorem ipsum dolor sit, amet consectetur
|
|
52
|
+
adipisicing elit. Dicta autem incidunt voluptatum dignissimos, aperiam
|
|
53
|
+
nostrum, explicabo adipisci ipsum magnam iste id accusamus, sed
|
|
54
|
+
inventore ex eaque? Vitae iusto odit temporibus.
|
|
55
|
+
</ScrollArea>
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
export const Scrollbar: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
scrollbar: true,
|
|
62
|
+
},
|
|
63
|
+
render: ({ ...args }) => {
|
|
64
|
+
return (
|
|
65
|
+
<ScrollArea
|
|
66
|
+
className="h-100 w-100 flex align-center justify-center p-large-10"
|
|
67
|
+
style={{ maxWidth: 1024, margin: "0 auto" }}
|
|
68
|
+
{...args}
|
|
69
|
+
>
|
|
70
|
+
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Dicta autem
|
|
71
|
+
incidunt voluptatum dignissimos, aperiam nostrum, explicabo adipisci
|
|
72
|
+
ipsum magnam iste id accusamus, sed inventore ex eaque? Vitae iusto odit
|
|
73
|
+
temporibus. Lorem ipsum dolor sit, amet consectetur adipisicing elit.
|
|
74
|
+
Dicta autem incidunt voluptatum dignissimos, aperiam nostrum, explicabo
|
|
75
|
+
adipisci ipsum magnam iste id accusamus, sed inventore ex eaque? Vitae
|
|
76
|
+
iusto odit temporibus. Lorem ipsum dolor sit, amet consectetur
|
|
77
|
+
adipisicing elit. Dicta autem incidunt voluptatum dignissimos, aperiam
|
|
78
|
+
nostrum, explicabo adipisci ipsum magnam iste id accusamus, sed
|
|
79
|
+
inventore ex eaque? Vitae iusto odit temporibus. Lorem ipsum dolor sit,
|
|
80
|
+
amet consectetur adipisicing elit. Dicta autem incidunt voluptatum
|
|
81
|
+
dignissimos, aperiam nostrum, explicabo adipisci ipsum magnam iste id
|
|
82
|
+
accusamus, sed inventore ex eaque? Vitae iusto odit temporibus. Lorem
|
|
83
|
+
ipsum dolor sit, amet consectetur adipisicing elit. Dicta autem incidunt
|
|
84
|
+
voluptatum dignissimos, aperiam nostrum, explicabo adipisci ipsum magnam
|
|
85
|
+
iste id accusamus, sed inventore ex eaque? Vitae iusto odit temporibus.
|
|
86
|
+
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Dicta autem
|
|
87
|
+
incidunt voluptatum dignissimos, aperiam nostrum, explicabo adipisci
|
|
88
|
+
ipsum magnam iste id accusamus, sed inventore ex eaque? Vitae iusto odit
|
|
89
|
+
temporibus. Lorem ipsum dolor sit, amet consectetur adipisicing elit.
|
|
90
|
+
Dicta autem incidunt voluptatum dignissimos, aperiam nostrum, explicabo
|
|
91
|
+
adipisci ipsum magnam iste id accusamus, sed inventore ex eaque? Vitae
|
|
92
|
+
iusto odit temporibus. Lorem ipsum dolor sit, amet consectetur
|
|
93
|
+
adipisicing elit. Dicta autem incidunt voluptatum dignissimos, aperiam
|
|
94
|
+
nostrum, explicabo adipisci ipsum magnam iste id accusamus, sed
|
|
95
|
+
inventore ex eaque? Vitae iusto odit temporibus.
|
|
96
|
+
</ScrollArea>
|
|
97
|
+
);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { ScrollAreaWrapper } from "./styles";
|
|
5
|
+
|
|
6
|
+
export interface IScrollAreaProperties extends React.ComponentProps<any> {
|
|
7
|
+
scrollbar?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Add native scroll functionality to custom components.
|
|
12
|
+
*/
|
|
13
|
+
export const ScrollArea = ({
|
|
14
|
+
scrollbar = false,
|
|
15
|
+
children,
|
|
16
|
+
...restProps
|
|
17
|
+
}: IScrollAreaProperties) => {
|
|
18
|
+
return (
|
|
19
|
+
<ScrollAreaWrapper
|
|
20
|
+
aria-hidden="true"
|
|
21
|
+
data-scrollbar={scrollbar}
|
|
22
|
+
{...restProps}
|
|
23
|
+
>
|
|
24
|
+
{children}
|
|
25
|
+
</ScrollAreaWrapper>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import styled, { css } from "styled-components";
|
|
2
|
+
|
|
3
|
+
export const HiddenScrollbar = css`
|
|
4
|
+
scrollbar-width: none;
|
|
5
|
+
|
|
6
|
+
&::-webkit-scrollbar {
|
|
7
|
+
display: none;
|
|
8
|
+
width: 0;
|
|
9
|
+
height: 0;
|
|
10
|
+
}
|
|
11
|
+
&::-moz-scrollbar {
|
|
12
|
+
display: none;
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
interface ScrollContainerProps {
|
|
17
|
+
$height?: string;
|
|
18
|
+
$width?: string;
|
|
19
|
+
$thumbColor?: string;
|
|
20
|
+
$trackColor?: string;
|
|
21
|
+
$thumbHoverColor?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const CustomScrollbar = css<ScrollContainerProps>`
|
|
25
|
+
height: ${({ $height }) => $height || "100%"};
|
|
26
|
+
width: ${({ $width }) => $width || "100%"};
|
|
27
|
+
overflow-y: auto;
|
|
28
|
+
overflow-x: hidden;
|
|
29
|
+
|
|
30
|
+
&::-webkit-scrollbar {
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
|
|
33
|
+
width: var(--measurement-medium-10);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&::-webkit-scrollbar-track {
|
|
37
|
+
background: ${({ $trackColor }) => $trackColor || "transparent"};
|
|
38
|
+
border-radius: var(--measurement-medium-30);
|
|
39
|
+
border: none;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&::-webkit-scrollbar-thumb {
|
|
43
|
+
background: ${({ $thumbColor }) =>
|
|
44
|
+
$thumbColor || "var(--font-color-alpha-10)"};
|
|
45
|
+
border-radius: var(--measurement-medium-30);
|
|
46
|
+
transition: background-color 0.2s ease;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&::-webkit-scrollbar-thumb:hover {
|
|
50
|
+
background: ${({ $thumbHoverColor, $thumbColor }) =>
|
|
51
|
+
$thumbHoverColor || $thumbColor || "var(--font-color-alpha-20)"};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Firefox
|
|
55
|
+
scrollbar-width: thin;
|
|
56
|
+
scrollbar-color: ${({ $thumbColor, $trackColor }) =>
|
|
57
|
+
`${$thumbColor || "var(--font-color-alpha-10)"} ${
|
|
58
|
+
$trackColor || "transparent"
|
|
59
|
+
}`};
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
export const ScrollAreaWrapper = styled.div<any>`
|
|
63
|
+
overflow: scroll;
|
|
64
|
+
|
|
65
|
+
&[data-scrollbar="true"] {
|
|
66
|
+
${CustomScrollbar}
|
|
67
|
+
}
|
|
68
|
+
&[data-scrollbar="false"] {
|
|
69
|
+
${HiddenScrollbar}
|
|
70
|
+
}
|
|
71
|
+
`;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
|
|
4
|
+
import { Sheet } from "..";
|
|
5
|
+
import { ComponentSizeEnum, ComponentSideEnum } from "../../../../types";
|
|
6
|
+
|
|
7
|
+
// Duplicated doc: The JSDoc content isn't rendering on Storybook.
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Sheets are component that provides additional information in a top layer.
|
|
11
|
+
*
|
|
12
|
+
* **Best practices:**
|
|
13
|
+
*
|
|
14
|
+
* - Use semantic HTML elements to structure the content of the sheet.
|
|
15
|
+
* - Ensure that the sheet is visible and accessible to all users, including those using assistive technologies.
|
|
16
|
+
* - Use keyboard shortcuts to provide an alternative way of interacting with the sheet.
|
|
17
|
+
* - Ensure that the sheet is responsive and adapts to different screen sizes and orientations.
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
const meta = {
|
|
21
|
+
title: "Components/Sheet",
|
|
22
|
+
component: Sheet,
|
|
23
|
+
tags: ["autodocs"],
|
|
24
|
+
} satisfies Meta<typeof Sheet>;
|
|
25
|
+
export default meta;
|
|
26
|
+
|
|
27
|
+
type Story = StoryObj<typeof meta>;
|
|
28
|
+
export const Default = {
|
|
29
|
+
args: {
|
|
30
|
+
raw: false,
|
|
31
|
+
open: false,
|
|
32
|
+
lock: false,
|
|
33
|
+
overlay: false,
|
|
34
|
+
closeOnInteract: false,
|
|
35
|
+
shortcut: false,
|
|
36
|
+
sizing: "small",
|
|
37
|
+
side: "right",
|
|
38
|
+
hotkey: "k",
|
|
39
|
+
bindkey: null,
|
|
40
|
+
},
|
|
41
|
+
argTypes: {
|
|
42
|
+
side: {
|
|
43
|
+
options: [ComponentSideEnum.Right, ComponentSideEnum.Left],
|
|
44
|
+
control: { type: "radio" },
|
|
45
|
+
},
|
|
46
|
+
sizing: {
|
|
47
|
+
options: [
|
|
48
|
+
ComponentSizeEnum.Small,
|
|
49
|
+
ComponentSizeEnum.Medium,
|
|
50
|
+
ComponentSizeEnum.Large,
|
|
51
|
+
],
|
|
52
|
+
control: { type: "radio" },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
render: ({ ...args }) => (
|
|
56
|
+
<Sheet.Root>
|
|
57
|
+
<Sheet.Trigger>⇔</Sheet.Trigger>
|
|
58
|
+
<Sheet>
|
|
59
|
+
🐻❄️
|
|
60
|
+
<Sheet.Trigger>⇔</Sheet.Trigger>
|
|
61
|
+
</Sheet>
|
|
62
|
+
</Sheet.Root>
|
|
63
|
+
),
|
|
64
|
+
};
|
|
65
|
+
export const DefaultOpen: Story = {
|
|
66
|
+
render: ({ ...args }) => (
|
|
67
|
+
<Sheet.Root>
|
|
68
|
+
<Sheet.Trigger>⇔</Sheet.Trigger>
|
|
69
|
+
<Sheet open lock={false}>
|
|
70
|
+
🐻❄️
|
|
71
|
+
<Sheet.Trigger>⇔</Sheet.Trigger>
|
|
72
|
+
</Sheet>
|
|
73
|
+
</Sheet.Root>
|
|
74
|
+
),
|
|
75
|
+
};
|
|
76
|
+
export const Shortcut: Story = {
|
|
77
|
+
render: ({ ...args }) => (
|
|
78
|
+
<Sheet.Root>
|
|
79
|
+
<Sheet.Trigger>⇔</Sheet.Trigger>
|
|
80
|
+
<Sheet shortcut bindkey="ctrlKey" hotkey="t">
|
|
81
|
+
🐻❄️
|
|
82
|
+
<Sheet.Trigger>⇔</Sheet.Trigger>
|
|
83
|
+
</Sheet>
|
|
84
|
+
</Sheet.Root>
|
|
85
|
+
),
|
|
86
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { IReactChildren } from "../../../../../types";
|
|
3
|
+
|
|
4
|
+
type MethodsType = React.Dispatch<React.SetStateAction<boolean>> | any;
|
|
5
|
+
interface IContextProperties {
|
|
6
|
+
id: Record<string, string>;
|
|
7
|
+
states: Record<string, string | boolean>;
|
|
8
|
+
methods: Record<string, MethodsType>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const SheetContext = React.createContext<IContextProperties>({
|
|
12
|
+
id: {},
|
|
13
|
+
states: {},
|
|
14
|
+
methods: {},
|
|
15
|
+
});
|
|
16
|
+
export const useSheet = () => React.useContext(SheetContext);
|
|
17
|
+
|
|
18
|
+
export const SheetProvider = ({ children }: IReactChildren): JSX.Element => {
|
|
19
|
+
const context = useSheetProvider();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<SheetContext.Provider value={context}>{children}</SheetContext.Provider>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function useSheetProvider(): IContextProperties {
|
|
27
|
+
const containerId = React.useId();
|
|
28
|
+
const triggerId = React.useId();
|
|
29
|
+
const controlId = React.useId();
|
|
30
|
+
|
|
31
|
+
const [open, setOpen] = React.useState<boolean>(false);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
id: {
|
|
35
|
+
containerId,
|
|
36
|
+
triggerId,
|
|
37
|
+
controlId,
|
|
38
|
+
},
|
|
39
|
+
states: {
|
|
40
|
+
open,
|
|
41
|
+
},
|
|
42
|
+
methods: {
|
|
43
|
+
setOpen,
|
|
44
|
+
toggle: () => setOpen(!open),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
import { useKeyPress, useDisabledScroll } from "@usefui/hooks";
|
|
6
|
+
import { SheetProvider, useSheet } from "./hooks";
|
|
7
|
+
|
|
8
|
+
import { Button, Overlay } from "../";
|
|
9
|
+
import { SheetWrapper } from "./styles";
|
|
10
|
+
|
|
11
|
+
import { applyDataState } from "../utils";
|
|
12
|
+
|
|
13
|
+
import { IButtonProperties } from "../button";
|
|
14
|
+
import {
|
|
15
|
+
ComponentVariantEnum,
|
|
16
|
+
ComponentSizeEnum,
|
|
17
|
+
ComponentSideEnum,
|
|
18
|
+
IComponentStyling,
|
|
19
|
+
IComponentSize,
|
|
20
|
+
IComponentControlProperties,
|
|
21
|
+
IReactChildren,
|
|
22
|
+
KeyBindingEnum,
|
|
23
|
+
} from "../../../../types";
|
|
24
|
+
|
|
25
|
+
export interface ISheetProperties
|
|
26
|
+
extends IComponentStyling,
|
|
27
|
+
IComponentSize,
|
|
28
|
+
IComponentControlProperties,
|
|
29
|
+
React.ComponentProps<"dialog"> {
|
|
30
|
+
side?: "left" | "right";
|
|
31
|
+
lock?: boolean;
|
|
32
|
+
overlay?: boolean;
|
|
33
|
+
closeOnInteract?: boolean;
|
|
34
|
+
open?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ISheetComposition {
|
|
38
|
+
Root: typeof SheetRoot;
|
|
39
|
+
Trigger: typeof SheetTrigger;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const SheetRoot = ({ children }: IReactChildren) => {
|
|
43
|
+
return <SheetProvider>{children}</SheetProvider>;
|
|
44
|
+
};
|
|
45
|
+
SheetRoot.displayName = "Sheet.Root";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Sheets are component that provides additional information in a top layer.
|
|
49
|
+
*
|
|
50
|
+
* **Best practices:**
|
|
51
|
+
*
|
|
52
|
+
* - Use semantic HTML elements to structure the content of the sheet.
|
|
53
|
+
* - Ensure that the sheet is visible and accessible to all users, including those using assistive technologies.
|
|
54
|
+
* - Use keyboard shortcuts to provide an alternative way of interacting with the sheet.
|
|
55
|
+
* - Ensure that the sheet is responsive and adapts to different screen sizes and orientations.
|
|
56
|
+
*
|
|
57
|
+
* @param {ISheetProperties} props - The props for the Sheet component.
|
|
58
|
+
* @param {boolean} props.raw - Define whether the component is styled or not.
|
|
59
|
+
* @param {string} props.shortcut - The key combination used as keyboard shortcuts to trigger the sheet.
|
|
60
|
+
* @param {string} props.hotkey - The key to use in the key combination for the keyboard shortcuts.
|
|
61
|
+
* @param {KeyBindingEnum} props.bindkey - The modifier key to use in the key combination.
|
|
62
|
+
* @param {TComponentSide} props.sizing - The size of the sheet.
|
|
63
|
+
* @param {ComponentSideEnum} props.side - The side of the sheet.
|
|
64
|
+
* @param {boolean} props.open - Whether the sheet should be open by default.
|
|
65
|
+
* @param {boolean} props.lock - Whether the sheet blocks the scroll once opened. True by default.
|
|
66
|
+
* @param {boolean} props.overlay - Whether the sheet has an overlay between the page and the sheet itself.
|
|
67
|
+
* @param {boolean} props.closeOnInteract - Whether the over should be closed if interacted with.
|
|
68
|
+
* @param {ReactNode} props.children - The content to be rendered inside the sheet.
|
|
69
|
+
* @returns {ReactElement} The Sheet component.
|
|
70
|
+
*/
|
|
71
|
+
const Sheet = (props: ISheetProperties) => {
|
|
72
|
+
const {
|
|
73
|
+
raw,
|
|
74
|
+
sizing = ComponentSizeEnum.Medium,
|
|
75
|
+
side = ComponentSideEnum.Right,
|
|
76
|
+
lock = true,
|
|
77
|
+
overlay = true,
|
|
78
|
+
closeOnInteract = true,
|
|
79
|
+
open,
|
|
80
|
+
shortcut,
|
|
81
|
+
bindkey = KeyBindingEnum.Ctrl,
|
|
82
|
+
hotkey,
|
|
83
|
+
children,
|
|
84
|
+
...restProps
|
|
85
|
+
} = props;
|
|
86
|
+
const { id, states, methods } = useSheet();
|
|
87
|
+
const { toggle } = methods;
|
|
88
|
+
const shortcutControls = useKeyPress(String(hotkey), true, bindkey);
|
|
89
|
+
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
if (open && toggle) return toggle();
|
|
92
|
+
}, [open]);
|
|
93
|
+
|
|
94
|
+
React.useEffect(() => {
|
|
95
|
+
if (shortcut && shortcutControls && toggle) {
|
|
96
|
+
return toggle();
|
|
97
|
+
}
|
|
98
|
+
}, [shortcutControls]);
|
|
99
|
+
|
|
100
|
+
useDisabledScroll(lock && Boolean(states.open));
|
|
101
|
+
return (
|
|
102
|
+
<React.Fragment>
|
|
103
|
+
{states.open && (
|
|
104
|
+
<React.Fragment>
|
|
105
|
+
<SheetWrapper
|
|
106
|
+
role="dialog"
|
|
107
|
+
tabIndex={-1}
|
|
108
|
+
id={String(id.containerId)}
|
|
109
|
+
aria-label={props["aria-label"] ?? `${id.containerId}-sheet`}
|
|
110
|
+
aria-labelledby={String(id.containerId)}
|
|
111
|
+
open={Boolean(states.open)}
|
|
112
|
+
data-expanded={Boolean(states.open)}
|
|
113
|
+
data-state={applyDataState(Boolean(states.open))}
|
|
114
|
+
data-size={sizing}
|
|
115
|
+
data-side={side}
|
|
116
|
+
data-raw={Boolean(raw)}
|
|
117
|
+
{...restProps}
|
|
118
|
+
>
|
|
119
|
+
{children}
|
|
120
|
+
</SheetWrapper>
|
|
121
|
+
{overlay && (
|
|
122
|
+
<Overlay
|
|
123
|
+
onClick={() => toggle && toggle(!states.open)}
|
|
124
|
+
visible={Boolean(states.open)}
|
|
125
|
+
closeOnInteract={closeOnInteract}
|
|
126
|
+
aria-label={
|
|
127
|
+
props["aria-label"]
|
|
128
|
+
? `${props["aria-label"]}-overlay`
|
|
129
|
+
: `${id.containerId}-sheet-overlay`
|
|
130
|
+
}
|
|
131
|
+
/>
|
|
132
|
+
)}
|
|
133
|
+
</React.Fragment>
|
|
134
|
+
)}
|
|
135
|
+
</React.Fragment>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
Sheet.displayName = "Sheet";
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Sheet.Trigger are used to triggers the expansion and collapse of the associated Sheet component.
|
|
142
|
+
*
|
|
143
|
+
* **Best practices:**
|
|
144
|
+
*
|
|
145
|
+
* - Use a clear and descriptive title for the trigger that accurately conveys the content of the associated sheet section.
|
|
146
|
+
* - Ensure that the trigger can be operated using only the keyboard.
|
|
147
|
+
* - Ensure that the focus is properly managed when the trigger is activated.
|
|
148
|
+
*
|
|
149
|
+
* @param {IButtonProperties} props - The props for the Sheet.Trigger component.
|
|
150
|
+
* @param {ReactNode} props.children - The content to be rendered inside the Sheet trigger.
|
|
151
|
+
* @returns {ReactElement} The Sheet.Trigger component.
|
|
152
|
+
*/
|
|
153
|
+
const SheetTrigger = (props: IButtonProperties) => {
|
|
154
|
+
const {
|
|
155
|
+
raw,
|
|
156
|
+
onClick,
|
|
157
|
+
variant = ComponentVariantEnum.Ghost,
|
|
158
|
+
sizing = ComponentSizeEnum.Small,
|
|
159
|
+
children,
|
|
160
|
+
...restProps
|
|
161
|
+
} = props;
|
|
162
|
+
const { id, methods, states } = useSheet();
|
|
163
|
+
const { toggle } = methods;
|
|
164
|
+
|
|
165
|
+
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
166
|
+
if (onClick) onClick(event);
|
|
167
|
+
if (toggle) toggle(!states.open);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<Button
|
|
172
|
+
id={id.triggerId}
|
|
173
|
+
aria-controls={String(id.containerId)}
|
|
174
|
+
data-state={applyDataState(Boolean(states.open))}
|
|
175
|
+
variant={variant}
|
|
176
|
+
sizing={sizing}
|
|
177
|
+
raw={Boolean(raw)}
|
|
178
|
+
onClick={handleClick}
|
|
179
|
+
{...restProps}
|
|
180
|
+
>
|
|
181
|
+
{children}
|
|
182
|
+
</Button>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
SheetTrigger.displayName = "Sheet.Trigger";
|
|
186
|
+
|
|
187
|
+
Sheet.Root = SheetRoot;
|
|
188
|
+
Sheet.Trigger = SheetTrigger;
|
|
189
|
+
|
|
190
|
+
export { Sheet, SheetRoot, SheetTrigger };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import styled, { css } from "styled-components";
|
|
2
|
+
|
|
3
|
+
const SheetStyles = css`
|
|
4
|
+
all: unset;
|
|
5
|
+
position: fixed;
|
|
6
|
+
background-color: var(--body-color);
|
|
7
|
+
border: var(--measurement-small-10) solid transparent;
|
|
8
|
+
padding: var(--measurement-medium-60);
|
|
9
|
+
height: 100%;
|
|
10
|
+
z-index: var(--depth-default-100);
|
|
11
|
+
|
|
12
|
+
@keyframes slide-right {
|
|
13
|
+
0% {
|
|
14
|
+
transform: translateX(calc(var(--measurement-large-90) * 2));
|
|
15
|
+
}
|
|
16
|
+
100% {
|
|
17
|
+
transform: translateX(0);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
@keyframes slide-left {
|
|
21
|
+
0% {
|
|
22
|
+
transform: translateX(calc(var(--measurement-large-90) * -2));
|
|
23
|
+
}
|
|
24
|
+
100% {
|
|
25
|
+
transform: translateX(0);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
animation-delay: 0.2s;
|
|
30
|
+
animation-duration: 0.2s;
|
|
31
|
+
animation-timing-function: cubic-bezier(0.19, 1, 0.22, 1);
|
|
32
|
+
animation-fill-mode: backwards;
|
|
33
|
+
`;
|
|
34
|
+
const SheetSizeStyles = css`
|
|
35
|
+
&[data-size="small"] {
|
|
36
|
+
width: var(--measurement-large-80);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&[data-size="medium"] {
|
|
40
|
+
width: var(--measurement-large-90);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&[data-size="large"] {
|
|
44
|
+
width: calc(var(--measurement-large-90) * 1.5);
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
const SheetSideStyles = css`
|
|
48
|
+
top: 0;
|
|
49
|
+
|
|
50
|
+
&[data-side="right"] {
|
|
51
|
+
right: 0;
|
|
52
|
+
border-left-color: var(--font-color-alpha-10);
|
|
53
|
+
animation-name: slide-right;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&[data-side="left"] {
|
|
57
|
+
left: 0;
|
|
58
|
+
border-right-color: var(--font-color-alpha-10);
|
|
59
|
+
animation-name: slide-left;
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
export const SheetWrapper = styled.dialog<any>`
|
|
64
|
+
&[data-raw="false"] {
|
|
65
|
+
${SheetStyles}
|
|
66
|
+
${SheetSideStyles}
|
|
67
|
+
${SheetSizeStyles}
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
|
|
4
|
+
import { Switch } from "..";
|
|
5
|
+
import { ComponentSizeEnum, ComponentVariantEnum } from "../../../../types";
|
|
6
|
+
|
|
7
|
+
// Duplicated doc: The JSDoc content isn't rendering on Storybook.
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Switch are toggle components that allows the user to turn a setting on or off.
|
|
11
|
+
*
|
|
12
|
+
* **Best practices:**
|
|
13
|
+
*
|
|
14
|
+
* - Use a clear and descriptive label for each switch.
|
|
15
|
+
* - The interaction must have predictable behavior.
|
|
16
|
+
*/
|
|
17
|
+
const meta = {
|
|
18
|
+
title: "Components/Switch",
|
|
19
|
+
component: Switch,
|
|
20
|
+
tags: ["autodocs"],
|
|
21
|
+
decorators: [
|
|
22
|
+
(Story) => (
|
|
23
|
+
<div className="m-medium-30">
|
|
24
|
+
<Story />
|
|
25
|
+
</div>
|
|
26
|
+
),
|
|
27
|
+
],
|
|
28
|
+
} satisfies Meta<typeof Switch>;
|
|
29
|
+
export default meta;
|
|
30
|
+
|
|
31
|
+
type Story = StoryObj<typeof meta>;
|
|
32
|
+
export const Default: Story = {
|
|
33
|
+
args: {
|
|
34
|
+
raw: false,
|
|
35
|
+
defaultChecked: false,
|
|
36
|
+
},
|
|
37
|
+
argTypes: {
|
|
38
|
+
variant: {
|
|
39
|
+
options: [ComponentVariantEnum.Primary, ComponentVariantEnum.Ghost],
|
|
40
|
+
control: { type: "radio" },
|
|
41
|
+
},
|
|
42
|
+
sizing: {
|
|
43
|
+
options: [
|
|
44
|
+
ComponentSizeEnum.Small,
|
|
45
|
+
ComponentSizeEnum.Medium,
|
|
46
|
+
ComponentSizeEnum.Large,
|
|
47
|
+
],
|
|
48
|
+
control: { type: "radio" },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
render: ({ ...args }) => (
|
|
52
|
+
<Switch.Root>
|
|
53
|
+
<Switch>
|
|
54
|
+
<Switch.Thumb />
|
|
55
|
+
</Switch>
|
|
56
|
+
</Switch.Root>
|
|
57
|
+
),
|
|
58
|
+
};
|
|
59
|
+
export const DefaultChecked: Story = {
|
|
60
|
+
render: ({ ...args }) => (
|
|
61
|
+
<Switch.Root>
|
|
62
|
+
<Switch defaultChecked>
|
|
63
|
+
<Switch.Thumb />
|
|
64
|
+
</Switch>
|
|
65
|
+
</Switch.Root>
|
|
66
|
+
),
|
|
67
|
+
};
|
|
68
|
+
export const Sizes: Story = {
|
|
69
|
+
render: ({ ...args }) => (
|
|
70
|
+
<div className="flex g-medium-30">
|
|
71
|
+
{["large", "medium", "small"].map((item) => (
|
|
72
|
+
<Switch.Root key={item}>
|
|
73
|
+
<Switch sizing={item}>
|
|
74
|
+
<Switch.Thumb />
|
|
75
|
+
</Switch>
|
|
76
|
+
</Switch.Root>
|
|
77
|
+
))}
|
|
78
|
+
</div>
|
|
79
|
+
),
|
|
80
|
+
};
|
|
81
|
+
export const Variants: Story = {
|
|
82
|
+
render: ({ ...args }) => (
|
|
83
|
+
<div className="flex g-medium-30">
|
|
84
|
+
{["primary", "ghost"].map((item) => (
|
|
85
|
+
<Switch.Root key={item}>
|
|
86
|
+
<Switch name={item} variant={item}>
|
|
87
|
+
<Switch.Thumb />
|
|
88
|
+
</Switch>
|
|
89
|
+
<label htmlFor={item}>
|
|
90
|
+
<small>{item}</small>
|
|
91
|
+
</label>
|
|
92
|
+
</Switch.Root>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
),
|
|
96
|
+
};
|