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.
Files changed (145) hide show
  1. package/.prettierignore +3 -0
  2. package/.prettierrc +28 -0
  3. package/.storybook/main.ts +22 -0
  4. package/.storybook/preview.tsx +100 -0
  5. package/.storybook/styles/custom.scss +59 -0
  6. package/.storybook/styles/preview.css +58 -0
  7. package/.storybook/vitest.setup.ts +9 -0
  8. package/eslint.config.js +39 -0
  9. package/package.json +77 -2
  10. package/src/components/Avatar/Avatar.stories.tsx +118 -0
  11. package/src/components/Avatar/Avatar.tsx +65 -0
  12. package/src/components/Avatar/avatar.module.scss +77 -0
  13. package/src/components/Avatar/index.ts +2 -0
  14. package/src/components/BaseButton/BaseButton.tsx +36 -0
  15. package/src/components/BaseButton/base-button.module.scss +24 -0
  16. package/src/components/BaseButton/index.ts +2 -0
  17. package/src/components/Button/Button.stories.tsx +148 -0
  18. package/src/components/Button/Button.tsx +73 -0
  19. package/src/components/Button/button.module.scss +140 -0
  20. package/src/components/Button/index.ts +2 -0
  21. package/src/components/Checkbox/Checkbox.stories.tsx +180 -0
  22. package/src/components/Checkbox/Checkbox.tsx +71 -0
  23. package/src/components/Checkbox/checkbox.module.scss +82 -0
  24. package/src/components/Checkbox/index.ts +2 -0
  25. package/src/components/Dialog/Dialog.tsx +125 -0
  26. package/src/components/Dialog/dialog.module.scss +55 -0
  27. package/src/components/Dialog/index.ts +2 -0
  28. package/src/components/Drawer/Drawer.stories.tsx +89 -0
  29. package/src/components/Drawer/Drawer.tsx +57 -0
  30. package/src/components/Drawer/drawer.module.scss +170 -0
  31. package/src/components/Drawer/index.ts +2 -0
  32. package/src/components/Footer/Footer.stories.tsx +118 -0
  33. package/src/components/Footer/Footer.tsx +58 -0
  34. package/src/components/Footer/footer.module.scss +44 -0
  35. package/src/components/Footer/index.ts +2 -0
  36. package/src/components/Header/Header.stories.tsx +49 -0
  37. package/src/components/Header/Header.tsx +73 -0
  38. package/src/components/Header/header.module.scss +56 -0
  39. package/src/components/Header/index.ts +2 -0
  40. package/src/components/Highlight/Highlight.stories.tsx +83 -0
  41. package/src/components/Highlight/Highlight.tsx +40 -0
  42. package/src/components/Highlight/highlight.module.scss +47 -0
  43. package/src/components/Highlight/index.ts +2 -0
  44. package/src/components/Icon/Icon.tsx +46 -0
  45. package/src/components/Icon/icon.module.scss +17 -0
  46. package/src/components/Icon/index.ts +2 -0
  47. package/src/components/IconButton/IconButton.stories.tsx +179 -0
  48. package/src/components/IconButton/IconButton.tsx +65 -0
  49. package/src/components/IconButton/icon-button.module.scss +86 -0
  50. package/src/components/IconButton/index.ts +2 -0
  51. package/src/components/Layout/Layout.stories.tsx +88 -0
  52. package/src/components/Layout/Provider.tsx +47 -0
  53. package/src/components/Layout/context.ts +24 -0
  54. package/src/components/Layout/index.ts +2 -0
  55. package/src/components/Layout/layout.module.scss +17 -0
  56. package/src/components/List/List.stories.tsx +81 -0
  57. package/src/components/List/List.tsx +24 -0
  58. package/src/components/List/index.ts +2 -0
  59. package/src/components/List/list.module.scss +8 -0
  60. package/src/components/ListItem/ListItem.tsx +75 -0
  61. package/src/components/ListItem/index.ts +2 -0
  62. package/src/components/ListItem/list-item.module.scss +36 -0
  63. package/src/components/Modal/Modal.stories.tsx +95 -0
  64. package/src/components/Modal/Modal.tsx +94 -0
  65. package/src/components/Modal/index.ts +2 -0
  66. package/src/components/Modal/modal.module.scss +97 -0
  67. package/src/components/Odometer/Odometer.stories.tsx +66 -0
  68. package/src/components/Odometer/Odometer.tsx +45 -0
  69. package/src/components/Odometer/hooks/useOdometer.tsx +24 -0
  70. package/src/components/Odometer/index.ts +3 -0
  71. package/src/components/Odometer/odometer.module.scss +81 -0
  72. package/src/components/Odometer/odometr.d.ts +9 -0
  73. package/src/components/ScrollArea/ScrollArea.stories.tsx +58 -0
  74. package/src/components/ScrollArea/ScrollArea.tsx +63 -0
  75. package/src/components/ScrollArea/index.ts +2 -0
  76. package/src/components/ScrollArea/scroll-area.module.scss +54 -0
  77. package/src/components/SvgSprite/SvgSprite.tsx +21 -0
  78. package/src/components/SvgSprite/index.ts +2 -0
  79. package/src/components/Switch/Switch.stories.tsx +25 -0
  80. package/src/components/Switch/Switch.tsx +23 -0
  81. package/src/components/Switch/index.ts +2 -0
  82. package/src/components/Switch/switch.module.scss +65 -0
  83. package/src/components/Tag/Tag.stories.tsx +157 -0
  84. package/src/components/Tag/Tag.tsx +71 -0
  85. package/src/components/Tag/index.ts +2 -0
  86. package/src/components/Tag/tag.module.scss +118 -0
  87. package/src/components/TextArea/TextArea.stories.tsx +145 -0
  88. package/src/components/TextArea/TextArea.tsx +143 -0
  89. package/src/components/TextArea/index.ts +2 -0
  90. package/src/components/TextArea/text-area.module.scss +88 -0
  91. package/src/components/TextField/TextField.stories.tsx +177 -0
  92. package/src/components/TextField/TextField.tsx +162 -0
  93. package/src/components/TextField/index.ts +2 -0
  94. package/src/components/TextField/text-field.module.scss +129 -0
  95. package/src/components/Toast/Toast.stories.tsx +209 -0
  96. package/src/components/Toast/Toast.tsx +142 -0
  97. package/src/components/Toast/index.ts +2 -0
  98. package/src/components/Toast/toast.module.scss +267 -0
  99. package/src/components/Tooltip/Tooltip.stories.tsx +80 -0
  100. package/src/components/Tooltip/Tooltip.tsx +79 -0
  101. package/src/components/Tooltip/index.ts +2 -0
  102. package/src/components/Tooltip/tooltip.module.scss +93 -0
  103. package/src/components/View/View.stories.tsx +47 -0
  104. package/src/components/View/View.tsx +68 -0
  105. package/src/components/View/index.ts +2 -0
  106. package/src/components/View/view.module.scss +38 -0
  107. package/src/components/ViewDrawer/ViewDrawer.stories.tsx +75 -0
  108. package/src/components/ViewDrawer/ViewDrawer.tsx +24 -0
  109. package/src/components/ViewDrawer/index.ts +2 -0
  110. package/src/components/ViewModal/ViewModal.stories.tsx +68 -0
  111. package/src/components/ViewModal/ViewModal.tsx +24 -0
  112. package/src/components/ViewModal/index.ts +2 -0
  113. package/src/components/index.ts +29 -0
  114. package/src/components/types.ts +65 -0
  115. package/src/config/default.ts +3 -0
  116. package/src/config/index.ts +26 -0
  117. package/src/declaration.d.ts +8 -0
  118. package/src/index.ts +3 -0
  119. package/src/plugin/builder/ConfigBuilder.ts +32 -0
  120. package/src/plugin/builder/StyleBuilder.ts +34 -0
  121. package/src/plugin/builder/virtual.config.ts +7 -0
  122. package/src/plugin/finder/ConfigFinder.ts +26 -0
  123. package/src/plugin/finder/Finder.ts +76 -0
  124. package/src/plugin/finder/StyleFinder.ts +23 -0
  125. package/src/plugin/index.ts +70 -0
  126. package/src/plugin/types.ts +8 -0
  127. package/src/providers/UIProvider.tsx +26 -0
  128. package/src/providers/icons/IconsProvider.tsx +34 -0
  129. package/src/providers/icons/context.ts +22 -0
  130. package/src/providers/icons/index.ts +3 -0
  131. package/src/providers/index.ts +3 -0
  132. package/src/providers/theme/ThemeProvider.tsx +39 -0
  133. package/src/providers/theme/context.ts +30 -0
  134. package/src/providers/theme/index.ts +2 -0
  135. package/src/providers/theme/styles/default.scss +95 -0
  136. package/src/providers/theme/styles/reset.css +111 -0
  137. package/src/styles/mixins.scss +23 -0
  138. package/src/types/theme.ts +4 -0
  139. package/src/utils/index.ts +2 -0
  140. package/src/utils/react.ts +21 -0
  141. package/src/utils/utils.ts +12 -0
  142. package/tsconfig.json +18 -0
  143. package/vite.config.ts +11 -0
  144. package/vitest.workspace.ts +19 -0
  145. 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,2 @@
1
+ export {default as ListItem} from "./ListItem";
2
+ export type {ListItemProps, ListItemType} from "./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,2 @@
1
+ export {default as Modal, ModalRadius, modalPropsKeys} from "./Modal";
2
+ export type {ModalProps} from "./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,3 @@
1
+ export {default as Odometer} from "./Odometer";
2
+ export {default as useOdometer} from "./hooks/useOdometer";
3
+ export type {OdometerProps} from "./Odometer";
@@ -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,9 @@
1
+ declare module "odometer" {
2
+ class Odometer {
3
+ constructor(options: {el: HTMLElement; auto?: boolean; value?: number; format?: string; duration?: number});
4
+
5
+ update(value: number): void;
6
+ }
7
+
8
+ export default Odometer;
9
+ }
@@ -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
+ };