diff --git a/package-lock.json b/package-lock.json index 397e76a..5385e96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,14 @@ "version": "0.5.0", "license": "MIT", "dependencies": { + "@types/autosize": "^4.0.3", + "@types/react-highlight-words": "^0.20.0", "autosize": "^6.0.1", "classnames": "^2.5.1", "odometer": "^0.4.8", "radix-ui": "^1.1.3", "react-highlight-words": "^0.21.0", + "react-responsive-overflow-list": "^0.2.1", "sass": "^1.85.1", "ts-deepmerge": "^7.0.3" }, @@ -24,13 +27,12 @@ "@release-it/conventional-changelog": "^10.0.1", "@rsbuild/plugin-sass": "^1.4.0", "@storybook/react": "^9.1.3", - "@types/autosize": "^4.0.3", "@types/chrome": "^0.1.12", "@types/jest": "^30.0.0", + "@types/lodash": "^4.17.20", "@types/node": "^22.13.10", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", - "@types/react-highlight-words": "^0.20.0", "adnbn": "^0.4.2", "depcheck": "^1.4.7", "eslint": "^9.21.0", @@ -7876,7 +7878,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/autosize/-/autosize-4.0.3.tgz", "integrity": "sha512-o0ZyU3ePp3+KRbhHsY4ogjc+ZQWgVN5h6j8BHW5RII4cFKi6PEKK9QPAcphJVkD0dGpyFnD3VRR0WMvHVjCv9w==", - "dev": true, "license": "MIT" }, "node_modules/@types/babel__core": { @@ -8192,6 +8193,13 @@ "@types/node": "*" } }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mdx": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", @@ -8272,7 +8280,6 @@ "version": "19.2.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", - "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -8292,7 +8299,6 @@ "version": "0.20.0", "resolved": "https://registry.npmjs.org/@types/react-highlight-words/-/react-highlight-words-0.20.0.tgz", "integrity": "sha512-Qm512TiOakvtNzHJ2+TNVHnLn5cJ2wLQV0+LrhuispVth6dRf5b8ydjq3Kc0thpZ7bz4s6RnG6meboAXHWRK+Q==", - "dev": true, "license": "MIT", "dependencies": { "@types/react": "*" @@ -11599,7 +11605,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, "license": "MIT" }, "node_modules/dargs": { @@ -18704,6 +18709,16 @@ } } }, + "node_modules/react-responsive-overflow-list": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/react-responsive-overflow-list/-/react-responsive-overflow-list-0.2.1.tgz", + "integrity": "sha512-aA/0WLHrGeyVn0OzNaTsUc8/mFqvwkVwX3pfXhNhhVEGNZvqUKNlY3PnpHxWcWLEpzR2xzuesKVgQhFLZU4Uyw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", diff --git a/package.json b/package.json index f4cecbf..a1073fc 100644 --- a/package.json +++ b/package.json @@ -83,18 +83,20 @@ "odometer": "^0.4.8", "radix-ui": "^1.1.3", "react-highlight-words": "^0.21.0", + "react-responsive-overflow-list": "^0.2.1", "sass": "^1.85.1", "ts-deepmerge": "^7.0.3" }, "devDependencies": { "@commitlint/cli": "^20.0.0", "@commitlint/config-conventional": "^20.0.0", - "@release-it/conventional-changelog": "^10.0.1", - "@types/chrome": "^0.1.12", - "@types/jest": "^30.0.0", "@eslint/js": "^9.21.0", + "@release-it/conventional-changelog": "^10.0.1", "@rsbuild/plugin-sass": "^1.4.0", "@storybook/react": "^9.1.3", + "@types/chrome": "^0.1.12", + "@types/jest": "^30.0.0", + "@types/lodash": "^4.17.20", "@types/node": "^22.13.10", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", @@ -104,12 +106,12 @@ "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^15.15.0", - "prettier": "^3.5.3", "husky": "^9.1.7", "jest": "^30.1.3", - "release-it": "^19.0.5", + "prettier": "^3.5.3", "react": "^19.1.0", "react-dom": "^19.1.0", + "release-it": "^19.0.5", "rspack-plugin-virtual-module": "^1.0.0", "storybook": "^9.1.3", "storybook-react-rsbuild": "^2.1.0", diff --git a/src/components/Avatar/Avatar.stories.tsx b/src/components/Avatar/Avatar.stories.tsx index d2cb5bf..85d41b7 100644 --- a/src/components/Avatar/Avatar.stories.tsx +++ b/src/components/Avatar/Avatar.stories.tsx @@ -7,7 +7,7 @@ import AvatarComponent from "./Avatar"; import {AvatarRadius, AvatarSize} from "./types"; const sizes: (AvatarSize | "default")[] = [AvatarSize.Small, "default", AvatarSize.Medium, AvatarSize.Large]; -const radius: (AvatarRadius | "default")[] = [AvatarRadius.Small, AvatarRadius.Medium, AvatarRadius.Large, "default"]; +const radius: (AvatarRadius | "default")[] = [AvatarRadius.None, AvatarRadius.Small, AvatarRadius.Medium, AvatarRadius.Large, "default"]; const meta: Meta = { title: "Components/Avatar", @@ -39,7 +39,7 @@ export const Avatar: StoryObj = { export const AvatarRadiusGrid = () => { return ( -
+
{radius.map(radius => (
{capitalizeFirstLetter(radius)} @@ -103,7 +103,7 @@ export const SizeWithFallback = () => { export const SizeRadius = () => { return ( -
+
{sizes.map(size => radius.map(radius => (
diff --git a/src/components/Avatar/avatar.module.scss b/src/components/Avatar/avatar.module.scss index 67426be..d8564b3 100644 --- a/src/components/Avatar/avatar.module.scss +++ b/src/components/Avatar/avatar.module.scss @@ -42,6 +42,10 @@ $root: avatar; } // Radius + &--none-radius { + border-radius: 0; + } + &--small-radius { border-radius: var(--avatar-border-radius-sm, 20%); } diff --git a/src/components/Avatar/types.ts b/src/components/Avatar/types.ts index 62ca0e2..4844e23 100644 --- a/src/components/Avatar/types.ts +++ b/src/components/Avatar/types.ts @@ -5,6 +5,7 @@ export enum AvatarSize { } export enum AvatarRadius { + None = "none", Small = "small", Medium = "medium", Large = "large", diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/Button.stories.tsx index 83760c3..9dc8cad 100644 --- a/src/components/Button/Button.stories.tsx +++ b/src/components/Button/Button.stories.tsx @@ -7,7 +7,7 @@ import ButtonComponent from "./Button"; import {ButtonColor, ButtonRadius, ButtonSize, ButtonVariant} from "./types"; const variants: ButtonVariant[] = [ButtonVariant.Contained, ButtonVariant.Outlined, ButtonVariant.Text]; -const colors: (ButtonColor | "default")[] = ["default", ButtonColor.Primary, ButtonColor.Secondary, ButtonColor.Accent]; +const colors: (ButtonColor | "default")[] = ["default", ButtonColor.Primary, ButtonColor.Secondary, ButtonColor.Accent, ButtonColor.Error, ButtonColor.Success]; const sizes: (ButtonSize | "default")[] = [ButtonSize.Small, "default", ButtonSize.Medium, ButtonSize.Large]; const radius: (ButtonRadius | "default")[] = [ ButtonRadius.Small, @@ -57,7 +57,7 @@ export const Button: StoryObj = { export const VariantColor = () => { return ( -
+
{variants.map(variant => colors.map(color => (
@@ -73,7 +73,7 @@ export const VariantColor = () => { export const VariantColorDisabled = () => { return ( -
+
{variants.map(variant => colors.map(color => (
diff --git a/src/components/Button/button.module.scss b/src/components/Button/button.module.scss index 98249a1..10d2be5 100644 --- a/src/components/Button/button.module.scss +++ b/src/components/Button/button.module.scss @@ -5,7 +5,7 @@ $root: button; font-weight: var(--button-font-weight, 500); font-size: var(--button-font-size, var(--font-size, 14px)); letter-spacing: var(--button-letter-spacing, 0.5px); - line-height: var(--button-line-height, var(--line-height, 1 rem)); + line-height: var(--button-line-height, var(--line-height, 1rem)); height: var(--button-height, 34px); border-radius: var(--button-border-radius, 10px); padding: var(--button-padding, 0 16px); @@ -38,6 +38,16 @@ $root: button; color: #fff; background: var(--accent-color); } + + &.#{$root}--error-color { + color: #fff; + background: var(--error-color); + } + + &.#{$root}--success-color { + color: #fff; + background: var(--success-color); + } } &--outlined { @@ -55,17 +65,27 @@ $root: button; &.#{$root}--primary-color { color: var(--primary-color); - border-color: var(--button-outlined-border-primary-color); + border-color: var(--button-outlined-border-primary-color, var(--primary-color)); } &.#{$root}--secondary-color { color: var(--secondary-color); - border-color: var(--button-outlined-border-secondary-color); + border-color: var(--button-outlined-border-secondary-color, var(--secondary-color)); } &.#{$root}--accent-color { color: var(--accent-color); - border-color: var(--button-outlined-border-accent-color); + border-color: var(--button-outlined-border-accent-color, var(--accent-color-color)); + } + + &.#{$root}--error-color { + color: var(--error-color); + border-color: var(--button-outlined-border-error-color, var(--error-color)); + } + + &.#{$root}--success-color { + color: var(--success-color); + border-color: var(--button-outlined-border-success-color, var(--success-color)); } } @@ -92,6 +112,14 @@ $root: button; &.#{$root}--accent-color { color: var(--accent-color); } + + &.#{$root}--error-color { + color: var(--error-color); + } + + &.#{$root}--success-color { + color: var(--success-color); + } } // Sizes diff --git a/src/components/Button/types.ts b/src/components/Button/types.ts index e3a3383..3641b16 100644 --- a/src/components/Button/types.ts +++ b/src/components/Button/types.ts @@ -8,6 +8,8 @@ export enum ButtonColor { Primary = "primary", Secondary = "secondary", Accent = "accent", + Error = "error", + Success = "success", } export enum ButtonSize { diff --git a/src/components/List/list.module.scss b/src/components/List/list.module.scss index 35c4877..d835ee8 100644 --- a/src/components/List/list.module.scss +++ b/src/components/List/list.module.scss @@ -1,7 +1,8 @@ .list { display: flex; flex-direction: column; - gap: 10px; + gap: var(--list-gap, 10px); + width: 100%; padding: 0; margin: 0; list-style: none; diff --git a/src/components/ListItem/list-item.module.scss b/src/components/ListItem/list-item.module.scss index 58b8f8e..3ff72c5 100644 --- a/src/components/ListItem/list-item.module.scss +++ b/src/components/ListItem/list-item.module.scss @@ -2,7 +2,7 @@ display: flex; flex-direction: row; align-items: center; - gap: 10px; + gap: var(--list-item-gap, 10px); flex-wrap: nowrap; box-sizing: border-box; line-height: var(--list-item-line-height, var(--line-height, 1 rem)); diff --git a/src/components/ScrollArea/ScrollArea.tsx b/src/components/ScrollArea/ScrollArea.tsx index 5febea1..f196148 100644 --- a/src/components/ScrollArea/ScrollArea.tsx +++ b/src/components/ScrollArea/ScrollArea.tsx @@ -40,21 +40,8 @@ const ScrollArea: ForwardRefRenderFunction = (p const rootRef = React.useRef(null); const viewportRef = React.useRef(null); - useImperativeHandle( - ref, - () => ({ - ...rootRef.current!, - scrollTo: ((optionsOrX?: ScrollToOptions | number, y?: number) => { - if (typeof optionsOrX === 'number') { - viewportRef.current?.scrollTo(optionsOrX, y!); - } else { - viewportRef.current?.scrollTo(optionsOrX); - } - }) as HTMLElement['scrollTo'], + useImperativeHandle(ref, () => viewportRef.current!, []); - }), - [] - ); return ( div { - display: inherit !important; + & > div { + flex: 1; + display: flex !important; + flex-direction: column; } } diff --git a/src/components/Tabs/Tabs.stories.tsx b/src/components/Tabs/Tabs.stories.tsx new file mode 100644 index 0000000..6aed816 --- /dev/null +++ b/src/components/Tabs/Tabs.stories.tsx @@ -0,0 +1,129 @@ +import React, {PropsWithChildren} from "react"; +import {Meta, StoryObj} from "@storybook/react"; + +import {Header} from "../Header"; +import {Footer} from "../Footer"; +import {ViewportProvider} from "../Viewport"; + +import TabsComponent, {TabsProps} from "./Tabs"; +import TabsList, {TabsListProps} from "./TabsList"; +import TabsTrigger, {TabsTriggerProps} from "./TabsTrigger"; +import TabsContent from "./TabsContent"; + +type Props = TabsProps & TabsListProps & TabsTriggerProps + +const triggers = ["Current", "Active", "History"]; + +const meta: Meta = { + title: "Components/Tabs", + component: TabsComponent, + tags: ["autodocs"], + argTypes: { + reverse: { + control: {type: 'boolean'}, + description: 'Reverse list with content', + table: { + category: 'Tabs' + } + }, + + separator: { + control: {type: 'boolean'}, + description: 'Show separator in TabsList', + table: { + category: 'Tabs List' + } + }, + + indicator: { + control: {type: 'boolean'}, + description: 'Show indicator in TabsList', + table: { + category: 'Tabs List' + } + }, + + loop: { + control: {type: 'boolean'}, + description: 'When true, keyboard navigation will loop from last tab to first, and vice versa.', + table: { + category: 'Tabs List' + } + }, + + roundedEdges: { + control: {type: 'boolean'}, + description: 'When true, prevents the user from interacting with the tab.', + table: { + category: 'Tabs List' + } + }, + + disabled: { + control: {type: 'boolean'}, + description: 'When true, prevents the user from interacting with the tab.', + table: { + category: 'Tabs Trigger' + } + }, + + }, +}; + +export default meta; + +export const Tabs: StoryObj = { + args: { + defaultValue: 'Current', + separator: true, + indicator: true, + loop: true, + disabled: false, + roundedEdges: false + }, + + render: (args) => { + const {defaultValue, separator, indicator, loop, roundedEdges, reverse} = args; + return ( + +
+ + + {triggers.map((trigger) => ( + + {trigger} + + ))} + + {triggers.map((trigger) => ( + + {`${trigger} tab`} + + ))} + +