,
+ options,
+ );
+ };
+
+ await use(mount);
+};
diff --git a/playwright/core/types.ts b/playwright/core/types.ts
new file mode 100644
index 0000000000..0df0358b8a
--- /dev/null
+++ b/playwright/core/types.ts
@@ -0,0 +1,31 @@
+import type {ComponentFixtures} from '@playwright/experimental-ct-react';
+import type {
+ Locator,
+ PageScreenshotOptions,
+ PlaywrightTestArgs,
+ PlaywrightTestOptions,
+ PlaywrightWorkerArgs,
+ PlaywrightWorkerOptions,
+ TestFixture,
+} from '@playwright/test';
+
+type PlaywrightTestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & ComponentFixtures;
+type PlaywrightWorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions;
+type PlaywrightFixtures = PlaywrightTestFixtures & PlaywrightWorkerFixtures;
+export type PlaywrightFixture = TestFixture;
+
+export type Fixtures = {
+ mount: MountFixture;
+ expectScreenshot: ExpectScreenshotFixture;
+};
+
+export type MountFixture = ComponentFixtures['mount'];
+
+export interface ExpectScreenshotFixture {
+ (props?: CaptureScreenshotParams): Promise;
+}
+
+interface CaptureScreenshotParams extends PageScreenshotOptions {
+ screenshotName?: string;
+ component?: Locator;
+}
diff --git a/playwright/playwright.config.ts b/playwright/playwright.config.ts
new file mode 100644
index 0000000000..332e64c22d
--- /dev/null
+++ b/playwright/playwright.config.ts
@@ -0,0 +1,78 @@
+import {resolve} from 'path';
+
+import type {PlaywrightTestConfig} from '@playwright/experimental-ct-react';
+import {defineConfig, devices} from '@playwright/experimental-ct-react';
+import react from '@vitejs/plugin-react';
+
+function pathFromRoot(p: string) {
+ return resolve(__dirname, '../', p);
+}
+
+const reporter: PlaywrightTestConfig['reporter'] = [];
+
+reporter.push(
+ ['list'],
+ [
+ 'html',
+ {
+ open: process.env.CI ? 'never' : 'on-failure',
+ outputFolder: resolve(process.cwd(), 'playwright-report'),
+ },
+ ],
+);
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+const config: PlaywrightTestConfig = {
+ testDir: pathFromRoot('src'),
+ testMatch: '**/__tests__/*.visual.test.tsx',
+ updateSnapshots: process.env.UPDATE_REQUEST ? 'all' : 'missing',
+ snapshotPathTemplate:
+ '{testDir}/{testFileDir}/../__snapshots__/{testFileName}-snapshots/{arg}{-projectName}-linux{ext}',
+ /* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */
+ /* Maximum time one test can run for. */
+ timeout: 10 * 1000,
+ /* Run tests in files in parallel */
+ fullyParallel: true,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ forbidOnly: Boolean(process.env.CI),
+ /* Retry on CI only */
+ retries: process.env.CI ? 2 : 0,
+ /* Opt out of parallel tests on CI. */
+ workers: process.env.CI ? 8 : undefined,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter,
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ testIdAttribute: 'data-qa',
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
+ trace: 'on-first-retry',
+ headless: true,
+ /* Port to use for Playwright component endpoint. */
+ screenshot: 'only-on-failure',
+ timezoneId: 'UTC',
+ ctViteConfig: {
+ plugins: [react()],
+ },
+ },
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: 'chromium',
+ use: {
+ ...devices['Desktop Chrome'],
+ deviceScaleFactor: 2,
+ },
+ },
+ {
+ name: 'webkit',
+ use: {
+ ...devices['Desktop Safari'],
+ deviceScaleFactor: 2,
+ },
+ },
+ ],
+};
+
+export default defineConfig(config);
diff --git a/playwright/playwright/index.html b/playwright/playwright/index.html
new file mode 100644
index 0000000000..6572a36e5f
--- /dev/null
+++ b/playwright/playwright/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Test component page
+
+
+
+
+
+
diff --git a/playwright/playwright/index.scss b/playwright/playwright/index.scss
new file mode 100644
index 0000000000..7a17f9ab7f
--- /dev/null
+++ b/playwright/playwright/index.scss
@@ -0,0 +1 @@
+@import '../../styles/styles.scss';
diff --git a/playwright/playwright/index.tsx b/playwright/playwright/index.tsx
new file mode 100644
index 0000000000..67aac616fc
--- /dev/null
+++ b/playwright/playwright/index.tsx
@@ -0,0 +1 @@
+import './index.scss';
diff --git a/scripts/playwright-docker.sh b/scripts/playwright-docker.sh
new file mode 100755
index 0000000000..d7fd7df22c
--- /dev/null
+++ b/scripts/playwright-docker.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+IMAGE_NAME="mcr.microsoft.com/playwright"
+IMAGE_TAG="v1.38.1-jammy" # This version have to be synchronized with playwright version from package.json
+
+NODE_MODULES_CACHE_DIR="$HOME/.cache/uikit-playwright-docker-node-modules"
+
+run_command() {
+ docker run --rm --network host -it -w /work \
+ -v $(pwd):/work \
+ -v "$NODE_MODULES_CACHE_DIR:/work/node_modules" \
+ "$IMAGE_NAME:$IMAGE_TAG" \
+ /bin/bash -c "$1"
+}
+
+if [[ "$1" = "clear-cache" ]]; then
+ rm -rf "$NODE_MODULES_CACHE_DIR"
+ exit 0
+fi
+
+if [[ ! -d "$NODE_MODULES_CACHE_DIR" ]]; then
+ run_command 'npm ci'
+fi
+
+run_command "$1"
diff --git a/src/.eslintrc b/src/.eslintrc
deleted file mode 100644
index 9ecc6200c1..0000000000
--- a/src/.eslintrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "extends": "@gravity-ui/eslint-config/client"
-}
diff --git a/src/components/ActionTooltip/ActionTooltip.scss b/src/components/ActionTooltip/ActionTooltip.scss
index b750fab6a6..475472db7e 100644
--- a/src/components/ActionTooltip/ActionTooltip.scss
+++ b/src/components/ActionTooltip/ActionTooltip.scss
@@ -16,7 +16,7 @@ $block: '.#{variables.$ns}action-tooltip';
}
&__title {
- color: var(--yc-color-text-light-primary);
+ color: var(--g-color-text-light-primary);
}
&__hotkey {
@@ -25,6 +25,6 @@ $block: '.#{variables.$ns}action-tooltip';
&__description {
margin-top: 4px;
- color: var(--yc-color-text-light-secondary);
+ color: var(--g-color-text-light-secondary);
}
}
diff --git a/src/components/ActionTooltip/ActionTooltip.tsx b/src/components/ActionTooltip/ActionTooltip.tsx
index 07939c1d45..29f8ec2cbb 100644
--- a/src/components/ActionTooltip/ActionTooltip.tsx
+++ b/src/components/ActionTooltip/ActionTooltip.tsx
@@ -1,7 +1,10 @@
-import React, {ReactNode} from 'react';
+import React from 'react';
+
+import {Hotkey} from '../Hotkey';
+import type {HotkeyProps} from '../Hotkey';
+import {Tooltip} from '../Tooltip';
+import type {TooltipProps} from '../Tooltip';
import {block} from '../utils/cn';
-import {Hotkey, HotkeyProps} from '../Hotkey';
-import {Tooltip, TooltipProps} from '../Tooltip';
import './ActionTooltip.scss';
@@ -10,14 +13,14 @@ const b = block('action-tooltip');
export interface ActionTooltipProps
extends Pick<
TooltipProps,
- 'children' | 'disabled' | 'placement' | 'openDelay' | 'closeDelay' | 'className'
+ 'children' | 'disabled' | 'placement' | 'openDelay' | 'closeDelay' | 'className' | 'qa'
> {
title: string;
hotkey?: HotkeyProps['value'];
- description?: ReactNode;
+ description?: React.ReactNode;
}
-export const ActionTooltip: React.FC = function TooltipLayout(props) {
+export function ActionTooltip(props: ActionTooltipProps) {
const {title, hotkey, description, children, ...tooltipProps} = props;
return (
@@ -26,16 +29,16 @@ export const ActionTooltip: React.FC = function TooltipLayou
className={b(null, tooltipProps.className)}
contentClassName={b('layout')}
content={
- <>
+
{title}
{hotkey && }
{description &&
{description}
}
- >
+
}
>
{children}
);
-};
+}
diff --git a/src/components/ActionTooltip/__stories__/ActionTooltip.stories.tsx b/src/components/ActionTooltip/__stories__/ActionTooltip.stories.tsx
index 9dcb10570a..edd23e7e25 100644
--- a/src/components/ActionTooltip/__stories__/ActionTooltip.stories.tsx
+++ b/src/components/ActionTooltip/__stories__/ActionTooltip.stories.tsx
@@ -1,14 +1,17 @@
import React from 'react';
-import {Story} from '@storybook/react';
-import {ActionTooltip, ActionTooltipProps} from '../ActionTooltip';
+
+import type {StoryFn} from '@storybook/react';
+
import {Button} from '../../Button';
+import {ActionTooltip} from '../ActionTooltip';
+import type {ActionTooltipProps} from '../ActionTooltip';
export default {
- title: 'Components/ActionTooltip',
+ title: 'Components/Overlays/ActionTooltip',
component: ActionTooltip,
};
-const DefaultTemplate: Story = (args) => ;
+const DefaultTemplate: StoryFn = (args) => ;
export const Default = DefaultTemplate.bind({});
diff --git a/src/components/ActionTooltip/index.ts b/src/components/ActionTooltip/index.ts
index 2e8dc72941..444b77263a 100644
--- a/src/components/ActionTooltip/index.ts
+++ b/src/components/ActionTooltip/index.ts
@@ -1 +1,2 @@
-export {ActionTooltip, ActionTooltipProps} from './ActionTooltip';
+export {ActionTooltip} from './ActionTooltip';
+export type {ActionTooltipProps} from './ActionTooltip';
diff --git a/src/components/Alert/Alert.scss b/src/components/Alert/Alert.scss
new file mode 100644
index 0000000000..d0be391e7e
--- /dev/null
+++ b/src/components/Alert/Alert.scss
@@ -0,0 +1,21 @@
+@use '../variables';
+
+$block: '.#{variables.$ns}alert';
+
+#{$block} {
+ --yc-alert-border-radius: var(--yc-card-border-radius, 8px);
+
+ border-radius: var(--yc-alert-border-radius);
+
+ &_corners_square {
+ --yc-alert-border-radius: 0px;
+ }
+
+ &__text-content {
+ width: 100%;
+ }
+
+ &__actions_minContent {
+ width: min-content;
+ }
+}
diff --git a/src/components/Alert/Alert.test.tsx b/src/components/Alert/Alert.test.tsx
new file mode 100644
index 0000000000..9c88c858b0
--- /dev/null
+++ b/src/components/Alert/Alert.test.tsx
@@ -0,0 +1,104 @@
+import React from 'react';
+
+import {render, screen} from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import {Flex} from '../layout';
+
+import {Alert} from './Alert';
+import type {AlertTheme} from './types';
+
+const title = 'Where will you go, hero?';
+const message = 'Choose wisely: the end of the fairy tale depends on your decision';
+const right = 'To the right (lose the horse)';
+const center = 'Straight (find a wife)';
+const left = 'To the left (CC 235.2)';
+
+describe('Alert', () => {
+ test('render close button if callback passed', async () => {
+ const callback = jest.fn();
+
+ render();
+
+ const buttons = await screen.findAllByRole('button');
+
+ expect(buttons.length).toEqual(1);
+
+ await userEvent.click(buttons[0]);
+
+ expect(callback).toBeCalledTimes(1);
+ });
+
+ test('render actions as buttons if decl passed', async () => {
+ render(
+ ,
+ );
+
+ expect((await screen.findAllByRole('button')).length).toEqual(3);
+ });
+
+ test('render actions as is if component passed', async () => {
+ render({center}} />);
+
+ expect(await screen.findByText(center)).toBeInTheDocument();
+ });
+
+ test('has predicted styles if inline layout rendered', async () => {
+ const {container} = render(
+ ,
+ );
+
+ expect(container).toMatchSnapshot();
+ });
+
+ test.each(['danger', 'info', 'positive', 'success', 'warning', 'utility'])(
+ 'render correct icon if not normal theme',
+ async (theme) => {
+ const {container} = render(
+ ,
+ );
+
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
+ const icon = await container.querySelector('.yc-alert__icon');
+
+ expect(icon).toBeInTheDocument();
+ },
+ );
+
+ test("by default don't render icon component", async () => {
+ const {container} = render();
+
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
+ const icon = await container.querySelector('.yc-alert__icon');
+
+ expect(icon).not.toBeInTheDocument();
+ });
+
+ test('can render custom icon component if needed', async () => {
+ const customIconText = "i'ma icon";
+ render({customIconText}} />);
+
+ expect(await screen.findByText(customIconText)).toBeInTheDocument();
+ });
+});
diff --git a/src/components/Alert/Alert.tsx b/src/components/Alert/Alert.tsx
new file mode 100644
index 0000000000..c8a4a0ac0d
--- /dev/null
+++ b/src/components/Alert/Alert.tsx
@@ -0,0 +1,79 @@
+import React from 'react';
+
+import {Xmark} from '@gravity-ui/icons';
+
+import {Button} from '../Button';
+import {Card} from '../Card';
+import {Icon} from '../Icon';
+import {colorText} from '../Text';
+import {Flex, spacing} from '../layout';
+
+import {AlertAction} from './AlertAction';
+import {AlertActions} from './AlertActions';
+import {AlertContextProvider} from './AlertContextProvider';
+import {AlertIcon} from './AlertIcon';
+import {AlertTitle} from './AlertTitle';
+import {DEFAULT_ICON_SIZE, bAlert} from './constants';
+import type {AlertProps} from './types';
+
+export const Alert = (props: AlertProps) => {
+ const {
+ theme = 'normal',
+ view = 'filled',
+ layout = 'vertical',
+ message,
+ className,
+ corners,
+ style,
+ onClose,
+ align,
+ qa,
+ } = props;
+
+ return (
+
+
+
+ {props.icon || }
+
+
+
+ {typeof props.title === 'string' ? (
+
+ ) : (
+ props.title
+ )}
+ {message}
+
+
+ {Array.isArray(props.actions) ? (
+
+ ) : (
+ props.actions
+ )}
+
+ {onClose && (
+
+ )}
+
+
+
+ );
+};
+
+Alert.Icon = AlertIcon;
+Alert.Title = AlertTitle;
+Alert.Actions = AlertActions;
+Alert.Action = AlertAction;
diff --git a/src/components/Alert/AlertAction.tsx b/src/components/Alert/AlertAction.tsx
new file mode 100644
index 0000000000..ac42f7577d
--- /dev/null
+++ b/src/components/Alert/AlertAction.tsx
@@ -0,0 +1,12 @@
+import React from 'react';
+
+import {Button} from '../Button';
+
+import type {AlertActionProps} from './types';
+import {useAlertContext} from './useAlertContext';
+
+export const AlertAction = (props: AlertActionProps) => {
+ const {view} = useAlertContext();
+
+ return ;
+};
diff --git a/src/components/Alert/AlertActions.tsx b/src/components/Alert/AlertActions.tsx
new file mode 100644
index 0000000000..a90d87a270
--- /dev/null
+++ b/src/components/Alert/AlertActions.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+
+import {Flex} from '../layout';
+
+import {AlertAction} from './AlertAction';
+import {bAlert} from './constants';
+import type {AlertActionsProps} from './types';
+import {useAlertContext} from './useAlertContext';
+
+export const AlertActions = ({items, children, className}: AlertActionsProps) => {
+ const {layout} = useAlertContext();
+
+ return (
+
+ {items?.map(({handler, text}, i) => (
+
+ {text}
+
+ )) || children}
+
+ );
+};
diff --git a/src/components/Alert/AlertContext.tsx b/src/components/Alert/AlertContext.tsx
new file mode 100644
index 0000000000..231f3e5854
--- /dev/null
+++ b/src/components/Alert/AlertContext.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+import type {AlertContextType} from './types';
+
+export const AlertContext = React.createContext(null);
diff --git a/src/components/Alert/AlertContextProvider.tsx b/src/components/Alert/AlertContextProvider.tsx
new file mode 100644
index 0000000000..50b83ac1e5
--- /dev/null
+++ b/src/components/Alert/AlertContextProvider.tsx
@@ -0,0 +1,8 @@
+import React from 'react';
+
+import {AlertContext} from './AlertContext';
+import type {AlertContextProviderProps} from './types';
+
+export const AlertContextProvider = ({layout, view, children}: AlertContextProviderProps) => {
+ return {children};
+};
diff --git a/src/components/Alert/AlertIcon.tsx b/src/components/Alert/AlertIcon.tsx
new file mode 100644
index 0000000000..771ec18ade
--- /dev/null
+++ b/src/components/Alert/AlertIcon.tsx
@@ -0,0 +1,78 @@
+import React from 'react';
+
+import {
+ CircleCheck,
+ CircleCheckFill,
+ CircleInfo,
+ CircleInfoFill,
+ CircleXmark,
+ CircleXmarkFill,
+ Thunderbolt,
+ ThunderboltFill,
+ TriangleExclamation,
+ TriangleExclamationFill,
+} from '@gravity-ui/icons';
+
+import {Icon, IconData} from '../Icon';
+import {ColorTextBaseProps, colorText} from '../Text/colorText/colorText';
+
+import {DEFAULT_ICON_SIZE, bAlert} from './constants';
+import type {AlertIconProps, AlertTheme} from './types';
+
+const typeToIcon: Record<
+ AlertTheme,
+ {filled: React.ElementType; outlined: React.ElementType} | null
+> = {
+ danger: {
+ filled: CircleXmarkFill,
+ outlined: CircleXmark,
+ },
+ info: {
+ filled: CircleInfoFill,
+ outlined: CircleInfo,
+ },
+ positive: {
+ filled: CircleCheckFill,
+ outlined: CircleCheck,
+ },
+ success: {
+ filled: CircleCheckFill,
+ outlined: CircleCheck,
+ },
+ warning: {
+ filled: TriangleExclamationFill,
+ outlined: TriangleExclamation,
+ },
+ utility: {
+ filled: ThunderboltFill,
+ outlined: Thunderbolt,
+ },
+ normal: null,
+};
+
+export const AlertIcon = ({
+ className,
+ theme,
+ view = 'filled',
+ size = DEFAULT_ICON_SIZE,
+}: AlertIconProps) => {
+ const iconByTheme = typeToIcon[theme];
+
+ if (!iconByTheme) {
+ return null;
+ }
+
+ let color: ColorTextBaseProps['color'];
+
+ if (theme === 'success') {
+ color = 'positive';
+ } else if (theme !== 'normal') {
+ color = theme;
+ }
+
+ return (
+
+
+
+ );
+};
diff --git a/src/components/Alert/AlertTitle.tsx b/src/components/Alert/AlertTitle.tsx
new file mode 100644
index 0000000000..72499f9293
--- /dev/null
+++ b/src/components/Alert/AlertTitle.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+
+import {Text} from '../Text';
+
+import {bAlert} from './constants';
+import type {AlertTitleProps} from './types';
+
+export const AlertTitle = ({text, className}: AlertTitleProps) => {
+ return (
+
+ {text}
+
+ );
+};
diff --git a/src/components/Alert/README.md b/src/components/Alert/README.md
new file mode 100644
index 0000000000..af1d08b888
--- /dev/null
+++ b/src/components/Alert/README.md
@@ -0,0 +1,238 @@
+
+
+# Alert
+
+
+
+```tsx
+import {Alert} from '@gravity-ui/uikit';
+```
+
+### Theme
+
+`normal` - main theme (used by default).
+
+`info` - used for any kind of regular information.
+
+`success` - used for positive information.
+
+`warning` - used for information which needs attention.
+
+`danger` - used for hazard information.
+
+`utility` - used for utility information.
+
+
+
+
+
+```tsx
+
+
+
+
+
+
+```
+
+
+
+### View
+
+`filled` - used to adjust the background color of the alert (used by default).
+
+`outlined` - used to adjust the border color of the alert.
+
+
+
+
+
+```
+
+
+```
+
+
+
+### Layout
+
+`vertical` - used to direct users to content if there is property `actions` with buttons. For showing buttons below text (
+used
+by default).
+
+`horizontal` - used to direct users to content if there is property `actions` with buttons. For showing buttons to the right
+of text.
+
+
+
+
+
+```tsx
+button}/>
+button}/>
+```
+
+
+
+### Corners
+
+`rounded` - used for round corners of alert window (used by default).
+
+`square` - used for squared corners of alert window.
+
+
+
+
+
+```tsx
+
+
+```
+
+
+
+## Alert title
+
+`title` - the title of the alert. It has a lower priority than Alert.Title.
+
+
+
+
+
+```tsx
+} />
+```
+
+
+
+## Alert message
+
+`message` - message of the alert. It should fully explain the content of the alert.
+
+## Alert onClose
+
+`onClose` - callback function called when a user clicks the alert's close button. When this property is defined, a close button is visible.
+
+
+
+
+
+```tsx
+ alert('Close button pressed')}
+ title="Alert has close"
+ message="Alert has close"
+/>
+```
+
+
+
+### Align
+
+Determines how content inside the Alert component is vertically aligned.
+
+`baseline` - align used by default.
+
+`center` - content is vertically centered within the Alert component. Useful if actions take up
+more space than text,
+or if the icon must be in the middle of the card.
+
+
+
+
+
+```tsx
+button}/>
+button}/>
+```
+
+
+
+## Properties
+
+| Name | Description | Type | Default |
+| :-------- | :-------------------------------------------------------------------------- | :----------------------------------------------------------------: | :----------: |
+| theme | Alert appearance | `"normal"` `"info"` `"success"` `"warning"` `"danger"` `"utility"` | `"normal"` |
+| view | Enable/disable background color of the alert | `"filled"` `"outlined"` | `"filled"` |
+| layout | Used to direct users to content if there is property `actions` with buttons | `"vertical"` `"horizontal"` | `"vertical"` |
+| corners | Used for round/square corners of the alert window | `"rounded"` `"square"` | `"rounded"` |
+| title | Title of the alert | `string` | |
+| message | Message of the alert | `string` | |
+| onClose | A callback function called when the user clicks the alert's close button | `Function` | |
+| actions | Array of buttons or full custom components | `React.ReactNode` `"AlertAction"` | |
+| align | Determines how content inside the Alert component is vertically aligned | `"center"` `"baseline"` | `"baseline"` |
+| style | HTML style attribute | `React.CSSProperties` | |
+| className | Name of alert class | `string` | |
+| icon | Override default icon | `React.ReactNode` | |
+| qa | HTML `data-qa` attribute, used in tests. | `string` | |
diff --git a/src/components/Alert/__snapshots__/Alert.test.tsx.snap b/src/components/Alert/__snapshots__/Alert.test.tsx.snap
new file mode 100644
index 0000000000..2377d3aeb5
--- /dev/null
+++ b/src/components/Alert/__snapshots__/Alert.test.tsx.snap
@@ -0,0 +1,113 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Alert has predicted styles if inline layout rendered 1`] = `
+
+
+
+
+
+
+
+
+
+
+ Where will you go, hero?
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/components/Alert/__stories__/Alert.stories.tsx b/src/components/Alert/__stories__/Alert.stories.tsx
new file mode 100644
index 0000000000..4d9eda9512
--- /dev/null
+++ b/src/components/Alert/__stories__/Alert.stories.tsx
@@ -0,0 +1,137 @@
+import React from 'react';
+
+import type {Meta, StoryFn} from '@storybook/react';
+
+import {Button} from '../../Button';
+import {Col, Row} from '../../layout';
+import {Alert} from '../Alert';
+import type {AlertProps} from '../types';
+
+export default {
+ title: 'Components/Feedback/Alert',
+ component: Alert,
+} as Meta;
+
+const title = 'Where will you go, hero?';
+const message = 'Choose wisely: the end of the fairy tale depends on your decision';
+const right = 'To the right (lose the horse)';
+const center = 'Straight (find a wife)';
+const left = 'To the left (CC 235.2)';
+
+const stories: AlertProps[] = [
+ {
+ title,
+ message,
+ theme: 'danger',
+ view: 'filled',
+ onClose: () => alert('Close button pressed'),
+ actions: {right},
+ },
+ {
+ title:
);
diff --git a/src/components/Breadcrumbs/BreadcrumbsMore.tsx b/src/components/Breadcrumbs/BreadcrumbsMore.tsx
index b618131ea7..d4db1684fa 100644
--- a/src/components/Breadcrumbs/BreadcrumbsMore.tsx
+++ b/src/components/Breadcrumbs/BreadcrumbsMore.tsx
@@ -1,22 +1,16 @@
-import * as React from 'react';
+import React from 'react';
+
import {DropdownMenu} from '../DropdownMenu';
import {Link} from '../Link';
import {block} from '../utils/cn';
-import {BreadcrumbsProps} from './Breadcrumbs';
+
+import type {BreadcrumbsProps} from './Breadcrumbs';
import i18n from './i18n';
interface Props extends Pick {}
const b = block('breadcrumbs');
-function Switcher() {
- return (
-
- ...
-
- );
-}
-
export function BreadcrumbsMore({popupStyle, popupPlacement, items}: Props) {
return (
}
+ renderSwitcher={({onClick}) => (
+
+ ...
+
+ )}
/>
);
}
diff --git a/src/components/Breadcrumbs/BreadcrumbsSeparator.tsx b/src/components/Breadcrumbs/BreadcrumbsSeparator.tsx
index b9f2dbe205..e841db9cbf 100644
--- a/src/components/Breadcrumbs/BreadcrumbsSeparator.tsx
+++ b/src/components/Breadcrumbs/BreadcrumbsSeparator.tsx
@@ -1,6 +1,8 @@
-import * as React from 'react';
+import React from 'react';
+
import {block} from '../utils/cn';
-import {BreadcrumbsProps} from './Breadcrumbs';
+
+import type {BreadcrumbsProps} from './Breadcrumbs';
type Props = Pick;
diff --git a/src/components/Breadcrumbs/README.md b/src/components/Breadcrumbs/README.md
index 0477115580..5e3623a0d3 100644
--- a/src/components/Breadcrumbs/README.md
+++ b/src/components/Breadcrumbs/README.md
@@ -1,43 +1,356 @@
-## Breadcrumbs
+
-Breadcrumbs component. Can collapse breadcrumbs that cause overflow.
+# Breadcrumbs
-### PropTypes
+
-| Property | Type | Required | Default | Description |
-| :----------------------- | :--------- | :------: | :------ | :--------------------------------------------------------------------------------------------------------------------------- |
-| items | `Array` | ✓ | | Breadcrumb items array `BreadcrumbsItem[]` |
-| className | `String` | | | CSS class name of root element |
-| renderRootContent | `Function` | | | Custom render function of first item `(item: BreadcrumbsItem, isCurrent: boolean) => React.ReactNode;`) |
-| renderItemContent | `Function` | | | Custom render function of N+1 item `(item: BreadcrumbsItem, isCurrent: boolean, isPrevCurrent: boolean) => React.ReactNode;` |
-| renderItemDivider | `Function` | | | Custom render function of items separator `() => React.ReactNode;` |
-| lastDisplayedItemsCount | `Enum` | ✓ | | Number of items to display after items collapse control: `LastDisplayedItemsCount` |
-| firstDisplayedItemsCount | `Enum` | ✓ | | Number of items to display before items collapse control: `FirstDisplayedItemsCount` |
-| popupStyle | `String` | | | Style of collapsed items popup `staircase` |
+```tsx
+import {Breadcrumbs} from '@gravity-ui/uikit';
+```
+
+`Breadcrumbs` is a navigation element that shows the current location of a page within a website’s hierarchy. It provides links that allow users to return to higher levels in the hierarchy, making it easier to navigate a site with multiple layers. Breadcrumbs are especially useful for large websites and applications with a hierarchical organization of pages.
+
+## Appearance
+
+
-### Examples
+
```jsx
const breadcrumbs = [
{
- title: 'What is love',
+ text: 'Region',
+ action: () => {},
+ },
+ {
+ text: 'Country',
+ action: () => {},
},
{
- title: "Baby don't hurt me",
+ text: 'City',
+ action: () => {},
},
{
- title: "Don't hurt me",
+ text: 'District',
+ action: () => {},
},
{
- title: 'No more',
+ text: 'Street',
+ action: () => {},
},
];
return (
+);
+```
+
+
+
+### Custom divider
+
+
+
+
+
+```jsx
+const breadcrumbs = [
+ {
+ text: 'Region',
+ action: () => {},
+ },
+ {
+ text: 'Country',
+ action: () => {},
+ },
+ {
+ text: 'City',
+ action: () => {},
+ },
+ {
+ text: 'District',
+ action: () => {},
+ },
+ {
+ text: 'Street',
+ action: () => {},
+ },
+];
+
+return (
+ '>'}
+ firstDisplayedItemsCount={FirstDisplayedItemsCount.One}
+ lastDisplayedItemsCount={LastDisplayedItemsCount.One}
+ />
+);
+```
+
+
+
+### Custom title
+
+
+
+
+
+```jsx
+const breadcrumbs = [
+ {
+ text: 'Region',
+ title: 'Custom title for Region',
+ action: () => {},
+ },
+ {
+ text: 'Country',
+ title: 'Custom title for Country',
+ action: () => {},
+ },
+ {
+ text: 'City',
+ title: 'Custom title for City',
+ action: () => {},
+ },
+ {
+ text: 'District',
+ title: 'Custom title for District',
+ action: () => {},
+ },
+ {
+ text: 'Street',
+ title: 'Custom title for Street',
+ action: () => {},
+ },
+];
+
+return (
+
);
```
+
+
+
+## Properties
+
+| Name | Description | Type | Default |
+| :----------------------- | :------------------------------------------------------ | :------------------------------------------------------------------------------------------------------ | :------ |
+| items | Breadcrumb item array | `BreadcrumbsItem[]` | |
+| className | CSS class name of root element | `string` | |
+| renderRootContent | Custom render function of first item | `((item: BreadcrumbsItem, isCurrent: boolean) => React.ReactNode) \| undefined` | |
+| renderItemContent | Custom render function of N+1 item | `((item: BreadcrumbsItem, isCurrent: boolean, isPrevCurrent: boolean) => React.ReactNode) \| undefined` | |
+| renderItemDivider | Custom render function of items separator | `(() => React.ReactNode) \| undefined` | |
+| firstDisplayedItemsCount | Number of items to display before item collapse control | `FirstDisplayedItemsCount.Zero \| FirstDisplayedItemsCount.One` | |
+| lastDisplayedItemsCount | Number of items to display after item collapse control | `LastDisplayedItemsCount.One \| LastDisplayedItemsCount.Two` | |
+| popupStyle | Style of collapsed item popup | `"staircase" \| undefined` | |
+| qa | HTML `data-qa` attribute, used in tests | `string` | |
+
+### BreadcrumbsItem
+
+| Name | Description | Type | Default |
+| :----- | :--------------------- | :-------------------------------------------------------------------------------- | :------ |
+| text | Breadcrumb content | `string` | |
+| action | `click` event handler | `React.MouseEventHandler \| React.KeyboardEventHandler` | |
+| href | HTML `href` attribute | `string \| undefined` | |
+| items | Breadcrumb item array | `BreadcrumbsItem[] \| undefined` | |
+| title | HTML `title` attribute | `string \| undefined` | |
diff --git a/src/components/Breadcrumbs/__stories__/BreadCrumbs.stories.tsx b/src/components/Breadcrumbs/__stories__/Breadcrumbs.stories.tsx
similarity index 73%
rename from src/components/Breadcrumbs/__stories__/BreadCrumbs.stories.tsx
rename to src/components/Breadcrumbs/__stories__/Breadcrumbs.stories.tsx
index 714d10b8cc..5024d9ea0b 100644
--- a/src/components/Breadcrumbs/__stories__/BreadCrumbs.stories.tsx
+++ b/src/components/Breadcrumbs/__stories__/Breadcrumbs.stories.tsx
@@ -1,6 +1,10 @@
import React from 'react';
-import {Meta, Story} from '@storybook/react';
-import {Breadcrumbs, BreadcrumbsProps} from '../Breadcrumbs';
+
+import type {StoryFn} from '@storybook/react';
+
+import {Breadcrumbs} from '../Breadcrumbs';
+import type {BreadcrumbsProps} from '../Breadcrumbs';
+
import {BreadcrumbsShowcase} from './BreadcrumbsShowcase';
const action = () => {};
@@ -28,7 +32,7 @@ const items = [
];
export default {
- title: 'Components/Breadcrumbs',
+ title: 'Components/Navigation/Breadcrumbs',
component: Breadcrumbs,
// TODO: debug why it can't parse enum
argTypes: {
@@ -45,9 +49,9 @@ export default {
},
},
},
-} as unknown as Meta;
+};
-const DefaultTemplate: Story = (args) => ;
+const DefaultTemplate: StoryFn = (args) => ;
export const Default = DefaultTemplate.bind({});
Default.args = {
items,
@@ -55,7 +59,7 @@ Default.args = {
lastDisplayedItemsCount: 1,
};
-const ShowcaseTemplate: Story = (args) => ;
+const ShowcaseTemplate: StoryFn = (args) => ;
export const Showcase = ShowcaseTemplate.bind({});
Showcase.args = {
firstDisplayedItemsCount: 0,
diff --git a/src/components/Breadcrumbs/__stories__/BreadcrumbsShowcase.scss b/src/components/Breadcrumbs/__stories__/BreadcrumbsShowcase.scss
index 4ee4209d68..4342b1e92b 100644
--- a/src/components/Breadcrumbs/__stories__/BreadcrumbsShowcase.scss
+++ b/src/components/Breadcrumbs/__stories__/BreadcrumbsShowcase.scss
@@ -6,7 +6,7 @@
}
&__container {
- border: 1px dashed var(--yc-color-line-generic);
+ border: 1px dashed var(--g-color-line-generic);
& + & {
margin-top: 10px;
diff --git a/src/components/Breadcrumbs/__stories__/BreadcrumbsShowcase.tsx b/src/components/Breadcrumbs/__stories__/BreadcrumbsShowcase.tsx
index ce3eae0625..f9b5487c4d 100644
--- a/src/components/Breadcrumbs/__stories__/BreadcrumbsShowcase.tsx
+++ b/src/components/Breadcrumbs/__stories__/BreadcrumbsShowcase.tsx
@@ -1,9 +1,12 @@
import React from 'react';
-import block from 'bem-cn-lite';
-import {Breadcrumbs, BreadcrumbsProps} from '../Breadcrumbs';
+
+import {cn} from '../../utils/cn';
+import {Breadcrumbs} from '../Breadcrumbs';
+import type {BreadcrumbsProps} from '../Breadcrumbs';
+
import './BreadcrumbsShowcase.scss';
-const b = block('breadcrumbs-showcase');
+const b = cn('breadcrumbs-showcase');
const breadcrumbsItems = [
{
@@ -82,6 +85,17 @@ export function BreadcrumbsShowcase(props: BreadcrumbsShowcaseProps) {
items={breadcrumbsItems.map(({text}) => ({text, action: () => {}}))}
/>
+
+
Custom title
+ ({
+ text,
+ title: `Custom title for ${text}`,
+ action: () => {},
+ }))}
+ />
+
);
}
diff --git a/src/components/Breadcrumbs/__tests__/Breadcrumbs.test.tsx b/src/components/Breadcrumbs/__tests__/Breadcrumbs.test.tsx
index 78bac96b8c..19413ec3c4 100644
--- a/src/components/Breadcrumbs/__tests__/Breadcrumbs.test.tsx
+++ b/src/components/Breadcrumbs/__tests__/Breadcrumbs.test.tsx
@@ -1,5 +1,7 @@
-import {render, screen} from '@testing-library/react';
import React from 'react';
+
+import {render, screen} from '@testing-library/react';
+
import {Breadcrumbs} from '../Breadcrumbs';
const items = [
@@ -91,3 +93,16 @@ test('should allow to override separator', () => {
expect(screen.getAllByText('•')).toHaveLength(items.length);
});
+
+test('should display custom title', () => {
+ render(
+ ({...item, title: `Custom title for ${item.text}`}))}
+ lastDisplayedItemsCount={1}
+ firstDisplayedItemsCount={0}
+ />,
+ );
+
+ expect(screen.getByTitle('Custom title for Root')).toBeInTheDocument();
+ expect(screen.getByTitle('Custom title for Street')).toBeInTheDocument();
+});
diff --git a/src/components/Breadcrumbs/i18n/index.ts b/src/components/Breadcrumbs/i18n/index.ts
index a212626d81..1ee706f644 100644
--- a/src/components/Breadcrumbs/i18n/index.ts
+++ b/src/components/Breadcrumbs/i18n/index.ts
@@ -1,7 +1,8 @@
-import {registerKeyset} from '../../utils/registerKeyset';
+import {addComponentKeysets} from '../../utils/addComponentKeysets';
+
import en from './en.json';
import ru from './ru.json';
const COMPONENT = 'Breadcrumbs';
-export default registerKeyset({en, ru}, COMPONENT);
+export default addComponentKeysets({en, ru}, COMPONENT);
diff --git a/src/components/Button/Button.scss b/src/components/Button/Button.scss
index d7909252f6..08e5368764 100644
--- a/src/components/Button/Button.scss
+++ b/src/components/Button/Button.scss
@@ -2,6 +2,7 @@
@use '../../../styles/mixins';
$block: '.#{variables.$ns}button';
+$iconWidth: 16px;
@mixin button-text-color($color, $hoverColor: $color) {
&,
@@ -18,9 +19,16 @@ $block: '.#{variables.$ns}button';
}
#{$block} {
- --yc-button-height: 0;
--yc-button-background-color: transparent;
- --yc-button-background-color-hover: transparent;
+ --yc-button-background-color-hover: var(--g-color-base-simple-hover);
+ --yc-button-outline-color: var(--g-color-line-focus);
+ --yc-button-icon-space: calc(
+ var(--yc-button-padding) + var(--yc-button-icon-size) + var(--yc-button-icon-offset)
+ );
+ --yc-button-icon-position: calc(
+ var(--yc-button-padding) - (var(--yc-button-height) - var(--yc-button-icon-size)) / 2
+ );
+ --yc-button-font-size: var(--g-text-body-1-font-size);
@include mixins.button-reset();
display: inline-block;
@@ -29,6 +37,7 @@ $block: '.#{variables.$ns}button';
box-sizing: border-box;
height: var(--yc-button-height);
line-height: var(--yc-button-height);
+ font-size: var(--yc-button-font-size);
user-select: none;
text-align: center;
white-space: nowrap;
@@ -56,11 +65,11 @@ $block: '.#{variables.$ns}button';
}
&:focus::before {
- box-shadow: 0 0 0 2px var(--yc-color-line-misc);
+ outline: 2px solid var(--yc-button-outline-color);
}
&:focus:not(:focus-visible)::before {
- box-shadow: none;
+ outline: none;
}
&::after {
@@ -87,268 +96,211 @@ $block: '.#{variables.$ns}button';
&_size {
&_xs {
--yc-button-height: 20px;
-
- font-size: 13px;
-
- #{$block}__text {
- margin: 0 6px;
- }
-
- #{$block}__icon {
- width: var(--yc-button-height);
-
- &_side_left ~ #{$block}__text {
- margin-left: 18px;
- }
-
- &_side_right ~ #{$block}__text {
- margin-right: 18px;
- }
- }
-
- --yc-button-border-radius: var(--yc-border-radius-xs);
+ --yc-button-border-radius: var(--g-border-radius-xs);
+ --yc-button-padding: 6px;
+ --yc-button-icon-size: 12px;
+ --yc-button-icon-offset: 4px;
}
&_s {
--yc-button-height: 24px;
-
- font-size: 13px;
-
- #{$block}__text {
- margin: 0 10px;
- }
-
- #{$block}__icon {
- width: var(--yc-button-height);
-
- &_side_left ~ #{$block}__text {
- margin-left: 22px;
- }
-
- &_side_right ~ #{$block}__text {
- margin-right: 22px;
- }
- }
-
- --yc-button-border-radius: var(--yc-border-radius-s);
+ --yc-button-border-radius: var(--g-border-radius-s);
+ --yc-button-padding: 8px;
+ --yc-button-icon-size: 16px;
+ --yc-button-icon-offset: 4px;
}
&_m {
--yc-button-height: 28px;
-
- font-size: 13px;
-
- #{$block}__text {
- margin: 0 13px;
- }
-
- #{$block}__icon {
- width: var(--yc-button-height);
-
- &_side_left ~ #{$block}__text {
- margin-left: 25px;
- }
-
- &_side_right ~ #{$block}__text {
- margin-right: 25px;
- }
- }
-
- --yc-button-border-radius: var(--yc-border-radius-m);
+ --yc-button-border-radius: var(--g-border-radius-m);
+ --yc-button-padding: 12px;
+ --yc-button-icon-size: 16px;
+ --yc-button-icon-offset: 8px;
}
&_l {
--yc-button-height: 36px;
+ --yc-button-border-radius: var(--g-border-radius-l);
+ --yc-button-padding: 16px;
+ --yc-button-icon-size: 16px;
+ --yc-button-icon-offset: 8px;
+ }
- font-size: 13px;
+ &_xl {
+ --yc-button-height: 44px;
+ --yc-button-border-radius: var(--g-border-radius-xl);
+ --yc-button-padding: 24px;
+ --yc-button-icon-size: 20px;
+ --yc-button-icon-offset: 12px;
+ --yc-button-font-size: var(--g-text-body-2-font-size);
+ }
+ }
- #{$block}__text {
- margin: 0 18px;
- }
+ &_view {
+ &_normal {
+ --yc-button-background-color: var(--g-color-base-generic);
+ --yc-button-background-color-hover: var(--g-color-base-generic-hover);
- #{$block}__icon {
- width: var(--yc-button-height);
+ @include button-text-color(var(--g-color-text-primary));
+ }
- &_side_left ~ #{$block}__text {
- margin-left: 33px;
- }
+ &_action {
+ --yc-button-background-color: var(--g-color-base-brand);
+ --yc-button-background-color-hover: var(--g-color-base-brand-hover);
+ --yc-button-outline-color: var(--g-color-base-brand);
- &_side_right ~ #{$block}__text {
- margin-right: 33px;
- }
+ &:focus::before {
+ outline-offset: 1px;
}
- --yc-button-border-radius: var(--yc-border-radius-l);
+ @include button-text-color(var(--g-color-text-brand-contrast));
}
- &_xl {
- --yc-button-height: 44px;
-
- font-size: 15px;
+ &_outlined {
+ @include button-text-color(var(--g-color-text-primary));
- #{$block}__text {
- margin: 0 25px;
+ &::before {
+ border: 1px solid var(--g-color-line-generic);
}
+ }
- #{$block}__icon {
- width: var(--yc-button-height);
-
- &_side_left ~ #{$block}__text {
- margin-left: 40px;
- }
+ &_outlined-info {
+ @include button-text-color(var(--g-color-text-info));
- &_side_right ~ #{$block}__text {
- margin-right: 40px;
- }
+ &::before {
+ border: 1px solid var(--g-color-line-info);
}
-
- --yc-button-border-radius: var(--yc-border-radius-xl);
}
- }
-
- &_view {
- &_normal {
- --yc-button-background-color: var(--yc-color-base-generic);
- --yc-button-background-color-hover: var(--yc-color-base-generic-hover);
-
- @include button-text-color(var(--yc-color-text-primary));
- {$block}_selected {
- --yc-button-background-color: var(--yc-color-base-selection);
- --yc-button-background-color-hover: var(--yc-color-base-selection-hover);
+ &_outlined-success {
+ @include button-text-color(var(--g-color-text-positive));
- @include button-text-color(
- var(--yc-my-color-brand-text, var(--yc-color-text-special))
- );
+ &::before {
+ border: 1px solid var(--g-color-line-positive);
}
}
- &_action {
- --yc-button-background-color: var(--yc-color-base-special);
- --yc-button-background-color-hover: var(--yc-color-base-special-hover);
+ &_outlined-warning {
+ @include button-text-color(var(--g-color-text-warning));
- @include button-text-color(var(--yc-my-color-brand-text-contrast));
+ &::before {
+ border: 1px solid var(--g-color-line-warning);
+ }
}
- &_outlined,
- &_outlined-info,
&_outlined-danger {
- --yc-button-background-color: transparent;
- --yc-button-background-color-hover: var(--yc-color-base-simple-hover);
- }
-
- &_outlined {
- @include button-text-color(var(--yc-color-text-primary));
+ @include button-text-color(var(--g-color-text-danger));
&::before {
- border: 1px solid var(--yc-color-line-generic);
+ border: 1px solid var(--g-color-line-danger);
}
}
- &_outlined-info {
- @include button-text-color(var(--yc-color-text-info));
+ &_outlined-utility {
+ @include button-text-color(var(--g-color-text-utility));
&::before {
- border: 1px solid var(--yc-color-line-info);
+ border: 1px solid var(--g-color-line-utility);
}
}
- &_outlined-danger {
- @include button-text-color(var(--yc-color-text-danger));
+ &_outlined-action {
+ @include button-text-color(var(--g-color-text-brand));
&::before {
- border: 1px solid var(--yc-color-line-danger);
+ border: 1px solid var(--g-color-line-brand);
}
}
&_raised {
- --yc-button-background-color: var(--yc-color-base-float);
- --yc-button-background-color-hover: var(--yc-color-base-float-hover);
+ --yc-button-background-color: var(--g-color-base-float);
+ --yc-button-background-color-hover: var(--g-color-base-float-hover);
- @include button-text-color(var(--yc-color-text-primary));
+ @include button-text-color(var(--g-color-text-primary));
&::before {
- box-shadow: 0 3px 5px var(--yc-color-sfx-shadow);
- }
-
- &:focus:not(:focus-visible)::before {
- box-shadow: 0 3px 5px var(--yc-color-sfx-shadow);
+ box-shadow: 0 3px 5px var(--g-color-sfx-shadow);
}
&:active::before {
- box-shadow: 0 1px 2px var(--yc-color-sfx-shadow);
- }
-
- &:active:focus::before {
- box-shadow: 0 0 0 2px var(--yc-color-line-misc);
+ box-shadow: 0 1px 2px var(--g-color-sfx-shadow);
}
+ }
- &:active:focus:not(:focus-visible)::before {
- box-shadow: 0 1px 2px var(--yc-color-sfx-shadow);
- }
+ &_flat {
+ @include button-text-color(var(--g-color-text-primary));
}
- &_flat,
- &_flat-info,
- &_flat-danger,
&_flat-secondary {
- --yc-button-background-color: transparent;
- --yc-button-background-color-hover: var(--yc-color-base-simple-hover);
+ @include button-text-color(var(--g-color-text-secondary), var(--g-color-text-primary));
}
- &_flat {
- @include button-text-color(var(--yc-color-text-primary));
+ &_flat-info {
+ @include button-text-color(var(--g-color-text-info));
}
- &_flat-info {
- @include button-text-color(var(--yc-color-text-info));
+ &_flat-success {
+ @include button-text-color(var(--g-color-text-positive));
+ }
+
+ &_flat-warning {
+ @include button-text-color(var(--g-color-text-warning));
}
&_flat-danger {
- @include button-text-color(var(--yc-color-text-danger));
+ @include button-text-color(var(--g-color-text-danger));
}
- &_flat-secondary {
- @include button-text-color(
- var(--yc-color-text-secondary),
- var(--yc-color-text-primary)
- );
+ &_flat-utility {
+ @include button-text-color(var(--g-color-text-utility));
+ }
+
+ &_flat-action {
+ @include button-text-color(var(--g-color-text-brand));
}
&_normal-contrast {
- --yc-button-background-color: var(--yc-color-base-light);
- --yc-button-background-color-hover: var(--yc-color-base-light-hover);
+ --yc-button-background-color: var(--g-color-base-light);
+ --yc-button-background-color-hover: var(--g-color-base-light-hover);
+ --yc-button-outline-color: var(--g-color-line-light);
- @include button-text-color(var(--yc-color-text-dark-primary));
+ @include button-text-color(var(--g-color-text-dark-primary));
}
&_outlined-contrast {
--yc-button-background-color: transparent;
- --yc-button-background-color-hover: var(--yc-color-base-light-simple-hover);
+ --yc-button-background-color-hover: var(--g-color-base-light-simple-hover);
+ --yc-button-outline-color: var(--g-color-line-light);
- @include button-text-color(var(--yc-color-text-light-primary));
+ @include button-text-color(var(--g-color-text-light-primary));
&::before {
- border: 1px solid var(--yc-color-line-light);
+ border: 1px solid var(--g-color-line-light);
}
}
&_flat-contrast {
--yc-button-background-color: transparent;
- --yc-button-background-color-hover: var(--yc-color-base-light-simple-hover);
+ --yc-button-background-color-hover: var(--g-color-base-light-simple-hover);
+ --yc-button-outline-color: var(--g-color-line-light);
- @include button-text-color(var(--yc-color-text-light-primary));
+ @include button-text-color(var(--g-color-text-light-primary));
}
&_flat,
+ &_flat-secondary,
&_flat-info,
- &_flat-danger {
+ &_flat-success,
+ &_flat-warning,
+ &_flat-danger,
+ &_flat-utility,
+ &_flat-action {
{$block}_disabled:not(#{$block}_loading),
{$block}_disabled:not(#{$block}_loading) {
--yc-button-background-color: transparent;
--yc-button-background-color-hover: transparent;
- color: var(--yc-color-text-hint);
+ color: var(--g-color-text-hint);
}
}
@@ -358,7 +310,7 @@ $block: '.#{variables.$ns}button';
--yc-button-background-color: transparent;
--yc-button-background-color-hover: transparent;
- color: var(--yc-color-text-light-hint);
+ color: var(--g-color-text-light-hint);
}
}
@@ -366,10 +318,102 @@ $block: '.#{variables.$ns}button';
&_outlined-contrast {
{$block}_disabled:not(#{$block}_loading),
{$block}_disabled:not(#{$block}_loading) {
- --yc-button-background-color: var(--yc-color-base-light-disabled);
- --yc-button-background-color-hover: var(--yc-color-base-light-disabled);
+ --yc-button-background-color: var(--g-color-base-light-disabled);
+ --yc-button-background-color-hover: var(--g-color-base-light-disabled);
+
+ color: var(--g-color-text-light-secondary);
+ }
+ }
+ }
+
+ &_view {
+ &_normal,
+ &_action,
+ &_outlined,
+ &_outlined-action,
+ &_raised,
+ &_flat,
+ &_flat-secondary,
+ &_flat-action {
+ {$block}_selected {
+ --yc-button-background-color: var(--g-color-base-selection);
+ --yc-button-background-color-hover: var(--g-color-base-selection-hover);
+
+ @include button-text-color(var(--g-color-text-brand-heavy));
+
+ &::before {
+ border: none;
+ }
+ }
+ }
+
+ &_outlined-info,
+ &_flat-info {
+ {$block}_selected {
+ --yc-button-background-color: var(--g-color-base-info-light);
+ --yc-button-background-color-hover: var(--g-color-base-info-light-hover);
+
+ @include button-text-color(var(--g-color-text-info-heavy));
+
+ &::before {
+ border: none;
+ }
+ }
+ }
+
+ &_outlined-success,
+ &_flat-success {
+ {$block}_selected {
+ --yc-button-background-color: var(--g-color-base-positive-light);
+ --yc-button-background-color-hover: var(--g-color-base-positive-light-hover);
+
+ @include button-text-color(var(--g-color-text-positive-heavy));
+
+ &::before {
+ border: none;
+ }
+ }
+ }
+
+ &_outlined-warning,
+ &_flat-warning {
+ {$block}_selected {
+ --yc-button-background-color: var(--g-color-base-warning-light);
+ --yc-button-background-color-hover: var(--g-color-base-warning-light-hover);
+
+ @include button-text-color(var(--g-color-text-warning-heavy));
+
+ &::before {
+ border: none;
+ }
+ }
+ }
+
+ &_outlined-danger,
+ &_flat-danger {
+ {$block}_selected {
+ --yc-button-background-color: var(--g-color-base-danger-light);
+ --yc-button-background-color-hover: var(--g-color-base-danger-light-hover);
- color: var(--yc-color-text-light-secondary);
+ @include button-text-color(var(--g-color-text-danger-heavy));
+
+ &::before {
+ border: none;
+ }
+ }
+ }
+
+ &_outlined-utility,
+ &_flat-utility {
+ {$block}_selected {
+ --yc-button-background-color: var(--g-color-base-utility-light);
+ --yc-button-background-color-hover: var(--g-color-base-utility-light-hover);
+
+ @include button-text-color(var(--g-color-text-utility-heavy));
+
+ &::before {
+ border: none;
+ }
}
}
}
@@ -379,12 +423,14 @@ $block: '.#{variables.$ns}button';
&__text {
display: inline-block;
white-space: nowrap;
+ padding: 0 var(--yc-button-padding);
}
&__icon {
display: inline-block;
position: relative;
- height: 100%;
+ width: var(--yc-button-height);
+ height: var(--yc-button-height);
pointer-events: none;
&::after {
@@ -410,11 +456,27 @@ $block: '.#{variables.$ns}button';
}
&_side_left {
- left: 0;
+ left: var(--yc-button-icon-position);
+
+ & ~ #{$block}__text {
+ padding-left: var(--yc-button-icon-space);
+ }
}
&_side_right {
- right: 0;
+ right: var(--yc-button-icon-position);
+
+ & ~ #{$block}__text {
+ padding-right: var(--yc-button-icon-space);
+ }
+ }
+ }
+
+ &:has(#{$block}__icon:only-child) {
+ --yc-button-padding: 0;
+
+ &:not(#{$block}_width_max) {
+ width: var(--yc-button-height);
}
}
@@ -423,10 +485,10 @@ $block: '.#{variables.$ns}button';
pointer-events: none;
&:not(#{$block}_loading) {
- --yc-button-background-color: var(--yc-color-base-generic-accent-disabled);
- --yc-button-background-color-hover: var(--yc-color-base-generic-accent-disabled);
+ --yc-button-background-color: var(--g-color-base-generic-accent-disabled);
+ --yc-button-background-color-hover: var(--g-color-base-generic-accent-disabled);
- color: var(--yc-color-text-hint);
+ color: var(--g-color-text-hint);
&::before,
&:hover::before {
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index a87e2a6fa5..1a00533e08 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -1,10 +1,12 @@
-import React, {cloneElement} from 'react';
-import {DOMProps, QAProps} from '../types';
+import React from 'react';
+
+import type {DOMProps, QAProps} from '../types';
import {block} from '../utils/cn';
-import {isOfType} from '../utils/isOfType';
+import {isIcon} from '../utils/common';
import {eventBroker} from '../utils/event-broker';
+import {isOfType} from '../utils/isOfType';
+
import {ButtonIcon} from './ButtonIcon';
-import {isIcon} from '../utils/common';
import './Button.scss';
@@ -13,12 +15,20 @@ export type ButtonView =
| 'action' // Branded background, no border
| 'outlined' // No background, grey border
| 'outlined-info' // No background, with info-type border color
+ | 'outlined-success' // No background, with success-type border color
+ | 'outlined-warning' // No background, with warning-type border color
| 'outlined-danger' // No background, with danger-type border color
+ | 'outlined-utility' // No background, with utility-type border color
+ | 'outlined-action' // No background, with branded border color
| 'raised' // With white background and shadow
| 'flat' // No background, no border
+ | 'flat-secondary' // No background, no border, secondary-type text color
| 'flat-info' // No background, no border, info-type text color
+ | 'flat-success' // No background, no border, success-type text color
+ | 'flat-warning' // No background, no border, warning-type text color
| 'flat-danger' // No background, no border, danger-type text color
- | 'flat-secondary' // No background, no border, secondary-type text color
+ | 'flat-utility' // No background, no border, utility-type text color
+ | 'flat-action' // No background, no border, branded text color
| 'normal-contrast' // normal button appearance with contrast background
| 'outlined-contrast' // outlined button appearance with contrast background
| 'flat-contrast'; // flat button appearance with contrast background
@@ -79,7 +89,7 @@ const ButtonWithHandlers = React.forwardRef(function B
view = 'normal',
size = 'm',
pin = 'round-round',
- selected = false,
+ selected,
disabled = false,
loading = false,
width,
@@ -170,6 +180,7 @@ const ButtonWithHandlers = React.forwardRef(function B
ref={ref as React.Ref}
type={type}
disabled={disabled || loading}
+ aria-pressed={selected}
>
{prepareChildren(children)}
@@ -219,7 +230,7 @@ function prepareChildren(children: React.ReactNode) {
);
} else {
- leftIcon = cloneElement(item, {
+ leftIcon = React.cloneElement(item, {
side,
});
}
@@ -233,7 +244,7 @@ function prepareChildren(children: React.ReactNode) {
);
} else {
- rightIcon = cloneElement(item, {
+ rightIcon = React.cloneElement(item, {
side,
});
}
diff --git a/src/components/Button/ButtonIcon.tsx b/src/components/Button/ButtonIcon.tsx
index 1056494ced..e449700197 100644
--- a/src/components/Button/ButtonIcon.tsx
+++ b/src/components/Button/ButtonIcon.tsx
@@ -1,9 +1,10 @@
-import React, {PropsWithChildren} from 'react';
+import React from 'react';
+
import {block} from '../utils/cn';
const b = block('button');
-type Props = PropsWithChildren<{
+type Props = React.PropsWithChildren<{
className?: string;
side?: 'left' | 'right';
}>;
diff --git a/src/components/Button/README.md b/src/components/Button/README.md
index aaeacff4e8..1f46f041b7 100644
--- a/src/components/Button/README.md
+++ b/src/components/Button/README.md
@@ -1,162 +1,478 @@
-# Button
+
-## Usage examples
+# Button
-### Plain button
+
```tsx
-import React from 'react';
import {Button} from '@gravity-ui/uikit';
+```
+
+Buttons act as a trigger for certain actions. While this is their main purpose, in very rare cases,
+they can be used as links to navigate to another pages.
+
+## Appearance
+
+There are four `Button` view types: basic, outlined, flat and contrast.
+The `Button`'s appearance is determined by the `view` property.
+
+### Basic
+
+`action` - the most prominent button, used for the primary action on a screen which requires the most attention.
+We recommend using only one such button per page.
+
+`normal` - default type of the `Button`, designed for secondary actions or to maintain the importance of an
+action without drawing too much attention to it.
+
+`raised` - placed above the content as a "floating" element, usually with a fixed location.
+
+
+
+
+
+```tsx
+
+
+
+```
+
+
+
+### Outlined
+
+`outlined` - used for secondary actions that require less attention on a page. Can be used with or without a main button (only with an accented one).
+
+`outlined-action` - usually used as a link to another page or external resource.
+
+There are also semantic variants of this type, which can be used when additional semantics are needed: `outlined-info`, `outlined-success`, `outlined-warning`, `outlined-danger`.
+
+
+
+
+
+```tsx
+
+
+
+
+
+
+
```
-### Link
+
+
+### Flat
+
+`flat` - used for auxiliary actions that require the least attention on a page. It is often used in a list of buttons or action icons (with no text) in an editor.
+
+`flat-secondary` - less accented than the `flat` button. It's often used as the secondary button in dialog boxes and modal windows.
+
+`flat-action` - usually used as link to another page or external resource.
+
+There are also semantic variants of this view, which can be used in places where additional semantic needed: `flat-info`, `flat-success`, `flat-warning`, `flat-danger`.
+
+
+
+
```tsx
-import React from 'react';
-import {Button} from '@gravity-ui/uikit';
+
+
+
+
+
+
+
+
+```
+
+
+
+### Contrast
+
+`normal-contrast`, `outline-contrast` and `flat-contrast` buttons highlight actions against complex background, e.g., in a banner or against an inverse background.
+
+
+
+
+
+```tsx
+
+
+
+```
+
+
+
+## Icons
+
+To add an icon to the `Button`, you should use the [`Icon`](../Icon) component, a special wrapper for SVGs.
+
+
+
+
+
+```tsx
+
+
+
+
```
-### With icon
+
+
+## States
+
+The `Button` can be in different states.
+
+`disabled` - when the button is unavailable for some reason.
+
+`loading` - when some asynchronous processes are happening in the background, `selected` - when the user can switch between "Enable" and "Disable".
+
+
+
+
+
+```tsx
+
+
+
+```
+
+
+
+## Size
+
+To control the size of the `Button` use the `size` property. Default size is `m`.
+
+
+
+
```tsx
-import React from 'react';
-import {Button, Icon} from '@gravity-ui/uikit';
-
-import gearIcon from 'assets/icons/gear.svg';
-
-// Icon on the left
-const button1 = (
-
-);
-
-// Icon on the right
-const button2 = (
-
-);
-
-// Only icon
-const button3 = (
-
-);
-
-// Wrap component as button icon explicitly
-const button4 = (
-
-);
+
+
+
+
+
```
-## Props
-
-Inherits props from: [`DOMProps`](../README.md#domprops), [`QAProps`](../README.md#qaprops).
-
-```ts
-type ButtonView =
- | 'normal'
- | 'action'
- | 'outlined'
- | 'outlined-info'
- | 'outlined-danger'
- | 'raised'
- | 'flat'
- | 'flat-info'
- | 'flat-danger'
- | 'flat-secondary'
- | 'normal-contrast'
- | 'outlined-contrast'
- | 'flat-contrast';
-
-type ButtonSize = 'xs' | 's' | 'm' | 'l' | 'xl';
-
-type ButtonPin =
- | 'round-round'
- | 'brick-brick'
- | 'clear-clear'
- | 'circle-circle'
- | 'round-brick'
- | 'brick-round'
- | 'round-clear'
- | 'clear-round'
- | 'brick-clear'
- | 'clear-brick'
- | 'circle-brick'
- | 'brick-circle'
- | 'circle-clear'
- | 'clear-circle';
-
-interface ButtonProps extends DOMProps, QAProps {
- /**
- * Button appearance
- * @default 'normal'
- */
- view?: ButtonView;
- /**
- * Button size
- * @default 'm'
- */
- size?: ButtonSize;
- /**
- * Corners radius
- */
- pin?: ButtonPin;
- /** Selection state */
- selected?: boolean;
- /** Disabled state */
- disabled?: boolean;
- /** Pending state */
- loading?: boolean;
- /** Button width */
- width?: 'auto' | 'max';
- /** Tooltip */
- title?: string;
- /** HTML `id` attribute */
- id?: string;
- /** HTML `tabindex` attribute */
- tabIndex?: number;
- /** Button content. You can mix button text with `` component */
- children?: React.ReactNode;
- /**
- * HTML button `type` attribute
- * @default 'button'
- */
- type?: 'button' | 'submit' | 'reset';
- /** HTML `href` attribute */
- href: string;
- /** HTML `target` attribute. */
- target?: string;
- /** HTML `rel` attribute */
- rel?: string;
- /** Click handler */
- onClick?: (event: React.MouseEvent) => void;
- /** mouseenter event handler */
- onMouseEnter?: (event: React.MouseEvent) => void;
- /** mouseleave event handler */
- onMouseLeave?: (event: React.MouseEvent) => void;
- /** focus event handler */
- onFocus?: (event: React.FocusEvent) => void;
- /** blur event handler */
- onBlur?: (event: React.FocusEvent) => void;
- /** Additional control props */
- extraProps?:
- | React.ButtonHTMLAttributes
- | React.AnchorHTMLAttributes;
- /** Prop to override element type */
- component?: React.ElementType;
-}
+
+
+## Width
+
+The `width` property controls how the `Button` behaves inside the container.
+
+`auto` - limits the maximum width of the component, hides overflowing content with an ellipsis.
+
+`max` - matches the width to the width of the parent container, also hides overflowing content with an ellipsis.
+
+
+
+## Pin
+
+The `pin` property allows you to control the shape of the right and left edges and is usually used for combining multiple buttons in a single unit.
+The value of the `pin` property consists of left and edge style names divided by a dash, e.g. `"round-brick"`.
+The edge styles are: `round` (default), `circle`, `brick` and `clear`.
+
+
+
+
+
+```tsx
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
```
+
+
+
+## Properties
+
+| Name | Description | Type | Default |
+| :----------- | :-------------------------------------------------------- | :-----------------------------: | :-------------: |
+| children | Button content. You can mix text with `` component | `ReactNode` | |
+| className | HTML `class` attribute | `string` | |
+| component | Overrides the root component | `ElementType` | `"button"` |
+| disabled | Toggles `disabled` state | `false` | `false` |
+| extraProps | Any additional props | `Record` | |
+| href | HTML `href` attribute | `string` | |
+| id | HTML `id` attribute | `string` | |
+| loading | Toggles `loading` state | `false` | `false` |
+| onBlur | `blur` event handler | `Function` | |
+| onClick | `click` event handler | `Function` | |
+| onFocus | `focus` event handler | `Function` | |
+| onMouseEnter | `mouseenter` event handler | `Function` | |
+| onMouseLeave | `mouseleave` event handler | `Function` | |
+| pin | Sets button edges style | `string` | `"round-round"` |
+| qa | HTML `data-qa` attribute, used in tests | `string` | |
+| rel | HTML `rel` attribute | `string` | |
+| selected | Toggles `selected` state | | |
+| size | Sets button size | `string` | `"m"` |
+| style | HTML `style` attribute | `React.CSSProperties` | |
+| tabIndex | HTML `tabIndex` attribute | `number` | |
+| target | HTML `target` attribute | `string` | |
+| title | HTML `title` attribute | `string` | |
+| type | HTML `type` attribute | `"button"` `"submit"` `"reset"` | `"button"` |
+| view | Sets button appearance | `string` | `"normal"` |
+| width | `"auto"` `"max"` | `"auto"` `"max"` | |
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Default-dark-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Default-dark-chromium-linux.png
new file mode 100644
index 0000000000..dea526c489
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Default-dark-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Default-dark-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Default-dark-webkit-linux.png
new file mode 100644
index 0000000000..c33899c7f0
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Default-dark-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Default-light-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Default-light-chromium-linux.png
new file mode 100644
index 0000000000..b46c6bc267
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Default-light-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Default-light-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Default-light-webkit-linux.png
new file mode 100644
index 0000000000..c3c0f46458
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Default-light-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Disabled-dark-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Disabled-dark-chromium-linux.png
new file mode 100644
index 0000000000..677072332d
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Disabled-dark-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Disabled-dark-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Disabled-dark-webkit-linux.png
new file mode 100644
index 0000000000..81fa983d19
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Disabled-dark-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Disabled-light-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Disabled-light-chromium-linux.png
new file mode 100644
index 0000000000..942f03fb42
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Disabled-light-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Disabled-light-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Disabled-light-webkit-linux.png
new file mode 100644
index 0000000000..214d258999
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Disabled-light-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Icon-dark-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Icon-dark-chromium-linux.png
new file mode 100644
index 0000000000..1e52a72c32
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Icon-dark-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Icon-dark-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Icon-dark-webkit-linux.png
new file mode 100644
index 0000000000..65aa703101
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Icon-dark-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Icon-light-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Icon-light-chromium-linux.png
new file mode 100644
index 0000000000..5a3a3f01f6
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Icon-light-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Icon-light-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Icon-light-webkit-linux.png
new file mode 100644
index 0000000000..3d37307564
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Icon-light-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Link-dark-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Link-dark-chromium-linux.png
new file mode 100644
index 0000000000..128be6ba6d
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Link-dark-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Link-dark-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Link-dark-webkit-linux.png
new file mode 100644
index 0000000000..ad3c13cec6
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Link-dark-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Link-light-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Link-light-chromium-linux.png
new file mode 100644
index 0000000000..139a57ad53
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Link-light-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Link-light-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Link-light-webkit-linux.png
new file mode 100644
index 0000000000..ea37de8516
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Link-light-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Loading-dark-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Loading-dark-chromium-linux.png
new file mode 100644
index 0000000000..adfaf92fad
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Loading-dark-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Loading-dark-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Loading-dark-webkit-linux.png
new file mode 100644
index 0000000000..ae49330667
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Loading-dark-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Loading-light-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Loading-light-chromium-linux.png
new file mode 100644
index 0000000000..eb1b13668c
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Loading-light-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Loading-light-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Loading-light-webkit-linux.png
new file mode 100644
index 0000000000..6b052816cc
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Loading-light-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Pin-dark-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Pin-dark-chromium-linux.png
new file mode 100644
index 0000000000..e8f2955f50
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Pin-dark-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Pin-dark-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Pin-dark-webkit-linux.png
new file mode 100644
index 0000000000..cac5c69e36
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Pin-dark-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Pin-light-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Pin-light-chromium-linux.png
new file mode 100644
index 0000000000..a2984e05ea
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Pin-light-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Pin-light-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Pin-light-webkit-linux.png
new file mode 100644
index 0000000000..721a0eed00
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Pin-light-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Selected-dark-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Selected-dark-chromium-linux.png
new file mode 100644
index 0000000000..5d0dd06eee
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Selected-dark-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Selected-dark-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Selected-dark-webkit-linux.png
new file mode 100644
index 0000000000..796630fbca
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Selected-dark-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Selected-light-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Selected-light-chromium-linux.png
new file mode 100644
index 0000000000..1e02814c58
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Selected-light-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Selected-light-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Selected-light-webkit-linux.png
new file mode 100644
index 0000000000..47d5d56a7d
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Selected-light-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Size-dark-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Size-dark-chromium-linux.png
new file mode 100644
index 0000000000..69fd4a1b93
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Size-dark-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Size-dark-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Size-dark-webkit-linux.png
new file mode 100644
index 0000000000..9b9fc3c7bc
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Size-dark-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Size-light-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Size-light-chromium-linux.png
new file mode 100644
index 0000000000..99e26772fe
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Size-light-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Size-light-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Size-light-webkit-linux.png
new file mode 100644
index 0000000000..d78980b6ff
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Size-light-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-View-dark-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-View-dark-chromium-linux.png
new file mode 100644
index 0000000000..05d6e8331c
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-View-dark-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-View-dark-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-View-dark-webkit-linux.png
new file mode 100644
index 0000000000..9f8c4d2d2d
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-View-dark-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-View-light-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-View-light-chromium-linux.png
new file mode 100644
index 0000000000..542a9ca4f8
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-View-light-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-View-light-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-View-light-webkit-linux.png
new file mode 100644
index 0000000000..ed95915844
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-View-light-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Width-dark-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Width-dark-chromium-linux.png
new file mode 100644
index 0000000000..a9f0615fe4
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Width-dark-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Width-dark-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Width-dark-webkit-linux.png
new file mode 100644
index 0000000000..f5608c14fe
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Width-dark-webkit-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Width-light-chromium-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Width-light-chromium-linux.png
new file mode 100644
index 0000000000..3ad380cd73
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Width-light-chromium-linux.png differ
diff --git a/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Width-light-webkit-linux.png b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Width-light-webkit-linux.png
new file mode 100644
index 0000000000..c1b14ad431
Binary files /dev/null and b/src/components/Button/__snapshots__/Button.visual.test.tsx-snapshots/Button-render-story-Width-light-webkit-linux.png differ
diff --git a/src/components/Button/__stories__/Button.docs.mdx b/src/components/Button/__stories__/Button.docs.mdx
deleted file mode 100644
index 2eafd55403..0000000000
--- a/src/components/Button/__stories__/Button.docs.mdx
+++ /dev/null
@@ -1,161 +0,0 @@
-import {Source, ArgsTable} from '@storybook/addon-docs';
-import {DocsSource} from '../../../demo/DocsSource/DocsSource';
-import {Button} from '../Button';
-import {
- ButtonExampleViewAction,
- ButtonExampleViewNormal,
- ButtonExampleViewOutlined,
- ButtonExampleViewFlat,
- ButtonExampleViewRaised,
- ButtonExampleViewOutlinedDanger,
- ButtonExampleViewFlatDanger,
- ButtonExampleViewOutlinedInfo,
- ButtonExampleViewFlatInfo,
- ButtonExampleViewFlatSecondary,
- ButtonExampleViewSpecial,
- ButtonExampleState,
- ButtonExampleSize,
-} from './examples/ButtonExampleView/ButtonExampleView';
-
-
-# Button
-
-```ts
-import {Button} from '@gravity-ui/uikit';
-```
-
-[Description](#description)
-
-[Appearance](#appearance)
-
-- [Action](#action)
-- [Normal](#normal)
-- [Outline](#outline)
-- [Flat](#flat)
-- [Raised](#raised)
-- [Outlined-info](#outlined-info)
-- [Flat-info](#flat-info)
-- [Outlined-danger](#outlined-danger)
-- [Flat-danger](#flat-danger)
-- [Flat-secondary](#flat-secondary)
-- [Special](#special)
-
-[Button text](#button-text)
-
-[States](#states)
-
-[Size](#size)
-
-[Properties](#properties)
-
-## Description
-Buttons act as a trigger for certain actions. While this is their main purpose, in very rare cases,
-they are used in place of links to navigate to a different page.
-
-Buttons are also used in dialog boxes when the interface suggests that users perform some alternative actions.
-For example, continue or return to editing.
-
-## Appearance
-To design the UI of cloud services, we use 5 basic (`action`, `normal`, `outline`, `flat`, `raised`),
-5 additional (`outline-info`, `outline-danger`, `flat-info`, `flat-danger`, `flat-secondary`),
-and 3 special (`normal-contrast`, `outline-contrast`, `flat-contrast`) types of buttons.
-The button's appearance is controlled by the `view` property.
-
-### Action
-This is the most prominent button, used for the primary action on a screen, which requires the most attention.
-We recommend only using one such button per page (excluding dialog boxes).
-
-
-
-
-### Normal
-This is the main type of button (used by default). It is designed for secondary actions or when you have to maintain
-the importance of an action without drawing too much attention to it.
-
-
-
-
-### Outline
-Used for secondary actions that require less attention on a page. It can be used both with the main button and without it (only with an accented one).
-
-
-
-
-### Flat
-Used for auxiliary actions that require the least attention on a page. It is often used in a list of buttons or action icons (with no text) in an editor.
-
-
-
-
-### Raised
-The button placed above the content, usually with a fixed location.
-
-
-
-
-### Outlined-info
-Used to indicate where to go to a different service or external resource.
-
-
-
-
-### Flat-info
-Used to indicate where to go to a different service or external resource.
-
-
-
-
-### Outline-danger
-Danger buttons are used to indicate dangerous actions such as delete, stop, restart, or escape.
-`outline-danger` is used when there is enough space to place a full-featured button on a page or in a modal window.
-
-
-
-
-### Flat-danger
-Used to indicate a destructive action in a general list of actions on an object or in a drop-down menu.
-
-
-
-
-### Flat-secondary
-This button is less accented than a `flat` button. It's often used as the secondary button in dialog boxes and modal windows.
-
-
-
-
-### Special
-Buttons `normal-contrast`, `outline-contrast`, `flat-contrast` used to highlight actions against a complex background (for example, in a banner or against a color background).
-The usage type depends on the required degree of display.
-
-
-
-
-## Button text
-The name of a button should uniquely identify what happens if you click it. If it's an action, it should answer the question "What does it do?".
-
-- Button names should begin with a capital letter (Save, Rename, Create disk, or Add endpoint).
-- Actions on a page or in a modal window should be consistent with their headers (such as Create virtual disk, Create, or Cancel).
-- If it's a link, it should answer the question "What?" or "What does it do?".
-
-## States
-
-
-1. Default – The main state of a button that a user can interact with.
-2. Disabled – The state when a button is unavailable for some reason.
-3. Loading – The state when the user has performed an action and is waiting for a system response.
-4. Selected – The state when the user can switch between "Enable" and "Disable".
-
-## Size
-
-
-S – Used when standard buttons are too big (tables, small cards).
-
-M – Basic size, used in most components.
-
-L – Basic actions performed in a page's header, modal windows, or pop-ups.
-
-XL – Used on promo and landing pages.
-
-## Properties
-
diff --git a/src/components/Button/__stories__/Button.new.stories.tsx b/src/components/Button/__stories__/Button.new.stories.tsx
deleted file mode 100644
index 57d1028e54..0000000000
--- a/src/components/Button/__stories__/Button.new.stories.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import React from 'react';
-import {Meta, Story} from '@storybook/react';
-import {Button} from '../Button';
-import Docs from './Button.docs.mdx';
-import {Icon} from '../../Icon';
-import {GearIcon} from '../../icons/GearIcon';
-
-export default {
- title: 'Components/Basic/Button',
- id: 'components/Button',
- argTypes: {
- view: {
- options: [
- 'normal',
- 'action',
- 'outlined',
- 'outlined-info',
- 'outlined-danger',
- 'raised',
- 'flat',
- 'flat-info',
- 'flat-danger',
- 'flat-secondary',
- 'normal-contrast',
- 'outlined-contrast',
- 'flat-contrast',
- ],
- control: {type: 'select'},
- defaultValue: 'normal',
- },
- size: {
- options: ['xs', 's', 'm', 'l', 'xl'],
- control: {type: 'radio'},
- defaultValue: 'm',
- },
- state: {
- options: ['normal', 'disabled', 'loading', 'selected'],
- control: {type: 'radio'},
- defaultValue: 'normal',
- },
- width: {
- options: [undefined, 'auto', 'max'],
- control: {type: 'radio'},
- defaultValue: undefined,
- },
- content: {
- control: {type: 'text'},
- defaultValue: 'Button',
- },
- icon: {
- options: ['none', 'left', 'right', 'only'],
- control: {type: 'radio'},
- defaultValue: 'none',
- },
- },
- parameters: {
- order: -100,
- docs: {
- page: Docs,
- },
- },
-} as Meta;
-
-const iconSizeMap: Record = {
- s: 16,
- m: 18,
- l: 22,
- xl: 26,
-};
-
-export const Playground: Story = (args) => {
- const content = [args.content];
- const icon = ;
-
- if (args.icon === 'left') {
- content.unshift(icon);
- } else if (args.icon === 'right') {
- content.push(icon);
- } else if (args.icon === 'only') {
- content.splice(0, 1, icon);
- }
-
- return (
-
- );
-};
-Playground.storyName = 'Button';
diff --git a/src/components/Button/__stories__/Button.stories.tsx b/src/components/Button/__stories__/Button.stories.tsx
index 74233c2556..3d6a45618e 100644
--- a/src/components/Button/__stories__/Button.stories.tsx
+++ b/src/components/Button/__stories__/Button.stories.tsx
@@ -1,122 +1,144 @@
import React from 'react';
-import {Meta, Story} from '@storybook/react';
-import {Button, ButtonProps} from '../Button';
+
+import {
+ ArrowUpRightFromSquare,
+ ChevronDown,
+ CircleChevronRight,
+ Copy,
+ Globe,
+ Heart,
+} from '@gravity-ui/icons';
+import type {Meta, StoryObj} from '@storybook/react';
+
+import {Showcase} from '../../../demo/Showcase';
import {Icon as IconComponent} from '../../Icon/Icon';
-import {GearIcon} from '../../icons/GearIcon';
-import {ButtonShowcase} from './ButtonShowcase';
+import {Button} from '../Button';
+
+import {ButtonViewShowcase} from './ButtonViewShowcase';
export default {
- title: 'Components/Button',
+ title: 'Components/Inputs/Button',
component: Button,
} as Meta;
-const DefaultTemplate: Story = (args) => ;
-export const Default = DefaultTemplate.bind({});
+type Story = StoryObj;
+
+export const Default: Story = {args: {children: 'Button'}};
+
+export const View: Story = {
+ render: (args) => ,
+};
-const SizeTemplate: Story = (args) => (
- <>
-
-
-
-
-
-
-
-
-
- >
-);
-export const Size = SizeTemplate.bind({});
+export const Size: Story = {
+ render: (args) => (
+
+
+
+
+
+
+
+ ),
+};
-const IconTemplate: Story = (args) => (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
-);
-export const Icon = IconTemplate.bind({});
+export const Icon: Story = {
+ render: (args) => (
+
+
+
+
+
+
+
+ ),
+};
-export const Selected: Story = (args) => {
- const [selected, setSelected] = React.useState(true);
+export const Disabled: Story = {
+ args: {
+ ...Default.args,
+ disabled: true,
+ },
+};
- return (
-
- );
+export const Selected: Story = {
+ args: {
+ ...Default.args,
+ selected: true,
+ },
};
-export const Link: Story = (args) => {
- return (
-
- );
+export const Loading: Story = {
+ args: {
+ ...Default.args,
+ loading: true,
+ },
};
-export const ButtonInRouter: Story = (args) => {
- return (
-
-
-
- );
+export const Width: Story = {
+ render: (args) => {
+ return (
+