adnbn-ui 0.0.1 → 0.0.2
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/.prettierignore +3 -0
- package/.prettierrc +28 -0
- package/.storybook/main.ts +22 -0
- package/.storybook/preview.tsx +100 -0
- package/.storybook/styles/custom.scss +59 -0
- package/.storybook/styles/preview.css +58 -0
- package/.storybook/vitest.setup.ts +9 -0
- package/eslint.config.js +39 -0
- package/package.json +77 -2
- package/src/components/Avatar/Avatar.stories.tsx +118 -0
- package/src/components/Avatar/Avatar.tsx +65 -0
- package/src/components/Avatar/avatar.module.scss +77 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/BaseButton/BaseButton.tsx +36 -0
- package/src/components/BaseButton/base-button.module.scss +24 -0
- package/src/components/BaseButton/index.ts +2 -0
- package/src/components/Button/Button.stories.tsx +148 -0
- package/src/components/Button/Button.tsx +73 -0
- package/src/components/Button/button.module.scss +140 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +180 -0
- package/src/components/Checkbox/Checkbox.tsx +71 -0
- package/src/components/Checkbox/checkbox.module.scss +82 -0
- package/src/components/Checkbox/index.ts +2 -0
- package/src/components/Dialog/Dialog.tsx +125 -0
- package/src/components/Dialog/dialog.module.scss +55 -0
- package/src/components/Dialog/index.ts +2 -0
- package/src/components/Drawer/Drawer.stories.tsx +89 -0
- package/src/components/Drawer/Drawer.tsx +57 -0
- package/src/components/Drawer/drawer.module.scss +170 -0
- package/src/components/Drawer/index.ts +2 -0
- package/src/components/Footer/Footer.stories.tsx +118 -0
- package/src/components/Footer/Footer.tsx +58 -0
- package/src/components/Footer/footer.module.scss +44 -0
- package/src/components/Footer/index.ts +2 -0
- package/src/components/Header/Header.stories.tsx +49 -0
- package/src/components/Header/Header.tsx +73 -0
- package/src/components/Header/header.module.scss +56 -0
- package/src/components/Header/index.ts +2 -0
- package/src/components/Highlight/Highlight.stories.tsx +83 -0
- package/src/components/Highlight/Highlight.tsx +40 -0
- package/src/components/Highlight/highlight.module.scss +47 -0
- package/src/components/Highlight/index.ts +2 -0
- package/src/components/Icon/Icon.tsx +46 -0
- package/src/components/Icon/icon.module.scss +17 -0
- package/src/components/Icon/index.ts +2 -0
- package/src/components/IconButton/IconButton.stories.tsx +179 -0
- package/src/components/IconButton/IconButton.tsx +65 -0
- package/src/components/IconButton/icon-button.module.scss +86 -0
- package/src/components/IconButton/index.ts +2 -0
- package/src/components/Layout/Layout.stories.tsx +88 -0
- package/src/components/Layout/Provider.tsx +47 -0
- package/src/components/Layout/context.ts +24 -0
- package/src/components/Layout/index.ts +2 -0
- package/src/components/Layout/layout.module.scss +17 -0
- package/src/components/List/List.stories.tsx +81 -0
- package/src/components/List/List.tsx +24 -0
- package/src/components/List/index.ts +2 -0
- package/src/components/List/list.module.scss +8 -0
- package/src/components/ListItem/ListItem.tsx +75 -0
- package/src/components/ListItem/index.ts +2 -0
- package/src/components/ListItem/list-item.module.scss +36 -0
- package/src/components/Modal/Modal.stories.tsx +95 -0
- package/src/components/Modal/Modal.tsx +94 -0
- package/src/components/Modal/index.ts +2 -0
- package/src/components/Modal/modal.module.scss +97 -0
- package/src/components/Odometer/Odometer.stories.tsx +66 -0
- package/src/components/Odometer/Odometer.tsx +45 -0
- package/src/components/Odometer/hooks/useOdometer.tsx +24 -0
- package/src/components/Odometer/index.ts +3 -0
- package/src/components/Odometer/odometer.module.scss +81 -0
- package/src/components/Odometer/odometr.d.ts +9 -0
- package/src/components/ScrollArea/ScrollArea.stories.tsx +58 -0
- package/src/components/ScrollArea/ScrollArea.tsx +63 -0
- package/src/components/ScrollArea/index.ts +2 -0
- package/src/components/ScrollArea/scroll-area.module.scss +54 -0
- package/src/components/SvgSprite/SvgSprite.tsx +21 -0
- package/src/components/SvgSprite/index.ts +2 -0
- package/src/components/Switch/Switch.stories.tsx +25 -0
- package/src/components/Switch/Switch.tsx +23 -0
- package/src/components/Switch/index.ts +2 -0
- package/src/components/Switch/switch.module.scss +65 -0
- package/src/components/Tag/Tag.stories.tsx +157 -0
- package/src/components/Tag/Tag.tsx +71 -0
- package/src/components/Tag/index.ts +2 -0
- package/src/components/Tag/tag.module.scss +118 -0
- package/src/components/TextArea/TextArea.stories.tsx +145 -0
- package/src/components/TextArea/TextArea.tsx +143 -0
- package/src/components/TextArea/index.ts +2 -0
- package/src/components/TextArea/text-area.module.scss +88 -0
- package/src/components/TextField/TextField.stories.tsx +177 -0
- package/src/components/TextField/TextField.tsx +162 -0
- package/src/components/TextField/index.ts +2 -0
- package/src/components/TextField/text-field.module.scss +129 -0
- package/src/components/Toast/Toast.stories.tsx +209 -0
- package/src/components/Toast/Toast.tsx +142 -0
- package/src/components/Toast/index.ts +2 -0
- package/src/components/Toast/toast.module.scss +267 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +80 -0
- package/src/components/Tooltip/Tooltip.tsx +79 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/components/Tooltip/tooltip.module.scss +93 -0
- package/src/components/View/View.stories.tsx +47 -0
- package/src/components/View/View.tsx +68 -0
- package/src/components/View/index.ts +2 -0
- package/src/components/View/view.module.scss +38 -0
- package/src/components/ViewDrawer/ViewDrawer.stories.tsx +75 -0
- package/src/components/ViewDrawer/ViewDrawer.tsx +24 -0
- package/src/components/ViewDrawer/index.ts +2 -0
- package/src/components/ViewModal/ViewModal.stories.tsx +68 -0
- package/src/components/ViewModal/ViewModal.tsx +24 -0
- package/src/components/ViewModal/index.ts +2 -0
- package/src/components/index.ts +29 -0
- package/src/components/types.ts +65 -0
- package/src/config/default.ts +3 -0
- package/src/config/index.ts +26 -0
- package/src/declaration.d.ts +8 -0
- package/src/index.ts +3 -0
- package/src/plugin/builder/ConfigBuilder.ts +32 -0
- package/src/plugin/builder/StyleBuilder.ts +34 -0
- package/src/plugin/builder/virtual.config.ts +7 -0
- package/src/plugin/finder/ConfigFinder.ts +26 -0
- package/src/plugin/finder/Finder.ts +76 -0
- package/src/plugin/finder/StyleFinder.ts +23 -0
- package/src/plugin/index.ts +70 -0
- package/src/plugin/types.ts +8 -0
- package/src/providers/UIProvider.tsx +26 -0
- package/src/providers/icons/IconsProvider.tsx +34 -0
- package/src/providers/icons/context.ts +22 -0
- package/src/providers/icons/index.ts +3 -0
- package/src/providers/index.ts +3 -0
- package/src/providers/theme/ThemeProvider.tsx +39 -0
- package/src/providers/theme/context.ts +30 -0
- package/src/providers/theme/index.ts +2 -0
- package/src/providers/theme/styles/default.scss +95 -0
- package/src/providers/theme/styles/reset.css +111 -0
- package/src/styles/mixins.scss +23 -0
- package/src/types/theme.ts +4 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/react.ts +21 -0
- package/src/utils/utils.ts +12 -0
- package/tsconfig.json +18 -0
- package/vite.config.ts +11 -0
- package/vitest.workspace.ts +19 -0
- package/components/Button/index.ts +0 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
import React, {ComponentProps, forwardRef, JSX, memo, ReactNode} from "react";
|
2
|
+
import classnames from "classnames";
|
3
|
+
|
4
|
+
import {cloneOrCreateElement} from "../../utils";
|
5
|
+
import {useComponentProps} from "../../providers";
|
6
|
+
|
7
|
+
import styles from "./list-item.module.scss";
|
8
|
+
|
9
|
+
type TagType = keyof JSX.IntrinsicElements;
|
10
|
+
export type ListItemType = HTMLLIElement;
|
11
|
+
|
12
|
+
export interface ListItemProps extends ComponentProps<"li"> {
|
13
|
+
left?: ReactNode;
|
14
|
+
right?: ReactNode;
|
15
|
+
primary?: ReactNode;
|
16
|
+
secondary?: ReactNode;
|
17
|
+
leftTag?: TagType;
|
18
|
+
rightTag?: TagType;
|
19
|
+
primaryTag?: TagType;
|
20
|
+
secondaryTag?: TagType;
|
21
|
+
primaryClassName?: string;
|
22
|
+
secondaryClassName?: string;
|
23
|
+
centerClassName?: string;
|
24
|
+
leftClassName?: string;
|
25
|
+
rightClassName?: string;
|
26
|
+
}
|
27
|
+
|
28
|
+
const ListItem = forwardRef<ListItemType, ListItemProps>((props, ref) => {
|
29
|
+
const {
|
30
|
+
left,
|
31
|
+
right,
|
32
|
+
primary,
|
33
|
+
secondary,
|
34
|
+
leftTag = "div",
|
35
|
+
rightTag = "div",
|
36
|
+
primaryTag = "div",
|
37
|
+
secondaryTag = "div",
|
38
|
+
children,
|
39
|
+
className,
|
40
|
+
leftClassName,
|
41
|
+
rightClassName,
|
42
|
+
centerClassName,
|
43
|
+
primaryClassName,
|
44
|
+
secondaryClassName,
|
45
|
+
role = "list-item",
|
46
|
+
...other
|
47
|
+
} = {...useComponentProps("listItem"), ...props};
|
48
|
+
|
49
|
+
return (
|
50
|
+
<li {...other} ref={ref} role={role} className={classnames(styles["list-item"], className)}>
|
51
|
+
{cloneOrCreateElement(left, {className: classnames(styles["list-item__left"], leftClassName)}, leftTag)}
|
52
|
+
|
53
|
+
{(primary || secondary) && (
|
54
|
+
<div className={classnames(styles["list-item__center"], centerClassName)}>
|
55
|
+
{cloneOrCreateElement(
|
56
|
+
primary,
|
57
|
+
{className: classnames(styles["list-item__primary"], primaryClassName)},
|
58
|
+
primaryTag
|
59
|
+
)}
|
60
|
+
{cloneOrCreateElement(
|
61
|
+
secondary,
|
62
|
+
{className: classnames(styles["list-item__secondary"], secondaryClassName)},
|
63
|
+
secondaryTag
|
64
|
+
)}
|
65
|
+
</div>
|
66
|
+
)}
|
67
|
+
|
68
|
+
{cloneOrCreateElement(right, {className: classnames(styles["list-item__right"], rightClassName)}, rightTag)}
|
69
|
+
|
70
|
+
{children}
|
71
|
+
</li>
|
72
|
+
);
|
73
|
+
});
|
74
|
+
|
75
|
+
export default memo(ListItem);
|
@@ -0,0 +1,36 @@
|
|
1
|
+
.list-item {
|
2
|
+
display: flex;
|
3
|
+
flex-direction: row;
|
4
|
+
align-items: center;
|
5
|
+
gap: 10px;
|
6
|
+
flex-wrap: nowrap;
|
7
|
+
box-sizing: border-box;
|
8
|
+
|
9
|
+
&__center {
|
10
|
+
flex-grow: 1;
|
11
|
+
display: flex;
|
12
|
+
overflow: hidden;
|
13
|
+
flex-direction: column;
|
14
|
+
justify-content: center;
|
15
|
+
}
|
16
|
+
|
17
|
+
&__primary,
|
18
|
+
&__secondary {
|
19
|
+
overflow: hidden;
|
20
|
+
white-space: nowrap;
|
21
|
+
text-overflow: ellipsis;
|
22
|
+
}
|
23
|
+
&__primary {
|
24
|
+
color: var(--list-item-primary-color, var(--text-primary-color));
|
25
|
+
}
|
26
|
+
&__secondary {
|
27
|
+
color: var(--list-item-secondary-color, var(--text-secondary-color));
|
28
|
+
}
|
29
|
+
|
30
|
+
&__left,
|
31
|
+
&__right {
|
32
|
+
display: flex;
|
33
|
+
align-items: center;
|
34
|
+
justify-content: center;
|
35
|
+
}
|
36
|
+
}
|
@@ -0,0 +1,95 @@
|
|
1
|
+
import {useState} from "react";
|
2
|
+
import {Meta} from "@storybook/react";
|
3
|
+
|
4
|
+
import {capitalizeFirstLetter, hideInTable} from "../../utils";
|
5
|
+
|
6
|
+
import {Button, Header} from "../index";
|
7
|
+
|
8
|
+
import ModalComponent, {ModalProps, ModalRadius} from "./Modal";
|
9
|
+
|
10
|
+
const radius: (ModalRadius | "default")[] = [
|
11
|
+
ModalRadius.None,
|
12
|
+
ModalRadius.Small,
|
13
|
+
"default",
|
14
|
+
ModalRadius.Medium,
|
15
|
+
ModalRadius.Large,
|
16
|
+
];
|
17
|
+
|
18
|
+
const meta: Meta<typeof ModalComponent> = {
|
19
|
+
title: "Components/Modal",
|
20
|
+
component: ModalComponent,
|
21
|
+
tags: ["autodocs"],
|
22
|
+
argTypes: {
|
23
|
+
radius: {
|
24
|
+
options: radius,
|
25
|
+
control: {type: "select"},
|
26
|
+
},
|
27
|
+
modal: {
|
28
|
+
description:
|
29
|
+
"The modality of the dialog. When set to true, interaction with outside elements will be disabled and only dialog content will be visible to screen readers.",
|
30
|
+
control: {type: "boolean"},
|
31
|
+
type: "boolean",
|
32
|
+
},
|
33
|
+
closeButton: {
|
34
|
+
options: [true, false, {children: "❌"}],
|
35
|
+
control: {type: "select"},
|
36
|
+
},
|
37
|
+
speed: {
|
38
|
+
type: "number",
|
39
|
+
},
|
40
|
+
onClose: hideInTable,
|
41
|
+
children: hideInTable,
|
42
|
+
className: hideInTable,
|
43
|
+
description: hideInTable,
|
44
|
+
overlayClassName: hideInTable,
|
45
|
+
childrenClassName: hideInTable,
|
46
|
+
},
|
47
|
+
};
|
48
|
+
|
49
|
+
export default meta;
|
50
|
+
|
51
|
+
export const Modal = (props: ModalProps & {label?: string}) => {
|
52
|
+
const [open, setOpen] = useState(false);
|
53
|
+
const {label = "Open Modal", ...other} = props;
|
54
|
+
return (
|
55
|
+
<div>
|
56
|
+
<Button onClick={() => setOpen(true)}>{label}</Button>
|
57
|
+
<ModalComponent open={open} onOpenChange={setOpen} {...other}>
|
58
|
+
<div
|
59
|
+
style={{
|
60
|
+
display: "flex",
|
61
|
+
alignItems: "center",
|
62
|
+
justifyContent: "center",
|
63
|
+
height: "100%",
|
64
|
+
gap: "30px",
|
65
|
+
}}
|
66
|
+
>
|
67
|
+
<Header
|
68
|
+
title="Volume Up Plus"
|
69
|
+
subtitle="Adjust the current tab's volume with the slider. Switch to any audio tab in one click."
|
70
|
+
before="❤️"
|
71
|
+
/>
|
72
|
+
<Button style={{margin: "50px auto", maxWidth: "max-content"}} onClick={() => setOpen(false)}>
|
73
|
+
Close Modal
|
74
|
+
</Button>
|
75
|
+
</div>
|
76
|
+
</ModalComponent>
|
77
|
+
</div>
|
78
|
+
);
|
79
|
+
};
|
80
|
+
|
81
|
+
export const Radius = () => {
|
82
|
+
return (
|
83
|
+
<div className="grid-wrapper" style={{gridTemplateColumns: "repeat(5, auto)"}}>
|
84
|
+
{radius.map(radius => (
|
85
|
+
<div key={radius} className="item-card">
|
86
|
+
<Modal
|
87
|
+
radius={radius !== "default" ? radius : undefined}
|
88
|
+
fullscreen={false}
|
89
|
+
label={`${capitalizeFirstLetter(radius)} radius`}
|
90
|
+
/>
|
91
|
+
</div>
|
92
|
+
))}
|
93
|
+
</div>
|
94
|
+
);
|
95
|
+
};
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import React, {FC, isValidElement, memo, ReactElement, useCallback} from "react";
|
2
|
+
import classnames from "classnames";
|
3
|
+
|
4
|
+
import {useComponentProps} from "../../providers";
|
5
|
+
import {cloneOrCreateElement} from "../../utils";
|
6
|
+
|
7
|
+
import {Dialog, DialogProps, dialogPropsKeys} from "../Dialog";
|
8
|
+
import {IconButton, IconButtonProps} from "../IconButton";
|
9
|
+
|
10
|
+
import styles from "./modal.module.scss";
|
11
|
+
|
12
|
+
export enum ModalRadius {
|
13
|
+
None = "none",
|
14
|
+
Small = "small",
|
15
|
+
Medium = "medium",
|
16
|
+
Large = "large",
|
17
|
+
}
|
18
|
+
|
19
|
+
export interface ModalProps extends DialogProps {
|
20
|
+
radius?: ModalRadius;
|
21
|
+
closeButton?: boolean | IconButtonProps | ReactElement;
|
22
|
+
onClose?: () => void;
|
23
|
+
}
|
24
|
+
|
25
|
+
export const modalPropsKeys = new Set<keyof ModalProps>(["radius", "closeButton", "onClose", ...dialogPropsKeys]);
|
26
|
+
|
27
|
+
const Modal: FC<ModalProps> = props => {
|
28
|
+
const {
|
29
|
+
radius,
|
30
|
+
fullscreen = true,
|
31
|
+
closeButton = true,
|
32
|
+
onClose,
|
33
|
+
onOpenChange,
|
34
|
+
children,
|
35
|
+
className,
|
36
|
+
overlayClassName,
|
37
|
+
childrenClassName,
|
38
|
+
...other
|
39
|
+
} = {...useComponentProps("modal"), ...props};
|
40
|
+
|
41
|
+
const handleClose = useCallback(
|
42
|
+
(event: React.MouseEvent<HTMLButtonElement>) => {
|
43
|
+
onClose?.();
|
44
|
+
onOpenChange?.(false);
|
45
|
+
if (typeof closeButton === "object" && !isValidElement(closeButton)) {
|
46
|
+
closeButton?.onClick?.(event);
|
47
|
+
}
|
48
|
+
},
|
49
|
+
[onClose, onOpenChange, closeButton]
|
50
|
+
);
|
51
|
+
|
52
|
+
const renderCloseButton = useCallback(() => {
|
53
|
+
if (!closeButton) return null;
|
54
|
+
|
55
|
+
if (isValidElement(closeButton)) return closeButton;
|
56
|
+
|
57
|
+
const closeButtonProps = typeof closeButton === "object" ? closeButton : {};
|
58
|
+
|
59
|
+
return (
|
60
|
+
<IconButton
|
61
|
+
aria-label="Close"
|
62
|
+
children="✖"
|
63
|
+
{...closeButtonProps}
|
64
|
+
onClick={handleClose}
|
65
|
+
className={classnames(styles["modal-close"], closeButtonProps.className)}
|
66
|
+
/>
|
67
|
+
);
|
68
|
+
}, [closeButton, handleClose]);
|
69
|
+
|
70
|
+
return (
|
71
|
+
<Dialog
|
72
|
+
{...other}
|
73
|
+
onOpenChange={onOpenChange}
|
74
|
+
overlayClassName={classnames(styles["modal-overlay"], overlayClassName)}
|
75
|
+
className={classnames(
|
76
|
+
styles["modal-content"],
|
77
|
+
{
|
78
|
+
[styles["modal-content--fullscreen"]]: fullscreen,
|
79
|
+
[styles[`modal-content--${radius}-radius`]]: radius,
|
80
|
+
},
|
81
|
+
className
|
82
|
+
)}
|
83
|
+
>
|
84
|
+
{cloneOrCreateElement(
|
85
|
+
children,
|
86
|
+
{className: classnames(styles["modal-children"], childrenClassName)},
|
87
|
+
"div"
|
88
|
+
)}
|
89
|
+
{renderCloseButton()}
|
90
|
+
</Dialog>
|
91
|
+
);
|
92
|
+
};
|
93
|
+
|
94
|
+
export default memo(Modal);
|
@@ -0,0 +1,97 @@
|
|
1
|
+
@use "../../styles/mixins" as dir;
|
2
|
+
|
3
|
+
$root: modal;
|
4
|
+
|
5
|
+
.#{$root} {
|
6
|
+
&-overlay {
|
7
|
+
background-color: var(--modal-overlay-bg-color, var(--dialog-overlay-bg-color, #111));
|
8
|
+
}
|
9
|
+
|
10
|
+
&-content {
|
11
|
+
top: 50%;
|
12
|
+
left: 50%;
|
13
|
+
transform: translate(-50%, -50%);
|
14
|
+
width: var(--modal-width, 90vw);
|
15
|
+
max-width: var(--modal-max-width, 350px);
|
16
|
+
max-height: var(--modal-max-height, 85vh);
|
17
|
+
padding: var(--modal-padding, 0);
|
18
|
+
border-radius: var(--modal-border-radius, 10px);
|
19
|
+
box-shadow: var(--modal-box-shadow, 0 0 4px rgba(0, 0, 0, 0.5));
|
20
|
+
background-color: var(--modal-bg-color, var(--bg-primary-color));
|
21
|
+
transition:
|
22
|
+
background-color var(--transition-speed-sm),
|
23
|
+
color var(--transition-speed-sm);
|
24
|
+
|
25
|
+
&[data-state="open"] {
|
26
|
+
animation-name: contentShow;
|
27
|
+
}
|
28
|
+
|
29
|
+
&[data-state="closed"] {
|
30
|
+
animation-name: contentHide;
|
31
|
+
}
|
32
|
+
|
33
|
+
&--fullscreen {
|
34
|
+
max-width: 100%;
|
35
|
+
max-height: 100%;
|
36
|
+
width: 100%;
|
37
|
+
height: 100%;
|
38
|
+
border-radius: 0 !important;
|
39
|
+
}
|
40
|
+
|
41
|
+
&--none-radius {
|
42
|
+
border-radius: 0;
|
43
|
+
}
|
44
|
+
|
45
|
+
&--small-radius {
|
46
|
+
border-radius: var(--modal-border-radius-sm, 5px);
|
47
|
+
}
|
48
|
+
|
49
|
+
&--medium-radius {
|
50
|
+
border-radius: var(--modal-border-radius-sm, 15px);
|
51
|
+
}
|
52
|
+
|
53
|
+
&--large-radius {
|
54
|
+
border-radius: var(--modal-border-radius-sm, 20px);
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
&-children {
|
59
|
+
display: flex;
|
60
|
+
flex-direction: column;
|
61
|
+
}
|
62
|
+
|
63
|
+
&-close {
|
64
|
+
position: absolute !important;
|
65
|
+
top: var(--modal-close-offset, 5px);
|
66
|
+
|
67
|
+
@include dir.ltr {
|
68
|
+
right: var(--modal-close-offset, 5px);
|
69
|
+
}
|
70
|
+
|
71
|
+
@include dir.rtl {
|
72
|
+
left: var(--modal-close-offset, 5px);
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
@keyframes contentShow {
|
78
|
+
from {
|
79
|
+
opacity: 0;
|
80
|
+
transform: translate(-50%, -48%) scale(var(--modal-animation-content-scale, 0.96));
|
81
|
+
}
|
82
|
+
to {
|
83
|
+
opacity: 1;
|
84
|
+
transform: translate(-50%, -50%) scale(1);
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
@keyframes contentHide {
|
89
|
+
from {
|
90
|
+
opacity: 1;
|
91
|
+
transform: translate(-50%, -50%) scale(1);
|
92
|
+
}
|
93
|
+
to {
|
94
|
+
opacity: 0;
|
95
|
+
transform: translate(-50%, -48%) scale(var(--modal-animation-content-scale, 0.96));
|
96
|
+
}
|
97
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import {useEffect, useState} from "react";
|
2
|
+
import {Meta, StoryObj} from "@storybook/react";
|
3
|
+
|
4
|
+
import {hideInTable} from "../../utils";
|
5
|
+
|
6
|
+
import OdometerComponent from "./Odometer";
|
7
|
+
|
8
|
+
const meta: Meta<typeof OdometerComponent> = {
|
9
|
+
title: "Components/Odometer",
|
10
|
+
component: OdometerComponent,
|
11
|
+
tags: ["autodocs"],
|
12
|
+
argTypes: {
|
13
|
+
value: {
|
14
|
+
description: "The number to display on the odometer.",
|
15
|
+
control: {type: "number"},
|
16
|
+
},
|
17
|
+
format: {
|
18
|
+
description: "Formatting string for odometer value.",
|
19
|
+
control: {type: "text"},
|
20
|
+
},
|
21
|
+
duration: {
|
22
|
+
description: "Animation duration in milliseconds.",
|
23
|
+
control: {type: "number"},
|
24
|
+
},
|
25
|
+
auto: hideInTable,
|
26
|
+
className: hideInTable,
|
27
|
+
},
|
28
|
+
};
|
29
|
+
|
30
|
+
export default meta;
|
31
|
+
|
32
|
+
export const Odometer: StoryObj<typeof OdometerComponent> = {
|
33
|
+
args: {
|
34
|
+
value: 1234,
|
35
|
+
duration: 500,
|
36
|
+
format: "d",
|
37
|
+
},
|
38
|
+
};
|
39
|
+
|
40
|
+
export const OdometerCount = () => {
|
41
|
+
const [value, setValue] = useState(100);
|
42
|
+
|
43
|
+
useEffect(() => {
|
44
|
+
const interval = setInterval(() => {
|
45
|
+
setValue(prev => ++prev);
|
46
|
+
}, 1000);
|
47
|
+
|
48
|
+
return () => clearInterval(interval);
|
49
|
+
}, []);
|
50
|
+
|
51
|
+
return <OdometerComponent value={value} />;
|
52
|
+
};
|
53
|
+
|
54
|
+
export const OdometerRandom = () => {
|
55
|
+
const [value, setValue] = useState(100);
|
56
|
+
|
57
|
+
useEffect(() => {
|
58
|
+
const interval = setInterval(() => {
|
59
|
+
setValue(prev => prev + Math.floor(Math.random() * 10) + 1);
|
60
|
+
}, 3000);
|
61
|
+
|
62
|
+
return () => clearInterval(interval);
|
63
|
+
}, []);
|
64
|
+
|
65
|
+
return <OdometerComponent value={value} duration={1000} />;
|
66
|
+
};
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import React, {FC, memo, useRef} from "react";
|
2
|
+
|
3
|
+
import classNames from "classnames";
|
4
|
+
|
5
|
+
import {useComponentProps} from "../../providers";
|
6
|
+
|
7
|
+
import useOdometer, {OdometerOptions} from "./hooks/useOdometer";
|
8
|
+
|
9
|
+
import styles from "./odometer.module.scss";
|
10
|
+
|
11
|
+
export interface OdometerProps extends OdometerOptions {
|
12
|
+
value: number;
|
13
|
+
className?: string;
|
14
|
+
}
|
15
|
+
|
16
|
+
const Odometer: FC<OdometerProps> = props => {
|
17
|
+
const {
|
18
|
+
value,
|
19
|
+
auto = false,
|
20
|
+
format = "d",
|
21
|
+
duration = 250,
|
22
|
+
className,
|
23
|
+
...other
|
24
|
+
} = {...useComponentProps("odometer"), ...props};
|
25
|
+
|
26
|
+
const targetRef = useRef(null);
|
27
|
+
|
28
|
+
useOdometer(targetRef, value, {
|
29
|
+
...other,
|
30
|
+
auto,
|
31
|
+
format,
|
32
|
+
duration,
|
33
|
+
});
|
34
|
+
|
35
|
+
return (
|
36
|
+
<span
|
37
|
+
ref={targetRef}
|
38
|
+
dir="ltr"
|
39
|
+
style={{"--speed": `${duration}ms`} as React.CSSProperties}
|
40
|
+
className={classNames(styles["odometer"], className)}
|
41
|
+
/>
|
42
|
+
);
|
43
|
+
};
|
44
|
+
|
45
|
+
export default memo(Odometer);
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import {RefObject, useEffect, useRef} from "react";
|
2
|
+
import Odometer from "odometer";
|
3
|
+
|
4
|
+
export interface OdometerOptions {
|
5
|
+
auto?: boolean;
|
6
|
+
format?: string;
|
7
|
+
duration?: number;
|
8
|
+
}
|
9
|
+
|
10
|
+
export default (ref: RefObject<HTMLElement | null>, value: number, options: OdometerOptions = {}) => {
|
11
|
+
const od = useRef<Odometer | null>(null);
|
12
|
+
|
13
|
+
useEffect(() => {
|
14
|
+
if (ref.current === null) return;
|
15
|
+
|
16
|
+
od.current = new Odometer({...options, el: ref.current, value});
|
17
|
+
}, [ref, options, value]);
|
18
|
+
|
19
|
+
useEffect(() => {
|
20
|
+
od.current?.update(value);
|
21
|
+
}, [value]);
|
22
|
+
|
23
|
+
return od.current;
|
24
|
+
};
|
@@ -0,0 +1,81 @@
|
|
1
|
+
$root: odometer;
|
2
|
+
|
3
|
+
.#{$root} {
|
4
|
+
display: inline-block;
|
5
|
+
position: relative;
|
6
|
+
color: var(--odometer-color, var(--text-primary-color));
|
7
|
+
font-family: var(--odometer-font-family, var(--font-family)), sans-serif;
|
8
|
+
|
9
|
+
&-digit {
|
10
|
+
display: inline-block;
|
11
|
+
position: relative;
|
12
|
+
|
13
|
+
&-spacer {
|
14
|
+
display: inline-block;
|
15
|
+
visibility: hidden;
|
16
|
+
}
|
17
|
+
|
18
|
+
&-inner {
|
19
|
+
text-align: left;
|
20
|
+
display: block;
|
21
|
+
position: absolute;
|
22
|
+
top: 0;
|
23
|
+
left: 0;
|
24
|
+
right: 0;
|
25
|
+
bottom: 0;
|
26
|
+
overflow: hidden;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
&-ribbon {
|
31
|
+
display: block;
|
32
|
+
|
33
|
+
&-inner {
|
34
|
+
display: block;
|
35
|
+
-webkit-backface-visibility: hidden;
|
36
|
+
|
37
|
+
.#{$root}-animating-up & {
|
38
|
+
transition: transform var(--speed);
|
39
|
+
}
|
40
|
+
|
41
|
+
.#{$root}-animating-up.#{$root}-animating & {
|
42
|
+
transform: translateY(-100%);
|
43
|
+
}
|
44
|
+
|
45
|
+
.#{$root}-animating-down & {
|
46
|
+
transform: translateY(-100%);
|
47
|
+
}
|
48
|
+
|
49
|
+
.#{$root}-animating-down.#{$root}-animating & {
|
50
|
+
transition: transform var(--speed);
|
51
|
+
transform: translateY(0);
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
&-value {
|
57
|
+
display: block;
|
58
|
+
text-align: center;
|
59
|
+
transform: translateZ(0);
|
60
|
+
}
|
61
|
+
|
62
|
+
&-last-value {
|
63
|
+
position: absolute;
|
64
|
+
}
|
65
|
+
|
66
|
+
&-inside {
|
67
|
+
transition:
|
68
|
+
all var(--speed),
|
69
|
+
color var(--transition-speed-sm);
|
70
|
+
|
71
|
+
&:before {
|
72
|
+
content: "";
|
73
|
+
width: 0;
|
74
|
+
display: inline-block;
|
75
|
+
color: inherit;
|
76
|
+
transition:
|
77
|
+
all var(--speed),
|
78
|
+
color var(--transition-speed-sm);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import {Meta, StoryObj} from "@storybook/react";
|
2
|
+
|
3
|
+
import {hideInTable} from "../../utils";
|
4
|
+
|
5
|
+
import ScrollAreaComponent from "./ScrollArea";
|
6
|
+
|
7
|
+
const meta: Meta<typeof ScrollAreaComponent> = {
|
8
|
+
title: "Components/ScrollArea",
|
9
|
+
component: ScrollAreaComponent,
|
10
|
+
tags: ["autodocs"],
|
11
|
+
argTypes: {
|
12
|
+
type: {
|
13
|
+
description:
|
14
|
+
"Describes the nature of scrollbar visibility, similar to how the scrollbar preferences in MacOS control visibility of native scrollbars.",
|
15
|
+
options: ["auto", "always", "scroll", "hover"],
|
16
|
+
control: {type: "select"},
|
17
|
+
},
|
18
|
+
scrollHideDelay: {
|
19
|
+
description:
|
20
|
+
'If type is set to either "scroll" or "hover", this prop determines the length of time, in milliseconds, before the scrollbars are hidden after the user stops interacting with scrollbars.',
|
21
|
+
},
|
22
|
+
dir: {
|
23
|
+
description:
|
24
|
+
"The reading direction of the scroll area. If omitted, inherits globally from DirectionProvider or assumes LTR (left-to-right) reading mode.",
|
25
|
+
options: ["ltr", "rtl"],
|
26
|
+
control: {type: "select"},
|
27
|
+
},
|
28
|
+
|
29
|
+
style: hideInTable,
|
30
|
+
className: hideInTable,
|
31
|
+
thumbClassName: hideInTable,
|
32
|
+
cornerClassName: hideInTable,
|
33
|
+
viewportClassName: hideInTable,
|
34
|
+
scrollbarClassName: hideInTable,
|
35
|
+
},
|
36
|
+
};
|
37
|
+
|
38
|
+
export default meta;
|
39
|
+
|
40
|
+
export const ScrollArea: StoryObj<typeof ScrollAreaComponent> = {
|
41
|
+
args: {
|
42
|
+
type: "hover",
|
43
|
+
dir: "ltr",
|
44
|
+
xOffset: 3,
|
45
|
+
yOffset: 3,
|
46
|
+
scrollHideDelay: 600,
|
47
|
+
style: {
|
48
|
+
padding: "10px",
|
49
|
+
width: "300px",
|
50
|
+
height: "150px",
|
51
|
+
borderRadius: "10px",
|
52
|
+
color: "var(--text-secondary-color)",
|
53
|
+
background: "var(--bg-secondary-color)",
|
54
|
+
},
|
55
|
+
children:
|
56
|
+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam vehicula, justo at congue malesuada, arcu elit malesuada eros, ut tempor justo libero a est. Donec sit amet tortor nec justo auctor sagittis. Suspendisse potenti. Fusce gravida, libero vel auctor pretium, odio risus vehicula nunc, et ultricies nunc arcu a neque. Aliquam erat volutpat. Morbi vulputate erat nec lectus vestibulum, at ullamcorper risus aliquet",
|
57
|
+
},
|
58
|
+
};
|