diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index e5f09811f13..8ccb47e7486 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -6,6 +6,8 @@ on: - master - dev - next-insol + - next-platform-ai + - next-sbcom paths-ignore: - 'docs/**' - 'website/**' diff --git a/.github/workflows/documentation-deploy-pr.yml b/.github/workflows/documentation-deploy-pr.yml index 2ac7766831f..9f023f08069 100644 --- a/.github/workflows/documentation-deploy-pr.yml +++ b/.github/workflows/documentation-deploy-pr.yml @@ -5,6 +5,8 @@ on: branches: - dev - next-insol + - next-platform-ai + - next-sbcom paths-ignore: - 'docs/**' pull_request: diff --git a/.github/workflows/publish-canary.yml b/.github/workflows/publish-canary.yml index c6b1e34456e..c5b19090748 100644 --- a/.github/workflows/publish-canary.yml +++ b/.github/workflows/publish-canary.yml @@ -4,6 +4,8 @@ on: pull_request: branches: - next-insol + - next-platform-ai + - next-sbcom - master pull_request_target: branches: diff --git a/cypress/snapshots/b2c/chromium/Popover/plasma-b2c Popover -- [PLASMA-T1690] Popover placement=right, trigger=click, skidding=100, distance=0.snap.png b/cypress/snapshots/b2c/chromium/Popover/plasma-b2c Popover -- [PLASMA-T1690] Popover placement=right, trigger=click, skidding=100, distance=0.snap.png index 9108f645611..2b2848e87a9 100644 Binary files a/cypress/snapshots/b2c/chromium/Popover/plasma-b2c Popover -- [PLASMA-T1690] Popover placement=right, trigger=click, skidding=100, distance=0.snap.png and b/cypress/snapshots/b2c/chromium/Popover/plasma-b2c Popover -- [PLASMA-T1690] Popover placement=right, trigger=click, skidding=100, distance=0.snap.png differ diff --git a/cypress/snapshots/b2c/chromium/Popover/plasma-b2c Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png b/cypress/snapshots/b2c/chromium/Popover/plasma-b2c Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png index 4c6aeb6722c..17a43fdbab1 100644 Binary files a/cypress/snapshots/b2c/chromium/Popover/plasma-b2c Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png and b/cypress/snapshots/b2c/chromium/Popover/plasma-b2c Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png differ diff --git a/cypress/snapshots/cs/chromium/Popover/sdds-cs Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png b/cypress/snapshots/cs/chromium/Popover/sdds-cs Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png index 438aefd2964..de8b1c3f056 100644 Binary files a/cypress/snapshots/cs/chromium/Popover/sdds-cs Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png and b/cypress/snapshots/cs/chromium/Popover/sdds-cs Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png differ diff --git a/cypress/snapshots/cs/chromium/Popover/sdds-cs Popover -- resizable.snap.png b/cypress/snapshots/cs/chromium/Popover/sdds-cs Popover -- resizable.snap.png index 58f88aaf723..3dd29ce4438 100644 Binary files a/cypress/snapshots/cs/chromium/Popover/sdds-cs Popover -- resizable.snap.png and b/cypress/snapshots/cs/chromium/Popover/sdds-cs Popover -- resizable.snap.png differ diff --git a/cypress/snapshots/giga/chromium/Popover/plasma-giga Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png b/cypress/snapshots/giga/chromium/Popover/plasma-giga Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png index d8d6654e6d7..f0bbf777365 100644 Binary files a/cypress/snapshots/giga/chromium/Popover/plasma-giga Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png and b/cypress/snapshots/giga/chromium/Popover/plasma-giga Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png differ diff --git a/cypress/snapshots/giga/chromium/Popover/plasma-giga Popover -- resizable.snap.png b/cypress/snapshots/giga/chromium/Popover/plasma-giga Popover -- resizable.snap.png index eb6e165ce56..ada53ed20fd 100644 Binary files a/cypress/snapshots/giga/chromium/Popover/plasma-giga Popover -- resizable.snap.png and b/cypress/snapshots/giga/chromium/Popover/plasma-giga Popover -- resizable.snap.png differ diff --git a/cypress/snapshots/insol/chromium/Popover/sdds-insol Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png b/cypress/snapshots/insol/chromium/Popover/sdds-insol Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png index 2e0ea5ee066..fcafb880e14 100644 Binary files a/cypress/snapshots/insol/chromium/Popover/sdds-insol Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png and b/cypress/snapshots/insol/chromium/Popover/sdds-insol Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png differ diff --git a/cypress/snapshots/insol/chromium/Popover/sdds-insol Popover -- resizable.snap.png b/cypress/snapshots/insol/chromium/Popover/sdds-insol Popover -- resizable.snap.png index ed6e0e2bb22..9bbb796fb88 100644 Binary files a/cypress/snapshots/insol/chromium/Popover/sdds-insol Popover -- resizable.snap.png and b/cypress/snapshots/insol/chromium/Popover/sdds-insol Popover -- resizable.snap.png differ diff --git a/cypress/snapshots/web/chromium/Popover/plasma-web Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png b/cypress/snapshots/web/chromium/Popover/plasma-web Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png index 0bda8b17684..abb558ed2ac 100644 Binary files a/cypress/snapshots/web/chromium/Popover/plasma-web Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png and b/cypress/snapshots/web/chromium/Popover/plasma-web Popover -- [PLASMA-T1692] Popover placement=auto, trigger=click, closeOnOverlay, hasArrow=false.snap.png differ diff --git a/docs/how-to-create-core-component.md b/docs/how-to-create-core-component.md new file mode 100644 index 00000000000..602c01e97f9 --- /dev/null +++ b/docs/how-to-create-core-component.md @@ -0,0 +1,1029 @@ +# Руководство по созданию core компонентов + +Это руководство описывает процесс создания новых компонентов в экосистеме SDDS. + +Архитектура построена на разделении **ядра** (логика и структура) и **вертикалей** (токены и стилизация для конкретного бренда). + +## Содержание + +1. [Обзор архитектуры](#обзор-архитектуры) +2. [Создание компонента](#создание-компонента) +3. [Работа с токенами](#работа-с-токенами) +4. [Variations (вариации)](#variations-вариации) +5. [Intersections (пересечения вариаций)](#intersections-пересечения-вариаций) +6. [Сложные компоненты](#сложные-компоненты) +7. [Композиция компонентов](#композиция-компонентов) +8. [Accessibility](#accessibility) +9. [Тестирование](#тестирование) +10. [Checklist перед PR](#checklist-перед-pr) +11. [Полезные команды](#полезные-команды) +12. [FAQ](#faq) +13. [Ссылки](#ссылки) + +--- + +## Обзор архитектуры + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Вертикали │ +│ sdds-serv, sdds-insol, sdds-cs, ... │ +│ ───────────────────────────────────────────────────────── │ +│ • Токены (цвета, размеры, отступы) │ +│ • Вариации (view, size, ...) с конкретными значениями │ +│ • Дефолтные значения пропсов │ +└─────────────────────────────────────────────────────────────┘ + │ + │ mergeConfig() + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Ядро (plasma-new-hope) │ +│ ───────────────────────────────────────────────────────── │ +│ • Layout (JSX-структура компонента) │ +│ • Базовые CSS-стили │ +│ • Логика поведения │ +│ • TypeScript типы │ +│ • CSS-переменные (токены) без значений │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Ключевые концепции + +- **Ядро (`plasma-new-hope`)** — содержит логику, структуру и CSS-переменные компонентов +- **Вертикаль** — библиотека/пакет для конкретного бренда/продукта (sdds-serv, plasma-giga и т.д.) +- **Токены** — CSS-переменные, которые определяют внешний вид +- **Variations** — набор стилей для разных значений пропсов (view, size, disabled) +- **mergeConfig** — функция слияния конфигурации ядра с конфигурацией вертикали +- **component** — фабрика, создающая React-компонент из конфигурации + +--- + +## Создание компонента + +### Порядок действий + +1. Создать файлы компонента в ядре (`plasma-new-hope`) +2. Создать конфигурацию и сборку в вертикали (например, `sdds-serv`) +3. Проверить через Storybook и build пакета + +### Структура файлов + +**В ядре** (`packages/plasma-new-hope/src/components/MyComponent/`): + +``` +MyComponent/ +├── index.ts # Экспорт компонента из ядра +├── MyComponent.tsx # Layout-функция и конфигурация +├── MyComponent.types.ts # TypeScript типы +├── MyComponent.tokens.ts # CSS-переменные (токены) +├── MyComponent.styles.ts # Базовые стили и styled-компоненты +└── variations/ # Стили для каждой вариации + ├── _view/ + │ └── base.ts # CSS для view (обязательно) + ├── _size/ + │ └── base.ts + └── _disabled/ # Дополнительные вариации по необходимости + └── base.ts +``` + +**В вертикали** (`packages/sdds-serv/src/components/MyComponent/`): + +``` +MyComponent/ +├── index.ts # Экспорт компонента +├── MyComponent.ts # Сборка компонента +└── MyComponent.config.ts # Конфигурация токенов для бренда +``` + +--- + +### 1. Определите токены + +Файл `MyComponent.tokens.ts` — описание ключей CSS-переменных. Каждый ключ — имя CSS-переменной, которая получит конкретное значение в конфигурации вертикали. + +```typescript +// CSS-классы для состояний и модификаторов +export const classes = { + active: 'sdds-core-component-name-active', + disabled: 'sdds-core-component-name-disabled', +}; + +// Публичные токены (переопределяются в вертикалях) +export const tokens = { + // Цвета + color: '--sdds-core-component-name-color', + backgroundColor: '--sdds-core-component-name-background-color', + colorHover: '--sdds-core-component-name-color-hover', + contentBeforeColor: '--sdds-core-component-name-content-before-color', + + // Размеры и отступы + height: '--sdds-core-component-name-height', + padding: '--sdds-core-component-name-padding', + borderRadius: '--sdds-core-component-name-border-radius', + gap: '--sdds-core-component-name-gap', + + // Типографика для title + titleFontFamily: '--sdds-core-component-name-title-font-family', + titleFontSize: '--sdds-core-component-name-title-font-size', + titleFontStyle: '--sdds-core-component-name-title-font-style', + titleFontWeight: '--sdds-core-component-name-title-font-weight', + titleLetterSpacing: '--sdds-core-component-name-title-letter-spacing', + titleLineHeight: '--sdds-core-component-name-title-line-height', + + // Состояния + disabledOpacity: '--sdds-core-component-name-disabled-opacity', + focusColor: '--sdds-core-component-name-focus-color', + + // Вложенные элементы + closeIconSize: '--sdds-core-component-name-close-icon-size', + closeIconColor: '--sdds-core-component-name-close-icon-color', + closeIconColorOnHover: '--sdds-core-component-name-close-icon-color-on-hover', +}; +``` + +Связь между объявлением и использованием: + +```typescript +// tokens.ts — объявление ключа +padding: '--sdds-core-component-name-padding'// MyComponent.config.ts (вертикаль) — присвоение значения +`${tokens.padding}: 0.75rem;`; +``` + +--- + +### 2. Определите типы + +Файл `MyComponent.types.ts`: + +```typescript +import type { ReactNode, HTMLAttributes } from 'react'; + +export type MyComponentProps = { + /** + * Заголовок компонента + */ + title?: ReactNode; + /** + * Текст компонента + */ + text?: ReactNode; + /** + * Вид компонента + */ + view?: string; + /** + * Размер компонента + */ + size?: string; + /** + * Наличие кнопки закрытия + */ + hasClose?: boolean; + /** + * Callback при нажатии на кнопку закрытия + */ + onCloseButtonClick?: () => void; +} & HTMLAttributes; +``` + +--- + +### 3. Создайте базовые стили + +Файл `MyComponent.styles.ts`: + +```typescript +import { styled } from '@linaria/react'; +import { css } from '@linaria/core'; + +import { tokens } from './MyComponent.tokens'; + +// Базовые стили для корневого элемента +export const base = css` + position: relative; + display: flex; + box-sizing: border-box; +`; + +// Styled-компоненты для внутренних элементов +export const ContentBefore = styled.div` + display: flex; +`; + +export const ContentWrapper = styled.div` + display: flex; + flex-direction: column; +`; + +export const Title = styled.div` + box-sizing: border-box; +`; +``` + +--- + +### 4. Создайте вариации + +#### Файл `variations/_view/base.ts`: + +```typescript +import { css } from '@linaria/core'; + +import { tokens } from '../../MyComponent.tokens'; +import { ContentBefore } from '../../MyComponent.styles'; + +export const base = css` + background: var(${tokens.backgroundColor}); + color: var(${tokens.color}); + + /* Стилизация вложенных styled-компонентов через CSS */ + ${ContentBefore} { + color: var(${tokens.contentBeforeColor}); + fill: var(${tokens.contentBeforeColor}); + } +`; +``` + +#### Файл `variations/_size/base.ts`: + +```ts +import { css } from '@linaria/core'; + +import { classes, tokens } from '../../MyComponent.tokens'; +import { ContentBefore, Title } from '../../MyComponent.styles'; + +export const base = css` + padding: var(${tokens.padding}); + border-radius: var(${tokens.borderRadius}); + gap: var(${tokens.gap}); + + /* Модификатор stretch через CSS-класс */ + &.${classes.stretch} { + width: 100%; + height: 100%; + } + + /* Стилизация вложенных элементов */ + ${ContentBefore} { + width: var(${tokens.closeIconSize}); + height: var(${tokens.closeIconSize}); + } + + /* Типографика */ + ${Title} { + font-family: var(${tokens.titleFontFamily}); + font-size: var(${tokens.titleFontSize}); + font-style: var(${tokens.titleFontStyle}); + font-weight: var(${tokens.titleFontWeight}); + letter-spacing: var(${tokens.titleLetterSpacing}); + line-height: var(${tokens.titleLineHeight}); + } +`; +``` + +--- + +### 5. Создайте layout и конфигурацию в ядре + +Файл `MyComponent.tsx` — содержит layout-функцию (JSX-структуру) и конфигурацию компонента. + +- **`RootProps`** — тип из `src/engines`, описывающий корневой React-компонент, который engine создаёт на основе конфигурации. Первый параметр — тип HTML-элемента (должен соответствовать `tag` в конфигурации), второй — тип пропсов компонента. +- **`classnames(...classes)`** — утилита из `src/utils`. Объединяет строки и условные классы: `classnames('a', false && 'b', 'c')` → `'a c'`. + +```tsx +import React, { forwardRef } from 'react'; + +import { classnames } from '../../utils'; +import type { RootProps } from '../../engines'; + +import type { MyComponentProps } from './MyComponent.types'; +import { base as viewCSS } from './variations/_view/base'; +import { base as sizeCSS } from './variations/_size/base'; +import { base, ContentBefore, ContentWrapper, Title } from './MyComponent.styles'; +import { classes } from './MyComponent.tokens'; + +export const myComponentRoot = (Root: RootProps) => + forwardRef( + ( + { className, title, text, contentBefore, size, view, stretch, hasClose, onCloseButtonClick, ...rest }, + ref, + ) => { + return ( + + {contentBefore && {contentBefore}} + {title && {title}} + + ); + }, + ); + +export const myComponentConfig = { + name: 'MyComponent', + tag: 'div', + layout: myComponentRoot, + base, + variations: { + view: { + css: viewCSS, + }, + size: { + css: sizeCSS, + }, + }, + defaults: { + view: 'default', + size: 'm', + }, +}; +``` + +> **Примечание:** Значения в `defaults` определяются для каждого компонента индивидуально. Например, Button использует `view: 'secondary'`, а Note — `view: 'default'`. Выбирайте значения, соответствующие наиболее частому сценарию использования. + +--- + +### 6. Экспортируйте компонент из ядра + +Файл `index.ts`: + +```ts +export { myComponentRoot, myComponentConfig } from './MyComponent'; +export { tokens as myComponentTokens } from './MyComponent.tokens'; + +// Экспорт classes опционален — добавляйте, если классы нужны снаружи компонента +// export { classes as myComponentClasses } from './MyComponent.tokens'; + +// Типы экспортируются отдельно +export type { MyComponentProps } from './MyComponent.types'; +``` + +> **Примечание:** Токены (`tokens`) экспортируются всегда — они используются в конфигурациях вертикалей. Экспорт `classes` опционален — только если CSS-классы нужны внешним потребителям. + +--- + +### 7. Создайте конфигурацию в вертикали + +Файл `MyComponent.config.ts` — заполняет токены конкретными значениями бренда: + +```ts +import { css, myComponentTokens as tokens } from '@salutejs/plasma-new-hope/styled-components'; + +export const config = { + defaults: { + view: 'default', + size: 'm', + }, + variations: { + view: { + default: css` + ${tokens.backgroundColor}: var(--surface-transparent-secondary); + ${tokens.color}: var(--text-primary); + ${tokens.contentBeforeColor}: var(--text-accent); + `, + positive: css` + ${tokens.backgroundColor}: var(--surface-transparent-positive); + ${tokens.color}: var(--text-primary); + ${tokens.contentBeforeColor}: var(--text-positive); + `, + }, + size: { + s: css` + ${tokens.padding}: 0.625rem; + ${tokens.borderRadius}: 0.625rem; + ${tokens.gap}: 0.375rem; + + ${tokens.titleFontFamily}: var(--plasma-typo-body-xs-font-family); + ${tokens.titleFontSize}: var(--plasma-typo-body-xs-font-size); + ${tokens.titleFontStyle}: var(--plasma-typo-body-xs-font-style); + ${tokens.titleFontWeight}: var(--plasma-typo-body-xs-bold-font-weight); + ${tokens.titleLetterSpacing}: var(--plasma-typo-body-xs-letter-spacing); + ${tokens.titleLineHeight}: var(--plasma-typo-body-xs-line-height); + + ${tokens.closeIconSize}: 1rem; + `, + }, + }, +}; +``` + +--- + +### 8. Соберите компонент в вертикали + +Файл `MyComponent.ts`: + +```typescript +import { myComponentConfig, component, mergeConfig } from '@salutejs/plasma-new-hope/styled-components'; + +import { config } from './MyComponent.config'; + +const mergedConfig = mergeConfig(myComponentConfig, config); + +export const MyComponent = component(mergedConfig); +``` + +#### Компонент с несколькими конфигурациями (`createConditionalComponent`) + +Если компонент имеет несколько визуальных вариантов, которые требуют **разных конфигураций** (например, `default` и `clear` appearance), используйте `createConditionalComponent`. Он выбирает нужный компонент в runtime на основе значения пропса. + +Пример: `TextField` с двумя appearance — стандартный и `clear`: + +``` +TextField/ +├── index.ts +├── TextField.tsx +├── TextField.config.ts # Конфигурация для default appearance +└── TextField.clear.config.ts # Конфигурация для clear appearance +``` + +```typescript +// TextField.tsx +import { + textFieldConfig, + component, + mergeConfig, + createConditionalComponent, +} from '@salutejs/plasma-new-hope/styled-components'; + +import { config } from './TextField.config'; +import { config as clearConfig } from './TextField.clear.config'; + +// Создаём два компонента с разными конфигурациями +const mergedConfigDefault = mergeConfig(textFieldConfig, config); +export const TextFieldDefault = component(mergedConfigDefault); + +const mergedConfigClear = mergeConfig(textFieldConfig, clearConfig); +export const TextFieldClear = component(mergedConfigClear); + +// Объединяем через createConditionalComponent +export const TextField = createConditionalComponent(TextFieldDefault, [ + { + conditions: { prop: 'appearance', value: 'clear' }, + component: TextFieldClear, + }, +]); +``` + +Если `appearance="clear"` — рендерится `TextFieldClear`, иначе — `TextFieldDefault`. + +--- + +### 9. Экспортируйте из вертикали + +> **Важно:** Вертикаль не создаёт собственные токены — она реэкспортирует их из `@salutejs/plasma-new-hope/styled-components`. Это гарантирует единый источник правды для имён CSS-переменных. Реэкспорт `classes` — только если они экспортированы из ядра. + +Файл `index.ts`: + +```typescript +export { MyComponent } from './MyComponent'; + +// Токены реэкспортируются из ядра, а не определяются заново +export { myComponentTokens } from '@salutejs/plasma-new-hope/styled-components'; + +// Классы реэкспортируются только если они были экспортированы из ядра (шаг 6) +// export { myComponentClasses } from '@salutejs/plasma-new-hope/styled-components'; +``` + +--- + +### 10. Проверьте + +```bash +# Bootstrap зависимостей +npm run bootstrap + +# Сборка пакета +cd packages/sdds-serv && npm run build + +# Запуск Storybook +npm run storybook +``` + +> Примеры реальных компонентов: [Note](../../packages/plasma-new-hope/src/components/Note/), [Button](../../packages/plasma-new-hope/src/components/Button/), [Attach](../../packages/plasma-new-hope/src/components/Attach/). + +--- + +## Работа с токенами + +### Именование токенов + +``` +--sdds-core-{component}-{element}-{property}[-{state}] + +Примеры: +--sdds-core-button-color +--sdds-core-button-background-color-hover +--sdds-core-note-title-font-size +--sdds-core-attach-cell-label-color +``` + +### Категории токенов + +```typescript +export const tokens = { + // Цвета основного элемента + color: '--sdds-core-component-color', + backgroundColor: '--sdds-core-component-background-color', + + // Цвета состояний + colorHover: '--sdds-core-component-color-hover', + colorActive: '--sdds-core-component-color-active', + + // Размеры + height: '--sdds-core-component-height', + width: '--sdds-core-component-width', + padding: '--sdds-core-component-padding', + gap: '--sdds-core-component-gap', + borderRadius: '--sdds-core-component-border-radius', + + // Типографика (для каждого текстового элемента) + titleFontFamily: '--sdds-core-component-title-font-family', + titleFontSize: '--sdds-core-component-title-font-size', + // ... остальные font-* + + textFontFamily: '--sdds-core-component-text-font-family', + // ... + + // Вложенные элементы + iconSize: '--sdds-core-component-icon-size', + iconColor: '--sdds-core-component-icon-color', + + // Состояния + disabledOpacity: '--sdds-core-component-disabled-opacity', + focusColor: '--sdds-core-component-focus-color', +}; +``` + +### Приватные токены + +Для внутренних вычислений используйте приватные токены: + +```typescript +export const privateTokens = { + computedWidth: '--sdds-core_private-component-width', + computedOffset: '--sdds-core_private-component-offset', +}; +``` + +Приватные токены: + +- Начинаются с `--sdds-core_private-` +- Не переопределяются в вертикалях +- Используются для динамических значений, вычисляемых в runtime + +--- + +## Variations (вариации) + +### Стандартные вариации + +| Вариация | Назначение | +| -------- | --------------------------------------------------------------------------------- | +| `view` | Визуальный вид (default, primary, secondary, accent, positive, warning, negative) | +| `size` | Размер (xs, s, m, l, xl) | + +### Дополнительные вариации + +Для сложных компонентов можно добавлять специфичные вариации: + +```ts +export const config = { + variations: { + view: { + css: viewCSS, + }, + size: { + css: sizeCSS, + }, + disabled: { + css: disabledCSS, + attrs: true, + // Добавляет HTML-атрибут disabled к элементу + }, + focused: { + css: focusedCSS, + }, + helperTextView: { + css: helperTextViewCSS, + }, + // Дополнительная вариация + }, + defaults: { + view: 'default', + size: 'm', + helperTextView: 'default', + }, +}; +``` + +#### Параметр `attrs` + +Параметр `attrs: true` в вариации указывает, что значение пропса должно быть также добавлено как HTML-атрибут к корневому DOM-элементу. + +По умолчанию engine использует значение пропса только для выбора CSS-класса вариации. С `attrs: true` значение дополнительно пробрасывается как HTML-атрибут. Это критично для нативного поведения браузера. + +**Когда использовать:** + +- `disabled` — нативный атрибут, отключающий интерактивность элемента (фокус, клик, форма) +- Другие булевые атрибуты, которые должны отражаться в DOM для accessibility или нативного поведения + +**Пример:** Без `attrs: true` пропс `disabled` применит только CSS-стили (opacity, pointer-events), но элемент останется фокусируемым и доступным для форм. С `attrs: true` к DOM-элементу добавляется `disabled="true"`, что обеспечивает корректное нативное поведение. + +```ts +// Без attrs: рендерится