diff --git a/config/storybook/StyleDecorator.tsx b/config/storybook/StyleDecorator.tsx
new file mode 100644
index 0000000..8649fc8
--- /dev/null
+++ b/config/storybook/StyleDecorator.tsx
@@ -0,0 +1,8 @@
+import type { Decorator } from '@storybook/react'
+import '@/app/styles/index.scss'
+
+export const StyleDecorator: Decorator = (Story) => (
+ <>
+
+ >
+)
diff --git a/config/storybook/main.ts b/config/storybook/main.ts
index 83bffe7..0143b29 100644
--- a/config/storybook/main.ts
+++ b/config/storybook/main.ts
@@ -49,7 +49,13 @@ const config: StorybookConfig = {
if (typeof rule === 'object' && rule !== null && 'test' in rule) {
const test = rule.test
if (test instanceof RegExp) {
- return !(test.test('.css') || test.test('.scss') || test.test('.sass'))
+ // Удаляем правила для CSS/SCSS и SVG
+ return !(
+ test.test('.css') ||
+ test.test('.scss') ||
+ test.test('.sass') ||
+ test.test('.svg')
+ )
}
}
return true
@@ -58,6 +64,12 @@ const config: StorybookConfig = {
// Использование конфигурации CSS loader из проекта
config.module.rules.push(buildCssLoader(true))
+ // Добавление поддержки SVGR для SVG иконок
+ config.module.rules.push({
+ test: /\.svg$/,
+ use: ['@svgr/webpack'],
+ })
+
return config
},
diff --git a/config/storybook/preview.ts b/config/storybook/preview.ts
index f2fe9cd..90dc982 100644
--- a/config/storybook/preview.ts
+++ b/config/storybook/preview.ts
@@ -1,6 +1,8 @@
import type { Preview } from '@storybook/react'
+import { StyleDecorator } from './StyleDecorator.tsx'
const preview: Preview = {
+ decorators: [StyleDecorator],
parameters: {
controls: {
matchers: {
@@ -13,7 +15,7 @@ const preview: Preview = {
values: [
{
name: 'light',
- value: '#ffffff',
+ value: '#F4F5F9',
},
{
name: 'dark',
diff --git a/package.json b/package.json
index b9ddcc5..e6522ae 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"author": "",
"license": "ISC",
"dependencies": {
+ "classnames": "^2.5.1",
"gsap": "^3.13.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7de266f..e708ee5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
+ classnames:
+ specifier: ^2.5.1
+ version: 2.5.1
gsap:
specifier: ^3.13.0
version: 3.13.0
@@ -2211,6 +2214,9 @@ packages:
cjs-module-lexer@1.4.3:
resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}
+ classnames@2.5.1:
+ resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
+
clean-css@5.3.3:
resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==}
engines: {node: '>= 10.0'}
@@ -7278,6 +7284,8 @@ snapshots:
cjs-module-lexer@1.4.3: {}
+ classnames@2.5.1: {}
+
clean-css@5.3.3:
dependencies:
source-map: 0.6.1
diff --git a/src/app/styles/variables.scss b/src/app/styles/variables.scss
index eaf7af7..c0e24c7 100644
--- a/src/app/styles/variables.scss
+++ b/src/app/styles/variables.scss
@@ -6,6 +6,7 @@
--color-bg: #F4F5F9;
--color-border: rgb(66 86 122 / 10%);
--color-blue: #3877EE;
+ --color-white: #FFFFFF;
// Градиенты
--gradient-primary: linear-gradient(to right, #3877EE, #EF5DA8);
diff --git a/src/shared/assets/chevron--left.svg b/src/shared/assets/chevron--left.svg
new file mode 100644
index 0000000..ae01d1a
--- /dev/null
+++ b/src/shared/assets/chevron--left.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/shared/ui/Button/Button.module.scss b/src/shared/ui/Button/Button.module.scss
new file mode 100644
index 0000000..1157614
--- /dev/null
+++ b/src/shared/ui/Button/Button.module.scss
@@ -0,0 +1,78 @@
+.button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ padding: 0;
+ outline: none;
+ font-family: var(--font-family-main);
+
+ &:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+
+ // Variants
+ &.round {
+ border-radius: 50%;
+ aspect-ratio: 1;
+ }
+
+ &.regular {
+ border-radius: 1em;
+ padding: 0.5em 1em;
+ }
+
+ // Sizes
+ &.small {
+ height: 40px;
+ font-size: 14px;
+ }
+
+ &.medium {
+ height: 50px;
+ font-size: 18px;
+ }
+
+ &.large {
+ height: 60px;
+ font-size: 24px;
+ }
+
+ // Color Schemes
+ &.primary {
+ $color-primary: var(--color-primary);
+ background-color: transparent;
+ color: $color-primary;
+ border: 1px solid $color-primary;
+
+ &:hover:not(:disabled) {
+ background-color: var(--color-white);
+ }
+ }
+
+ &.secondary {
+ $color-blue: var(--color-blue);
+ background-color: var(--color-white);
+ color: $color-blue;
+ box-shadow: 0px 0px 15px rgb($color-blue / 10%);
+ }
+
+ // Icon handling
+ .icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+
+ svg {
+ width: 40%;
+ height: 40%;
+ object-fit: contain;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/shared/ui/Button/Button.stories.tsx b/src/shared/ui/Button/Button.stories.tsx
new file mode 100644
index 0000000..78ea6ff
--- /dev/null
+++ b/src/shared/ui/Button/Button.stories.tsx
@@ -0,0 +1,102 @@
+import type { Meta, StoryObj } from '@storybook/react'
+import { Button } from './Button'
+import ChevronLeftIcon from '@/shared/assets/chevron--left.svg'
+
+const meta = {
+ title: 'Shared/Button',
+ component: Button,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+ argTypes: {
+ variant: {
+ control: 'select',
+ options: ['round', 'regular'],
+ description: 'Вариант внешнего вида',
+ },
+ size: {
+ control: 'select',
+ options: ['small', 'medium', 'large'],
+ description: 'Размер кнопки',
+ },
+ colorScheme: {
+ control: 'select',
+ options: ['primary', 'secondary'],
+ description: 'Цветовая схема',
+ },
+ disabled: {
+ control: 'boolean',
+ description: 'Активность кнопки',
+ },
+ onClick: { action: 'clicked' },
+ },
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+/**
+ * Базовая кнопка
+ */
+export const Default: Story = {
+ args: {
+ children: 'Submit',
+ variant: 'regular',
+ size: 'medium',
+ colorScheme: 'primary',
+ },
+}
+
+/**
+ * Альтернативная цветовая схема
+ */
+export const SecondaryColorScheme: Story = {
+ args: {
+ children: 'Submit',
+ variant: 'regular',
+ size: 'medium',
+ colorScheme: 'secondary',
+ },
+}
+
+/**
+ * Маленькая кнопка
+ */
+export const Small: Story = {
+ args: {
+ children: 'Submit',
+ size: 'small',
+ },
+}
+
+/**
+ * Большая кнопка
+ */
+export const Large: Story = {
+ args: {
+ children: 'Submit',
+ size: 'large',
+ },
+}
+
+/**
+ * Кнопка с SVG иконкой (шеврон)
+ */
+export const WithIcon: Story = {
+ args: {
+ children: ,
+ variant: 'round',
+ size: 'medium',
+ },
+}
+
+/**
+ * Отключенная кнопка
+ */
+export const Disabled: Story = {
+ args: {
+ children: 'Submit',
+ disabled: true,
+ },
+}
diff --git a/src/shared/ui/Button/Button.tsx b/src/shared/ui/Button/Button.tsx
new file mode 100644
index 0000000..8eb1195
--- /dev/null
+++ b/src/shared/ui/Button/Button.tsx
@@ -0,0 +1,60 @@
+import { ButtonHTMLAttributes, memo, PropsWithChildren } from 'react'
+import classNames from 'classnames'
+import styles from './Button.module.scss'
+
+export type ButtonVariant = 'round' | 'regular'
+export type ButtonSize = 'small' | 'medium' | 'large'
+export type ButtonColorScheme = 'primary' | 'secondary'
+
+export interface ButtonProps extends ButtonHTMLAttributes, PropsWithChildren {
+ /**
+ * Вариант внешнего вида кнопки
+ * @default 'round'
+ */
+ variant?: ButtonVariant
+ /**
+ * Размер кнопки
+ * @default 'medium'
+ */
+ size?: ButtonSize
+ /**
+ * Цветовая схема
+ * @default 'timeframe'
+ */
+ colorScheme?: ButtonColorScheme
+}
+
+/**
+ * Универсальный компонент кнопки для использования в слайдерах и других элементах интерфейса.
+ * Поддерживает различные варианты отображения, размеры и цветовые схемы.
+ */
+export const Button = memo((props: ButtonProps) => {
+ const {
+ className,
+ children,
+ variant = 'round',
+ size = 'medium',
+ colorScheme = 'primary',
+ disabled,
+ ...otherProps
+ } = props
+
+ const mods: Record = {
+ [styles[variant]]: true,
+ [styles[size]]: true,
+ [styles[colorScheme]]: true,
+ }
+
+ return (
+
+ )
+})
+
+Button.displayName = 'Button'
diff --git a/src/shared/ui/Button/index.ts b/src/shared/ui/Button/index.ts
new file mode 100644
index 0000000..e69c4f3
--- /dev/null
+++ b/src/shared/ui/Button/index.ts
@@ -0,0 +1,2 @@
+export { Button } from './Button'
+export type { ButtonProps, ButtonVariant, ButtonSize, ButtonColorScheme } from './Button'
diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts
new file mode 100644
index 0000000..d7f8c69
--- /dev/null
+++ b/src/shared/ui/index.ts
@@ -0,0 +1,2 @@
+export { Button } from './Button'
+export type { ButtonProps } from './Button'