diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 344714b4..f100a9cb 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - feature/styles-debug # Allows you to run this workflow manually from the Actions tab workflow_dispatch: diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index e3545785..25afca18 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -6,22 +6,76 @@ on: permissions: id-token: write - contents: read + contents: write jobs: publish-npm: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 20 registry-url: https://registry.npmjs.org/ - name: Update npm run: npm install -g npm@latest - - run: npm ci --production + - run: npm ci - uses: reedyuk/npm-version@1.1.1 with: version: ${{github.ref_name}} - - run: npm run copy:dist - - run: npm publish + - name: Sync src/lib/package.json version with root package.json + run: | + node -e "const fs=require('fs'); const path='src/lib/package.json'; const root=JSON.parse(fs.readFileSync('package.json')); const lib=JSON.parse(fs.readFileSync(path)); if(lib.version!==root.version){ lib.version=root.version; fs.writeFileSync(path, JSON.stringify(lib,null,2)+'\n'); }" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add src/lib/package.json + # commit only if there are staged changes + git diff --cached --quiet || (git commit -m "chore(release): sync src/lib package version to ${{github.ref_name}}" && git push origin HEAD:refs/heads/${{ github.event.release.target_commitish }}) + - run: npm run build:lib + - name: Prepare dist/package.json + run: | + set -euo pipefail + # Ensure dist exists and has its own package.json generated by the build + if [ ! -d dist ]; then echo "dist directory not found"; exit 1; fi + if [ ! -f dist/package.json ]; then echo "dist/package.json not found — expected build to generate it"; exit 1; fi + # Sync version in dist/package.json with root package.json (silent) + node -e "const fs=require('fs'); const root=JSON.parse(fs.readFileSync('package.json')); const p='dist/package.json'; const d=JSON.parse(fs.readFileSync(p)); if(d.version!==root.version){ d.version=root.version; fs.writeFileSync(p, JSON.stringify(d,null,2)+'\n'); }" + - name: Fix package.json issues inside dist (optional) + run: | + npm --prefix ./dist pkg fix || true + - name: Normalize dist/package.json (ensure correct name, version and repository) + run: | + node -e "const fs=require('fs'); const root=require('./package.json'); const srcExists=fs.existsSync('src/lib/package.json'); const src=srcExists? require('./src/lib/package.json'): null; const p='dist/package.json'; const d=JSON.parse(fs.readFileSync(p)); const wantedName= src? src.name : root.name; const wantedVersion=root.version; let changed=false; if(d.name!==wantedName){ d.name=wantedName; changed=true;} if(d.version!==wantedVersion){ d.version=wantedVersion; changed=true;} const normalizeRepo=(r)=>{ if(!r) return null; if(typeof r==='string') return r.replace(/^git\+/, '').replace(/\.git$/,''); if(r.url) return r.url.replace(/^git\+/, '').replace(/\.git$/,''); return null; }; const wantedRepo = normalizeRepo(root.repository) || (process.env.GITHUB_REPOSITORY? 'https://github.com/'+process.env.GITHUB_REPOSITORY : null); if(wantedRepo){ if(!d.repository){ d.repository = { type: 'git', url: wantedRepo }; changed=true; } else { const currentRepo=normalizeRepo(d.repository); if(currentRepo!==wantedRepo){ d.repository = { type: 'git', url: wantedRepo }; changed=true; } } } if(changed){ fs.writeFileSync(p, JSON.stringify(d,null,2)+'\n'); }" + - name: Pack dist and verify package.json inside tarball + run: | + set -euo pipefail + PACKAGE_DIR=dist + VERSION=$(node -e "console.log(require('./${PACKAGE_DIR}/package.json').version)") + # create tarball in workspace (pack local folder) + TARFILE=$(npm pack "./$PACKAGE_DIR") + mkdir -p verify_unpack + tar -xzf "$TARFILE" -C verify_unpack + # read version from inside tarball + INNER_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('verify_unpack/package/package.json')).version)") + if [ "$INNER_VERSION" != "$VERSION" ]; then + echo "Error: version inside tarball ($INNER_VERSION) does not match dist/package.json ($VERSION)" >&2 + exit 1 + fi + # make TARFILE available to next step + echo "TARFILE=$TARFILE" >> $GITHUB_ENV + - name: Publish verified tarball to npm (use tag for prerelease versions) + run: | + set -euo pipefail + PACKAGE_DIR=dist + VERSION=$(node -e "console.log(require('./${PACKAGE_DIR}/package.json').version)") + TARFILE=${{ env.TARFILE }} + NAME=$(node -e "console.log(require('./${PACKAGE_DIR}/package.json').name)") + if [[ "$VERSION" == *-* ]]; then + TAG=$(echo "$VERSION" | sed -E 's/^[0-9]+\.[0-9]+\.[0-9]+-([^\.]+).*/\1/') + npm publish "$TARFILE" --tag "$TAG" + else + npm publish "$TARFILE" + fi + echo "Published ${NAME}@${VERSION}" diff --git a/.gitignore b/.gitignore index 9276a2cc..863dd859 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,10 @@ api-generator/typedoc.json # файлы в этим папках компилятся и должны создаваться при сборке src/assets/components/themes -./storybook-static -./debug-storybook.log -./documentation.json +/storybook-static +/debug-storybook.log +/documentation.json + +.claude/* + +.playwright-mcp/* diff --git a/.storybook/main.ts b/.storybook/main.ts index 9c4112e5..fb574771 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -2,7 +2,7 @@ import type { StorybookConfig } from '@storybook/angular'; const config: StorybookConfig = { stories: ['../src/stories/**/*.mdx', '../src/stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'], - addons: ['@storybook/addon-a11y', '@storybook/addon-docs'], + addons: ['@storybook/addon-a11y', '@storybook/addon-docs', '@storybook/addon-themes'], framework: '@storybook/angular' }; export default config; diff --git a/.storybook/manager.ts b/.storybook/manager.ts new file mode 100644 index 00000000..50bb7774 --- /dev/null +++ b/.storybook/manager.ts @@ -0,0 +1,12 @@ +import { addons } from 'storybook/manager-api'; + +addons.setConfig({ + layoutCustomisations: { + showToolbar(state, defaultValue) { + if (state.viewMode === 'docs') { + return false; + } + return defaultValue; + }, + }, +}); diff --git a/.storybook/preview.ts b/.storybook/preview.ts index acc752b9..b5b8be0c 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,15 +1,18 @@ import { applicationConfig, Preview } from '@storybook/angular'; import { setCompodocJson } from '@storybook/addon-docs/angular'; +import { withThemeByClassName } from '@storybook/addon-themes'; import docJson from '../documentation.json'; import { providePrimeNG } from 'primeng/config'; import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; -import Preset from '../src/prime-preset/theme.preset'; +import Preset from '../src/lib/providers/prime-preset/theme.preset'; import '!style-loader!css-loader!postcss-loader!sass-loader!../src/styles.scss'; setCompodocJson(docJson); +const DARK_MODE_SELECTOR = '.dark-mode'; + const preview: Preview = { decorators: [ applicationConfig({ @@ -19,15 +22,26 @@ const preview: Preview = { theme: { preset: Preset, options: { - darkModeSelector: false, + darkModeSelector: '.dark', cssLayer: false } } }) ] + }), + withThemeByClassName({ + themes: { + light: '', + dark: 'dark' + }, + defaultTheme: 'light' }) ], parameters: { + backgrounds: { disable: true }, + docs: { + globals: { theme: 'light' }, + }, controls: { matchers: { color: /(background|color)$/i, diff --git a/.storybook/tsconfig.json b/.storybook/tsconfig.json index 507a7145..664578be 100644 --- a/.storybook/tsconfig.json +++ b/.storybook/tsconfig.json @@ -5,6 +5,8 @@ "allowSyntheticDefaultImports": true, "resolveJsonModule": true }, - "include": ["../src/stories/**/*.stories.*", "./preview.ts", "../src/prime-preset/**/*"], + "include": ["../src/stories/**/*.stories.*", "./preview.ts", + "../src/lib/providers/prime-preset/**/*" + ], "files": ["./typings.d.ts"] } diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..43042a87 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +# Project Rules + +Основные правила и запреты — в `.claude/skills/generate-component/references/red-lines.md`. diff --git a/README.md b/README.md index 49f1abf4..9eaf1e4d 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,75 @@ # angular-ui-kit -angular-ui-kit - это пресет темы для primeng, а также storybook с демонстрацией и документацией используемых компонентов. -Он позволяет быстро и легко строить новые интерфейсы в фирменном стиле CDEK. +`angular-ui-kit` — это полноценная библиотека UI-компонентов и сервисов для Angular с готовыми стилями, storybook для демонстрации и документацией. +Библиотека позволяет быстро и удобно добавлять готовые фирменные компоненты CDEK в приложения. ## Использование -1. Установите пакет @cdek-it/angular-ui-kit +1. Установите пакет `@cdek-it/angular-ui-kit` ```shell npm install @cdek-it/angular-ui-kit ``` -2. Импортируйте пресет темы в ваш angular-проект +2. Подключите провайдеры в ваш angular-проект. Важно: для корректной работы стилей необходимо использовать `provideExtraThemes()` в списке провайдеров, например: ```ts -import Preset from '@cdek-it/angular-ui-kit/dist/theme.preset.ts'; +import { provideExtraThemes } from '@cdek-it/angular-ui-kit'; +import { provideBrowserGlobalErrorListeners } from '@angular/platform-browser'; +import { importProvidersFrom } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; export const appConfig: ApplicationConfig = { providers: [ - ..., - provideAnimations(), - providePrimeNG({ - theme: { - preset: Preset, - options: { - darkModeSelector: false, - cssLayer: false - } - } - }) + provideBrowserGlobalErrorListeners(), + importProvidersFrom(BrowserModule), + provideExtraThemes(), ] }; ``` +`provideExtraThemes()` необходим для правильной интеграции стилей библиотеки в приложение. + +## Пример использования компонентов + +Ниже простой пример использования входящих в библиотеку компонентов (вариант — `extra-button` и `extra-tag`). Вставьте в шаблон компонента или story: + +```html +
+ + + + +
+``` + +Примечание: Нужно добавить импорт соответствующего модуля/компонентов библиотеки в ваш модуль или компонент. Пример для standalone-компонента (используется `imports` в декораторе): + +```ts +import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ExtraButtonComponent, ExtraTagComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [ + ExtraButtonComponent, + CommonModule, + ExtraTagComponent, + ], + template: ` +
+ + + + +
+ ` +}) +export class AppExample {} +``` + ## Используемые технологии и связанные зависимости --- @@ -48,11 +85,14 @@ export const appConfig: ApplicationConfig = { - Storybook 10 +Важно: на данный момент компоненты библиотеки не работают без Zone.js. Убедитесь, что ваше приложение использует zone (Zone.js должен быть подключён). В большинстве стандартных Angular-приложений Zone.js подключён по умолчанию. + ## Структура проекта - `src/app` - базовое angular-приложение. Может использоваться как плейграунд для разработки и отладки. - `src/stories` - набор story с компонентами для storybook. -- `src/prime-preset` - пресет темы для primeng, а также токены. +- `src/prime-preset` - пресет темы и токены (используется библиотекой для совместимости с PrimeNG). +- `src/lib` - исходники компонентов и сервисов библиотеки (компоненты, сервисы, модули и публичный API). ## Запуск storybook @@ -77,33 +117,3 @@ npm run storybook 3. Убедитесь, что все состояния компонента выглядят верно. Если нет - смотрите раздел "[Правила доработки компонентов](#Правила доработки компонентов)" ниже. 4. Создать pull request в `main`, прикрепить его в задачу. Задачу отдать на ревью разработчикам и дизайнерам. В случае замечаний ориентируемся на пункт `3` выше. - -## Правила доработки компонентов - -### Компоненты primeng - -Если компонент несоответствует дизайну в figma, то: - -1. Проверяем верность токенов. Если есть ошибки - сообщаем мейнтейнеру. -2. Если токены верны, и проблему можно решить кастомизацией css - согласуем доработки с мейнтейнером. -3. Если кастомизации css недостаточно, но можно решить проблему через шаблоны - согласуем доработки с мейнтейнером. Пример ниже. -Например, для `inputtext` нужен крестик с очисткой. Непосредственно такой опции в primeng нет, но можно использовать `p-inputIcon` с иконкой крестика, и следующим `source`-кодом в story: - -``` -// template - - - - - -// ts -onClearClick() { - this.value = ''; -} -``` -Важно, что бы в story был верный `source`-код, что бы разработчики могли просто его копировать и с минимальными доработками использовать у себя. -4. Если вариантов решения проблемы через способы выше нет - сообщаем мейнтейнеру. Далее будет подниматься вопрос о необходимости написания своего компонента. - -### Кастомные компоненты - -*todo будут разработаны при необходимости* diff --git a/angular.json b/angular.json index 0cbea1d6..8e1cf2a9 100644 --- a/angular.json +++ b/angular.json @@ -26,9 +26,7 @@ "input": "public" } ], - "styles": [ - "src/styles.scss" - ] + "styles": ["src/styles.scss"] }, "configurations": { "production": { @@ -80,9 +78,7 @@ "input": "public" } ], - "styles": [ - "src/styles.scss" - ] + "styles": ["src/styles.scss"] } }, "storybook": { @@ -91,12 +87,7 @@ "configDir": ".storybook", "browserTarget": "angular-ui-kit:build", "compodoc": true, - "compodocArgs": [ - "-e", - "json", - "-d", - "." - ], + "compodocArgs": ["-e", "json", "-d", "."], "port": 6006 } }, @@ -106,16 +97,40 @@ "configDir": ".storybook", "browserTarget": "angular-ui-kit:build", "compodoc": true, - "compodocArgs": [ - "-e", - "json", - "-d", - "." - ], + "compodocArgs": ["-e", "json", "-d", "."], "outputDir": "storybook-static" } } } + }, + "angular-ui-kit-lib": { + "projectType": "library", + "root": "src", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:ng-packagr", + "options": { + "project": "src/lib/ng-package.json", + "tsConfig": "src/lib/tsconfig.lib.prod.json" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "tsConfig": "src/lib/tsconfig.spec.json", + "polyfills": ["zone.js", "zone.js/testing"] + } + }, + "lint": { + "builder": "@angular-eslint/builder:lint", + "options": { + "lintFilePatterns": ["src/lib/**/*.ts", "src/lib/**/*.html"] + } + } + } } + }, + "cli": { + "analytics": false } -} \ No newline at end of file +} diff --git a/docs/superpowers/plans/2026-04-16-inputnumber.md b/docs/superpowers/plans/2026-04-16-inputnumber.md new file mode 100644 index 00000000..ab3591e0 --- /dev/null +++ b/docs/superpowers/plans/2026-04-16-inputnumber.md @@ -0,0 +1,808 @@ +# InputNumber Component — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Создать Angular wrapper-компонент InputNumber с CSS-переопределениями и Storybook-сториями. + +**Architecture:** Standalone CVA-компонент `InputNumberComponent`, оборачивающий PrimeNG `p-inputnumber`. CSS-оверрайды в `src/prime-preset/tokens/components/inputnumber.ts`, подключаются через `map-tokens.ts`. Четыре стории: Default (динамический template из args), FloatLabel (нативный `p-inputnumber` внутри `p-floatlabel`), Currency, MinMax. + +**Tech Stack:** Angular 20, PrimeNG 20, Storybook 8, Tailwind, `dt()` токены, Tabler Icons. + +--- + +## File Map + +| Действие | Путь | +|---|---| +| Создать | `src/lib/components/inputnumber/inputnumber.component.ts` | +| Создать | `src/prime-preset/tokens/components/inputnumber.ts` | +| Изменить | `src/prime-preset/map-tokens.ts` | +| Создать | `src/stories/components/inputnumber/inputnumber.stories.ts` | +| Создать | `src/stories/components/inputnumber/examples/inputnumber-float-label.component.ts` | +| Создать | `src/stories/components/inputnumber/examples/inputnumber-currency.component.ts` | +| Создать | `src/stories/components/inputnumber/examples/inputnumber-minmax.component.ts` | + +--- + +### Task 1: CSS-переопределения InputNumber + +**Files:** +- Create: `src/prime-preset/tokens/components/inputnumber.ts` +- Modify: `src/prime-preset/map-tokens.ts` + +- [ ] **Step 1: Создать файл CSS-токенов** + +Создать `src/prime-preset/tokens/components/inputnumber.ts`: + +```typescript +export const inputnumberCss = ({ dt }: { dt: (token: string) => string }): string => ` + +/* ─── Кнопки +/− ─── */ +.p-inputnumber-button { + border-width: ${dt('inputnumber.extend.borderWidth')}; +} + +.p-inputnumber-horizontal .p-inputnumber-button { + min-height: ${dt('inputnumber.extend.extButton.height')}; +} + +/* ─── Disabled состояние кнопок ─── */ +.p-inputnumber-horizontal:has(.p-inputnumber-input:disabled) .p-inputnumber-button { + background: ${dt('inputtext.root.disabledBackground')}; + color: ${dt('inputtext.root.disabledColor')}; +} + +/* ─── Extra Large ─── */ +.p-inputnumber.p-inputnumber-xlg .p-inputnumber-input { + font-size: ${dt('inputtext.extend.extXlg.fontSize')}; + padding: ${dt('inputtext.extend.extXlg.paddingY')} ${dt('inputtext.extend.extXlg.paddingX')}; +} +`; +``` + +- [ ] **Step 2: Зарегистрировать CSS в map-tokens.ts** + +Открыть `src/prime-preset/map-tokens.ts`. Добавить импорт после строки с `inputtextCss`: + +```typescript +import { inputnumberCss } from './tokens/components/inputnumber'; +``` + +Добавить запись в объект `components` после блока `inputtext`: + +```typescript +inputnumber: { + ...(tokens.components.inputnumber as unknown as ComponentsDesignTokens['inputnumber']), + css: inputnumberCss, +}, +``` + +- [ ] **Step 3: Проверить компиляцию** + +```bash +cd /Users/d.khaliulin/Downloads/angular-ui-kit-feature-styles-debug +npx tsc --noEmit +``` + +Ожидается: нет ошибок. + +- [ ] **Step 4: Коммит** + +```bash +git add src/prime-preset/tokens/components/inputnumber.ts src/prime-preset/map-tokens.ts +git commit -m "feat(inputnumber): добавить CSS-переопределения токенов" +``` + +--- + +### Task 2: InputNumberComponent + +**Files:** +- Create: `src/lib/components/inputnumber/inputnumber.component.ts` + +- [ ] **Step 1: Создать компонент** + +Создать `src/lib/components/inputnumber/inputnumber.component.ts`: + +```typescript +import { Component, Input, forwardRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; +import { NgClass } from '@angular/common'; +import { InputNumber } from 'primeng/inputnumber'; + +export type InputNumberSize = 'small' | 'base' | 'large' | 'xlarge'; +export type InputNumberButtonLayout = 'horizontal' | 'vertical' | 'stacked'; +export type InputNumberMode = 'decimal' | 'currency'; + +@Component({ + selector: 'input-number', + standalone: true, + imports: [InputNumber, NgClass, FormsModule], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => InputNumberComponent), + multi: true, + }, + ], + template: ` + + `, +}) +export class InputNumberComponent implements ControlValueAccessor { + @Input() size: InputNumberSize = 'base'; + @Input() placeholder = ''; + @Input() disabled = false; + @Input() readonly = false; + @Input() invalid = false; + @Input() showButtons = true; + @Input() buttonLayout: InputNumberButtonLayout = 'horizontal'; + @Input() mode: InputNumberMode = 'decimal'; + @Input() currency = 'RUB'; + @Input() locale = 'ru-RU'; + @Input() prefix: string | undefined = undefined; + @Input() suffix: string | undefined = undefined; + @Input() min: number | undefined = undefined; + @Input() max: number | undefined = undefined; + @Input() step = 1; + @Input() minFractionDigits = 0; + @Input() maxFractionDigits = 20; + @Input() fluid = false; + + modelValue: number | null = null; + + private _onChange: (value: number | null) => void = () => {}; + onTouched: () => void = () => {}; + + get primeSize(): 'small' | 'large' | undefined { + if (this.size === 'small') return 'small'; + if (this.size === 'large' || this.size === 'xlarge') return 'large'; + return undefined; + } + + get sizeClass(): Record { + return { 'p-inputnumber-xlg': this.size === 'xlarge' }; + } + + onInputChange(event: { value: number | null | undefined }): void { + const value = event.value ?? null; + this.modelValue = value; + this._onChange(value); + } + + writeValue(value: number | null): void { + this.modelValue = value ?? null; + } + + registerOnChange(fn: (value: number | null) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } +} +``` + +- [ ] **Step 2: Проверить компиляцию** + +```bash +npx tsc --noEmit +``` + +Ожидается: нет ошибок. + +- [ ] **Step 3: Коммит** + +```bash +git add src/lib/components/inputnumber/inputnumber.component.ts +git commit -m "feat(inputnumber): добавить компонент InputNumberComponent" +``` + +--- + +### Task 3: FloatLabel story + +**Files:** +- Create: `src/stories/components/inputnumber/examples/inputnumber-float-label.component.ts` + +- [ ] **Step 1: Создать файл** + +Создать `src/stories/components/inputnumber/examples/inputnumber-float-label.component.ts`: + +```typescript +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { InputNumber } from 'primeng/inputnumber'; +import { FloatLabel } from 'primeng/floatlabel'; + +const template = ` +
+ + + + +
+`; +const styles = ''; + +@Component({ + selector: 'app-inputnumber-float-label', + standalone: true, + imports: [InputNumber, FloatLabel, FormsModule], + template, + styles, +}) +export class InputNumberFloatLabelComponent { + value: number | null = null; +} + +export const FloatLabelStory: StoryObj = { + name: 'FloatLabel', + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: + 'Интеграция с `p-floatlabel` — плавающая метка внутри поля. Требует нативный `p-inputnumber` как прямой дочерний элемент `p-floatlabel`.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { InputNumber } from 'primeng/inputnumber'; +import { FloatLabel } from 'primeng/floatlabel'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-inputnumber-float-label', + standalone: true, + imports: [InputNumber, FloatLabel, FormsModule], + template: \` + + + + + \`, +}) +export class InputNumberFloatLabelComponent { + value: number | null = null; +} + `, + }, + }, + }, +}; +``` + +- [ ] **Step 2: Проверить компиляцию** + +```bash +npx tsc --noEmit +``` + +Ожидается: нет ошибок. + +--- + +### Task 4: Currency story + +**Files:** +- Create: `src/stories/components/inputnumber/examples/inputnumber-currency.component.ts` + +- [ ] **Step 1: Создать файл** + +Создать `src/stories/components/inputnumber/examples/inputnumber-currency.component.ts`: + +```typescript +import { StoryObj } from '@storybook/angular'; +import { InputNumberComponent } from '../../../../lib/components/inputnumber/inputnumber.component'; + +type Story = StoryObj; + +export const Currency: Story = { + name: 'Currency', + render: (args) => ({ + props: { ...args, value: null }, + template: ` + + `, + }), + args: { + mode: 'currency', + currency: 'RUB', + locale: 'ru-RU', + minFractionDigits: 2, + maxFractionDigits: 2, + }, + parameters: { + docs: { + description: { + story: 'Режим валюты — форматирует значение с символом валюты по заданной локали.', + }, + source: { + language: 'ts', + code: ` +import { InputNumberComponent } from '@cdek-it/angular-ui-kit'; +import { FormsModule } from '@angular/forms'; + +// template: +// + `, + }, + }, + }, +}; +``` + +--- + +### Task 5: MinMax story + +**Files:** +- Create: `src/stories/components/inputnumber/examples/inputnumber-minmax.component.ts` + +- [ ] **Step 1: Создать файл** + +Создать `src/stories/components/inputnumber/examples/inputnumber-minmax.component.ts`: + +```typescript +import { StoryObj } from '@storybook/angular'; +import { InputNumberComponent } from '../../../../lib/components/inputnumber/inputnumber.component'; + +type Story = StoryObj; + +export const MinMax: Story = { + name: 'Min / Max / Step', + render: (args) => ({ + props: { ...args, value: null }, + template: ` + + `, + }), + args: { + min: 0, + max: 100, + step: 1, + placeholder: '0–100', + }, + parameters: { + docs: { + description: { + story: 'Ограничения min/max и шаг изменения через кнопки и клавиатуру.', + }, + source: { + language: 'ts', + code: ` +import { InputNumberComponent } from '@cdek-it/angular-ui-kit'; +import { FormsModule } from '@angular/forms'; + +// template: +// + `, + }, + }, + }, +}; +``` + +--- + +### Task 6: Main stories file + +**Files:** +- Create: `src/stories/components/inputnumber/inputnumber.stories.ts` + +- [ ] **Step 1: Создать файл** + +Создать `src/stories/components/inputnumber/inputnumber.stories.ts`: + +```typescript +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormsModule } from '@angular/forms'; +import { InputNumberComponent } from '../../../lib/components/inputnumber/inputnumber.component'; +import { InputNumberFloatLabelComponent, FloatLabelStory } from './examples/inputnumber-float-label.component'; +import { Currency } from './examples/inputnumber-currency.component'; +import { MinMax } from './examples/inputnumber-minmax.component'; + +type InputNumberArgs = InputNumberComponent; + +const meta: Meta = { + title: 'Components/Form/InputNumber', + component: InputNumberComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + InputNumberComponent, + FormsModule, + InputNumberFloatLabelComponent, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-inputnumber' }, + docs: { + description: { + component: `Числовое поле ввода с поддержкой форматирования и кнопок +/−. + +\`\`\`typescript +import { InputNumberComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + size: { + control: 'select', + options: ['small', 'base', 'large', 'xlarge'], + description: 'Размер поля', + table: { + category: 'Props', + defaultValue: { summary: "'base'" }, + type: { summary: "'small' | 'base' | 'large' | 'xlarge'" }, + }, + }, + placeholder: { + control: 'text', + description: 'Подсказка при пустом поле', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + disabled: { + control: 'boolean', + description: 'Отключает взаимодействие', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + readonly: { + control: 'boolean', + description: 'Только для чтения', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + invalid: { + control: 'boolean', + description: 'Невалидное состояние', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + showButtons: { + control: 'boolean', + description: 'Показывать кнопки +/−', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + buttonLayout: { + control: 'select', + options: ['horizontal', 'vertical', 'stacked'], + description: 'Расположение кнопок', + table: { + category: 'Props', + defaultValue: { summary: "'horizontal'" }, + type: { summary: "'horizontal' | 'vertical' | 'stacked'" }, + }, + }, + mode: { + control: 'select', + options: ['decimal', 'currency'], + description: 'Режим отображения значения', + table: { + category: 'Props', + defaultValue: { summary: "'decimal'" }, + type: { summary: "'decimal' | 'currency'" }, + }, + }, + currency: { + control: 'text', + description: 'Код валюты ISO 4217, используется при mode="currency"', + table: { + category: 'Props', + defaultValue: { summary: "'RUB'" }, + type: { summary: 'string' }, + }, + }, + locale: { + control: 'text', + description: 'Локаль для форматирования числа', + table: { + category: 'Props', + defaultValue: { summary: "'ru-RU'" }, + type: { summary: 'string' }, + }, + }, + prefix: { + control: 'text', + description: 'Префикс перед значением', + table: { + category: 'Props', + type: { summary: 'string' }, + }, + }, + suffix: { + control: 'text', + description: 'Суффикс после значения (например, "%")', + table: { + category: 'Props', + type: { summary: 'string' }, + }, + }, + min: { + control: 'number', + description: 'Минимально допустимое значение', + table: { + category: 'Props', + type: { summary: 'number' }, + }, + }, + max: { + control: 'number', + description: 'Максимально допустимое значение', + table: { + category: 'Props', + type: { summary: 'number' }, + }, + }, + step: { + control: 'number', + description: 'Шаг изменения значения', + table: { + category: 'Props', + defaultValue: { summary: '1' }, + type: { summary: 'number' }, + }, + }, + minFractionDigits: { + control: { type: 'number', min: 0, max: 20 }, + description: 'Минимальное количество знаков после запятой', + table: { + category: 'Props', + defaultValue: { summary: '0' }, + type: { summary: 'number' }, + }, + }, + maxFractionDigits: { + control: { type: 'number', min: 0, max: 20 }, + description: 'Максимальное количество знаков после запятой', + table: { + category: 'Props', + defaultValue: { summary: '20' }, + type: { summary: 'number' }, + }, + }, + fluid: { + control: 'boolean', + description: 'Растягивает поле на всю ширину контейнера', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + // Hidden computed props + modelValue: { table: { disable: true } }, + primeSize: { table: { disable: true } }, + sizeClass: { table: { disable: true } }, + }, + args: { + size: 'base', + placeholder: '0', + disabled: false, + readonly: false, + invalid: false, + showButtons: true, + buttonLayout: 'horizontal', + mode: 'decimal', + currency: 'RUB', + locale: 'ru-RU', + step: 1, + minFractionDigits: 0, + maxFractionDigits: 20, + fluid: false, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.size && args.size !== 'base') parts.push(`size="${args.size}"`); + if (args.placeholder) parts.push(`placeholder="${args.placeholder}"`); + if (args.disabled) parts.push(`[disabled]="true"`); + if (args.readonly) parts.push(`[readonly]="true"`); + if (args.invalid) parts.push(`[invalid]="true"`); + if (!args.showButtons) parts.push(`[showButtons]="false"`); + if (args.buttonLayout && args.buttonLayout !== 'horizontal') parts.push(`buttonLayout="${args.buttonLayout}"`); + if (args.mode && args.mode !== 'decimal') parts.push(`mode="${args.mode}"`); + if (args.mode === 'currency' && args.currency) parts.push(`currency="${args.currency}"`); + if (args.locale && args.locale !== 'ru-RU') parts.push(`locale="${args.locale}"`); + if (args.prefix) parts.push(`prefix="${args.prefix}"`); + if (args.suffix) parts.push(`suffix="${args.suffix}"`); + if (args.min !== undefined) parts.push(`[min]="${args.min}"`); + if (args.max !== undefined) parts.push(`[max]="${args.max}"`); + if (args.step && args.step !== 1) parts.push(`[step]="${args.step}"`); + if (args.minFractionDigits) parts.push(`[minFractionDigits]="${args.minFractionDigits}"`); + if (args.maxFractionDigits && args.maxFractionDigits !== 20) parts.push(`[maxFractionDigits]="${args.maxFractionDigits}"`); + if (args.fluid) parts.push(`[fluid]="true"`); + parts.push(`[(ngModel)]="value"`); + + const template = ``; + + return { props: { ...args, value: null }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { FloatLabelStory as FloatLabel, Currency, MinMax }; +``` + +- [ ] **Step 2: Проверить компиляцию** + +```bash +npx tsc --noEmit +``` + +Ожидается: нет ошибок. + +- [ ] **Step 3: Коммит** + +```bash +git add src/stories/components/inputnumber/ +git commit -m "feat(inputnumber): добавить Storybook-стории" +``` + +--- + +### Task 7: Финальная проверка + +- [ ] **Step 1: Полная проверка TypeScript** + +```bash +npx tsc --noEmit +``` + +Ожидается: нет ошибок. + +- [ ] **Step 2: Запустить Storybook и проверить визуально** + +```bash +npm run storybook +``` + +Открыть `http://localhost:6006` и проверить: +- `Components/Form/InputNumber` → Default: Controls меняют пропсы, code-snippet обновляется +- FloatLabel: метка анимируется при фокусе/вводе +- Currency: значение форматируется с символом ₽ +- Min/Max: кнопки не выходят за диапазон 0–100 + +- [ ] **Step 3: Финальный коммит** + +```bash +git add -A +git commit -m "feat(inputnumber): компонент InputNumber готов" +``` + +--- + +## Self-Review + +**Spec coverage:** +- ✅ Компонент с CVA — Task 2 +- ✅ Все 18 пропсов — Task 2 (InputNumberComponent) +- ✅ Size mapping (primeSize + p-inputnumber-xlg) — Task 2 +- ✅ Tabler Icons через `incrementButtonIcon` / `decrementButtonIcon` — Task 2 +- ✅ CSS overrides (border, height, disabled, xlarge) — Task 1 +- ✅ map-tokens регистрация — Task 1 +- ✅ Default story с динамическим template — Task 6 +- ✅ FloatLabel story (нативный p-inputnumber) — Task 3 +- ✅ Currency story — Task 4 +- ✅ MinMax story — Task 5 +- ✅ modelValue / primeSize / sizeClass скрыты в argTypes — Task 6 + +**Placeholder scan:** нет TBD/TODO. + +**Type consistency:** `InputNumberSize`, `InputNumberButtonLayout`, `InputNumberMode` определены в Task 2 и используются в `argTypes` Task 6 через строковые литералы — согласованы. diff --git a/docs/superpowers/specs/2026-04-16-inputnumber-design.md b/docs/superpowers/specs/2026-04-16-inputnumber-design.md new file mode 100644 index 00000000..a0331966 --- /dev/null +++ b/docs/superpowers/specs/2026-04-16-inputnumber-design.md @@ -0,0 +1,163 @@ +# InputNumber Component — Design Spec + +**Date:** 2026-04-16 +**Branch:** `form.inputnumber` (to be created) +**Reference:** [Vue InputNumber](https://github.com/cdek-it/vue-ui-kit/tree/form.InputNumber/src/plugins/prime/stories/Form/InputNumber) + +--- + +## Overview + +Angular wrapper component for PrimeNG `InputNumber`, following the same patterns as `InputTextComponent`. Provides a styled numeric input with optional increment/decrement buttons, currency formatting, and min/max/step constraints. Integrates with Angular Forms via `ControlValueAccessor`. + +--- + +## File Structure + +``` +src/lib/components/inputnumber/ + inputnumber.component.ts + +src/prime-preset/tokens/components/ + inputnumber.ts ← new CSS override file + +src/prime-preset/ + map-tokens.ts ← add inputnumber CSS + +src/stories/components/inputnumber/ + inputnumber.stories.ts + examples/ + inputnumber-float-label.component.ts + inputnumber-currency.component.ts + inputnumber-minmax.component.ts +``` + +--- + +## Component API (`InputNumberComponent`) + +**Selector:** `input-number` +**Standalone:** yes +**CVA value type:** `number | null` + +### Inputs + +| Prop | Type | Default | Description | +|---|---|---|---| +| `size` | `'small' \| 'base' \| 'large' \| 'xlarge'` | `'base'` | Размер поля | +| `placeholder` | `string` | `''` | Подсказка при пустом поле | +| `disabled` | `boolean` | `false` | Отключает взаимодействие | +| `readonly` | `boolean` | `false` | Только для чтения | +| `invalid` | `boolean` | `false` | Невалидное состояние | +| `showButtons` | `boolean` | `true` | Показывать кнопки +/− | +| `buttonLayout` | `'horizontal' \| 'vertical' \| 'stacked'` | `'horizontal'` | Расположение кнопок | +| `mode` | `'decimal' \| 'currency'` | `'decimal'` | Режим отображения | +| `currency` | `string` | `'RUB'` | Код валюты (ISO 4217) при `mode="currency"` | +| `locale` | `string` | `'ru-RU'` | Локаль форматирования | +| `prefix` | `string \| undefined` | `undefined` | Префикс перед значением | +| `suffix` | `string \| undefined` | `undefined` | Суффикс после значения | +| `min` | `number \| undefined` | `undefined` | Минимальное значение | +| `max` | `number \| undefined` | `undefined` | Максимальное значение | +| `step` | `number` | `1` | Шаг изменения | +| `minFractionDigits` | `number` | `0` | Мин. знаков после запятой | +| `maxFractionDigits` | `number` | `20` | Макс. знаков после запятой | +| `fluid` | `boolean` | `false` | Растягивает на всю ширину | + +### Size mapping + +| `size` | `pSize` (PrimeNG) | CSS class | +|---|---|---| +| `'small'` | `'small'` | — | +| `'base'` | `undefined` | — | +| `'large'` | `'large'` | — | +| `'xlarge'` | `'large'` | `p-inputnumber-xlg` (on host) | + +The `p-inputnumber-xlg` class is applied via `[ngClass]` on the `p-inputnumber` element so CSS cascade can target `.p-inputnumber-xlg .p-inputnumber-input`. + +### Icons + +Increment button: `` via `#incrementicon` ng-template. +Decrement button: `` via `#decrementicon` ng-template. + +### CVA + +- `writeValue(v: number | null)` — stores to `modelValue` +- `registerOnChange` / `registerOnTouched` — standard +- `setDisabledState` — sets `disabled` +- `onValueChange(v: number | null)` — called on PrimeNG `(onInput)` event, calls `_onChange` + +--- + +## CSS Overrides (`src/prime-preset/tokens/components/inputnumber.ts`) + +```typescript +export const inputnumberCss = ({ dt }) => ` + .p-inputnumber-button { + border-width: ${dt('inputnumber.extend.borderWidth')}; + } + + .p-inputnumber-horizontal .p-inputnumber-button { + min-height: ${dt('inputnumber.extend.extButton.height')}; + } + + .p-inputnumber-horizontal:has(.p-inputnumber-input:disabled) .p-inputnumber-button { + background: ${dt('inputtext.root.disabledBackground')}; + color: ${dt('inputtext.root.disabledColor')}; + } + + .p-inputnumber.p-inputnumber-xlg .p-inputnumber-input { + font-size: ${dt('inputtext.extend.extXlg.fontSize')}; + padding: ${dt('inputtext.extend.extXlg.paddingY')} ${dt('inputtext.extend.extXlg.paddingX')}; + } +`; +``` + +--- + +## map-tokens.ts + +Add import and entry: + +```typescript +import { inputnumberCss } from './tokens/components/inputnumber'; + +// in components: +inputnumber: { + ...(tokens.components.inputnumber as unknown as ComponentsDesignTokens['inputnumber']), + css: inputnumberCss, +}, +``` + +--- + +## Stories + +### `inputnumber.stories.ts` + +- `meta`: `title: 'Components/Form/InputNumber'`, `component: InputNumberComponent`, `tags: ['autodocs']` +- `argTypes`: all props from API table above +- `args`: defaults from API table +- `Default` story: dynamic template built from args (same pattern as InputText Default) +- Re-exports: `FloatLabel`, `Currency`, `MinMax` + +### `examples/inputnumber-float-label.component.ts` + +Uses native `p-inputnumber` (not the wrapper) as direct child of `p-floatlabel variant="in"`, because PrimeNG FloatLabel CSS relies on sibling selectors that don't work through wrapper components. Shows `showButtons`, `buttonLayout="horizontal"`, Tabler icon templates. `controls: { disable: true }`. + +### `examples/inputnumber-currency.component.ts` + +Pure `StoryObj` (no `@Component`), `render: (args) => ({ props: { ...args, value: null }, template })`. Args preset: `mode: 'currency'`, `currency: 'RUB'`, `locale: 'ru-RU'`. All other props bound through Controls. + +### `examples/inputnumber-minmax.component.ts` + +Pure `StoryObj`. Args preset: `min: 0`, `max: 100`, `step: 1`. Shows constraint behaviour. + +--- + +## Constraints + +- No `styles: [...]` in Angular `@Component` decorator — use `const styles = ''` (webpack base64 path bug) +- Storybook story layout: Tailwind classes only, no inline `style="..."` +- Float label: always use native `p-inputnumber` directly — never the wrapper component — inside `p-floatlabel` +- Default story must build template dynamically from args so the code snippet updates with Controls +- `source.code` in float-label example should not include the outer `
` wrapper diff --git a/package-lock.json b/package-lock.json index 9f93e08e..5fe8bcf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "@primeng/themes": "20.4.0", "@storybook/addon-a11y": "10.1.8", "@storybook/addon-docs": "10.1.8", + "@storybook/addon-themes": "^10.1.8", "@storybook/angular": "10.1.8", "@tabler/icons-webfont": "3.35.0", "@types/jasmine": "5.1.13", @@ -50,6 +51,7 @@ "karma-coverage": "2.2.1", "karma-jasmine": "5.1.0", "karma-jasmine-html-reporter": "2.1.0", + "ng-packagr": "20.3.2", "prettier": "3.7.4", "primeng": "20.4.0", "rxjs": "7.8.2", @@ -1121,13 +1123,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -6183,6 +6185,57 @@ "node": ">=12.11.0" } }, + "node_modules/@rollup/plugin-json": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", + "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.52.3", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", @@ -6491,6 +6544,26 @@ "win32" ] }, + "node_modules/@rollup/wasm-node": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/wasm-node/-/wasm-node-4.60.2.tgz", + "integrity": "sha512-FOfZOg752WSyKNefpSM3WrhggSTSuKuwcSfF7tdWC9PBYYg7BLwBR267uShFAI1ZyA0gNkdqK16LL9mNOPsQ1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -6656,6 +6729,23 @@ "storybook": "^10.1.8" } }, + "node_modules/@storybook/addon-themes": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-themes/-/addon-themes-10.1.8.tgz", + "integrity": "sha512-UqaajGe4F18Ne4Z5JUqq+OBS8CqgqvVja7J4ipRjvwP6Myh4/GZP7SXKcA17jrY7xWy4qnVdCSsEthviVMMLkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^10.1.8" + } + }, "node_modules/@storybook/angular": { "version": "10.1.8", "resolved": "https://registry.npmjs.org/@storybook/angular/-/angular-10.1.8.tgz", @@ -10073,6 +10163,13 @@ "node": ">= 12.0.0" } }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true, + "license": "ISC" + }, "node_modules/component-emitter": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-2.0.0.tgz", @@ -11247,6 +11344,16 @@ "node": ">= 0.8" } }, + "node_modules/dependency-graph": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -13109,6 +13216,23 @@ "node": ">= 0.6" } }, + "node_modules/find-cache-directory": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/find-cache-directory/-/find-cache-directory-6.0.0.tgz", + "integrity": "sha512-CvFd5ivA6HcSHbD+59P7CyzINHXzwhuQK8RY7CxJZtgDSAtRlHiCaQpZQ2lMR/WRyUIEmzUvL6G2AGurMfegZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/find-index": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", @@ -13132,6 +13256,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -14622,6 +14759,16 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/injection-js": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/injection-js/-/injection-js-2.6.1.tgz", + "integrity": "sha512-dbR5bdhi7TWDoCye9cByZqeg/gAfamm8Vu3G1KZOTYkOif8WkuM8CD0oeDPtZYMzT5YH76JAFB7bkmyY9OJi2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -17629,6 +17776,56 @@ "node": ">= 10" } }, + "node_modules/ng-packagr": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-20.3.2.tgz", + "integrity": "sha512-yW5ME0hqTz38r/th/7zVwX5oSIw1FviSA2PUlGZdVjghDme/KX6iiwmOBmlt9E9whNmwijEC6Gn3KKbrsBx8ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@rollup/plugin-json": "^6.1.0", + "@rollup/wasm-node": "^4.24.0", + "ajv": "^8.17.1", + "ansi-colors": "^4.1.3", + "browserslist": "^4.22.1", + "chokidar": "^4.0.1", + "commander": "^14.0.0", + "dependency-graph": "^1.0.0", + "esbuild": "^0.25.0", + "find-cache-directory": "^6.0.0", + "injection-js": "^2.4.0", + "jsonc-parser": "^3.3.1", + "less": "^4.2.0", + "ora": "^8.2.0", + "piscina": "^5.0.0", + "postcss": "^8.4.47", + "rollup-plugin-dts": "^6.2.0", + "rxjs": "^7.8.1", + "sass": "^1.81.0", + "tinyglobby": "^0.2.12" + }, + "bin": { + "ng-packagr": "src/cli/main.js" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "optionalDependencies": { + "rollup": "^4.24.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^20.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "tslib": "^2.3.0", + "typescript": ">=5.8 <6.0" + }, + "peerDependenciesMeta": { + "tailwindcss": { + "optional": true + } + } + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -19083,6 +19280,22 @@ "node": ">=16.20.0" } }, + "node_modules/pkg-dir": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-8.0.0.tgz", + "integrity": "sha512-4peoBq4Wks0riS0z8741NVv+/8IiTvqnZAr8QGgtdifrtpdXbNw/FxRS1l6NFqm4EMzuS0EDqNNx4XGaz8cuyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/polka": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/polka/-/polka-0.5.2.tgz", @@ -20377,6 +20590,49 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-dts": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.4.1.tgz", + "integrity": "sha512-l//F3Zf7ID5GoOfLfD8kroBjQKEKpy1qfhtAdnpibFZMffPaylrg1CoDC2vGkPeTeyxUe4bVFCln2EFuL7IGGg==", + "dev": true, + "license": "LGPL-3.0-only", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "@jridgewell/sourcemap-codec": "^1.5.5", + "convert-source-map": "^2.0.0", + "magic-string": "^0.30.21" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/Swatinem" + }, + "optionalDependencies": { + "@babel/code-frame": "^7.29.0" + }, + "peerDependencies": { + "rollup": "^3.29.4 || ^4", + "typescript": "^4.5 || ^5.0 || ^6.0" + } + }, + "node_modules/rollup-plugin-dts/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup-plugin-dts/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", diff --git a/package.json b/package.json index b4ef8b1f..8fa837c7 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,9 @@ "format:check": "prettier --check \"**/*.{js,ts,d.ts}\"", "build:check": "npm run format:check && npm run security:check", "build:storybook": "ng run angular-ui-kit:build-storybook", + "build:lib": "ng build angular-ui-kit-lib", "security:check": "npm audit --production --audit-level high", - "copy:dist": "cpx 'src/prime-preset/**/*.{json,ts}' dist/ && cpx 'src-tokens/theme.preset.ts' dist/" + "copy:dist": "cpx 'src/lib/providers/prime-preset/**/*.{json,ts}' dist/ && cpx 'src-tokens/theme.preset.ts' dist/" }, "repository": "github:cdek-it/angular-ui-kit", "devDependencies": { @@ -46,6 +47,7 @@ "@primeng/themes": "20.4.0", "@storybook/addon-a11y": "10.1.8", "@storybook/addon-docs": "10.1.8", + "@storybook/addon-themes": "^10.1.8", "@storybook/angular": "10.1.8", "@tabler/icons-webfont": "3.35.0", "@types/jasmine": "5.1.13", @@ -62,6 +64,8 @@ "karma-chrome-launcher": "3.2.0", "karma-coverage": "2.2.1", "karma-jasmine": "5.1.0", + "ng-packagr": "20.3.2", + "@primeuix/themes": "1.2.5", "karma-jasmine-html-reporter": "2.1.0", "prettier": "3.7.4", "primeng": "20.4.0", diff --git a/public/assets/images/avatar/avatar.png b/public/assets/images/avatar/avatar.png new file mode 100644 index 00000000..7c0bd535 Binary files /dev/null and b/public/assets/images/avatar/avatar.png differ diff --git a/public/assets/mascot.jpg b/public/assets/mascot.jpg new file mode 100644 index 00000000..adb02e35 Binary files /dev/null and b/public/assets/mascot.jpg differ diff --git a/scripts/prepare-theme.js b/scripts/prepare-theme.js new file mode 100644 index 00000000..e69de29b diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 6bdbf574..95ad618d 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -3,7 +3,7 @@ import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; import { providePrimeNG } from 'primeng/config'; -import Preset from '../prime-preset/theme.preset'; +import Preset from '../lib/providers/prime-preset/theme.preset'; export const appConfig: ApplicationConfig = { providers: [ diff --git a/src/app/provide-my-feature.ts b/src/app/provide-my-feature.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/assets/fonts/tt-fellows/TT_Fellows_DemiBold.woff2 b/src/assets/fonts/tt-fellows/TT_Fellows_DemiBold.woff2 new file mode 100644 index 00000000..ec670152 Binary files /dev/null and b/src/assets/fonts/tt-fellows/TT_Fellows_DemiBold.woff2 differ diff --git a/src/assets/fonts/tt-fellows/TT_Fellows_Regular.woff2 b/src/assets/fonts/tt-fellows/TT_Fellows_Regular.woff2 new file mode 100644 index 00000000..c4c67360 Binary files /dev/null and b/src/assets/fonts/tt-fellows/TT_Fellows_Regular.woff2 differ diff --git a/src/lib/components/avatar/avatar.component.ts b/src/lib/components/avatar/avatar.component.ts new file mode 100644 index 00000000..6abed91d --- /dev/null +++ b/src/lib/components/avatar/avatar.component.ts @@ -0,0 +1,51 @@ +import { Component, HostBinding, Input } from '@angular/core'; +import { Avatar } from 'primeng/avatar'; +import { AvatarGroup } from 'primeng/avatargroup'; + +export type AvatarSize = 'normal' | 'large' | 'xlarge'; +export type AvatarShape = 'square' | 'circle'; + +@Component({ + selector: 'extra-avatar', + standalone: true, + imports: [Avatar], + template: ` + + `, +}) +export class ExtraAvatarComponent { + @Input() label = ''; + @Input() icon = ''; + @Input() image = ''; + @Input() size: AvatarSize = 'normal'; + @Input() shape: AvatarShape = 'square'; + + @HostBinding('class') get hostClass(): string { + const classes = ['ui-avatar']; + if (this.size === 'large') classes.push('ui-avatar-lg'); + if (this.size === 'xlarge') classes.push('ui-avatar-xl'); + return classes.join(' '); + } + + get primeSize(): 'normal' | 'large' | 'xlarge' | undefined { + return this.size === 'normal' ? undefined : this.size; + } +} + +@Component({ + selector: 'extra-avatar-group', + standalone: true, + imports: [AvatarGroup], + template: ` + + + + `, +}) +export class ExtraAvatarGroupComponent { } diff --git a/src/lib/components/avatar/ng-package.json b/src/lib/components/avatar/ng-package.json new file mode 100644 index 00000000..3a74fd76 --- /dev/null +++ b/src/lib/components/avatar/ng-package.json @@ -0,0 +1,6 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} diff --git a/src/lib/components/avatar/public_api.ts b/src/lib/components/avatar/public_api.ts new file mode 100644 index 00000000..a1ddb6ff --- /dev/null +++ b/src/lib/components/avatar/public_api.ts @@ -0,0 +1 @@ +export * from './avatar.component'; diff --git a/src/lib/components/badge/badge.component.ts b/src/lib/components/badge/badge.component.ts new file mode 100644 index 00000000..2f6925d6 --- /dev/null +++ b/src/lib/components/badge/badge.component.ts @@ -0,0 +1,37 @@ +import { Component, Input } from '@angular/core'; +import { Badge } from 'primeng/badge'; + +export type BadgeSeverity = 'primary' | 'success' | 'info' | 'warning' | 'danger'; +export type BadgeSize = 'base' | 'large' | 'xlarge'; + +type PrimeBadgeSeverity = ReturnType; +type PrimeBadgeSize = ReturnType; + +@Component({ + selector: 'extra-badge', + standalone: true, + imports: [Badge], + template: ` + + ` +}) +export class ExtraBadgeComponent { + @Input() value: string | number = ''; + @Input() severity: BadgeSeverity = 'primary'; + @Input() size: BadgeSize = 'base'; + + get primeSeverity(): PrimeBadgeSeverity { + if (this.severity === 'primary') return null; + if (this.severity === 'warning') return 'warn'; + return this.severity as Exclude; + } + + get primeSize(): PrimeBadgeSize { + if (this.size === 'base') return null; + return this.size as PrimeBadgeSize; + } +} diff --git a/src/lib/components/badge/ng-package.json b/src/lib/components/badge/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/badge/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/badge/public_api.ts b/src/lib/components/badge/public_api.ts new file mode 100644 index 00000000..5f6ed3fe --- /dev/null +++ b/src/lib/components/badge/public_api.ts @@ -0,0 +1,3 @@ +export * from './badge.component'; + + diff --git a/src/lib/components/breadcrumb/breadcrumb.component.ts b/src/lib/components/breadcrumb/breadcrumb.component.ts new file mode 100644 index 00000000..8b242e8e --- /dev/null +++ b/src/lib/components/breadcrumb/breadcrumb.component.ts @@ -0,0 +1,19 @@ +import { Component, Input } from '@angular/core'; +import { Breadcrumb } from 'primeng/breadcrumb'; +import { MenuItem } from 'primeng/api'; + +@Component({ + selector: 'extra-breadcrumb', + standalone: true, + imports: [Breadcrumb], + template: ` + + `, +}) +export class ExtraBreadcrumbComponent { + @Input() model: MenuItem[] = []; + @Input() home: MenuItem | undefined = undefined; +} diff --git a/src/lib/components/breadcrumb/ng-package.json b/src/lib/components/breadcrumb/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/breadcrumb/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/breadcrumb/public_api.ts b/src/lib/components/breadcrumb/public_api.ts new file mode 100644 index 00000000..2def9761 --- /dev/null +++ b/src/lib/components/breadcrumb/public_api.ts @@ -0,0 +1,4 @@ +export * from './breadcrumb.component'; + + + diff --git a/src/lib/components/button/button.component.ts b/src/lib/components/button/button.component.ts new file mode 100644 index 00000000..47697892 --- /dev/null +++ b/src/lib/components/button/button.component.ts @@ -0,0 +1,78 @@ +import { Component, Input } from '@angular/core'; +import { Button, ButtonSeverity as PrimeButtonSeverity } from 'primeng/button'; + +export type ButtonVariant = 'primary' | 'secondary' | 'outlined' | 'text' | 'link'; +export type ButtonSeverity = 'success' | 'warning' | 'danger' | 'info' | null; +export type ButtonSize = 'small' | 'base' | 'large' | 'xlarge'; +export type ButtonIconPos = 'prefix' | 'postfix' | null; +export type BadgeSeverity = 'success' | 'info' | 'warning' | 'danger' | 'secondary' | 'contrast' | null; +type PrimeBadgeSeverity = Extract; + +@Component({ + selector: 'extra-button', + standalone: true, + imports: [Button], + template: ` + + ` +}) +export class ExtraButtonComponent { + @Input() label = 'Button'; + @Input() variant: ButtonVariant = 'primary'; + @Input() severity: ButtonSeverity = null; + @Input() size: ButtonSize = 'base'; + @Input() rounded = false; + @Input() iconPos: ButtonIconPos = null; + @Input() iconOnly = false; + @Input() icon = ''; + @Input() disabled = false; + @Input() loading = false; + @Input() badge = ''; + @Input() badgeSeverity: BadgeSeverity = null; + @Input() showBadge = false; + @Input() fluid = false; + @Input() ariaLabel: string | undefined = undefined; + @Input() autofocus = false; + @Input() tabindex: number | undefined = undefined; + @Input() text = false; + + get primeSize(): 'small' | 'large' | undefined { + if (this.size === 'small') return 'small'; + if (this.size === 'large') return 'large'; + return undefined; + } + + get primeIconPos(): 'left' | 'right' { + return this.iconPos === 'postfix' ? 'right' : 'left'; + } + + get primeSeverity(): PrimeButtonSeverity | null { + if (this.variant === 'secondary') return 'secondary'; + if (this.severity === 'warning') return 'warn'; + return this.severity; + } + + get primeBadgeSeverity(): PrimeBadgeSeverity { + if (this.badgeSeverity === 'warning') return 'warn'; + return this.badgeSeverity; + } +} diff --git a/src/lib/components/button/ng-package.json b/src/lib/components/button/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/button/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/button/public_api.ts b/src/lib/components/button/public_api.ts new file mode 100644 index 00000000..978e7b6f --- /dev/null +++ b/src/lib/components/button/public_api.ts @@ -0,0 +1,4 @@ +export * from './button.component'; + + + diff --git a/src/lib/components/card/card.component.ts b/src/lib/components/card/card.component.ts new file mode 100644 index 00000000..0979a4b2 --- /dev/null +++ b/src/lib/components/card/card.component.ts @@ -0,0 +1,73 @@ +import { + AfterContentInit, + ChangeDetectorRef, + Component, + ContentChildren, + Input, + QueryList, +} from '@angular/core'; +import { NgTemplateOutlet } from '@angular/common'; +import { Card } from 'primeng/card'; +import { PrimeTemplate, SharedModule } from 'primeng/api'; + +@Component({ + selector: 'extra-card', + host: { style: 'display: block' }, + standalone: true, + imports: [Card, SharedModule, NgTemplateOutlet], + template: ` + + @if (headerTpl) { + + + + } + @if (title || subtitle) { + +
+ @if (title) { +
{{ title }}
+ } + @if (subtitle) { +
{{ subtitle }}
+ } +
+
+ } + @if (contentTpl) { + + + + } + @if (footerTpl) { + + + + } +
+ `, +}) +export class ExtraCardComponent implements AfterContentInit { + @Input() title = ''; + @Input() subtitle = ''; + @Input() overlay = false; + + @ContentChildren(PrimeTemplate) templates!: QueryList; + + headerTpl?: PrimeTemplate; + contentTpl?: PrimeTemplate; + footerTpl?: PrimeTemplate; + + constructor(private cdr: ChangeDetectorRef) {} + + ngAfterContentInit(): void { + this.templates.forEach(tpl => { + switch (tpl.getType()) { + case 'header': this.headerTpl = tpl; break; + case 'content': this.contentTpl = tpl; break; + case 'footer': this.footerTpl = tpl; break; + } + }); + this.cdr.detectChanges(); + } +} diff --git a/src/lib/components/card/ng-package.json b/src/lib/components/card/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/card/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/card/public_api.ts b/src/lib/components/card/public_api.ts new file mode 100644 index 00000000..af94fd1f --- /dev/null +++ b/src/lib/components/card/public_api.ts @@ -0,0 +1,4 @@ +export * from './card.component'; + + + diff --git a/src/lib/components/checkbox/checkbox.component.ts b/src/lib/components/checkbox/checkbox.component.ts new file mode 100644 index 00000000..52dd219c --- /dev/null +++ b/src/lib/components/checkbox/checkbox.component.ts @@ -0,0 +1,106 @@ +import { Component, Input, Output, EventEmitter, forwardRef } from '@angular/core'; +import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { Checkbox, CheckboxChangeEvent } from 'primeng/checkbox'; + +export type CheckboxSize = 'small' | 'base' | 'large'; +export type CheckboxVariant = 'outlined' | 'filled'; + +@Component({ + selector: 'extra-checkbox', + standalone: true, + imports: [Checkbox, FormsModule], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ExtraCheckboxComponent), + multi: true + } + ], + template: ` + + ` +}) +export class ExtraCheckboxComponent implements ControlValueAccessor { + @Input() value: any = null; + @Input() binary = false; + @Input() disabled = false; + @Input() readonly = false; + @Input() indeterminate = false; + @Input() invalid = false; + @Input() size: CheckboxSize = 'base'; + @Input() variant: CheckboxVariant = 'outlined'; + @Input() checkboxIcon: string | undefined = undefined; + @Input() ariaLabel: string | undefined = undefined; + @Input() ariaLabelledBy: string | undefined = undefined; + @Input() tabindex: number | undefined = undefined; + @Input() inputId: string | undefined = undefined; + @Input() trueValue: any = true; + @Input() falseValue: any = false; + @Input() autofocus = false; + + @Output() onChange = new EventEmitter(); + @Output() onFocus = new EventEmitter(); + @Output() onBlur = new EventEmitter(); + + modelValue: any = false; + + private _onChange: (value: any) => void = () => {}; + private _onTouched: () => void = () => {}; + + // Геттеры — маппинг в PrimeNG API + get primeSize(): 'small' | 'large' | undefined { + if (this.size === 'small') return 'small'; + if (this.size === 'large') return 'large'; + return undefined; + } + + get primeVariant(): 'filled' | 'outlined' | undefined { + if (this.variant === 'filled') return 'filled'; + return undefined; + } + + onChangeHandler(event: CheckboxChangeEvent): void { + this._onChange(event.checked); + this._onTouched(); + this.onChange.emit(event); + } + + // ControlValueAccessor + writeValue(value: any): void { + this.modelValue = value; + } + + registerOnChange(fn: (value: any) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this._onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } +} diff --git a/src/lib/components/checkbox/ng-package.json b/src/lib/components/checkbox/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/checkbox/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/checkbox/public_api.ts b/src/lib/components/checkbox/public_api.ts new file mode 100644 index 00000000..ed763e4a --- /dev/null +++ b/src/lib/components/checkbox/public_api.ts @@ -0,0 +1,4 @@ +export * from './checkbox.component'; + + + diff --git a/src/lib/components/chip/chip.component.ts b/src/lib/components/chip/chip.component.ts new file mode 100644 index 00000000..5c0ed938 --- /dev/null +++ b/src/lib/components/chip/chip.component.ts @@ -0,0 +1,24 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Chip } from 'primeng/chip'; + +@Component({ + selector: 'extra-chip', + standalone: true, + imports: [Chip], + template: ` + + `, +}) +export class ExtraChipComponent { + @Input() label = ''; + @Input() icon = ''; + @Input() removable = false; + @Input() disabled = false; + @Output() onRemove = new EventEmitter(); +} diff --git a/src/lib/components/chip/ng-package.json b/src/lib/components/chip/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/chip/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/chip/public_api.ts b/src/lib/components/chip/public_api.ts new file mode 100644 index 00000000..c4ac1a5b --- /dev/null +++ b/src/lib/components/chip/public_api.ts @@ -0,0 +1,4 @@ +export * from './chip.component'; + + + diff --git a/src/lib/components/dialog/dialog-open.service.ts b/src/lib/components/dialog/dialog-open.service.ts new file mode 100644 index 00000000..3e36bc0d --- /dev/null +++ b/src/lib/components/dialog/dialog-open.service.ts @@ -0,0 +1,43 @@ +import { Injectable, Injector, Type } from '@angular/core'; +import { DialogService, DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { DialogSize } from './dialog.component'; + +export type ExtraDynamicDialogConfig = Omit, 'styleClass'> & { + size?: DialogSize; + styleClass?: string; +}; + +export { DynamicDialogRef, DynamicDialogConfig }; + +@Injectable({ providedIn: 'root' }) +export class ExtraDialogService { + + constructor(private readonly injector: Injector) { + } + + open(componentType: Type, config: ExtraDynamicDialogConfig = {}): DynamicDialogRef | null { + const { size, styleClass, ...rest } = config; + const sizeClass = this.toSizeClass(size); + const mergedStyleClass = [sizeClass, styleClass].filter(Boolean).join(' '); + + const childInjector = Injector.create({ + providers: [DialogService], + parent: this.injector, + }); + + const dialogService = childInjector.get(DialogService); + + return dialogService.open(componentType, { + ...rest, + ...(mergedStyleClass && { styleClass: mergedStyleClass }), + appendTo: rest.appendTo ?? 'body', + }); + } + + private toSizeClass(size?: DialogSize): string { + if (size === 'sm') return 'p-dialog-sm'; + if (size === 'lg') return 'p-dialog-lg'; + if (size === 'xlg') return 'p-dialog-xlg'; + return ''; + } +} diff --git a/src/lib/components/dialog/dialog.component.ts b/src/lib/components/dialog/dialog.component.ts new file mode 100644 index 00000000..17e498b9 --- /dev/null +++ b/src/lib/components/dialog/dialog.component.ts @@ -0,0 +1,60 @@ +import { Component, EventEmitter, Input, Output, TemplateRef } from '@angular/core'; +import { NgTemplateOutlet } from '@angular/common'; +import { Dialog } from 'primeng/dialog'; +import { PrimeTemplate } from 'primeng/api'; + +export type DialogSize = 'sm' | 'default' | 'lg' | 'xlg'; + +@Component({ + selector: 'extra-dialog', + host: { style: 'display: contents' }, + standalone: true, + imports: [Dialog, NgTemplateOutlet, PrimeTemplate], + template: ` + + @if (headerTemplate) { + + + + } + + @if (footerTemplate) { + + + + } + + `, +}) +export class ExtraDialogComponent { + @Input() header = ''; + @Input() visible = false; + @Input() modal = true; + @Input() size: DialogSize = 'default'; + @Input() dismissableMask = false; + @Input() closeOnEscape = true; + @Input() showHeader = true; + @Input() focusOnShow = false; + @Input() appendTo: string = 'body'; + @Input() headerTemplate: TemplateRef | null = null; + @Input() footerTemplate: TemplateRef | null = null; + @Output() visibleChange = new EventEmitter(); + + get sizeClass(): string { + if (this.size === 'sm') return 'p-dialog-sm'; + if (this.size === 'lg') return 'p-dialog-lg'; + if (this.size === 'xlg') return 'p-dialog-xlg'; + return ''; + } +} diff --git a/src/lib/components/dialog/ng-package.json b/src/lib/components/dialog/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/dialog/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/dialog/public_api.ts b/src/lib/components/dialog/public_api.ts new file mode 100644 index 00000000..49057693 --- /dev/null +++ b/src/lib/components/dialog/public_api.ts @@ -0,0 +1,5 @@ +export * from './dialog.component'; +export * from './dialog-open.service'; + + + diff --git a/src/lib/components/divider/divider.component.ts b/src/lib/components/divider/divider.component.ts new file mode 100644 index 00000000..28420442 --- /dev/null +++ b/src/lib/components/divider/divider.component.ts @@ -0,0 +1,26 @@ +import { Component, Input } from '@angular/core'; +import { Divider } from 'primeng/divider'; + +export type DividerLayout = 'horizontal' | 'vertical'; +export type DividerType = 'solid' | 'dashed' | 'dotted'; +export type DividerAlign = 'left' | 'center' | 'right' | 'top' | 'bottom'; + +@Component({ + selector: 'extra-divider', + standalone: true, + imports: [Divider], + template: ` + + + + `, +}) +export class ExtraDividerComponent { + @Input() layout: DividerLayout = 'horizontal'; + @Input() type: DividerType = 'solid'; + @Input() align: DividerAlign = 'center'; +} diff --git a/src/lib/components/divider/ng-package.json b/src/lib/components/divider/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/divider/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/divider/public_api.ts b/src/lib/components/divider/public_api.ts new file mode 100644 index 00000000..3e85d710 --- /dev/null +++ b/src/lib/components/divider/public_api.ts @@ -0,0 +1,4 @@ +export * from './divider.component'; + + + diff --git a/src/lib/components/inputgroup/input-group-addon.component.ts b/src/lib/components/inputgroup/input-group-addon.component.ts new file mode 100644 index 00000000..d547bdae --- /dev/null +++ b/src/lib/components/inputgroup/input-group-addon.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { InputGroupAddon } from 'primeng/inputgroupaddon'; + +@Component({ + selector: 'extra-input-group-addon', + standalone: true, + imports: [InputGroupAddon], + template: ` + + + + `, +}) +export class ExtraInputGroupAddonComponent {} diff --git a/src/lib/components/inputgroup/input-group.component.ts b/src/lib/components/inputgroup/input-group.component.ts new file mode 100644 index 00000000..80e129a9 --- /dev/null +++ b/src/lib/components/inputgroup/input-group.component.ts @@ -0,0 +1,24 @@ +import { Component, Input } from '@angular/core'; +import { NgClass } from '@angular/common'; +import { InputGroup } from 'primeng/inputgroup'; + +export type InputGroupSize = 'small' | 'base' | 'large' | 'xlarge'; + +@Component({ + selector: 'extra-input-group', + standalone: true, + imports: [InputGroup, NgClass], + template: ` + + + + `, +}) +export class ExtraInputGroupComponent { + @Input() size: InputGroupSize = 'base'; + + get sizeClass(): string { + if (this.size === 'xlarge') return 'p-inputgroup-xlg'; + return ''; + } +} diff --git a/src/lib/components/inputgroup/ng-package.json b/src/lib/components/inputgroup/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/inputgroup/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/inputgroup/public_api.ts b/src/lib/components/inputgroup/public_api.ts new file mode 100644 index 00000000..613429b8 --- /dev/null +++ b/src/lib/components/inputgroup/public_api.ts @@ -0,0 +1,3 @@ +export * from './input-group.component'; +export * from './input-group-addon.component'; + diff --git a/src/lib/components/inputmask/inputmask.component.ts b/src/lib/components/inputmask/inputmask.component.ts new file mode 100644 index 00000000..59e19c22 --- /dev/null +++ b/src/lib/components/inputmask/inputmask.component.ts @@ -0,0 +1,107 @@ +import { ChangeDetectionStrategy, Component, DestroyRef, inject, Injector, Input, OnInit, Output, EventEmitter } from '@angular/core'; +import { ControlValueAccessor, FormControl, NgControl, ReactiveFormsModule } from '@angular/forms'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { InputMask } from 'primeng/inputmask'; + +export type InputMaskSize = 'small' | 'base' | 'large' | 'xlarge'; + + +@Component({ + selector: 'extra-input-mask', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [InputMask, ReactiveFormsModule], + host: { + style: 'display: block', + '[class.input-mask-xlg]': 'size === "xlarge"', + }, + template: ` + + `, +}) +export class ExtraInputMaskComponent implements ControlValueAccessor, OnInit { + private readonly _injector = inject(Injector); + private readonly destroyRef = inject(DestroyRef); + private _ngControl: NgControl | null = null; + + readonly control = new FormControl(null); + + @Input() mask = ''; + @Input() slotChar = '_'; + @Input() autoClear = true; + @Input() showClear = false; + @Input() unmask = false; + @Input() placeholder = ''; + @Input() size: InputMaskSize = 'base'; + @Input() readonly = false; + @Input() fluid = false; + @Input() characterPattern = '[A-Za-z]'; + @Input() keepBuffer = false; + @Input() autocomplete = ''; + + @Output() onComplete = new EventEmitter(); + @Output() onFocusEvent = new EventEmitter(); + @Output() onBlurEvent = new EventEmitter(); + @Output() onInputEvent = new EventEmitter(); + @Output() onKeydownEvent = new EventEmitter(); + @Output() onClearEvent = new EventEmitter(); + + private _onChange: (value: string | null) => void = () => {}; + private _onTouched: () => void = () => {}; + + ngOnInit(): void { + this._ngControl = this._injector.get(NgControl, null, { self: true, optional: true }); + + this.control.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(v => this._onChange(v)); + } + + get invalid(): boolean { + return this._ngControl?.invalid ?? false; + } + + get primeSize(): 'small' | 'large' | undefined { + if (this.size === 'small') return 'small'; + if (this.size === 'large' || this.size === 'xlarge') return 'large'; + return undefined; + } + + writeValue(value: string | null): void { + this.control.setValue(value ?? null, { emitEvent: false }); + } + + registerOnChange(fn: (value: string | null) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this._onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + isDisabled ? this.control.disable({ emitEvent: false }) : this.control.enable({ emitEvent: false }); + } +} diff --git a/src/lib/components/inputmask/ng-package.json b/src/lib/components/inputmask/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/inputmask/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/inputmask/public_api.ts b/src/lib/components/inputmask/public_api.ts new file mode 100644 index 00000000..8a0a90ff --- /dev/null +++ b/src/lib/components/inputmask/public_api.ts @@ -0,0 +1,2 @@ +export * from './inputmask.component'; + diff --git a/src/lib/components/inputnumber/inputnumber.component.ts b/src/lib/components/inputnumber/inputnumber.component.ts new file mode 100644 index 00000000..4fd05a8c --- /dev/null +++ b/src/lib/components/inputnumber/inputnumber.component.ts @@ -0,0 +1,134 @@ +import { Component, Input, Output, EventEmitter, forwardRef, inject, Injector, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms'; +import { NgClass } from '@angular/common'; +import { InputNumber } from 'primeng/inputnumber'; +import { SharedModule } from 'primeng/api'; + +export type InputNumberSize = 'small' | 'base' | 'large' | 'xlarge'; +export type InputNumberButtonLayout = 'stacked' | 'horizontal' | 'vertical'; + +@Component({ + selector: 'extra-input-number', + standalone: true, + imports: [InputNumber, SharedModule, FormsModule, NgClass], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ExtraInputNumberComponent), + multi: true, + }, + ], + template: ` + + @if (!incrementButtonIcon) { + + + + } + @if (!decrementButtonIcon) { + + + + } + + `, +}) +export class ExtraInputNumberComponent implements ControlValueAccessor, OnInit { + private readonly _injector = inject(Injector); + private _ngControl: NgControl | null = null; + + ngOnInit(): void { + this._ngControl = this._injector.get(NgControl, null, { self: true, optional: true }); + } + + @Input() size: InputNumberSize = 'base'; + @Input() showButtons = false; + @Input() buttonLayout: InputNumberButtonLayout = 'stacked'; + @Input() mode = 'decimal'; + @Input() currency: string | undefined; + @Input() locale: string | undefined; + @Input() placeholder = ''; + @Input() readonly = false; + @Input() fluid = false; + @Input() min: number | undefined; + @Input() max: number | undefined; + @Input() step = 1; + @Input() prefix: string | undefined; + @Input() suffix: string | undefined; + @Input() minFractionDigits: number | undefined; + @Input() maxFractionDigits: number | undefined; + @Input() useGrouping = true; + @Input() incrementButtonIcon: string | undefined; + @Input() decrementButtonIcon: string | undefined; + + disabled = false; + + get invalid(): boolean { + return this._ngControl?.invalid ?? false; + } + + @Output() onInput = new EventEmitter<{ value: number | null }>(); + + modelValue: number | null = null; + + private _onChange: (value: number | null) => void = () => {}; + onTouched: () => void = () => {}; + + get inputSizeClass(): string { + if (this.size === 'small') return 'p-inputtext-sm'; + if (this.size === 'large' || this.size === 'xlarge') return 'p-inputtext-lg'; + return ''; + } + + get sizeClass(): Record { + return { 'p-inputnumber-xlg': this.size === 'xlarge' }; + } + + onModelChange(value: number | null): void { + this.modelValue = value; + this._onChange(value); + this.onInput.emit({ value }); + } + + writeValue(value: number | null): void { + this.modelValue = value ?? null; + } + + registerOnChange(fn: (value: number | null) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } +} diff --git a/src/lib/components/inputnumber/ng-package.json b/src/lib/components/inputnumber/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/inputnumber/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/inputnumber/public_api.ts b/src/lib/components/inputnumber/public_api.ts new file mode 100644 index 00000000..f3c2de28 --- /dev/null +++ b/src/lib/components/inputnumber/public_api.ts @@ -0,0 +1,2 @@ +export * from './inputnumber.component'; + diff --git a/src/lib/components/inputotp/inputotp.component.ts b/src/lib/components/inputotp/inputotp.component.ts new file mode 100644 index 00000000..41893730 --- /dev/null +++ b/src/lib/components/inputotp/inputotp.component.ts @@ -0,0 +1,108 @@ +import { Component, DestroyRef, forwardRef, inject, Injector, Input, OnInit, Output, EventEmitter } from '@angular/core'; +import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, NgControl, ReactiveFormsModule } from '@angular/forms'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { NgClass } from '@angular/common'; +import { InputOtp, InputOtpChangeEvent } from 'primeng/inputotp'; + +export type InputOtpSize = 'small' | 'base' | 'large' | 'xlarge'; + +@Component({ + selector: 'extra-input-otp', + standalone: true, + imports: [InputOtp, ReactiveFormsModule, NgClass], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ExtraInputOtpComponent), + multi: true, + }, + ], + template: ` + + `, +}) +export class ExtraInputOtpComponent implements ControlValueAccessor, OnInit { + private readonly _injector = inject(Injector); + private readonly destroyRef = inject(DestroyRef); + private _ngControl: NgControl | null = null; + + readonly control = new FormControl(null); + + @Input() length = 4; + @Input() mask = false; + @Input() integerOnly = false; + @Input() readonly = false; + @Input() size: InputOtpSize = 'base'; + @Input() tabindex: number | null = null; + @Input() autofocus = false; + + disabled = false; + + @Output() onChange = new EventEmitter(); + @Output() onFocus = new EventEmitter(); + @Output() onBlur = new EventEmitter(); + + private _onChange: (value: any) => void = () => {}; + private _onTouched: () => void = () => {}; + + ngOnInit(): void { + this._ngControl = this._injector.get(NgControl, null, { self: true, optional: true }); + + this.control.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(v => { + this._onChange(v); + this._onTouched(); + }); + } + + get invalid(): boolean { + return this._ngControl?.invalid ?? false; + } + + get primeSize(): 'small' | 'large' | undefined { + if (this.size === 'small') return 'small'; + if (this.size === 'large' || this.size === 'xlarge') return 'large'; + return undefined; + } + + get sizeClass(): Record { + return { 'p-inputotp-xlg': this.size === 'xlarge' }; + } + + onChangeHandler(event: InputOtpChangeEvent): void { + this.onChange.emit(event); + } + + writeValue(value: any): void { + this.control.setValue(value ?? null, { emitEvent: false }); + } + + registerOnChange(fn: (value: any) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this._onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + isDisabled ? this.control.disable({ emitEvent: false }) : this.control.enable({ emitEvent: false }); + } +} diff --git a/src/lib/components/inputotp/ng-package.json b/src/lib/components/inputotp/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/inputotp/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/inputotp/public_api.ts b/src/lib/components/inputotp/public_api.ts new file mode 100644 index 00000000..e0badf94 --- /dev/null +++ b/src/lib/components/inputotp/public_api.ts @@ -0,0 +1,2 @@ +export * from './inputotp.component'; + diff --git a/src/lib/components/inputtext/inputtext.component.ts b/src/lib/components/inputtext/inputtext.component.ts new file mode 100644 index 00000000..a4c32932 --- /dev/null +++ b/src/lib/components/inputtext/inputtext.component.ts @@ -0,0 +1,131 @@ +import { Component, Input, Output, EventEmitter, booleanAttribute, forwardRef, inject, Injector, OnInit } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms'; +import { NgClass } from '@angular/common'; +import { InputText } from 'primeng/inputtext'; +import { IconField } from 'primeng/iconfield'; +import { InputIcon } from 'primeng/inputicon'; + +export type InputTextSize = 'small' | 'base' | 'large' | 'xlarge'; + + +@Component({ + selector: 'extra-input-text', + standalone: true, + imports: [InputText, IconField, InputIcon, NgClass], + host: { style: 'display: contents' }, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ExtraInputTextComponent), + multi: true + } + ], + template: ` + @if (showClear) { + + + + + } @else { + + } + ` +}) +export class ExtraInputTextComponent implements ControlValueAccessor, OnInit { + private readonly _injector = inject(Injector); + private _ngControl: NgControl | null = null; + + ngOnInit(): void { + this._ngControl = this._injector.get(NgControl, null, { self: true, optional: true }); + } + + @Input() placeholder = ''; + @Input() size: InputTextSize = 'base'; + @Input() readonly = false; + @Input({ transform: booleanAttribute }) showClear = false; + @Input() fluid = false; + + disabled = false; + + get invalid(): boolean { + return this._ngControl?.invalid ?? false; + } + + @Output() onClear = new EventEmitter(); + + modelValue = ''; + + private _onChange: (value: string) => void = () => {}; + + get primeSize(): 'small' | 'large' | never { + if (this.size === 'small') return 'small'; + if (this.size === 'large' || this.size === 'xlarge') return 'large'; + return undefined as never; + } + + get sizeClass(): Record { + return { 'p-inputtext-xlg': this.size === 'xlarge' }; + } + + onInput(event: Event): void { + const value = (event.target as HTMLInputElement).value; + this.modelValue = value; + this._onChange(value); + } + + onTouched: () => void = () => {}; + + clearValue(): void { + this.modelValue = ''; + this._onChange(''); + this.onClear.emit(); + } + + writeValue(value: string): void { + this.modelValue = value ?? ''; + } + + registerOnChange(fn: (value: string) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } +} diff --git a/src/lib/components/inputtext/ng-package.json b/src/lib/components/inputtext/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/inputtext/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/inputtext/public_api.ts b/src/lib/components/inputtext/public_api.ts new file mode 100644 index 00000000..52ee8fcb --- /dev/null +++ b/src/lib/components/inputtext/public_api.ts @@ -0,0 +1,4 @@ +export * from './inputtext.component'; + + + diff --git a/src/lib/components/listbox/listbox.component.ts b/src/lib/components/listbox/listbox.component.ts new file mode 100644 index 00000000..2ed2f0e8 --- /dev/null +++ b/src/lib/components/listbox/listbox.component.ts @@ -0,0 +1,109 @@ +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, + TemplateRef, + forwardRef +} from '@angular/core'; +import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { Listbox, ListboxChangeEvent } from 'primeng/listbox'; +import { SharedModule } from 'primeng/api'; +import { NgTemplateOutlet } from '@angular/common'; + +@Component({ + selector: 'extra-listbox', + standalone: true, + imports: [Listbox, FormsModule, SharedModule, NgTemplateOutlet], + changeDetection: ChangeDetectionStrategy.OnPush, + providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ExtraListboxComponent), multi: true }], + template: ` + + @if (itemTemplate) { + + + + } + + ` +}) +export class ExtraListboxComponent implements ControlValueAccessor { + @Input() options: any[] = []; + @Input() optionLabel = 'label'; + @Input() optionValue: string | undefined = undefined; + @Input() multiple = false; + @Input() filter = false; + @Input() filterPlaceHolder: string | undefined = undefined; + @Input() checkmark = false; + @Input() group = false; + @Input() optionGroupLabel: string | undefined = undefined; + @Input() optionGroupChildren: string | undefined = undefined; + @Input() scrollHeight = '200px'; + @Input() emptyMessage: string | undefined = undefined; + @Input() itemTemplate: TemplateRef | null = null; + + @Output() onFocus = new EventEmitter(); + @Output() onBlur = new EventEmitter(); + + protected modelValue: any = null; + + + private _disabled = false; + private _onChange: (value: any) => void = () => {}; + private _onTouched: () => void = () => {}; + + get isDisabled(): boolean { + return this._disabled; + } + + + onChangeHandler(event: ListboxChangeEvent): void { + // Обновляем внутреннее значение и уведомляем форму об изменении. + this.modelValue = event.value; + this._onChange(event.value); + } + + onBlurHandler(event: FocusEvent): void { + // emit external onBlur and mark control as touched for forms + this.onBlur.emit(event); + this._onTouched(); + } + + writeValue(value: any): void { + this.modelValue = value; + } + + registerOnChange(fn: (value: any) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this._onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this._disabled = isDisabled; + } +} diff --git a/src/lib/components/listbox/ng-package.json b/src/lib/components/listbox/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/listbox/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/listbox/public_api.ts b/src/lib/components/listbox/public_api.ts new file mode 100644 index 00000000..fb44d91e --- /dev/null +++ b/src/lib/components/listbox/public_api.ts @@ -0,0 +1,4 @@ +export * from './listbox.component'; + + + diff --git a/src/lib/components/megamenu/megamenu.component.ts b/src/lib/components/megamenu/megamenu.component.ts new file mode 100644 index 00000000..27305dda --- /dev/null +++ b/src/lib/components/megamenu/megamenu.component.ts @@ -0,0 +1,78 @@ +import { Component, Input, TemplateRef } from '@angular/core'; +import { NgTemplateOutlet } from '@angular/common'; +import { MegaMenu } from 'primeng/megamenu'; +import { MegaMenuItem, PrimeTemplate } from 'primeng/api'; +import { Badge } from 'primeng/badge'; + +export type MegaMenuOrientation = 'horizontal' | 'vertical'; + +export interface MegaMenuModel extends Omit { + description?: string; + badge?: string; + items?: MegaMenuModel[][] | MegaMenuModel[]; +} + +@Component({ + selector: 'extra-megamenu', + host: { style: 'display: contents' }, + standalone: true, + imports: [MegaMenu, PrimeTemplate, NgTemplateOutlet, Badge], + template: ` + + + @if (itemTemplate) { + + + } @else { + + @if (item.icon) { + + } + @if ($any(item).description) { +
+ {{ item.label }} + {{ $any(item).description }} +
+ } @else { + {{ item.label }} + } + @if ($any(item).badge) { + + } + @if (hasSubmenu) { + + } +
+ } +
+
+ `, +}) +export class ExtraMegaMenuComponent { + @Input() model: MegaMenuItem[] = []; + @Input() orientation: MegaMenuOrientation = 'horizontal'; + @Input() breakpoint: string = '960px'; + @Input() scrollHeight: string = ''; + @Input() disabled: boolean = false; + @Input() ariaLabel: string | undefined = undefined; + @Input() ariaLabelledBy: string | undefined = undefined; + @Input() tabindex: number = 0; + @Input() itemTemplate: TemplateRef | null = null; +} diff --git a/src/lib/components/megamenu/ng-package.json b/src/lib/components/megamenu/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/megamenu/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/megamenu/public_api.ts b/src/lib/components/megamenu/public_api.ts new file mode 100644 index 00000000..5f3522b1 --- /dev/null +++ b/src/lib/components/megamenu/public_api.ts @@ -0,0 +1,4 @@ +export * from './megamenu.component'; + + + diff --git a/src/lib/components/menu/menu.component.ts b/src/lib/components/menu/menu.component.ts new file mode 100644 index 00000000..21b87981 --- /dev/null +++ b/src/lib/components/menu/menu.component.ts @@ -0,0 +1,59 @@ +import { Component, Input, TemplateRef, ViewChild } from '@angular/core'; +import { NgTemplateOutlet } from '@angular/common'; +import { Menu } from 'primeng/menu'; +import { MenuItem, PrimeTemplate } from 'primeng/api'; + +export interface ExtraMenuModel extends MenuItem { + caption?: string; +} + +@Component({ + selector: 'extra-menu', + host: { style: 'display: contents' }, + standalone: true, + imports: [Menu, PrimeTemplate, NgTemplateOutlet], + template: ` + + + @if (itemTemplate) { + + + } @else { + + @if (item.icon) { + + } + @if ($any(item).caption) { + + } @else { + {{ item.label }} + } + + } + + + `, +}) +export class ExtraMenuComponent { + @ViewChild('menuRef') menuRef!: Menu; + + @Input() model: ExtraMenuModel[] = []; + @Input() popup = false; + @Input() itemTemplate: TemplateRef | null = null; + + toggle(event: Event): void { + this.menuRef.toggle(event); + } +} diff --git a/src/lib/components/menu/ng-package.json b/src/lib/components/menu/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/menu/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/menu/public_api.ts b/src/lib/components/menu/public_api.ts new file mode 100644 index 00000000..50e5b520 --- /dev/null +++ b/src/lib/components/menu/public_api.ts @@ -0,0 +1,4 @@ +export * from './menu.component'; + + + diff --git a/src/lib/components/message/message.component.ts b/src/lib/components/message/message.component.ts new file mode 100644 index 00000000..ac46e2cc --- /dev/null +++ b/src/lib/components/message/message.component.ts @@ -0,0 +1,58 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Message } from 'primeng/message'; +import { ButtonDirective } from 'primeng/button'; +import { SharedModule } from 'primeng/api'; + +export type MessageSeverity = 'success' | 'info' | 'warn' | 'error' | 'secondary' | 'contrast'; + +const SEVERITY_ICONS: Record = { + info: 'ti ti-info-circle', + success: 'ti ti-circle-check', + warn: 'ti ti-alert-triangle', + error: 'ti ti-alert-circle', +}; + +@Component({ + selector: 'extra-message', + standalone: true, + imports: [Message, ButtonDirective, SharedModule], + template: ` + + +
+ +
+ {{ summary }} + @if (detail) { +
{{ detail }}
+ } + +
+ @if (closable) { + + } +
+
+ `, +}) +export class ExtraMessageComponent { + @Input() severity: MessageSeverity = 'info'; + @Input() summary = ''; + @Input() detail = ''; + @Input() icon: string | undefined = undefined; + @Input() closable = false; + @Input() life: number | undefined = undefined; + + @Output() onClose = new EventEmitter(); + + get resolvedIcon(): string { + return this.icon ?? SEVERITY_ICONS[this.severity] ?? 'ti ti-info-circle'; + } +} diff --git a/src/lib/components/message/ng-package.json b/src/lib/components/message/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/message/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/message/public_api.ts b/src/lib/components/message/public_api.ts new file mode 100644 index 00000000..df5522d9 --- /dev/null +++ b/src/lib/components/message/public_api.ts @@ -0,0 +1,2 @@ +export * from './message.component'; + diff --git a/src/lib/components/metergroup/metergroup.component.ts b/src/lib/components/metergroup/metergroup.component.ts new file mode 100644 index 00000000..f19cd8e9 --- /dev/null +++ b/src/lib/components/metergroup/metergroup.component.ts @@ -0,0 +1,38 @@ +import { Component, HostBinding, Input } from '@angular/core'; +import { MeterGroup, MeterItem } from 'primeng/metergroup'; + +export type MeterGroupOrientation = 'horizontal' | 'vertical'; +export type MeterGroupLabelPosition = 'start' | 'end'; +export type MeterGroupLabelOrientation = 'horizontal' | 'vertical'; + +@Component({ + selector: 'extra-metergroup', + standalone: true, + imports: [MeterGroup], + template: ` + + `, +}) +export class ExtraMeterGroupComponent { + @Input() value: MeterItem[] = []; + @Input() orientation: MeterGroupOrientation = 'horizontal'; + @Input() labelPosition: MeterGroupLabelPosition = 'end'; + @Input() labelOrientation: MeterGroupLabelOrientation = 'horizontal'; + + @HostBinding('style.display') get hostDisplay() { + return this.orientation === 'vertical' ? 'flex' : null; + } + + @HostBinding('style.height') get hostHeight() { + return this.orientation === 'vertical' ? '100%' : null; + } + + @HostBinding('style.flex') get hostFlex() { + return this.orientation === 'vertical' ? '1 1 0%' : null; + } +} diff --git a/src/lib/components/metergroup/ng-package.json b/src/lib/components/metergroup/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/metergroup/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/metergroup/public_api.ts b/src/lib/components/metergroup/public_api.ts new file mode 100644 index 00000000..810d4058 --- /dev/null +++ b/src/lib/components/metergroup/public_api.ts @@ -0,0 +1,4 @@ +export * from './metergroup.component'; + + + diff --git a/src/lib/components/paginator/ng-package.json b/src/lib/components/paginator/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/paginator/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/paginator/paginator.component.ts b/src/lib/components/paginator/paginator.component.ts new file mode 100644 index 00000000..6eab1b75 --- /dev/null +++ b/src/lib/components/paginator/paginator.component.ts @@ -0,0 +1,41 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Paginator } from 'primeng/paginator'; +import type { PaginatorState } from 'primeng/types/paginator'; + +@Component({ + selector: 'extra-paginator', + standalone: true, + imports: [Paginator], + template: ` + + `, +}) +export class ExtraPaginatorComponent { + @Input() first = 0; + @Input() rows = 10; + @Input() totalRecords = 0; + @Input() rowsPerPageOptions: any[] | undefined; + @Input() currentPageReportTemplate = '{currentPage} из {totalPages}'; + @Input() showCurrentPageReport = false; + @Input() showFirstLastIcon = true; + @Input() showJumpToPageDropdown = false; + @Input() showJumpToPageInput = false; + @Input() showPageLinks = true; + @Input() pageLinkSize = 5; + @Input() alwaysShow = true; + @Output() onPageChange = new EventEmitter(); +} diff --git a/src/lib/components/paginator/public_api.ts b/src/lib/components/paginator/public_api.ts new file mode 100644 index 00000000..e7878634 --- /dev/null +++ b/src/lib/components/paginator/public_api.ts @@ -0,0 +1,2 @@ +export * from './paginator.component'; + diff --git a/src/lib/components/panelmenu/ng-package.json b/src/lib/components/panelmenu/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/panelmenu/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/panelmenu/panelmenu.component.ts b/src/lib/components/panelmenu/panelmenu.component.ts new file mode 100644 index 00000000..2dd63adc --- /dev/null +++ b/src/lib/components/panelmenu/panelmenu.component.ts @@ -0,0 +1,58 @@ +import { AfterViewChecked, ChangeDetectionStrategy, Component, ElementRef, HostListener, Input } from '@angular/core'; +import { PanelMenu } from 'primeng/panelmenu'; +import { MenuItem } from 'primeng/api'; + +@Component({ + selector: 'extra-panelmenu', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [PanelMenu], + template: ` + + `, +}) +export class ExtraPanelMenuComponent implements AfterViewChecked { + @Input() model: MenuItem[] = []; + @Input() multiple = false; + @Input() tabindex: number | undefined = undefined; + + private activeItemId: string | null = null; + + constructor(private readonly el: ElementRef) {} + + @HostListener('click', ['$event']) + onItemClick(event: MouseEvent): void { + const target = event.target as Element; + + if (target.closest('.p-panelmenu-header')) return; + + const item = target.closest('.p-panelmenu-item'); + if (!item) return; + + this.activeItemId = item.id || null; + this.applyActiveClass(); + } + + ngAfterViewChecked(): void { + if (this.activeItemId) { + this.applyActiveClass(); + } + } + + private applyActiveClass(): void { + const root = this.el.nativeElement; + root.querySelectorAll('.p-panelmenu-item-active') + .forEach(el => el.classList.remove('p-panelmenu-item-active')); + + if (this.activeItemId) { + const active = root.querySelector(`#${CSS.escape(this.activeItemId)}`); + if (active) { + active.classList.add('p-panelmenu-item-active'); + } + } + } +} diff --git a/src/lib/components/panelmenu/public_api.ts b/src/lib/components/panelmenu/public_api.ts new file mode 100644 index 00000000..dc4de688 --- /dev/null +++ b/src/lib/components/panelmenu/public_api.ts @@ -0,0 +1,2 @@ +export * from './panelmenu.component'; + diff --git a/src/lib/components/password/ng-package.json b/src/lib/components/password/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/password/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/password/password.component.ts b/src/lib/components/password/password.component.ts new file mode 100644 index 00000000..a93017be --- /dev/null +++ b/src/lib/components/password/password.component.ts @@ -0,0 +1,136 @@ +import { ChangeDetectionStrategy, Component, ContentChild, EventEmitter, Input, Output, TemplateRef, forwardRef } from '@angular/core'; +import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { NgTemplateOutlet } from '@angular/common'; +import { Password } from 'primeng/password'; +import { PrimeTemplate } from 'primeng/api'; +import { FloatLabel } from 'primeng/floatlabel'; + +export type PasswordSize = 'small' | 'base' | 'large' | 'xlarge'; + +@Component({ + selector: 'extra-password', + host: { style: 'display: block' }, + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [Password, FormsModule, FloatLabel, NgTemplateOutlet, PrimeTemplate], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ExtraPasswordComponent), + multi: true, + }, + ], + template: ` + @if (floatLabel) { + + + + + } @else { + + } + + + + @if (headerTemplate) { + + + + } + @if (footerTemplate) { + + + + } + + + `, +}) +export class ExtraPasswordComponent implements ControlValueAccessor { + @ContentChild('header') headerTemplate: TemplateRef | null = null; + @ContentChild('footer') footerTemplate: TemplateRef | null = null; + + @Input() feedback = true; + @Input() toggleMask = false; + @Input() disabled = false; + @Input() placeholder: string | undefined = undefined; + @Input() size: PasswordSize = 'base'; + @Input() variant: 'filled' | 'outlined' = 'outlined'; + @Input() fluid = false; + @Input() invalid = false; + @Input() floatLabel = false; + @Input() label = ''; + @Input() promptLabel = 'Введите пароль'; + @Input() weakLabel = 'Слабый'; + @Input() mediumLabel = 'Средний'; + @Input() strongLabel = 'Надёжный'; + @Input() inputId: string | undefined = undefined; + @Input() inputStyleClass: string | undefined = undefined; + @Input() ariaLabel: string | undefined = undefined; + @Input() ariaLabelledBy: string | undefined = undefined; + @Input() appendTo: any = 'body'; + @Input() autofocus = false; + + @Output() onFocus = new EventEmitter(); + @Output() onBlur = new EventEmitter(); + + get sizeClass(): string { + if (this.size === 'small') return 'p-inputtext-sm'; + if (this.size === 'large') return 'p-inputtext-lg'; + if (this.size === 'xlarge') return 'p-inputtext-lg p-inputtext-xlg'; + return ''; + } + + get computedInputStyleClass(): string { + return [this.sizeClass, this.inputStyleClass].filter(Boolean).join(' '); + } + + modelValue: string | null = null; + + private _onChange: (value: string | null) => void = () => {}; + private _onTouched: () => void = () => {}; + + handleChange(value: string | null): void { + this.modelValue = value; + this._onChange(value); + this._onTouched(); + } + + writeValue(value: string | null): void { + this.modelValue = value; + } + + registerOnChange(fn: (value: string | null) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this._onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } +} diff --git a/src/lib/components/password/public_api.ts b/src/lib/components/password/public_api.ts new file mode 100644 index 00000000..60d0b863 --- /dev/null +++ b/src/lib/components/password/public_api.ts @@ -0,0 +1,2 @@ +export * from './password.component'; + diff --git a/src/lib/components/progressbar/ng-package.json b/src/lib/components/progressbar/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/progressbar/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/progressbar/progressbar.component.ts b/src/lib/components/progressbar/progressbar.component.ts new file mode 100644 index 00000000..bbd0dc04 --- /dev/null +++ b/src/lib/components/progressbar/progressbar.component.ts @@ -0,0 +1,22 @@ +import { Component, Input } from '@angular/core'; +import { ProgressBar } from 'primeng/progressbar'; + +export type ProgressBarMode = 'determinate' | 'indeterminate'; + +@Component({ + selector: 'extra-progressbar', + standalone: true, + imports: [ProgressBar], + template: ` + + `, +}) +export class ExtraProgressBarComponent { + @Input() value = 0; + @Input() mode: ProgressBarMode = 'determinate'; + @Input() showValue = true; +} diff --git a/src/lib/components/progressbar/public_api.ts b/src/lib/components/progressbar/public_api.ts new file mode 100644 index 00000000..472f668c --- /dev/null +++ b/src/lib/components/progressbar/public_api.ts @@ -0,0 +1,2 @@ +export * from './progressbar.component'; + diff --git a/src/lib/components/progressspinner/ng-package.json b/src/lib/components/progressspinner/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/progressspinner/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/progressspinner/progressspinner.component.ts b/src/lib/components/progressspinner/progressspinner.component.ts new file mode 100644 index 00000000..d5a24d8d --- /dev/null +++ b/src/lib/components/progressspinner/progressspinner.component.ts @@ -0,0 +1,35 @@ +import { Component, Input } from '@angular/core'; +import { ProgressSpinnerModule } from 'primeng/progressspinner'; // We use Module since PrimeNG 17/18 might export it this way. Wait, earlier we saw ProgressSpinner is standalone? Actually ProgressSpinner in v18 is standalone, but importing it as ProgressSpinner works. +// Let's import the component directly. Wait, index.d.ts exports { ProgressSpinner, ProgressSpinnerModule }. Either is fine. Let's use ProgressSpinner. +import { ProgressSpinner } from 'primeng/progressspinner'; + +export type ProgressSpinnerSize = 'small' | 'medium' | 'large' | 'xlarge'; + +@Component({ + selector: 'extra-progressspinner', + standalone: true, + imports: [ProgressSpinner], + template: ` + + ` +}) +export class ExtraProgressSpinnerComponent { + @Input() size: ProgressSpinnerSize = 'medium'; + @Input() multicolor = true; + @Input() strokeWidth = '2'; + @Input() fill = 'none'; + @Input() animationDuration = '2s'; + @Input() ariaLabel: string | undefined = undefined; + + get primeStyleClass(): string { + const sizeClass = `p-progressspinner-${this.size}`; + const colorClass = this.multicolor ? '' : 'p-progressspinner-monochrome'; + return `${sizeClass} ${colorClass}`.trim(); + } +} diff --git a/src/lib/components/progressspinner/public_api.ts b/src/lib/components/progressspinner/public_api.ts new file mode 100644 index 00000000..291ee78d --- /dev/null +++ b/src/lib/components/progressspinner/public_api.ts @@ -0,0 +1,4 @@ +export * from './progressspinner.component'; + + + diff --git a/src/lib/components/radiobutton/ng-package.json b/src/lib/components/radiobutton/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/radiobutton/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/radiobutton/public_api.ts b/src/lib/components/radiobutton/public_api.ts new file mode 100644 index 00000000..a5ada333 --- /dev/null +++ b/src/lib/components/radiobutton/public_api.ts @@ -0,0 +1,4 @@ +export * from './radiobutton.component'; + + + diff --git a/src/lib/components/radiobutton/radiobutton.component.ts b/src/lib/components/radiobutton/radiobutton.component.ts new file mode 100644 index 00000000..b80f3513 --- /dev/null +++ b/src/lib/components/radiobutton/radiobutton.component.ts @@ -0,0 +1,92 @@ +import { Component, Input, Output, EventEmitter, forwardRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; +import { RadioButton, RadioButtonClickEvent } from 'primeng/radiobutton'; + +export type RadiobuttonVariant = 'outlined' | 'filled'; +export type RadiobuttonSize = 'small' | 'base' | 'large'; + +@Component({ + selector: 'extra-radiobutton', + standalone: true, + imports: [RadioButton, FormsModule], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ExtraRadiobuttonComponent), + multi: true, + }, + ], + template: ` + + `, +}) +export class ExtraRadiobuttonComponent implements ControlValueAccessor { + @Input() value: any = null; + @Input() name: string | undefined = undefined; + @Input() disabled = false; + @Input() invalid = false; + @Input() variant: RadiobuttonVariant = 'outlined'; + @Input() size: RadiobuttonSize = 'base'; + @Input() inputId: string | undefined = undefined; + @Input() tabindex: number | undefined = undefined; + @Input() ariaLabel: string | undefined = undefined; + @Input() ariaLabelledBy: string | undefined = undefined; + @Input() autofocus = false; + + @Output() onClick = new EventEmitter(); + @Output() onFocus = new EventEmitter(); + @Output() onBlur = new EventEmitter(); + + modelValue: any = null; + + private _onChange: (value: any) => void = () => {}; + private _onTouched: () => void = () => {}; + + get primeSize(): 'small' | 'large' | undefined { + if (this.size === 'small') return 'small'; + if (this.size === 'large') return 'large'; + return undefined; + } + + get primeVariant(): 'filled' | undefined { + return this.variant === 'filled' ? 'filled' : undefined; + } + + onClickHandler(event: RadioButtonClickEvent): void { + this._onChange(event.value); + this._onTouched(); + this.onClick.emit(event); + } + + writeValue(value: any): void { + this.modelValue = value; + } + + registerOnChange(fn: (value: any) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this._onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } +} diff --git a/src/lib/components/rating/ng-package.json b/src/lib/components/rating/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/rating/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/rating/public_api.ts b/src/lib/components/rating/public_api.ts new file mode 100644 index 00000000..832deb2b --- /dev/null +++ b/src/lib/components/rating/public_api.ts @@ -0,0 +1,4 @@ +export * from './rating.component'; + + + diff --git a/src/lib/components/rating/rating.component.ts b/src/lib/components/rating/rating.component.ts new file mode 100644 index 00000000..2baa883b --- /dev/null +++ b/src/lib/components/rating/rating.component.ts @@ -0,0 +1,68 @@ +import { Component, EventEmitter, Input, Output, forwardRef } from '@angular/core'; +import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { Rating } from 'primeng/rating'; + +export type RatingValue = number | null; + +@Component({ + selector: 'extra-rating', + standalone: true, + imports: [Rating, FormsModule], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ExtraRatingComponent), + multi: true, + }, + ], + template: ` + + `, +}) +export class ExtraRatingComponent implements ControlValueAccessor { + @Input() stars = 5; + @Input() readonly = false; + @Input() disabled = false; + @Input() autofocus = false; + + @Output() onRate = new EventEmitter(); + @Output() onFocus = new EventEmitter(); + @Output() onBlur = new EventEmitter(); + + modelValue: RatingValue = null; + + private onChange: (value: RatingValue) => void = () => {}; + private onTouched: () => void = () => {}; + + handleChange(value: RatingValue): void { + this.modelValue = value; + this.onChange(value); + this.onTouched(); + } + + writeValue(value: RatingValue): void { + this.modelValue = value; + } + + registerOnChange(fn: (value: RatingValue) => void): void { + this.onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } +} diff --git a/src/lib/components/scroll-panel/ng-package.json b/src/lib/components/scroll-panel/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/scroll-panel/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/scroll-panel/public_api.ts b/src/lib/components/scroll-panel/public_api.ts new file mode 100644 index 00000000..dd07e831 --- /dev/null +++ b/src/lib/components/scroll-panel/public_api.ts @@ -0,0 +1,2 @@ +export * from './scroll-panel.component'; + diff --git a/src/lib/components/scroll-panel/scroll-panel.component.ts b/src/lib/components/scroll-panel/scroll-panel.component.ts new file mode 100644 index 00000000..b340ba03 --- /dev/null +++ b/src/lib/components/scroll-panel/scroll-panel.component.ts @@ -0,0 +1,22 @@ +import { Component, Input } from '@angular/core'; +import { ScrollPanel } from 'primeng/scrollpanel'; + +@Component({ + selector: 'extra-scroll-panel', + host: { style: 'display: block' }, + standalone: true, + imports: [ScrollPanel], + template: ` + + + + `, +}) +export class ExtraScrollPanelComponent { + @Input() step = 10; + @Input() height = '100px'; + @Input() width = '100%'; +} diff --git a/src/lib/components/select/ng-package.json b/src/lib/components/select/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/select/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/select/public_api.ts b/src/lib/components/select/public_api.ts new file mode 100644 index 00000000..fa139e5f --- /dev/null +++ b/src/lib/components/select/public_api.ts @@ -0,0 +1,2 @@ +export * from './select.component'; + diff --git a/src/lib/components/select/select.component.ts b/src/lib/components/select/select.component.ts new file mode 100644 index 00000000..e07291bc --- /dev/null +++ b/src/lib/components/select/select.component.ts @@ -0,0 +1,184 @@ +import { Component, EventEmitter, forwardRef, inject, Injector, Input, OnInit, Output, TemplateRef } from '@angular/core'; +import { NgClass, NgTemplateOutlet } from '@angular/common'; +import { ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { FormsModule } from '@angular/forms'; +import { Select } from 'primeng/select'; +import { FloatLabel } from 'primeng/floatlabel'; +import { PrimeTemplate } from 'primeng/api'; +import { AnimationEvent as NativeAnimationEvent } from '@angular/animations'; +import type { SelectChangeEvent, SelectFilterEvent } from 'primeng/types/select'; + +export type SelectSize = 'small' | 'base' | 'large' | 'xlarge'; + +export interface AnimationEvent extends NativeAnimationEvent {} + +// export class AnimationEvent + +@Component({ + selector: 'extra-select', + standalone: true, + imports: [Select, NgClass, NgTemplateOutlet, PrimeTemplate, FormsModule, FloatLabel], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ExtraSelectComponent), + multi: true, + }, + ], + template: ` + @if (floatLabel) { + + + + + } @else { + + } + + + + @if (optionTemplate) { + + + + } + @if (selectedItemTemplate) { + + + + } + @if (optionGroupTemplate) { + + + + } + + + `, +}) +export class ExtraSelectComponent implements ControlValueAccessor, OnInit { + private readonly _injector = inject(Injector); + private _ngControl: NgControl | null = null; + + ngOnInit(): void { + this._ngControl = this._injector.get(NgControl, null, { self: true, optional: true }); + } + + @Input() options: any[] | null | undefined; + @Input() optionLabel: string | undefined; + @Input() optionValue: string | undefined; + @Input() optionDisabled: string | undefined; + @Input() optionGroupLabel: string | undefined; + @Input() optionGroupChildren = 'items'; + @Input() group = false; + @Input() placeholder = ''; + @Input() size: SelectSize = 'base'; + @Input() filter = false; + @Input() showClear = false; + @Input() editable = false; + @Input() readonly = false; + @Input() loading = false; + @Input() inputId: string | undefined; + @Input() appendTo: any = 'body'; + @Input() floatLabel = false; + @Input() label = ''; + @Input() checkmark = true; + @Input() checkmarkIcon = 'ea5e'; + @Input() emptyMessage = 'Нет данных'; + @Input() emptyFilterMessage = 'Результаты не найдены'; + @Input() optionTemplate: TemplateRef | null = null; + @Input() selectedItemTemplate: TemplateRef | null = null; + @Input() optionGroupTemplate: TemplateRef | null = null; + + disabled = false; + modelValue: any = null; + + @Output() onClear = new EventEmitter(); + @Output() onFilter = new EventEmitter(); + @Output() onShow = new EventEmitter(); + @Output() onHide = new EventEmitter(); + @Output() onFocus = new EventEmitter(); + @Output() onBlur = new EventEmitter(); + + get invalid(): boolean { + return !!(this._ngControl?.invalid && this._ngControl?.touched); + } + + get primeSize(): 'small' | 'large' | undefined { + if (this.size === 'small') return 'small'; + if (this.size === 'large') return 'large'; + return undefined; + } + + get panelStyle(): Record { + const char = String.fromCodePoint(parseInt(this.checkmarkIcon, 16)); + return { '--p-select-checkmark-content': `"${char}"` }; + } + + get selectClasses(): Record { + return { + 'p-select-xlg': this.size === 'xlarge', + 'p-invalid': this.invalid, + }; + } + + private _onChange: (value: any) => void = () => {}; + private _onTouched: () => void = () => {}; + + onSelectChange(event: SelectChangeEvent): void { + this.modelValue = event.value; + this._onChange(event.value); + } + + handleBlur(event: Event): void { + this._onTouched(); + this.onBlur.emit(event); + } + + writeValue(value: any): void { + this.modelValue = value ?? null; + } + + registerOnChange(fn: (value: any) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this._onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } +} diff --git a/src/lib/components/skeleton/ng-package.json b/src/lib/components/skeleton/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/skeleton/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/skeleton/public_api.ts b/src/lib/components/skeleton/public_api.ts new file mode 100644 index 00000000..baa522a2 --- /dev/null +++ b/src/lib/components/skeleton/public_api.ts @@ -0,0 +1,4 @@ +export * from './skeleton.component'; + + + diff --git a/src/lib/components/skeleton/skeleton.component.ts b/src/lib/components/skeleton/skeleton.component.ts new file mode 100644 index 00000000..e97f3fe6 --- /dev/null +++ b/src/lib/components/skeleton/skeleton.component.ts @@ -0,0 +1,30 @@ +import { Component, Input } from '@angular/core'; +import { Skeleton } from 'primeng/skeleton'; + +export type SkeletonShape = 'rectangle' | 'circle'; +export type SkeletonAnimation = 'wave' | 'none'; + +@Component({ + selector: 'extra-skeleton', + host: { style: 'display: block' }, + standalone: true, + imports: [Skeleton], + template: ` + + `, +}) +export class ExtraSkeletonComponent { + @Input() shape: SkeletonShape = 'rectangle'; + @Input() animation: SkeletonAnimation = 'wave'; + @Input() width = '100%'; + @Input() height = '1rem'; + @Input() size: string | undefined = undefined; + @Input() borderRadius: string | undefined = undefined; +} diff --git a/src/lib/components/slider/ng-package.json b/src/lib/components/slider/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/slider/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/slider/public_api.ts b/src/lib/components/slider/public_api.ts new file mode 100644 index 00000000..7e3ff9d5 --- /dev/null +++ b/src/lib/components/slider/public_api.ts @@ -0,0 +1,4 @@ +export * from './slider.component'; + + + diff --git a/src/lib/components/slider/slider.component.ts b/src/lib/components/slider/slider.component.ts new file mode 100644 index 00000000..30ae3c46 --- /dev/null +++ b/src/lib/components/slider/slider.component.ts @@ -0,0 +1,91 @@ +import { ChangeDetectionStrategy, Component, EventEmitter, forwardRef, Input, OnChanges, OnDestroy, Output, SimpleChanges } from '@angular/core'; +import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms'; +import { Slider } from 'primeng/slider'; +import type { SliderSlideEndEvent } from 'primeng/slider'; +import { Subscription } from 'rxjs'; + +export type SliderOrientation = 'horizontal' | 'vertical'; + +@Component({ + selector: 'extra-slider', + host: { style: 'display: block' }, + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [Slider, ReactiveFormsModule], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ExtraSliderComponent), + multi: true, + }, + ], + template: ` + + `, +}) +export class ExtraSliderComponent implements ControlValueAccessor, OnChanges, OnDestroy { + @Input() min = 0; + @Input() max = 100; + @Input() step: number | undefined = undefined; + @Input() range = false; + @Input() orientation: SliderOrientation = 'horizontal'; + @Input() set disabled(value: boolean) { + value ? this.control.disable() : this.control.enable(); + } + @Output() onSlideEnd = new EventEmitter(); + + readonly control = new FormControl(0, { nonNullable: true }); + + private _onChange: (value: number | number[]) => void = () => {}; + private _onTouched: () => void = () => {}; + private readonly _sub: Subscription; + + constructor() { + this._sub = this.control.valueChanges.subscribe(v => this._onChange(v)); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['range']) { + const current = this.control.value; + if (this.range && !Array.isArray(current)) { + this.control.setValue([this.min, this.max], { emitEvent: false }); + } else if (!this.range && Array.isArray(current)) { + this.control.setValue(current[0], { emitEvent: false }); + } + } + } + + ngOnDestroy(): void { + this._sub.unsubscribe(); + } + + writeValue(value: number | number[]): void { + this.control.setValue(this.normalize(value ?? 0), { emitEvent: false }); + } + + registerOnChange(fn: (value: number | number[]) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this._onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + isDisabled ? this.control.disable() : this.control.enable(); + } + + private normalize(value: number | number[]): number | number[] { + if (this.range && !Array.isArray(value)) return [this.min, this.max]; + if (!this.range && Array.isArray(value)) return value[0]; + return value; + } +} diff --git a/src/lib/components/tabs/ng-package.json b/src/lib/components/tabs/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/tabs/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/tabs/public_api.ts b/src/lib/components/tabs/public_api.ts new file mode 100644 index 00000000..26af794e --- /dev/null +++ b/src/lib/components/tabs/public_api.ts @@ -0,0 +1,4 @@ +export * from './tabs.component'; + + + diff --git a/src/lib/components/tabs/tabs.component.ts b/src/lib/components/tabs/tabs.component.ts new file mode 100644 index 00000000..5fa9f6e7 --- /dev/null +++ b/src/lib/components/tabs/tabs.component.ts @@ -0,0 +1,64 @@ +import { Component, Input } from '@angular/core'; +import { Tabs } from 'primeng/tabs'; +import { TabList } from 'primeng/tabs'; +import { Tab } from 'primeng/tabs'; +import { TabPanels } from 'primeng/tabs'; +import { TabPanel } from 'primeng/tabs'; +import { Badge } from 'primeng/badge'; + +export interface TabItem { + value: string; + label: string; + icon?: string; + disabled?: boolean; + badge?: string; + badgeSeverity?: 'success' | 'info' | 'warn' | 'danger' | 'secondary' | 'contrast'; + content?: string; +} + +@Component({ + selector: 'extra-tabs', + standalone: true, + imports: [Tabs, TabList, Tab, TabPanels, TabPanel, Badge], + template: ` + + + @for (tab of tabs; track tab.value) { + + @if (tab.icon) { + + } + {{ tab.label }} + @if (tab.badge) { + + } + + } + + + @for (tab of tabs; track tab.value) { + +

{{ tab.content }}

+
+ } +
+
+ `, +}) +export class ExtraTabsComponent { + @Input() value: string | number | undefined = '0'; + @Input() tabs: TabItem[] = []; + @Input() scrollable = false; + @Input() lazy = false; +} diff --git a/src/lib/components/tag/ng-package.json b/src/lib/components/tag/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/tag/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/tag/public_api.ts b/src/lib/components/tag/public_api.ts new file mode 100644 index 00000000..cf42576c --- /dev/null +++ b/src/lib/components/tag/public_api.ts @@ -0,0 +1,4 @@ +export * from './tag.component'; + + + diff --git a/src/lib/components/tag/tag.component.ts b/src/lib/components/tag/tag.component.ts new file mode 100644 index 00000000..7aedbd14 --- /dev/null +++ b/src/lib/components/tag/tag.component.ts @@ -0,0 +1,29 @@ +import { Component, Input } from '@angular/core'; +import { Tag } from 'primeng/tag'; + +export type TagSeverity = 'primary' | 'secondary' | 'success' | 'info' | 'warn' | 'danger'; + +@Component({ + selector: 'extra-tag', + standalone: true, + imports: [Tag], + template: ` + + `, +}) +export class ExtraTagComponent { + @Input() value = ''; + @Input() severity: TagSeverity = 'primary'; + @Input() rounded = false; + @Input() icon = ''; + + get primeSeverity(): 'secondary' | 'success' | 'info' | 'warn' | 'danger' | undefined { + if (this.severity === 'primary') return undefined; + return this.severity; + } +} diff --git a/src/lib/components/textarea/ng-package.json b/src/lib/components/textarea/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/textarea/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/textarea/public_api.ts b/src/lib/components/textarea/public_api.ts new file mode 100644 index 00000000..1e7c627e --- /dev/null +++ b/src/lib/components/textarea/public_api.ts @@ -0,0 +1,2 @@ +export * from './textarea.component'; + diff --git a/src/lib/components/textarea/textarea.component.ts b/src/lib/components/textarea/textarea.component.ts new file mode 100644 index 00000000..dbfe2595 --- /dev/null +++ b/src/lib/components/textarea/textarea.component.ts @@ -0,0 +1,142 @@ +import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter, forwardRef, inject, Injector, OnInit } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms'; +import { NgClass } from '@angular/common'; +import { Textarea } from 'primeng/textarea'; +import { IconField } from 'primeng/iconfield'; +import { InputIcon } from 'primeng/inputicon'; + +export type TextareaSize = 'small' | 'base' | 'large' | 'xlarge'; + +@Component({ + selector: 'extra-textarea', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [Textarea, IconField, InputIcon, NgClass], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ExtraTextareaComponent), + multi: true, + }, + ], + template: ` + @if (showClear) { + + + + + } @else { + + } + `, +}) +export class ExtraTextareaComponent implements ControlValueAccessor, OnInit { + private readonly _injector = inject(Injector); + private _ngControl: NgControl | null = null; + + ngOnInit(): void { + this._ngControl = this._injector.get(NgControl, null, { self: true, optional: true }); + } + + @Input() placeholder = ''; + @Input() size: TextareaSize = 'base'; + @Input() readonly = false; + @Input() showClear = false; + @Input() fluid = false; + @Input() autoResize = false; + @Input() rows = 3; + @Input() cols?: number; + + disabled = false; + + get invalid(): boolean { + return this._ngControl?.invalid ?? false; + } + + @Output() onResize = new EventEmitter<{ height: string } | {}>(); + @Output() onClear = new EventEmitter(); + + modelValue = ''; + + private _onChange: (value: string) => void = () => {}; + + get primeSize(): 'small' | 'large' | never { + if (this.size === 'small') return 'small'; + if (this.size === 'large') return 'large'; + return undefined as never; + } + + get sizeClass(): Record { + return { 'p-textarea-xlg': this.size === 'xlarge' }; + } + + onInput(event: Event): void { + const value = (event.target as HTMLTextAreaElement).value; + this.modelValue = value; + this._onChange(value); + } + + onTouched: () => void = () => {}; + + clearValue(): void { + this.modelValue = ''; + this._onChange(''); + this.onClear.emit(); + } + + writeValue(value: string): void { + this.modelValue = value ?? ''; + } + + registerOnChange(fn: (value: string) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this.onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } +} diff --git a/src/lib/components/tieredmenu/ng-package.json b/src/lib/components/tieredmenu/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/tieredmenu/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/tieredmenu/public_api.ts b/src/lib/components/tieredmenu/public_api.ts new file mode 100644 index 00000000..7cf9805e --- /dev/null +++ b/src/lib/components/tieredmenu/public_api.ts @@ -0,0 +1,4 @@ +export * from './tieredmenu.component'; + + + diff --git a/src/lib/components/tieredmenu/tieredmenu.component.ts b/src/lib/components/tieredmenu/tieredmenu.component.ts new file mode 100644 index 00000000..d4f92f67 --- /dev/null +++ b/src/lib/components/tieredmenu/tieredmenu.component.ts @@ -0,0 +1,58 @@ +import { AfterViewChecked, ChangeDetectionStrategy, Component, ElementRef, HostListener, Input } from '@angular/core'; +import { TieredMenu } from 'primeng/tieredmenu'; +import { MenuItem } from 'primeng/api'; + +@Component({ + selector: 'extra-tieredmenu', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TieredMenu], + template: ` + + `, +}) +export class ExtraTieredMenuComponent implements AfterViewChecked { + @Input() model: MenuItem[] = []; + @Input() autoDisplay = true; + @Input() tabindex: number | undefined = undefined; + + private activeItemId: string | null = null; + + constructor(private readonly el: ElementRef) {} + + @HostListener('click', ['$event']) + onItemClick(event: MouseEvent): void { + const target = event.target as Element; + const item = target.closest('.p-tieredmenu-item'); + if (!item) return; + + const hasSubmenu = item.querySelector(':scope > .p-tieredmenu-submenu, :scope > [class*="content-container"]'); + if (hasSubmenu) return; + + this.activeItemId = item.id || null; + this.applyActiveClass(); + } + + ngAfterViewChecked(): void { + if (this.activeItemId) { + this.applyActiveClass(); + } + } + + private applyActiveClass(): void { + const root = this.el.nativeElement; + root.querySelectorAll('.p-tieredmenu-item-checked') + .forEach(el => el.classList.remove('p-tieredmenu-item-checked')); + + if (this.activeItemId) { + const active = root.querySelector(`#${CSS.escape(this.activeItemId)}`); + if (active) { + active.classList.add('p-tieredmenu-item-checked'); + } + } + } +} diff --git a/src/lib/components/timeline/ng-package.json b/src/lib/components/timeline/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/timeline/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/timeline/public_api.ts b/src/lib/components/timeline/public_api.ts new file mode 100644 index 00000000..0a2cf2e4 --- /dev/null +++ b/src/lib/components/timeline/public_api.ts @@ -0,0 +1,4 @@ +export * from './timeline.component'; + + + diff --git a/src/lib/components/timeline/timeline.component.ts b/src/lib/components/timeline/timeline.component.ts new file mode 100644 index 00000000..c8297b06 --- /dev/null +++ b/src/lib/components/timeline/timeline.component.ts @@ -0,0 +1,67 @@ +import { Component, Input, ContentChild, TemplateRef, HostBinding } from '@angular/core'; +import { Timeline } from 'primeng/timeline'; +import { SharedModule } from 'primeng/api'; +import { NgTemplateOutlet } from '@angular/common'; + +export type TimelineLine = 'solid' | 'dashed' | 'dotted' | 'none'; + +@Component({ + selector: 'extra-timeline', + standalone: true, + imports: [Timeline, SharedModule, NgTemplateOutlet], + template: ` + + + + + + + @if (showCaption) { + @if (oppositeTemplate) { + + } @else { +   + } + } + + + @if (markerTemplate || icon) { + + @if (markerTemplate) { + + } @else { + + + + } + + } + + + + {{ event }} + + ` +}) +export class ExtraTimelineComponent { + @Input() value: any[] = []; + @Input() align: 'left' | 'right' | 'alternate' | 'top' | 'bottom' = 'left'; + @Input() layout: 'vertical' | 'horizontal' = 'vertical'; + @Input() showCaption: boolean = true; + @Input() line: TimelineLine = 'solid'; + @Input() icon = ''; + @Input() markerColor = ''; + + @HostBinding('attr.data-line') get dataLine() { + return this.line; + } + @HostBinding('style.--timeline-marker-color') get markerColorVar() { + return this.markerColor || null; + } + + @ContentChild('content') contentTemplate?: TemplateRef; + @ContentChild('opposite') oppositeTemplate?: TemplateRef; + @ContentChild('marker') markerTemplate?: TemplateRef; +} diff --git a/src/lib/components/toast/ng-package.json b/src/lib/components/toast/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/toast/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/toast/provide-toast.ts b/src/lib/components/toast/provide-toast.ts new file mode 100644 index 00000000..639e9f51 --- /dev/null +++ b/src/lib/components/toast/provide-toast.ts @@ -0,0 +1,17 @@ +import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core'; +import { MessageService } from 'primeng/api'; + +/** + * Регистрирует зависимости, необходимые для работы `ExtraToastService` и ``. + * Вызывать один раз в `ApplicationConfig.providers` или в `bootstrapApplication`. + * + * @example + * // app.config.ts + * export const appConfig: ApplicationConfig = { + * providers: [provideExtraToast()], + * }; + */ +export function provideExtraToast(): EnvironmentProviders { + return makeEnvironmentProviders([MessageService]); +} + diff --git a/src/lib/components/toast/public_api.ts b/src/lib/components/toast/public_api.ts new file mode 100644 index 00000000..b4c66422 --- /dev/null +++ b/src/lib/components/toast/public_api.ts @@ -0,0 +1,6 @@ +export * from './toast.component'; +export * from './toast.service'; +export * from './provide-toast'; + + + diff --git a/src/lib/components/toast/toast.component.ts b/src/lib/components/toast/toast.component.ts new file mode 100644 index 00000000..6fedad9a --- /dev/null +++ b/src/lib/components/toast/toast.component.ts @@ -0,0 +1,50 @@ +import { Component, Input } from '@angular/core'; +import { Toast } from 'primeng/toast'; +import { SharedModule } from 'primeng/api'; + +export type ToastSeverity = 'success' | 'info' | 'warn' | 'error' | 'secondary' | 'contrast'; +export type ToastPosition = + | 'top-right' + | 'top-left' + | 'top-center' + | 'bottom-right' + | 'bottom-left' + | 'bottom-center' + | 'center'; + +const SEVERITY_ICONS: Record = { + info: 'ti ti-info-circle', + success: 'ti ti-circle-check', + warn: 'ti ti-alert-triangle', + error: 'ti ti-alert-circle', +}; + +@Component({ + selector: 'extra-toast', + standalone: true, + imports: [Toast, SharedModule], + template: ` + + +
+ +
+ {{ message.summary }} + @if (message.detail) { +
{{ message.detail }}
+ } +
+
+
+ `, +}) +export class ExtraToastComponent { + @Input() position: ToastPosition = 'top-right'; + @Input() key: string | undefined = undefined; + @Input() life = 5000; + @Input() pt: Record | undefined = undefined; + + resolveIcon(message: { severity?: string; icon?: string }): string { + return message.icon ?? SEVERITY_ICONS[message.severity ?? 'info'] ?? 'ti ti-info-circle'; + } +} diff --git a/src/lib/components/toast/toast.service.ts b/src/lib/components/toast/toast.service.ts new file mode 100644 index 00000000..eac20f46 --- /dev/null +++ b/src/lib/components/toast/toast.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { MessageService } from 'primeng/api'; +import { ToastSeverity } from './toast.component'; + +export interface ExtraToastMessage { + key?: string; + severity?: ToastSeverity; + summary?: string; + detail?: string; + life?: number; + icon?: string; + closable?: boolean; + data?: unknown; +} + +@Injectable({ providedIn: 'root' }) +export class ExtraToastService { + + constructor(private readonly messageService: MessageService) {} + + add(message: ExtraToastMessage): void { + this.messageService.add(message); + } + + clear(key?: string): void { + this.messageService.clear(key); + } +} diff --git a/src/lib/components/toggleswitch/ng-package.json b/src/lib/components/toggleswitch/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/toggleswitch/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/toggleswitch/public_api.ts b/src/lib/components/toggleswitch/public_api.ts new file mode 100644 index 00000000..b72d0762 --- /dev/null +++ b/src/lib/components/toggleswitch/public_api.ts @@ -0,0 +1,2 @@ +export * from './toggleswitch.component'; + diff --git a/src/lib/components/toggleswitch/toggleswitch.component.ts b/src/lib/components/toggleswitch/toggleswitch.component.ts new file mode 100644 index 00000000..2be71acf --- /dev/null +++ b/src/lib/components/toggleswitch/toggleswitch.component.ts @@ -0,0 +1,68 @@ +import { Component, EventEmitter, Optional, Output, Self } from '@angular/core'; +import { ControlValueAccessor, FormsModule, NgControl } from '@angular/forms'; +import { ToggleSwitch } from 'primeng/toggleswitch'; + +@Component({ + selector: 'extra-toggleswitch', + standalone: true, + imports: [ToggleSwitch, FormsModule], + template: ` + + `, +}) +export class ExtraToggleSwitchComponent implements ControlValueAccessor { + @Output() onChange = new EventEmitter(); + @Output() onFocus = new EventEmitter(); + @Output() onBlur = new EventEmitter(); + + modelValue = false; + + private _disabled = false; + + private _onChange: (value: boolean) => void = () => {}; + private _onTouched: () => void = () => {}; + + constructor(@Optional() @Self() private ngControl: NgControl) { + if (ngControl) { + ngControl.valueAccessor = this; + } + } + + get isDisabled(): boolean { + return this._disabled; + } + + get isInvalid(): boolean { + return !!this.ngControl?.invalid; + } + + handleChange(value: boolean): void { + this.modelValue = value; + this._onChange(value); + this._onTouched(); + } + + writeValue(value: boolean): void { + this.modelValue = value ?? false; + } + + registerOnChange(fn: (value: boolean) => void): void { + this._onChange = fn; + } + + registerOnTouched(fn: () => void): void { + this._onTouched = fn; + } + + setDisabledState(isDisabled: boolean): void { + this._disabled = isDisabled; + } +} diff --git a/src/lib/components/tooltip/ng-package.json b/src/lib/components/tooltip/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/components/tooltip/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/components/tooltip/public_api.ts b/src/lib/components/tooltip/public_api.ts new file mode 100644 index 00000000..d1afcc18 --- /dev/null +++ b/src/lib/components/tooltip/public_api.ts @@ -0,0 +1,4 @@ +export * from './tooltip.directive'; + + + diff --git a/src/lib/components/tooltip/tooltip.directive.ts b/src/lib/components/tooltip/tooltip.directive.ts new file mode 100644 index 00000000..9995b2ae --- /dev/null +++ b/src/lib/components/tooltip/tooltip.directive.ts @@ -0,0 +1,37 @@ +import { Directive, Input } from '@angular/core'; +import { Tooltip } from 'primeng/tooltip'; + +export type TooltipPosition = 'right' | 'left' | 'top' | 'bottom'; +export type TooltipEvent = 'hover' | 'focus' | 'both'; + +@Directive({ + selector: '[extra-tooltip]', + standalone: true, + hostDirectives: [ + { + directive: Tooltip, + inputs: [ + 'pTooltip: tooltip', + 'tooltipPosition: position', + 'tooltipEvent: event', + 'showDelay: showDelay', + 'hideDelay: hideDelay', + 'tooltipDisabled: disabled', + 'escape: escape', + 'autoHide: autoHide', + 'fitContent: fitContent', + 'hideOnEscape: hideOnEscape', + 'positionTop: positionTop', + 'positionLeft: positionLeft' + ] + } + ] +}) +export class ExtraTooltipDirective { + @Input() tooltip: string | undefined; + @Input() position: TooltipPosition = 'right'; + @Input() event: TooltipEvent = 'hover'; + @Input() showDelay: number | undefined; + @Input() hideDelay: number | undefined; + @Input() disabled: boolean = false; +} diff --git a/src/lib/ng-package.json b/src/lib/ng-package.json new file mode 100644 index 00000000..61073a16 --- /dev/null +++ b/src/lib/ng-package.json @@ -0,0 +1,13 @@ +{ + "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../dist", + "deleteDestPath": true, + "lib": { + "entryFile": "public_api.ts" + }, + "allowedNonPeerDependencies": [ + "@primeng/themes", + "primeng", + "@primeuix/themes" + ] +} diff --git a/src/lib/package.json b/src/lib/package.json new file mode 100644 index 00000000..90d73023 --- /dev/null +++ b/src/lib/package.json @@ -0,0 +1,13 @@ +{ + "name": "@cdek-it/angular-ui-kit", + "version": "0.2.4-test", + "peerDependencies": { + "@angular/common": "^20.3.15", + "@angular/core": "^20.3.15" + }, + "dependencies": { + "@primeng/themes": "20.4.0", + "primeng": "20.4.0", + "@primeuix/themes": "1.2.5" + } +} diff --git a/src/lib/providers/ng-package.json b/src/lib/providers/ng-package.json new file mode 100644 index 00000000..ecdf8fea --- /dev/null +++ b/src/lib/providers/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "ng-packagr/ng-package.schema.json", + "lib": { + "entryFile": "public_api.ts" + } +} + diff --git a/src/lib/providers/prime-preset/map-tokens.ts b/src/lib/providers/prime-preset/map-tokens.ts new file mode 100644 index 00000000..72dcaad0 --- /dev/null +++ b/src/lib/providers/prime-preset/map-tokens.ts @@ -0,0 +1,96 @@ +import { Preset } from '@primeuix/themes/types'; +import type { ComponentsDesignTokens } from '@primeuix/themes/types'; +// по другому импорт не работает +import type { AuraBaseDesignTokens } from '../../../../node_modules/@primeuix/themes/dist/aura/base'; + +import tokens from './tokens/tokens.json'; +import { avatarCss } from './tokens/components/avatar'; +import { breadcrumbCss } from './tokens/components/breadcrumb'; +import { buttonCss } from './tokens/components/button'; +import { cardCss } from './tokens/components/card'; +import { checkboxCss } from './tokens/components/checkbox'; +import { inputmaskCss } from './tokens/components/inputmask'; +import { inputtextCss } from './tokens/components/inputtext'; +import { progressspinnerCss } from './tokens/components/progressspinner'; +import { passwordCss } from './tokens/components/password'; +import { tagCss } from './tokens/components/tag'; +import { textareaCss } from './tokens/components/textarea'; +import { tooltipCss } from './tokens/components/tooltip'; +import { inputgroupCss } from './tokens/components/inputgroup' +import { megamenuCss } from './tokens/components/megamenu'; +import { selectCss } from './tokens/components/select'; +import { messageCss } from './tokens/components/message'; +import { inputotpCss } from './tokens/components/inputotp'; + +const presetTokens: Preset = { + primitive: tokens.primitive as unknown as AuraBaseDesignTokens['primitive'], + semantic: tokens.semantic as unknown as AuraBaseDesignTokens['semantic'], + components: { + ...(tokens.components as unknown as ComponentsDesignTokens), + avatar: { + ...(tokens.components.avatar as unknown as ComponentsDesignTokens['avatar']), + css: avatarCss, + }, + card: { + ...(tokens.components.card as unknown as ComponentsDesignTokens['card']), + css: cardCss, + }, + checkbox: { + ...(tokens.components.checkbox as unknown as ComponentsDesignTokens['checkbox']), + css: checkboxCss, + }, + button: { + ...(tokens.components.button as unknown as ComponentsDesignTokens['button']), + css: buttonCss, + }, + message: { + ...(tokens.components.message as unknown as ComponentsDesignTokens['message']), + css: messageCss, + }, + progressspinner: { + ...(tokens.components.progressspinner as unknown as ComponentsDesignTokens['progressspinner']), + css: progressspinnerCss, + }, + inputotp: { + ...(tokens.components.inputotp as unknown as ComponentsDesignTokens['inputotp']), + css: inputotpCss, + }, + inputtext: { + ...(tokens.components.inputtext as unknown as ComponentsDesignTokens['inputtext']), + css: inputtextCss, + }, + inputmask: { + css: inputmaskCss, + }, + inputgroup: { + ...(tokens.components.inputgroup as unknown as ComponentsDesignTokens['inputgroup']), + css: inputgroupCss, + }, + tag: { + ...(tokens.components.tag as unknown as ComponentsDesignTokens['tag']), + css: tagCss, + }, + textarea: { + ...(tokens.components.textarea as unknown as ComponentsDesignTokens['textarea']), + css: textareaCss, + }, + tooltip: { + ...(tokens.components.tooltip as unknown as ComponentsDesignTokens['tooltip']), + css: tooltipCss, + }, + megamenu: { + ...(tokens.components.megamenu as unknown as ComponentsDesignTokens['megamenu']), + css: megamenuCss, + }, + select: { + ...(tokens.components.select as unknown as ComponentsDesignTokens['select']), + css: selectCss, + }, + passwordCss: { + ...(tokens.components.password as unknown as ComponentsDesignTokens['password']), + css: passwordCss + } + } as ComponentsDesignTokens, +}; + +export default presetTokens; diff --git a/src/prime-preset/theme.preset.ts b/src/lib/providers/prime-preset/theme.preset.ts similarity index 100% rename from src/prime-preset/theme.preset.ts rename to src/lib/providers/prime-preset/theme.preset.ts diff --git a/src/lib/providers/prime-preset/tokens/components/avatar.ts b/src/lib/providers/prime-preset/tokens/components/avatar.ts new file mode 100644 index 00000000..2c80d571 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/avatar.ts @@ -0,0 +1,33 @@ +export const avatarCss = ({ dt }: { dt: (token: string) => string }): string => ` + :root { + --p-avatar-extend-border-color: ${dt('avatar.extend.borderColor')}; + --p-avatar-extend-circle-border-radius: ${dt('avatar.extend.circle.borderRadius')}; + --p-avatar-group-border-color: ${dt('content.background')}; + --p-avatar-group-offset: calc(-1 * ${dt('media.padding.300')}); + --p-avatar-lg-group-offset: calc(-1 * ${dt('media.padding.300')}); + --p-avatar-xl-group-offset: calc(-1 * ${dt('media.padding.600')}); + } + + /* ─── Группировка: отступы для кастомных классов хост-элемента ─── */ + .p-avatar-group .ui-avatar + .ui-avatar { + margin-inline-start: var(--p-avatar-group-offset); + } + + .p-avatar-group .ui-avatar-lg + .ui-avatar-lg { + margin-inline-start: var(--p-avatar-lg-group-offset); + } + + .p-avatar-group .ui-avatar-xl + .ui-avatar-xl { + margin-inline-start: var(--p-avatar-xl-group-offset); + } + + /* ─── Круглая форма: clip изображения по максимальному border-radius ─── */ + .p-avatar.p-avatar-circle { + border-radius: var(--p-avatar-extend-circle-border-radius); + overflow: hidden; + } + + .p-overlaybadge.p-overlaybadge { + width: fit-content; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/badge.ts b/src/lib/providers/prime-preset/tokens/components/badge.ts new file mode 100644 index 00000000..9aff43a4 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/badge.ts @@ -0,0 +1,25 @@ +export const badgeCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Badge extend: публикуем кастомные переменные в :root ─── */ + :root { + --p-badge-extend-dot-success-background: ${dt('badge.extend.extDot.success.background')}; + --p-badge-extend-dot-info-background: ${dt('badge.extend.extDot.info.background')}; + --p-badge-extend-dot-warn-background: ${dt('badge.extend.extDot.warn.background')}; + --p-badge-extend-dot-danger-background: ${dt('badge.extend.extDot.danger.background')}; + --p-badge-extend-dot-lg-size: ${dt('badge.extend.extDot.lg.size')}; + --p-badge-extend-dot-xlg-size: ${dt('badge.extend.extDot.xlg.size')}; + --p-badge-extend-padding: ${dt('badge.extend.ext.padding')}; + } + + /* ─── Dot-вариант: бейдж без значения ─── */ + .p-badge.p-badge-dot { + padding: var(--p-badge-extend-padding); + } + + .p-badge.p-badge-dot.p-badge-success { background: var(--p-badge-extend-dot-success-background); } + .p-badge.p-badge-dot.p-badge-info { background: var(--p-badge-extend-dot-info-background); } + .p-badge.p-badge-dot.p-badge-warn { background: var(--p-badge-extend-dot-warn-background); } + .p-badge.p-badge-dot.p-badge-danger { background: var(--p-badge-extend-dot-danger-background); } + + .p-badge.p-badge-dot.p-badge-lg { min-width: unset; width: var(--p-badge-extend-dot-lg-size); height: var(--p-badge-extend-dot-lg-size); } + .p-badge.p-badge-dot.p-badge-xl { min-width: unset; width: var(--p-badge-extend-dot-xlg-size); height: var(--p-badge-extend-dot-xlg-size); } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/breadcrumb.ts b/src/lib/providers/prime-preset/tokens/components/breadcrumb.ts new file mode 100644 index 00000000..5c6fe006 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/breadcrumb.ts @@ -0,0 +1,24 @@ +export const breadcrumbCss = ({ dt }: { dt: (token: string) => string }): string => ` + .p-breadcrumb-item-link { + padding: ${dt('breadcrumb.extend.extItem.padding')}; + font-size: ${dt('fonts.fontSize.200')}; + } + + .p-breadcrumb-item-link:hover { + background: ${dt('breadcrumb.extend.hoverBackground')}; + } + + .p-breadcrumb-item-icon { + font-size: ${dt('breadcrumb.extend.iconSize')}; + } + + .p-breadcrumb-item:last-child .p-breadcrumb-item-link { + opacity: ${dt('opacity.500')}; + pointer-events: none; + cursor: default; + } + + .p-breadcrumb-item:last-child .p-breadcrumb-item-link:hover { + background: ${dt('transparent')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/button.ts b/src/lib/providers/prime-preset/tokens/components/button.ts new file mode 100644 index 00000000..70c844d6 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/button.ts @@ -0,0 +1,172 @@ +/** + * Кастомная CSS-стилизация для компонента p-button. + * Публикует extend-токены как CSS-переменные и применяет глобальные стили. + * Подключается в components.ts: `import { buttonCss } from './components/button'` + */ +export const buttonCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Button extend: публикуем кастомные переменные в :root ─── */ + :root { + --p-button-extend-disabled-background: ${dt('button.extend.disabledBackground')}; + --p-button-extend-disabled-color: ${dt('button.extend.disabledColor')}; + --p-button-extend-border-width: ${dt('button.extend.borderWidth')}; + --p-button-extend-icon-size-sm: ${dt('button.extend.iconSize.sm')}; + --p-button-extend-icon-size-md: ${dt('button.extend.iconSize.md')}; + --p-button-extend-icon-size-lg: ${dt('button.extend.iconSize.lg')}; + --p-button-extend-ext-link-padding-x: ${dt('button.extend.extLink.paddingX')}; + --p-button-extend-ext-link-padding-y: ${dt('button.extend.extLink.paddingY')}; + --p-button-extend-ext-link-color-hover: ${dt('button.extend.extLink.colorHover')}; + --p-button-extend-ext-link-sm-icon-only-width: ${dt('button.extend.extLink.sm.iconOnlyWidth')}; + --p-button-extend-ext-link-base-icon-only-width: ${dt('button.extend.extLink.base.iconOnlyWidth')}; + --p-button-extend-ext-link-lg-icon-only-width: ${dt('button.extend.extLink.lg.iconOnlyWidth')}; + --p-button-extend-ext-link-xlg-icon-only-width: ${dt('button.extend.extLink.xlg.iconOnlyWidth')}; + --p-button-extend-ext-sm-border-radius: ${dt('button.extend.extSm.borderRadius')}; + --p-button-extend-ext-sm-gap: ${dt('button.extend.extSm.gap')}; + --p-button-extend-ext-lg-border-radius: ${dt('button.extend.extLg.borderRadius')}; + --p-button-extend-ext-lg-gap: ${dt('button.extend.extLg.gap')}; + --p-button-extend-ext-lg-height: ${dt('button.extend.extLg.height')}; + --p-button-extend-ext-xlg-border-radius: ${dt('button.extend.extXlg.borderRadius')}; + --p-button-extend-ext-xlg-gap: ${dt('button.extend.extXlg.gap')}; + --p-button-extend-ext-xlg-icon-only-width: ${dt('button.extend.extXlg.iconOnlyWidth')}; + --p-button-extend-ext-xlg-padding-x: ${dt('button.extend.extXlg.paddingX')}; + --p-button-extend-ext-xlg-padding-y: ${dt('button.extend.extXlg.paddingY')}; + --p-button-extend-ext-xlg-height: ${dt('button.extend.extXlg.height')}; + } + + /* ─── Шрифт для текста кнопки ─── */ + .p-button.p-component .p-button-label { + font-family: var(--p-fonts-font-family-heading, 'TT Fellows', sans-serif); + } + + /* ─── Button badge ─── */ + .p-button, .p-ripple.p-button { + position: relative; + overflow: visible; + } + + .p-button .p-badge { + position: absolute; + inset-block-start: 0; + inset-inline-end: 0; + transform: translate(50%, -50%); + transform-origin: 100% 0; + margin: 0; + } + + /* ─── Размеры иконок ─── */ + .p-button .p-button-icon { + font-size: var(--p-button-extend-icon-size-md); + } + .p-button.p-button-sm .p-button-icon { + font-size: var(--p-button-extend-icon-size-sm); + } + .p-button.p-button-lg .p-button-icon { + font-size: var(--p-button-extend-icon-size-lg); + } + .p-button-xlg.p-button .p-button-icon, + .p-button-link.p-button-xlg .p-button-icon { + font-size: var(--p-button-extend-icon-size-lg); + } + + /* ─── Disabled / loading ─── */ + .p-button:is(.p-disabled, :disabled, .p-button-loading) { + mix-blend-mode: inherit; + opacity: var(--p-opacity-1000); + color: var(--p-button-extend-disabled-color); + background: var(--p-button-extend-disabled-background); + border-color: var(--p-button-extend-disabled-background); + } + .p-button.p-button-outlined:is(.p-disabled, :disabled, .p-button-loading) { + color: var(--p-button-extend-disabled-color); + background: transparent; + border-color: transparent; + } + .p-button.p-button-text:is(.p-disabled, :disabled, .p-button-loading) { + color: var(--p-button-extend-disabled-color); + background: transparent; + border-color: transparent; + } + .p-button.p-button-link:is(.p-disabled, :disabled, .p-button-loading) { + color: var(--p-button-extend-disabled-color); + background: transparent; + border-color: transparent; + } + + /* ─── Link кнопки ─── */ + .p-button-link.p-button:is(.p-button, .p-button-xlg) { + padding: var(--p-button-extend-ext-link-padding-y) var(--p-button-extend-ext-link-padding-x); + } + .p-button-link.p-button { + width: min-content; + } + .p-button-link.p-button.p-button-xlg { + font-size: var(--p-fonts-font-size-600); + } + .p-button.p-button-link:not(:disabled):hover { + color: var(--p-button-extend-ext-link-color-hover); + } + .p-button.p-button-link:not(:disabled):hover .p-button-label { + text-decoration: none; + } + + /* ─── Icon-only link кнопки ─── */ + .p-button-link.p-button-icon-only { + width: var(--p-button-extend-ext-link-base-icon-only-width); + height: var(--p-button-extend-ext-link-base-icon-only-width); + } + .p-button-link.p-button-icon-only.p-button-sm { + width: var(--p-button-extend-ext-link-sm-icon-only-width); + height: var(--p-button-extend-ext-link-sm-icon-only-width); + } + .p-button-link.p-button-icon-only.p-button-lg { + width: var(--p-button-extend-ext-link-lg-icon-only-width); + height: var(--p-button-extend-ext-link-lg-icon-only-width); + } + + /* ─── Line-height ─── */ + .p-button-sm { + line-height: var(--p-fonts-line-height-250); + } + .p-button:is(.p-button-lg, .p-button-xlg) { + line-height: var(--p-fonts-line-height-550); + } + + /* ─── Border-radius для lg / xlg ─── */ + .p-button:is(.p-button-lg, .p-button-xlg):not(.p-button-rounded) { + border-radius: var(--p-button-extend-ext-lg-border-radius); + } + .p-button-xlg.p-button:not(.p-button-rounded) { + border-radius: var(--p-button-extend-ext-xlg-border-radius); + } + + /* ─── Padding / font-size / height для lg ─── */ + .p-button-lg.p-button:not(.p-button-icon-only):not(.p-button-link) { + padding: var(--p-button-lg-padding-y) var(--p-button-lg-padding-x); + font-size: var(--p-button-lg-font-size); + height: var(--p-controls-icon-only-850); + } + + /* ─── Padding / font-size / height для xlg ─── */ + .p-button-xlg.p-button:not(.p-button-icon-only):not(.p-button-link) { + padding: var(--p-button-extend-ext-xlg-padding-y) var(--p-button-extend-ext-xlg-padding-x); + font-size: var(--p-fonts-font-size-500); + height: var(--p-controls-icon-only-900); + } + + /* ─── Icon-only размеры ─── */ + .p-button-icon-only { + width: var(--p-button-icon-only-width); + height: var(--p-button-icon-only-width); + } + .p-button-sm.p-button-icon-only { + width: var(--p-button-sm-icon-only-width); + height: var(--p-button-sm-icon-only-width); + } + .p-button-lg.p-button-icon-only { + width: var(--p-button-lg-icon-only-width); + height: var(--p-button-lg-icon-only-width); + } + .p-button-xlg.p-button-icon-only { + width: var(--p-button-extend-ext-xlg-icon-only-width); + height: var(--p-button-extend-ext-xlg-icon-only-width); + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/card.ts b/src/lib/providers/prime-preset/tokens/components/card.ts new file mode 100644 index 00000000..7bfac62c --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/card.ts @@ -0,0 +1,38 @@ +/** + * Кастомная CSS-стилизация для компонента p-card. + * Публикует extend-токены как CSS-переменные и применяет глобальные стили. + * Подключается в map-tokens.ts: `import { cardCss } from './tokens/components/card'` + */ +export const cardCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Card extend: публикуем кастомные переменные в :root ─── */ + :root { + --p-card-extend-border-color: ${dt('card.extend.borderColor')}; + --p-card-extend-border-width: ${dt('card.extend.borderWidth')}; + } + + /* ─── Card base styles ─── */ + .p-card.p-component { + border: var(--p-card-extend-border-width) solid var(--p-card-extend-border-color); + overflow: hidden; + box-shadow: none; + } + + /* ─── Overlay variant ─── */ + .p-card.p-component.shadow-md { + box-shadow: ${dt('overlay.popover.shadow')}; + } + + /* ─── Caption (Title & Subtitle wrapper) ─── */ + .p-card-caption { + display: flex; + flex-direction: column; + gap: ${dt('card.caption.gap')}; + } + + /* ─── Subtitle typography ─── */ + .p-card-subtitle { + font-family: ${dt('fonts.fontFamily.heading')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/checkbox.ts b/src/lib/providers/prime-preset/tokens/components/checkbox.ts new file mode 100644 index 00000000..a1e3d355 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/checkbox.ts @@ -0,0 +1,74 @@ +export const checkboxCss = ({ dt }: { dt: (token: string) => string }): string => ` +/* ─── Label типографика ─── */ +.checkbox-label { + display: flex; + align-items: center; + color: ${dt('text.color')}; + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.300')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: normal; + cursor: pointer; +} + +.checkbox-label--hover { + color: ${dt('text.primaryColor')}; +} + +.checkbox-label--disabled { + color: ${dt('text.mutedColor')}; + cursor: default; +} + +.checkbox-caption { + color: ${dt('text.secondaryColor')}; + font-family: ${dt('fonts.fontFamily.heading')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: normal; +} + +.checkbox-caption--hover { + color: ${dt('text.primaryColor')}; +} + +.checkbox-caption--disabled { + color: ${dt('text.disabledColor')}; +} + +/* Переопределение ширины border для checkbox */ +.p-checkbox-box { + border-width: ${dt('checkbox.root.extend.borderWidth')}; +} + +/* Состояние indeterminate - фон и border как у checked */ +.p-checkbox-indeterminate .p-checkbox-box { + background: ${dt('checkbox.root.checkedBackground')}; + border-color: ${dt('checkbox.root.checkedBorderColor')}; +} + +/* Состояние indeterminate - цвет иконки как у checked */ +.p-checkbox-indeterminate .p-checkbox-icon { + color: ${dt('checkbox.icon.checkedColor')}; +} + +/* Состояние hover для indeterminate */ +.p-checkbox-indeterminate:not(.p-disabled):has(.p-checkbox-input:hover) .p-checkbox-box { + background: ${dt('checkbox.root.checkedHoverBackground')}; + border-color: ${dt('checkbox.root.checkedHoverBorderColor')}; +} + +/* Focus ring с зеленым цветом для валидных состояний */ +.p-checkbox:not(.p-disabled):not(.p-checkbox-checked):not(.p-invalid):has(.p-checkbox-input:focus-visible) .p-checkbox-box, +.p-checkbox-checked:not(.p-disabled):not(.p-invalid):has(.p-checkbox-input:focus-visible) .p-checkbox-box, +.p-checkbox-indeterminate:not(.p-disabled):not(.p-invalid):has(.p-checkbox-input:focus-visible) .p-checkbox-box { + box-shadow: 0 0 0 ${dt('checkbox.root.focusRing.focusRing')} ${dt('focusRing.extend.success')}; +} + +/* Focus ring с красным цветом для состояний с ошибкой */ +.p-checkbox.p-invalid .p-checkbox-box, +.p-checkbox-checked.p-invalid .p-checkbox-box, +.p-checkbox-indeterminate.p-invalid .p-checkbox-box { + box-shadow: 0 0 0 ${dt('focusRing.width')} ${dt('focusRing.extend.invalid')}; +} +`; diff --git a/src/lib/providers/prime-preset/tokens/components/chip.ts b/src/lib/providers/prime-preset/tokens/components/chip.ts new file mode 100644 index 00000000..d4e93526 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/chip.ts @@ -0,0 +1,45 @@ +/** + * Кастомная CSS-стилизация для компонента p-chip. + * Публикует extend-токены как CSS-переменные и применяет глобальные стили. + * Подключается в map-tokens.ts: `import { chipCss } from './components/chip'` + */ +export const chipCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Chip extend: публикуем кастомные переменные в :root ─── */ + :root { + --p-chip-extend-border-color: ${dt('chip.extend.borderColor')}; + --p-chip-extend-border-width: ${dt('chip.extend.borderWidth')}; + } + + /* ─── Граница чипа ─── */ + .p-chip { + border: var(--p-chip-extend-border-width) solid var(--p-chip-extend-border-color); + } + + /* ─── Типографика лейбла ─── */ + .p-chip-label { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.300')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: ${dt('fonts.lineHeight.400')}; + } + + /* ─── Сброс уменьшенного padding PrimeNG при наличии кнопки удаления ─── */ + .p-chip:has(.p-chip-remove-icon) { + padding-inline-end: ${dt('chip.root.paddingX')}; + } + + /* ─── Focus ring иконки удаления ─── */ + .p-chip-remove-icon:focus-visible { + outline: ${dt('chip.removeIcon.focusRing.width')} solid ${dt('focusRing.extend.success')}; + } + + /* ─── Disabled состояние ─── */ + .p-chip.p-disabled { + opacity: ${dt('opacity.500')}; + pointer-events: none; + } + + .p-chip.p-disabled .p-chip-remove-icon { + pointer-events: none; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/dialog.ts b/src/lib/providers/prime-preset/tokens/components/dialog.ts new file mode 100644 index 00000000..c157e61e --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/dialog.ts @@ -0,0 +1,53 @@ +export const dialogCss = ({ dt }: { dt: (token: string) => string }): string => ` + .p-dialog .p-dialog-title { + font-family: ${dt('fonts.fontFamily.heading')}; + font-size: ${dt('dialog.title.fontSize')}; + font-weight: ${dt('dialog.title.fontWeight')}; + line-height: ${dt('fonts.lineHeight.550')}; + } + + .p-dialog .p-dialog-content { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.300')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: ${dt('fonts.lineHeight.500')}; + } + + .p-dialog .p-dialog-header { + border-bottom: ${dt('borderWidth.100')} solid ${dt('dialog.root.borderColor')}; + display: flex; + align-items: center; + justify-content: space-between; + } + + .p-dialog .p-dialog-header-actions { + display: flex; + align-items: center; + margin-left: auto; + } + + .p-dialog .p-dialog-header-actions .p-dialog-close-button.p-button-text:focus-visible, + .p-dialog .p-dialog-header-actions .p-dialog-close-button.p-button:focus-visible, + .p-dialog .p-button-text:focus-visible, + .p-dialog .p-button:focus-visible { + outline: 0 none; + outline-color: transparent; + box-shadow: none; + } + + .p-dialog { + width: ${dt('sizing.80x')}; + } + + .p-dialog.p-component.p-dialog-sm { + width: ${dt('overlay.sm.width')}; + } + + .p-dialog.p-component.p-dialog-lg { + width: ${dt('overlay.lg.width')}; + } + + .p-dialog.p-component.p-dialog-xlg { + width: ${dt('overlay.xlg.width')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/divider.ts b/src/lib/providers/prime-preset/tokens/components/divider.ts new file mode 100644 index 00000000..de3e3581 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/divider.ts @@ -0,0 +1,34 @@ +/** + * Кастомная CSS-стилизация для компонента p-divider. + * Публикует extend-токены как CSS-переменные и применяет глобальные стили. + * Подключается в map-tokens.ts: `import { dividerCss } from './components/divider'` + */ +export const dividerCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Divider extend: публикуем кастомные переменные в :root ─── */ + :root { + --p-divider-extend-content-gap: ${dt('divider.extend.content.gap')}; + --p-divider-extend-icon-size: ${dt('divider.extend.iconSize')}; + } + + /* ─── Контент разделителя ─── */ + .p-divider-content { + display: flex; + align-items: center; + gap: var(--p-divider-extend-content-gap); + font-family: ${dt('fonts.fontFamily.heading')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.demibold')}; + } + + .p-divider-content .ti { + font-size: var(--p-divider-extend-icon-size); + } + + /* ─── Вертикальное выравнивание ─── */ + .p-divider.p-divider-vertical.p-divider-top .p-divider-content { + align-items: flex-start; + } + .p-divider.p-divider-vertical.p-divider-bottom .p-divider-content { + align-items: flex-end; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/inputgroup.ts b/src/lib/providers/prime-preset/tokens/components/inputgroup.ts new file mode 100644 index 00000000..5efcbff0 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/inputgroup.ts @@ -0,0 +1,100 @@ +export const inputgroupCss = ({ dt }: { dt: (token: string) => string }): string => ` + +/* ─── Корректировка flex-layout через Angular-обёртки ─── */ + +/* + * display: contents делает враппер-элементы прозрачными в layout-дереве: + * p-inputgroupaddon и input.p-inputtext становятся прямыми flex-элементами + * p-inputgroup. Это позволяет: + * - align-items: stretch работать напрямую → правильная высота аддона + * - границам быть на одном уровне → нет удвоения top/bottom border + * - flex: 1 1 auto; width: 1% на input работать нативно через PrimeNG + * CSS-селекторы по-прежнему работают (DOM-дерево не меняется). + */ +.p-inputgroup > input-group-addon { + display: contents; +} + +.p-inputgroup > input-text { + display: contents; +} + +/* ─── Корректировка border-radius и границ ─── */ + +/* + * p-inputgroupaddon является :first-child И :last-child своего прямого родителя + * input-group-addon, поэтому PrimeNG добавляет ему оба inline-бордера. + * Сбрасываем их и переназначаем по позиции аддона в группе. + */ +.p-inputgroup > input-group-addon > .p-inputgroupaddon { + border-radius: 0; + border-inline-start: none; + border-inline-end: none; +} + +/* Первый элемент группы — аддон: левые углы + левая граница */ +.p-inputgroup > input-group-addon:first-child > .p-inputgroupaddon { + border-start-start-radius: ${dt('inputgroup.addon.borderRadius')}; + border-end-start-radius: ${dt('inputgroup.addon.borderRadius')}; + border-inline-start: ${dt('inputgroup.extend.borderWidth')} solid ${dt('inputgroup.addon.borderColor')}; +} + +/* Последний элемент группы — аддон: правые углы + правая граница */ +.p-inputgroup > input-group-addon:last-child > .p-inputgroupaddon { + border-start-end-radius: ${dt('inputgroup.addon.borderRadius')}; + border-end-end-radius: ${dt('inputgroup.addon.borderRadius')}; + border-inline-end: ${dt('inputgroup.extend.borderWidth')} solid ${dt('inputgroup.addon.borderColor')}; +} + +/* Аддон сразу после другого аддона: левая граница как разделитель */ +.p-inputgroup > input-group-addon + input-group-addon > .p-inputgroupaddon { + border-inline-start: ${dt('inputgroup.extend.borderWidth')} solid ${dt('inputgroup.addon.borderColor')}; +} + +/* Сброс border-radius у input внутри input-text-обёртки */ +.p-inputgroup > input-text .p-inputtext { + border-radius: 0; + margin: 0; +} + +/* Аддон: только горизонтальные границы (top/bottom), inline-бордеры управляются позиционно */ +.p-inputgroup > input-group-addon > .p-inputgroupaddon { + border-block-start: ${dt('inputgroup.extend.borderWidth')} solid ${dt('inputgroup.addon.borderColor')}; + border-block-end: ${dt('inputgroup.extend.borderWidth')} solid ${dt('inputgroup.addon.borderColor')}; +} + +/* Первый элемент группы — input: левые углы */ +.p-inputgroup > input-text:first-child .p-inputtext { + border-start-start-radius: ${dt('inputgroup.addon.borderRadius')}; + border-end-start-radius: ${dt('inputgroup.addon.borderRadius')}; +} + +/* Последний элемент группы — input: правые углы */ +.p-inputgroup > input-text:last-child .p-inputtext { + border-start-end-radius: ${dt('inputgroup.addon.borderRadius')}; + border-end-end-radius: ${dt('inputgroup.addon.borderRadius')}; +} + +/* ─── Addon в disabled состоянии ─── */ +.p-inputgroup:has(input[disabled]) .p-inputgroupaddon, +.p-inputgroup:has(.p-inputtext[disabled]) .p-inputgroupaddon, +.p-inputgroup:has(.p-component[disabled]) .p-inputgroupaddon { + background: ${dt('inputtext.root.disabledBackground')}; + color: ${dt('inputtext.root.disabledColor')}; +} + +/* ─── Иконка внутри addon ─── */ +.p-inputgroup .p-inputgroupaddon i { + font-size: ${dt('inputgroup.extend.iconSize')}; +} + +/* ─── Extra Large ─── */ +.p-inputgroup.p-inputgroup-xlg .p-inputgroupaddon { + font-size: ${dt('inputtext.extend.extXlg.fontSize')}; + padding: ${dt('inputtext.extend.extXlg.paddingY')} ${dt('inputtext.extend.extXlg.paddingX')}; +} + +.p-inputgroup.p-inputgroup-xlg .p-inputgroupaddon i { + font-size: ${dt('inputtext.extend.extXlg.fontSize')}; +} +`; diff --git a/src/lib/providers/prime-preset/tokens/components/inputmask.ts b/src/lib/providers/prime-preset/tokens/components/inputmask.ts new file mode 100644 index 00000000..8132a1ac --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/inputmask.ts @@ -0,0 +1,8 @@ +export const inputmaskCss = ({ dt }: { dt: (token: string) => string }): string => ` + +/* ─── Sizes ─── */ +input-mask.input-mask-xlg .p-inputtext { + font-size: ${dt('inputtext.extend.extXlg.fontSize')}; + padding: ${dt('inputtext.extend.extXlg.paddingY')} ${dt('inputtext.extend.extXlg.paddingX')}; +} +`; diff --git a/src/lib/providers/prime-preset/tokens/components/inputnumber.ts b/src/lib/providers/prime-preset/tokens/components/inputnumber.ts new file mode 100644 index 00000000..950a3c1c --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/inputnumber.ts @@ -0,0 +1,57 @@ +export const inputnumberCss = ({ dt }: { dt: (token: string) => string }): string => ` + +/* ─── Базовые стили ─── */ +.p-inputnumber .p-inputnumber-input { + font-family: ${dt('fonts.fontFamily.base')}; +} + +.p-inputnumber .p-inputnumber-input::placeholder { + font-family: ${dt('fonts.fontFamily.base')}; +} + +.p-floatlabel:has(.p-inputnumber) label { + font-family: ${dt('fonts.fontFamily.base')}; +} + +/* ─── Кнопки увеличения/уменьшения ─── */ +.p-inputnumber-button { + border-width: ${dt('inputnumber.extend.borderWidth')}; +} + +.p-inputnumber-horizontal .p-inputnumber-button { + min-height: ${dt('inputnumber.extend.extButton.height')}; + border: ${dt('inputnumber.extend.borderWidth')} solid ${dt('inputnumber.button.borderColor')}; +} + +.p-inputnumber-horizontal .p-inputnumber-decrement-button { + border-right: none; +} + +/* ─── Focus ─── */ +.p-inputnumber .p-inputnumber-input:enabled:focus { + box-shadow: 0 0 0 ${dt('inputtext.focusRing.width')} ${dt('inputtext.focusRing.color')}; +} + +/* ─── Invalid + Focus ─── */ +.p-inputnumber.p-invalid .p-inputnumber-input:focus { + border-color: ${dt('inputtext.root.invalidBorderColor')}; + box-shadow: 0 0 0 1px ${dt('inputtext.root.invalidBorderColor')}; +} + +/* ─── Disabled состояние ─── */ +.p-inputnumber-horizontal:has(.p-inputnumber-input:disabled) .p-inputnumber-button { + background: ${dt('inputtext.root.disabledBackground')}; + color: ${dt('inputtext.root.disabledColor')}; +} + +/* ─── FloatLabel: кнопки на полную высоту поля ─── */ +.p-floatlabel:has(.p-inputnumber-horizontal) .p-inputnumber-button { + align-self: stretch; +} + +/* ─── Extra Large ─── */ +.p-inputnumber.p-inputnumber-xlg .p-inputnumber-input { + font-size: ${dt('inputtext.extend.extXlg.fontSize')}; + padding: ${dt('inputtext.extend.extXlg.paddingY')} ${dt('inputtext.extend.extXlg.paddingX')}; +} +`; diff --git a/src/lib/providers/prime-preset/tokens/components/inputotp.ts b/src/lib/providers/prime-preset/tokens/components/inputotp.ts new file mode 100644 index 00000000..de80e1d9 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/inputotp.ts @@ -0,0 +1,51 @@ +export const inputotpCss = ({ dt }: { dt: (token: string) => string }): string => ` +/* Стили границы */ +.p-inputotp.p-component .p-inputtext { + border-width: ${dt('inputotp.extend.borderWidth')}; + padding-inline: 0; +} + +/* ─── Disabled ─── */ +.p-inputotp.p-component .p-inputtext:disabled { + background: ${dt('inputtext.root.disabledBackground')}; + color: ${dt('inputtext.root.disabledColor')}; +} + +/* ─── Readonly ─── */ +.p-inputotp.p-component .p-inputtext:enabled:read-only { + background: ${dt('inputtext.extend.readonlyBackground')}; + color: ${dt('inputtext.root.color')}; +} + +/* ─── Focus ─── */ +.p-inputotp.p-component .p-inputtext:enabled:focus { + box-shadow: 0 0 0 ${dt('inputtext.focusRing.width')} ${dt('inputtext.focusRing.color')}; +} + +/* ─── Invalid + Focus ─── */ +.p-inputotp.p-component .p-inputtext.p-invalid:focus { + border-color: ${dt('inputtext.root.invalidBorderColor')}; + box-shadow: 0 0 0 ${dt('inputtext.focusRing.width')} ${dt('focusRing.extend.invalid')}; +} + +/* ─── Small ─── */ +.p-inputotp.p-component .p-inputtext.p-inputtext-sm { + padding-block: ${dt('inputtext.root.sm.paddingY')}; +} + +/* ─── Base ─── */ +.p-inputotp.p-component .p-inputtext:not(.p-inputtext-sm):not(.p-inputtext-lg):not(.p-inputtext-xlg) { + padding-block: ${dt('inputtext.root.paddingY')}; +} + +/* ─── Large ─── */ +.p-inputotp.p-component .p-inputtext.p-inputtext-lg { + padding-block: ${dt('inputtext.root.lg.paddingY')}; +} + +/* ─── Extra Large ─── */ +.p-inputotp.p-component.p-inputotp-xlg .p-inputtext { + font-size: ${dt('inputtext.extend.extXlg.fontSize')}; + padding-block: ${dt('inputtext.extend.extXlg.paddingY')}; +} +`; diff --git a/src/lib/providers/prime-preset/tokens/components/inputtext.ts b/src/lib/providers/prime-preset/tokens/components/inputtext.ts new file mode 100644 index 00000000..25541c20 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/inputtext.ts @@ -0,0 +1,58 @@ +export const inputtextCss = ({ dt }: { dt: (token: string) => string }): string => ` + +/* ─── Базовые стили ─── */ +.p-inputtext { + border-width: ${dt('inputtext.extend.borderWidth')}; + line-height: ${dt('fonts.lineHeight.250')}; + font-family: ${dt('fonts.fontFamily.base')}; +} + +.p-inputtext::placeholder { + font-family: ${dt('fonts.fontFamily.base')}; +} + +.p-floatlabel:has(.p-inputtext) label { + font-family: ${dt('fonts.fontFamily.base')}; +} + +/* ─── Disabled ─── */ +.p-inputtext:disabled { + background: ${dt('inputtext.root.disabledBackground')}; + color: ${dt('inputtext.root.disabledColor')}; +} + +/* ─── Readonly ─── */ +.p-inputtext:enabled:read-only { + background: ${dt('inputtext.extend.readonlyBackground')}; + color: ${dt('inputtext.root.color')}; +} + +/* ─── Focus ─── */ +.p-inputtext:enabled:focus { + box-shadow: 0 0 0 ${dt('inputtext.focusRing.width')} ${dt('inputtext.focusRing.color')}; +} + +/* ─── Invalid + Focus ─── */ +.p-inputtext.p-invalid:focus { + border-color: ${dt('inputtext.root.invalidBorderColor')}; + box-shadow: 0 0 0 ${dt('inputtext.focusRing.width')} ${dt('focusRing.extend.invalid')}; +} + +/* ─── Extra Large ─── */ +.p-inputtext.p-inputtext-xlg { + font-size: ${dt('inputtext.extend.extXlg.fontSize')}; + padding: ${dt('inputtext.extend.extXlg.paddingY')} ${dt('inputtext.extend.extXlg.paddingX')}; +} + +/* ─── IconField ─── */ +.p-iconfield[data-pc-name="iconfield"] { + width: fit-content; +} + +.p-iconfield .p-inputicon { + font-size: ${dt('inputtext.extend.iconSize')}; + width: ${dt('inputtext.extend.iconSize')}; + height: ${dt('inputtext.extend.iconSize')}; + cursor: pointer; +} +`; diff --git a/src/lib/providers/prime-preset/tokens/components/listbox.ts b/src/lib/providers/prime-preset/tokens/components/listbox.ts new file mode 100644 index 00000000..05489d17 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/listbox.ts @@ -0,0 +1,49 @@ +export const listboxCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Listbox extend: публикуем кастомные переменные в :root ─── */ + :root { + --p-listbox-extend-ext-option-gap: ${dt('listbox.extend.extOption.gap')}; + --p-listbox-extend-ext-option-label-gap: ${dt('listbox.extend.extOption.label.gap')}; + --p-listbox-extend-ext-option-caption-color: ${dt('listbox.extend.extOption.caption.color')}; + --p-listbox-extend-ext-option-caption-striped-color: ${dt('listbox.extend.extOption.caption.stripedColor')}; + } + + /* ─── Расположение элемента списка ─── */ + .p-listbox-option { + display: flex; + align-items: center; + gap: var(--p-listbox-extend-ext-option-gap); + } + + /* Многострочный контент (иконка + label-group): выравнивание по верху */ + .p-listbox-option:has(.p-listbox-option-label-group) { + align-items: flex-start; + } + + .p-listbox-option:has(.p-listbox-option-check-icon) { + gap: unset; + } + + /* ─── Группа: текст + подпись ─── */ + .p-listbox-option-label-group { + display: flex; + flex-direction: column; + gap: var(--p-listbox-extend-ext-option-label-gap); + } + + /* ─── Подпись элемента списка ─── */ + .p-listbox-option-caption { + color: var(--p-listbox-extend-ext-option-caption-color); + font-size: ${dt('fonts.fontSize.200')}; + font-family: ${dt('fonts.fontFamily.heading')}; + } + + /* ─── Галочка выбора ─── */ + .p-listbox-check-icon { + margin-inline-start: ${dt('listbox.checkmark.gutterStart')}; + margin-inline-end: ${dt('listbox.checkmark.gutterEnd')}; + } + + .p-listbox .p-listbox-list .p-listbox-option.p-listbox-option-selected .p-listbox-option-check-icon { + color: ${dt('listbox.option.selectedColor')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/megamenu.ts b/src/lib/providers/prime-preset/tokens/components/megamenu.ts new file mode 100644 index 00000000..542864b7 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/megamenu.ts @@ -0,0 +1,42 @@ +export const megamenuCss = ({ dt }: { dt: (token: string) => string }): string => ` +/* ─── Размер иконок ─── */ +.p-megamenu-submenu-icon, +.p-megamenu-item-icon { + font-size: ${dt('megamenu.extend.iconSize')}; +} + +/* ─── Типографика пунктов меню ─── */ +.p-megamenu-item-label { + font-size: ${dt('fonts.fontSize.300')}; + font-weight: ${dt('fonts.fontWeight.regular')}; +} + +/* ─── Caption (описание) для кастомных пунктов ─── */ +.p-megamenu .megamenu-item-label { + display: flex; + flex-direction: column; + gap: ${dt('megamenu.extend.extItem.caption.gap')}; +} + +.p-megamenu .megamenu-item-caption { + font-size: ${dt('fonts.fontSize.200')}; + color: ${dt('megamenu.extend.extItem.caption.color')}; +} + +/* ─── Иконка мобильной кнопки ─── */ +.p-megamenu-mobile-button-icon { + font-size: ${dt('megamenu.extend.iconSize')}; +} + +/* ─── Размер ширины панели по контенту и позиционирование для активных пунктов горизонтального вида от начала пункта меню ─── */ +.p-megamenu-root-list > .p-megamenu-item-active > .p-megamenu-overlay, +.p-megamenu-vertical .p-megamenu-root-list > .p-megamenu-item-active > .p-megamenu-overlay { + min-width: fit-content; + left: unset; +} + +/* ─── Позиционирование оверлея от пункта для вертикального вида ─── */ +.p-megamenu.p-megamenu-vertical .p-megamenu-root-list > .p-megamenu-item-active > .p-megamenu-overlay { + left: 100%; +} +`; diff --git a/src/lib/providers/prime-preset/tokens/components/menu.ts b/src/lib/providers/prime-preset/tokens/components/menu.ts new file mode 100644 index 00000000..2ef5fcfc --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/menu.ts @@ -0,0 +1,67 @@ +export const menuCss = ({ dt }: { dt: (token: string) => string }): string => ` + .p-menu.p-component { + padding: ${dt('menu.extend.paddingY')} ${dt('menu.extend.paddingX')}; + } + + .p-menu .p-menu-item-content .p-menu-item-link .p-menu-item-label { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.300')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: ${dt('fonts.lineHeight.400')}; + } + + .p-menu .p-menu-item-content .menu-item-label { + display: flex; + flex-direction: column; + gap: ${dt('menu.extend.extItem.caption.gap')}; + } + + .p-menu .p-menu-item-content .menu-item-caption { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + color: ${dt('menu.colorScheme.light.extend.extItem.caption.color')}; + } + + .p-menu .p-menu-item:not(.p-disabled) .p-menu-item-content:hover, + .p-menu .p-menu-item:not(.p-disabled) .p-menu-item-content:hover .p-menu-item-link, + .p-menu .p-menu-item:not(.p-disabled) .p-menu-item-content:hover .p-menu-item-label, + .p-menu .p-menu-item:not(.p-disabled) .p-menu-item-content:hover .p-menu-item-icon { + background: ${dt('menu.colorScheme.light.item.focusBackground')}; + color: ${dt('menu.colorScheme.light.item.focusColor')}; + } + + .p-menu .p-menu-item.p-menuitem-checked > .p-menu-item-content, + .p-menu .p-menu-item.p-focus > .p-menu-item-content { + background: ${dt('menu.extend.extItem.activeBackground')}; + color: ${dt('menu.extend.extItem.activeColor')}; + } + + .p-menu .p-menu-item.p-menuitem-checked > .p-menu-item-content .p-menu-item-link, + .p-menu .p-menu-item.p-menuitem-checked > .p-menu-item-content .p-menu-item-label, + .p-menu .p-menu-item.p-focus > .p-menu-item-content .p-menu-item-link, + .p-menu .p-menu-item.p-focus > .p-menu-item-content .p-menu-item-label { + color: ${dt('menu.extend.extItem.activeColor')}; + } + + .p-menu .p-menu-item.p-menuitem-checked > .p-menu-item-content .p-menu-item-icon, + .p-menu .p-menu-item.p-focus > .p-menu-item-content .p-menu-item-icon { + color: ${dt('menu.colorScheme.light.extend.extItem.icon.activeColor')}; + } + + .p-menu .p-menu-item.p-menuitem-checked:not(.p-disabled) > .p-menu-item-content:hover { + background: ${dt('menu.colorScheme.light.item.focusBackground')}; + color: ${dt('menu.colorScheme.light.item.focusColor')}; + } + + .p-menu .p-menu-item.p-menuitem-checked:not(.p-disabled) > .p-menu-item-content:hover .p-menu-item-icon { + color: ${dt('menu.colorScheme.light.item.focusColor')}; + } + + .p-menu .p-menu-submenu-label { + text-transform: uppercase; + font-size: ${dt('fonts.fontSize.200')}; + font-family: ${dt('fonts.fontFamily.heading')}; + line-height: ${dt('fonts.lineHeight.400')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/message.ts b/src/lib/providers/prime-preset/tokens/components/message.ts new file mode 100644 index 00000000..6e39b2bc --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/message.ts @@ -0,0 +1,150 @@ +export const messageCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* Основной контейнер message */ + .p-message { + width: ${dt('message.extend.width')}; + overflow: hidden; + position: relative; + } + + /* Контент message с приоритизацией align-items */ + .p-message .p-message-content { + display: flex; + align-items: flex-start; + width: stretch; + border-radius: ${dt('message.root.borderRadius')}; + } + + /* Текстовый блок message */ + .p-message-text { + flex: 1; + display: flex; + flex-direction: column; + gap: ${dt('message.extend.extText.gap')}; + } + + /* Заголовок message */ + .p-message-summary { + font-family: ${dt('fonts.fontFamily.base')}; + font-weight: ${dt('message.text.fontWeight')}; + line-height: ${dt('fonts.lineHeight.250')}; + font-size: ${dt('message.text.fontSize')}; + } + + /* Детальное описание message */ + .p-message .p-message-detail { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.200')}; + line-height: ${dt('fonts.lineHeight.250')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + } + + /* Кнопка закрытия message */ + .p-message .p-message-content .p-message-close-button { + width: ${dt('message.closeButton.width')}; + height: ${dt('message.closeButton.height')}; + margin: 0; + padding: 0; + right: 0; + } + + /* Общие стили border для кнопки закрытия всех типов message */ + .p-message-info .p-message-close-button, + .p-message-success .p-message-close-button, + .p-message-warn .p-message-close-button, + .p-message-error .p-message-close-button { + border: ${dt('message.extend.extCloseButton.width')} solid; + } + + /* Общие стили для акцентной линии всех типов message */ + .p-message-info .p-message-accent-line, + .p-message-success .p-message-accent-line, + .p-message-warn .p-message-accent-line, + .p-message-error .p-message-accent-line { + width: ${dt('message.extend.extAccentLine.width')}; + position: absolute; + left: 0; + top: 0; + bottom: 0; + border-radius: ${dt('message.root.borderRadius')} 0 0 ${dt('message.root.borderRadius')}; + } + + /* Стили для message типа Info */ + .p-message-info .p-message-icon { + color: ${dt('message.extend.extInfo.color')}; + } + + .p-message-info .p-message-close-button { + color: ${dt('message.extend.extInfo.closeButton.color')}; + border-color: ${dt('message.extend.extInfo.closeButton.borderColor')}; + } + + .p-message.p-message-info .p-message-close-button.p-button-text:not(:disabled):hover { + background: ${dt('message.colorScheme.light.info.closeButton.hoverBackground')}; + border-color: ${dt('message.extend.extInfo.closeButton.borderColor')}; + color: ${dt('message.extend.extInfo.closeButton.color')}; + } + + .p-message-info .p-message-accent-line { + background: ${dt('message.extend.extInfo.color')}; + } + + /* Стили для message типа Success */ + .p-message-success .p-message-icon { + color: ${dt('message.extend.extSuccess.color')}; + } + + .p-message-success .p-message-close-button { + color: ${dt('message.extend.extSuccess.closeButton.color')}; + border-color: ${dt('message.extend.extSuccess.closeButton.borderColor')}; + } + + .p-message.p-message-success .p-message-close-button.p-button-text:not(:disabled):hover { + background: ${dt('message.colorScheme.light.success.closeButton.hoverBackground')}; + border-color: ${dt('message.extend.extSuccess.closeButton.borderColor')}; + color: ${dt('message.extend.extSuccess.closeButton.color')}; + } + + .p-message-success .p-message-accent-line { + background: ${dt('message.extend.extSuccess.color')}; + } + + /* Стили для message типа Warn */ + .p-message-warn .p-message-icon { + color: ${dt('message.extend.extWarn.color')}; + } + + .p-message-warn .p-message-close-button { + color: ${dt('message.extend.extWarn.closeButton.color')}; + border-color: ${dt('message.extend.extWarn.closeButton.borderColor')}; + } + + .p-message.p-message-warn .p-message-close-button.p-button-text:not(:disabled):hover { + background: ${dt('message.colorScheme.light.warn.closeButton.hoverBackground')}; + border-color: ${dt('message.extend.extWarn.closeButton.borderColor')}; + color: ${dt('message.extend.extWarn.closeButton.color')}; + } + + .p-message-warn .p-message-accent-line { + background: ${dt('message.extend.extWarn.color')}; + } + + /* Стили для message типа Error */ + .p-message-error .p-message-icon { + color: ${dt('message.extend.extError.color')}; + } + + .p-message-error .p-message-close-button { + color: ${dt('message.extend.extError.closeButton.color')}; + border-color: ${dt('message.extend.extError.closeButton.borderColor')}; + } + + .p-message.p-message-error .p-message-close-button.p-button-text:not(:disabled):hover { + background: ${dt('message.colorScheme.light.error.closeButton.hoverBackground')}; + border-color: ${dt('message.extend.extError.closeButton.borderColor')}; + color: ${dt('message.extend.extError.closeButton.color')}; + } + + .p-message-error .p-message-accent-line { + background: ${dt('message.extend.extError.color')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/metergroup.ts b/src/lib/providers/prime-preset/tokens/components/metergroup.ts new file mode 100644 index 00000000..91b53797 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/metergroup.ts @@ -0,0 +1,17 @@ +export const metergroupCss = ({ dt }: { dt: (path: string) => string }) => ` + .p-metergroup-label-text { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: ${dt('fonts.lineHeight.200')}; + color: ${dt('metergroup.extend.extLabel.color')}; + } + .p-metergroup-label .p-metergroup-label-text + span { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.medium')}; + color: ${dt('text.color')}; + } + .p-metergroup-horizontal .p-metergroup-meter { min-height: 100%; } + .p-metergroup-vertical .p-metergroup-meter { min-width: 100%; } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/paginator.ts b/src/lib/providers/prime-preset/tokens/components/paginator.ts new file mode 100644 index 00000000..061b759f --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/paginator.ts @@ -0,0 +1,18 @@ +export const paginatorCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Current page report ─── */ + .p-paginator .p-paginator-current { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.300')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: ${dt('fonts.lineHeight.250')}; + color: ${dt('paginator.currentPageReport.color')}; + } + + /* ─── Page number buttons ─── */ + .p-paginator .p-paginator-page { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.300')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: ${dt('fonts.lineHeight.250')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/panelmenu.ts b/src/lib/providers/prime-preset/tokens/components/panelmenu.ts new file mode 100644 index 00000000..0ef79947 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/panelmenu.ts @@ -0,0 +1,68 @@ +export const panelmenuCss = ({ dt }: { dt: (token: string) => string }): string => ` + .p-panelmenu { + gap: ${dt('panelmenu.extend.extPanel.gap')}; + } + + .p-panelmenu-panel { + padding: ${dt('panelmenu.extend.extPanel.gap')}; + } + + .p-panelmenu-header-content, + .p-panelmenu-item-content { + font-size: ${dt('fonts.fontSize.300')}; + } + + .p-panelmenu-submenu-icon { + font-size: ${dt('panelmenu.extend.iconSize')}; + } + + /* ─── Active & Focused States ─── */ + + .p-panelmenu .p-panelmenu-item.p-panelmenu-item-active > .p-panelmenu-item-content, + .p-panelmenu .p-panelmenu-item.p-focus > .p-panelmenu-item-content, + .p-panelmenu .p-panelmenu-header.p-focus .p-panelmenu-header-content { + background: ${dt('panelmenu.extend.extItem.activeBackground')}; + color: ${dt('panelmenu.extend.extItem.activeColor')}; + } + + .p-panelmenu .p-panelmenu-item.p-panelmenu-item-active > .p-panelmenu-item-content :is(.p-panelmenu-item-link, .p-panelmenu-item-label, .p-panelmenu-item-icon, .p-panelmenu-submenu-icon), + .p-panelmenu .p-panelmenu-item.p-focus > .p-panelmenu-item-content :is(.p-panelmenu-item-link, .p-panelmenu-item-label, .p-panelmenu-item-icon, .p-panelmenu-header-icon, .p-panelmenu-submenu-icon), + .p-panelmenu .p-panelmenu-header.p-focus .p-panelmenu-header-content :is(.p-panelmenu-header-link, .p-panelmenu-header-label, .p-panelmenu-submenu-icon, .p-panelmenu-item-icon, .p-panelmenu-header-icon) { + color: ${dt('panelmenu.extend.extItem.activeColor')}; + } + + /* ─── Hover on Active States ─── */ + + .p-panelmenu .p-panelmenu-item.p-panelmenu-item-active:not(.p-disabled) > .p-panelmenu-item-content:hover, + .p-panelmenu .p-panelmenu-item.p-focus:not(.p-disabled) > .p-panelmenu-item-content:hover, + .p-panelmenu .p-panelmenu-header.p-focus .p-panelmenu-header-content:hover { + background: ${dt('panelmenu.item.focusBackground')}; + color: ${dt('panelmenu.item.focusColor')}; + } + + .p-panelmenu .p-panelmenu-item.p-panelmenu-item-active:not(.p-disabled) > .p-panelmenu-item-content:hover :is(.p-panelmenu-item-link, .p-panelmenu-item-label), + .p-panelmenu .p-panelmenu-item.p-focus:not(.p-disabled) > .p-panelmenu-item-content:hover :is(.p-panelmenu-item-link, .p-panelmenu-item-label), + .p-panelmenu .p-panelmenu-header.p-focus .p-panelmenu-header-content:hover :is(.p-panelmenu-header-link, .p-panelmenu-header-label) { + color: ${dt('panelmenu.item.focusColor')}; + } + + .p-panelmenu .p-panelmenu-item.p-panelmenu-item-active:not(.p-disabled) > .p-panelmenu-item-content:hover :is(.p-panelmenu-item-icon, .p-panelmenu-submenu-icon), + .p-panelmenu .p-panelmenu-item.p-focus:not(.p-disabled) > .p-panelmenu-item-content:hover :is(.p-panelmenu-item-icon, .p-panelmenu-submenu-icon), + .p-panelmenu .p-panelmenu-header.p-focus .p-panelmenu-header-content:hover :is(.p-panelmenu-submenu-icon, .p-panelmenu-item-icon) { + color: ${dt('panelmenu.item.icon.focusColor')}; + } + + /* ─── Captions ─── */ + + .p-panelmenu .panelmenu-item-label { + display: flex; + flex-direction: column; + gap: ${dt('panelmenu.extend.extItem.caption.gap')}; + } + + .p-panelmenu .panelmenu-item-caption { + font-size: ${dt('fonts.fontSize.200')}; + line-height: ${dt('fonts.lineHeight.450')}; + color: ${dt('panelmenu.extend.extItem.caption.color')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/password.ts b/src/lib/providers/prime-preset/tokens/components/password.ts new file mode 100644 index 00000000..b682ad02 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/password.ts @@ -0,0 +1,97 @@ +/** + * Кастомная CSS-стилизация для компонента p-password. + * Подключается в map-tokens.ts: `import { passwordCss } from './components/password'` + */ +export const passwordCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Иконки управления ─── */ + .p-password-toggle-mask-icon, + .p-icon.p-password-toggle-mask-icon.p-password-unmask-icon { + cursor: pointer; + color: ${dt('password.icon.color')}; + } + + /* ─── Оверлей и индикатор ─── */ + .p-password-overlay { + border-width: ${dt('password.extend.borderWidth')}; + } + + .p-password-meter-text { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: ${dt('fonts.lineHeight.250')}; + color: ${dt('password.overlay.color')}; + } + + /* ─── Focus ─── */ + .p-password:has(.p-inputtext:enabled:focus) { + box-shadow: 0 0 0 ${dt('inputtext.focusRing.width')} ${dt('inputtext.focusRing.color')}; + border-radius: ${dt('inputtext.root.borderRadius')}; + } + + /* ─── Invalid + Focus ─── */ + .p-password:has(.p-inputtext.p-invalid:focus) { + box-shadow: 0 0 0 ${dt('inputtext.focusRing.width')} ${dt('focusRing.extend.invalid')}; + border-radius: ${dt('inputtext.root.borderRadius')}; + } + + .p-password:has(.p-inputtext.p-invalid:focus) .p-inputtext { + border-color: ${dt('inputtext.root.invalidBorderColor')}; + } + + /* ─── FloatLabel ─── */ + .p-floatlabel:has(.p-password) label { + font-family: ${dt('fonts.fontFamily.base')}; + font-weight: ${dt('floatlabel.root.fontWeight')}; + line-height: ${dt('fonts.lineHeight.250')}; + color: ${dt('floatlabel.root.color')}; + } + + .p-floatlabel:has(.p-password) .p-floatlabel-active label { + font-weight: ${dt('floatlabel.root.active.fontWeight')}; + } + + .p-floatlabel-in .p-password .p-inputtext { + font-family: ${dt('fonts.fontFamily.base')}; + padding-block-start: ${dt('floatlabel.in.input.paddingTop')}; + padding-block-end: ${dt('floatlabel.in.input.paddingBottom')}; + } + + /* ─── Кастомный контент (правила пароля) ─── */ + .p-password-rules { + display: flex; + flex-direction: column; + gap: ${dt('password.content.gap')}; + margin: 0; + padding: 0; + list-style: none; + } + + .p-password-rule { + display: flex; + align-items: center; + gap: ${dt('password.content.gap')}; + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: ${dt('fonts.lineHeight.250')}; + color: ${dt('password.overlay.color')}; + } + + /* ─── Состояния иконок правил ─── */ + .p-password-rule i { + font-size: ${dt('fonts.fontSize.200')}; + } + + .p-password-rule .ti-circle { + color: ${dt('surface.400')}; + } + + .p-password-rule .ti-circle-check { + color: ${dt('password.colorScheme.light.strength.strongBackground')}; + } + + .p-password-rule .ti-circle-x { + color: ${dt('password.colorScheme.light.strength.weakBackground')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/progressbar.ts b/src/lib/providers/prime-preset/tokens/components/progressbar.ts new file mode 100644 index 00000000..eda95979 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/progressbar.ts @@ -0,0 +1,5 @@ +export const progressbarCss = ({ dt }: { dt: (path: string) => string }) => ` + .p-progressbar-label { + font-family: ${dt('fonts.fontFamily.base')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/progressspinner.ts b/src/lib/providers/prime-preset/tokens/components/progressspinner.ts new file mode 100644 index 00000000..d901e3e1 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/progressspinner.ts @@ -0,0 +1,35 @@ +export const progressspinnerCss = ({ dt }: { dt: (token: string) => string }): string => ` +.p-progressspinner-circle { + stroke-width: ${dt('progressspinner.root.borderWidth')}; +} + +/* multicolor false */ +.p-progressspinner.p-progressspinner-monochrome .p-progressspinner-circle { + stroke: ${dt('primary.color')}; + animation: p-progressspinner-dash 1.5s ease-in-out infinite; +} + +.p-progressspinner.p-progressspinner-small, +.p-progressspinner.p-progressspinner-small .p-progressspinner-circle { + width: ${dt('progressspinner.extend.small')}; + height: ${dt('progressspinner.extend.small')}; +} + +.p-progressspinner.p-progressspinner-medium, +.p-progressspinner.p-progressspinner-medium .p-progressspinner-circle { + width: ${dt('progressspinner.extend.medium')}; + height: ${dt('progressspinner.extend.medium')}; +} + +.p-progressspinner.p-progressspinner-large, +.p-progressspinner.p-progressspinner-large .p-progressspinner-circle { + width: ${dt('progressspinner.extend.large')}; + height: ${dt('progressspinner.extend.large')}; +} + +.p-progressspinner.p-progressspinner-xlarge, +.p-progressspinner.p-progressspinner-xlarge .p-progressspinner-circle { + width: ${dt('progressspinner.extend.xlarge')}; + height: ${dt('progressspinner.extend.xlarge')}; +} +`; diff --git a/src/lib/providers/prime-preset/tokens/components/radiobutton.ts b/src/lib/providers/prime-preset/tokens/components/radiobutton.ts new file mode 100644 index 00000000..975dce6f --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/radiobutton.ts @@ -0,0 +1,16 @@ +export const radiobuttonCss = ({ dt }: { dt: (token: string) => string }): string => ` +/* Focus ring с зеленым цветом для валидных состояний */ +.p-radiobutton:not(.p-disabled):not(.p-invalid):has(.p-radiobutton-input:focus-visible) .p-radiobutton-box, +.p-radiobutton-checked:not(.p-disabled):not(.p-invalid):has(.p-radiobutton-input:focus-visible) .p-radiobutton-box { + outline: none; + box-shadow: 0 0 0 ${dt('radiobutton.focusRing.width')} ${dt('focusRing.extend.success')}; +} + +/* Focus ring с красным цветом для состояний с ошибкой */ +.p-radiobutton.p-invalid .p-radiobutton-box, +.p-radiobutton.p-invalid:not(.p-disabled):has(.p-radiobutton-input:focus-visible) .p-radiobutton-box, +.p-radiobutton-checked.p-invalid .p-radiobutton-box, +.p-radiobutton-checked.p-invalid:not(.p-disabled):has(.p-radiobutton-input:focus-visible) .p-radiobutton-box { + box-shadow: 0 0 0 ${dt('radiobutton.focusRing.width')} ${dt('focusRing.extend.invalid')}; +} +`; diff --git a/src/lib/providers/prime-preset/tokens/components/scroll-panel.ts b/src/lib/providers/prime-preset/tokens/components/scroll-panel.ts new file mode 100644 index 00000000..8bbe3691 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/scroll-panel.ts @@ -0,0 +1,20 @@ +/** + * Кастомная CSS-стилизация для компонента p-scrollpanel. + * Подключается в map-tokens.ts: `import { scrollPanelCss } from './tokens/components/scroll-panel'` + */ +export const scrollPanelCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Полоса прокрутки ─── */ + .p-scrollpanel-bar { + background: ${dt('scrollpanel.bar.background')}; + border-radius: ${dt('scrollpanel.bar.borderRadius')}; + transition-duration: ${dt('scrollpanel.root.transitionDuration')}; + } + + .p-scrollpanel-bar:focus-visible { + outline-width: ${dt('scrollpanel.bar.focusRing.width')}; + outline-style: ${dt('scrollpanel.bar.focusRing.style')}; + outline-color: ${dt('scrollpanel.bar.focusRing.color')}; + outline-offset: ${dt('scrollpanel.bar.focusRing.offset')}; + box-shadow: ${dt('scrollpanel.bar.focusRing.shadow')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/select.ts b/src/lib/providers/prime-preset/tokens/components/select.ts new file mode 100644 index 00000000..9a3bfd52 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/select.ts @@ -0,0 +1,106 @@ +export const selectCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Базовые стили ─── */ + .p-select.p-component { + width: 100%; + border-width: ${dt('select.extend.borderWidth')}; + line-height: ${dt('fonts.lineHeight.250')}; + } + + .p-select.p-component .p-select-label, + .p-select-option { + font-family: ${dt('fonts.fontFamily.base')}; + } + + /* ─── Focus ─── */ + .p-select.p-component:not(.p-disabled).p-focus { + box-shadow: 0 0 0 ${dt('select.root.focusRing.width')} ${dt('select.root.focusRing.color')}; + } + + /* ─── Invalid + Focus ─── */ + .p-select.p-component.p-invalid.p-focus { + border-color: ${dt('select.root.invalidBorderColor')}; + box-shadow: 0 0 0 ${dt('select.root.focusRing.width')} ${dt('focusRing.extend.invalid')}; + } + + /* ─── Readonly ─── */ + .p-select.p-component[readonly] { + background: ${dt('select.extend.readonlyBackground')}; + border-color: ${dt('select.root.borderColor')}; + color: ${dt('select.root.color')}; + cursor: default; + pointer-events: none; + } + + .p-select.p-component[readonly] :is(.p-select-dropdown .p-select-dropdown-icon, .p-select-clear-icon) { + color: ${dt('select.root.placeholderColor')}; + } + + /* ─── XLarge ─── */ + .p-select.p-component.p-select-xlg .p-select-label { + font-size: ${dt('inputtext.extend.extXlg.fontSize')}; + padding-block: ${dt('inputtext.extend.extXlg.paddingY')}; + padding-inline: ${dt('inputtext.extend.extXlg.paddingX')}; + } + + /* ─── FloatLabel ─── */ + .p-floatlabel:has(.p-select.p-component) label { + font-family: ${dt('fonts.fontFamily.base')}; + font-weight: ${dt('floatlabel.root.fontWeight')}; + line-height: ${dt('fonts.lineHeight.250')}; + color: ${dt('floatlabel.root.color')}; + } + + .p-floatlabel:has(.p-select.p-component) .p-floatlabel-active label { + font-weight: ${dt('floatlabel.root.active.fontWeight')}; + } + + .p-floatlabel-in .p-select.p-component .p-select-label { + font-family: ${dt('fonts.fontFamily.base')}; + padding-block-start: ${dt('floatlabel.in.input.paddingTop')}; + padding-block-end: ${dt('floatlabel.in.input.paddingBottom')}; + } + + /* ─── Checkmark: выбранный элемент ─── */ + .p-select-option:has(.p-select-option-check-icon) { + background: ${dt('select.option.selectedBackground')}; + color: ${dt('select.option.selectedColor')}; + } + + .p-select-option:has(.p-select-option-check-icon).p-focus { + background: ${dt('select.option.selectedFocusBackground')}; + color: ${dt('select.option.selectedFocusColor')}; + } + + /* Скрываем PrimeNG SVG, заменяем на tabler-иконку */ + .p-select-option .p-select-option-check-icon, + .p-select-option .p-select-option-blank-icon { + display: none; + } + + .p-select-option:has(.p-select-option-check-icon)::before, + .p-select-option:has(.p-select-option-blank-icon)::before { + font-family: 'tabler-icons'; + content: var(--p-select-checkmark-content, "\\ea5e"); + font-size: ${dt('select.extend.iconSize')}; + color: var(--p-select-option-selected-color); + flex-shrink: 0; + margin-inline-start: ${dt('select.checkmark.gutterStart')}; + margin-inline-end: ${dt('select.checkmark.gutterEnd')}; + } + + .p-select-option:has(.p-select-option-check-icon).p-focus::before { + color: var(--p-select-option-focus-color); + } + + .p-select-option:has(.p-select-option-blank-icon)::before { + visibility: hidden; + } + + /* ─── Иконки ─── */ + .p-select.p-component :is(.p-select-dropdown .p-select-dropdown-icon, .p-select-clear-icon, .p-select-loading-icon) { + font-size: ${dt('select.extend.iconSize')}; + width: ${dt('select.extend.iconSize')}; + height: ${dt('select.extend.iconSize')}; + color: ${dt('select.root.placeholderColor')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/skeleton.ts b/src/lib/providers/prime-preset/tokens/components/skeleton.ts new file mode 100644 index 00000000..42b74473 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/skeleton.ts @@ -0,0 +1,17 @@ +/** + * Кастомная CSS-стилизация для компонента p-skeleton. + * Публикует extend-токены как CSS-переменные и применяет минимальную ширину. + * Подключается в map-tokens.ts: `import { skeletonCss } from './tokens/components/skeleton'` + */ +export const skeletonCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Skeleton extend: публикуем кастомные переменные в :root ─── */ + :root { + --p-skeleton-extend-min-width: ${dt('skeleton.extend.minWidth')}; + --p-skeleton-extend-height: ${dt('skeleton.extend.height')}; + } + + /* ─── Минимальная ширина ─── */ + .p-skeleton { + min-width: var(--p-skeleton-extend-min-width); + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/slider.ts b/src/lib/providers/prime-preset/tokens/components/slider.ts new file mode 100644 index 00000000..0fb55b17 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/slider.ts @@ -0,0 +1,12 @@ +/** + * Кастомная CSS-стилизация для компонента p-slider. + * Подключается в map-tokens.ts: `import { sliderCss } from './components/slider'` + */ +export const sliderCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* ─── Focus ring ползунка ─── */ + .p-slider-handle:focus-visible { + outline: ${dt('slider.handle.focusRing.width')} ${dt('slider.handle.focusRing.style')} ${dt('focusRing.extend.success')}; + outline-offset: ${dt('slider.handle.focusRing.offset')}; + box-shadow: none; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/tabs.ts b/src/lib/providers/prime-preset/tokens/components/tabs.ts new file mode 100644 index 00000000..79e05ade --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/tabs.ts @@ -0,0 +1,26 @@ +const tabsCss = ({ dt }: { dt: (token: string) => string }): string => ` + +/* Переопределение overflow для видимости активной линии */ +.p-tabs .p-tablist.p-tablist, +.p-tabs .p-tablist-content.p-tablist-content, +.p-tabs .p-tablist-viewport.p-tablist-viewport { + overflow: visible; +} + +/* Типографика для label таба */ +.p-tabs .p-tablist .p-tab.p-tab { + display: flex; + align-items: center; + gap: ${dt('tabs.tab.gap')}; + font-family: ${dt('fonts.fontFamily.heading')}; + font-size: ${dt('fonts.fontSize.300')}; + font-weight: ${dt('fonts.fontWeight.demibold')}; +} + +/* Стили для tablist с правильной границей */ +.p-tabs .p-tablist { + border-bottom: 0.15rem solid ${dt('tabs.tablist.borderColor')}; +} +`; + +export { tabsCss }; diff --git a/src/lib/providers/prime-preset/tokens/components/tag.ts b/src/lib/providers/prime-preset/tokens/components/tag.ts new file mode 100644 index 00000000..7f84dbe3 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/tag.ts @@ -0,0 +1,6 @@ +export const tagCss = ({ dt }: { dt: (token: string) => string }): string => ` + .p-tag { + font-family: ${dt('fonts.fontFamily.base')}; + line-height: ${dt('fonts.lineHeight.250')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/textarea.ts b/src/lib/providers/prime-preset/tokens/components/textarea.ts new file mode 100644 index 00000000..263e6886 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/textarea.ts @@ -0,0 +1,67 @@ +export const textareaCss = ({ dt }: { dt: (token: string) => string }): string => ` + +/* --- Base --- */ +.p-textarea { + border-width: ${dt('textarea.extend.borderWidth')}; + line-height: ${dt('fonts.lineHeight.250')}; + min-height: ${dt('textarea.extend.minHeight')}; + font-family: ${dt('fonts.fontFamily.base')}; +} + +.p-textarea::placeholder { + font-family: ${dt('fonts.fontFamily.base')}; +} + +.p-floatlabel:has(.p-textarea) label { + font-family: ${dt('fonts.fontFamily.base')}; +} + +/* --- Sizes --- */ +.p-textarea.p-textarea-xlg { + font-size: ${dt('textarea.extend.extXlg.fontSize')}; + padding: ${dt('textarea.extend.extXlg.paddingY')} ${dt('textarea.extend.extXlg.paddingX')}; +} + +/* --- States --- */ +.p-textarea:enabled:read-only { + background: ${dt('textarea.extend.readonlyBackground')}; + color: ${dt('textarea.color')}; +} + +.p-textarea:disabled { + background: ${dt('textarea.disabledBackground')}; + color: ${dt('textarea.disabledColor')}; + opacity: 1; +} + +/* --- Focus --- */ +.p-textarea:enabled:focus { + box-shadow: 0 0 0 ${dt('textarea.focusRing.width')} ${dt('textarea.focusRing.color')}; +} + +/* --- Invalid + Focus --- */ +.p-textarea.p-invalid:focus { + border-color: ${dt('textarea.invalidBorderColor')}; + box-shadow: 0 0 0 ${dt('textarea.focusRing.width')} ${dt('focusRing.extend.invalid')}; +} + +/* --- ClearButton (showClear) --- */ +.p-iconfield:has(.p-textarea) { + display: block; + width: fit-content; +} + +.p-iconfield:has(.p-textarea) .p-textarea { + padding-right: ${dt('form.padding.700')}; +} + +.p-iconfield:has(.p-textarea) .p-inputicon { + top: ${dt('form.padding.500')}; + transform: none; + font-size: ${dt('textarea.extend.iconSize')}; + width: ${dt('textarea.extend.iconSize')}; + height: ${dt('textarea.extend.iconSize')}; + cursor: pointer; +} + +`; diff --git a/src/lib/providers/prime-preset/tokens/components/tieredmenu.ts b/src/lib/providers/prime-preset/tokens/components/tieredmenu.ts new file mode 100644 index 00000000..bdef994f --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/tieredmenu.ts @@ -0,0 +1,52 @@ +export const tieredmenuCss = ({ dt }: { dt: (token: string) => string }): string => ` + .p-tieredmenu { + width: min-content; + } + + .p-tieredmenu-item-content { + font-size: ${dt('fonts.fontSize.300')}; + } + + .p-tieredmenu-submenu-icon { + font-size: ${dt('tieredmenu.extend.iconSize')}; + } + + /* ─── Selected (checked) item ─── */ + + .p-tieredmenu .p-tieredmenu-item.p-tieredmenu-item-checked > .p-tieredmenu-item-content { + background: ${dt('tieredmenu.item.activeBackground')}; + color: ${dt('tieredmenu.item.activeColor')}; + } + + .p-tieredmenu .p-tieredmenu-item.p-tieredmenu-item-checked > .p-tieredmenu-item-content :is(.p-tieredmenu-item-link, .p-tieredmenu-item-label, .p-tieredmenu-item-icon, .p-tieredmenu-submenu-icon) { + color: ${dt('tieredmenu.item.activeColor')}; + } + + /* ─── Hover on selected ─── */ + + .p-tieredmenu .p-tieredmenu-item.p-tieredmenu-item-checked:not(.p-disabled) > .p-tieredmenu-item-content:hover { + background: ${dt('tieredmenu.item.focusBackground')}; + color: ${dt('tieredmenu.item.focusColor')}; + } + + .p-tieredmenu .p-tieredmenu-item.p-tieredmenu-item-checked:not(.p-disabled) > .p-tieredmenu-item-content:hover :is(.p-tieredmenu-item-link, .p-tieredmenu-item-label) { + color: ${dt('tieredmenu.item.focusColor')}; + } + + .p-tieredmenu .p-tieredmenu-item.p-tieredmenu-item-checked:not(.p-disabled) > .p-tieredmenu-item-content:hover :is(.p-tieredmenu-item-icon, .p-tieredmenu-submenu-icon) { + color: ${dt('tieredmenu.item.icon.focusColor')}; + } + + /* ─── Captions ─── */ + + .p-tieredmenu .p-tieredmenu-item-caption { + display: flex; + flex-direction: column; + gap: ${dt('tieredmenu.extend.extItem.caption.gap')}; + } + + .p-tieredmenu .p-tieredmenu-item-caption-text { + font-size: ${dt('fonts.fontSize.200')}; + color: ${dt('tieredmenu.extend.extItem.caption.color')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/timeline.ts b/src/lib/providers/prime-preset/tokens/components/timeline.ts new file mode 100644 index 00000000..562376ec --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/timeline.ts @@ -0,0 +1,71 @@ +export const timelineCss = ({ dt }: { dt: (token: string) => string }): string => ` + +/* ─── Типографика ─── */ +.p-timeline { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.300')}; + line-height: ${dt('fonts.lineHeight.500')}; + color: ${dt('text.color')}; +} + +/* ─── Маркер ─── */ +.p-timeline-event-marker { + border-width: ${dt('timeline.eventMarker.borderWidth')}; + background: ${dt('timeline.eventMarker.background')}; + border-color: ${dt('timeline.eventMarker.borderColor')}; +} + +.p-timeline-event-marker::before { + background: ${dt('timeline.eventMarker.content.background')}; +} + +/* ─── Коннектор ─── */ +.p-timeline-event-connector { + background: ${dt('timeline.eventConnector.color')}; +} + +/* ─── Стиль линии ─── */ +timeline[data-line="dashed"] .p-timeline-event-connector { + background: none; + border-left: ${dt('timeline.eventConnector.size')} dashed ${dt('timeline.eventConnector.color')}; +} + +timeline[data-line="dotted"] .p-timeline-event-connector { + background: none; + border-left: ${dt('timeline.eventConnector.size')} dotted ${dt('timeline.eventConnector.color')}; +} + +timeline[data-line="none"] .p-timeline-event-connector { + background: none; +} + +/* Горизонтальная ориентация линии */ +timeline[data-line="dashed"] .p-timeline-horizontal .p-timeline-event-connector { + border-left: none; + border-top: ${dt('timeline.eventConnector.size')} dashed ${dt('timeline.eventConnector.color')}; +} + +timeline[data-line="dotted"] .p-timeline-horizontal .p-timeline-event-connector { + border-left: none; + border-top: ${dt('timeline.eventConnector.size')} dotted ${dt('timeline.eventConnector.color')}; +} + +/* ─── Маркер-иконка (без бордера и фона) ─── */ +.p-timeline-event-marker:has(i) { + border: none; + background: none; +} + +.p-timeline-event-marker:has(i)::before { + display: none; +} + +/* ─── Кастомный цвет маркера ─── */ +timeline[style*="--timeline-marker-color"] .p-timeline-event-marker { + border-color: var(--timeline-marker-color); +} + +timeline[style*="--timeline-marker-color"] .p-timeline-event-marker i { + color: var(--timeline-marker-color); +} +`; diff --git a/src/lib/providers/prime-preset/tokens/components/toast.ts b/src/lib/providers/prime-preset/tokens/components/toast.ts new file mode 100644 index 00000000..adc64305 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/toast.ts @@ -0,0 +1,144 @@ +export const toastCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* Основной контейнер toast-сообщения */ + .p-toast-message { + width: ${dt('toast.root.width')}; + overflow: hidden; + border-width: ${dt('toast.root.borderWidth')}; + border-radius: ${dt('toast.root.borderRadius')}; + box-shadow: ${dt('toast.colorScheme.light.info.shadow')}; + position: relative; + } + + /* border-radius для контента toast-сообщения */ + .p-toast .p-toast-message .p-toast-message-content { + border-radius: ${dt('toast.root.borderRadius')}; + } + + /* Текстовый блок toast */ + .p-toast-message-text { + flex: 1; + display: flex; + flex-direction: column; + gap: ${dt('toast.text.gap')}; + } + + /* Заголовок toast */ + .p-toast-summary { + font-family: ${dt('fonts.fontFamily.base')}; + line-height: ${dt('fonts.lineHeight.250')}; + } + + /* Детальное описание toast */ + .p-toast-message .p-toast-detail { + font-family: ${dt('fonts.fontFamily.base')}; + line-height: ${dt('fonts.lineHeight.250')}; + } + + /* Кнопка закрытия toast-сообщения */ + .p-toast-message .p-toast-message-content .p-toast-close-button { + margin: 0; + padding: 0; + right: 0; + } + + /* Общие стили border для кнопки закрытия всех типов toast */ + .p-toast-message-info .p-toast-close-button, + .p-toast-message-success .p-toast-close-button, + .p-toast-message-warn .p-toast-close-button, + .p-toast-message-error .p-toast-close-button { + border: ${dt('toast.extend.extCloseButton.width')} solid; + } + + /* Общие стили для акцентной линии всех типов toast */ + .p-toast-message-info .p-toast-accent-line, + .p-toast-message-success .p-toast-accent-line, + .p-toast-message-warn .p-toast-accent-line, + .p-toast-message-error .p-toast-accent-line { + width: ${dt('toast.extend.extAccentLine.width')}; + position: absolute; + left: 0; + top: 0; + bottom: 0; + border-radius: ${dt('toast.root.borderRadius')} 0 0 ${dt('toast.root.borderRadius')}; + } + + /* Стили для toast типа Info */ + .p-toast-message-info .p-toast-message-icon { + color: ${dt('toast.extend.extInfo.color')}; + } + + .p-toast-message-info .p-toast-close-button { + color: ${dt('toast.extend.extInfo.closeButton.color')}; + border-color: ${dt('toast.extend.extInfo.closeButton.borderColor')}; + } + + .p-toast-message.p-toast-message-info .p-toast-close-button.p-button-text:not(:disabled):hover { + background: ${dt('toast.colorScheme.light.info.closeButton.hoverBackground')}; + border-color: ${dt('toast.extend.extInfo.closeButton.borderColor')}; + color: ${dt('toast.extend.extInfo.closeButton.color')}; + } + + .p-toast-message-info .p-toast-accent-line { + background: ${dt('toast.extend.extInfo.color')}; + } + + /* Стили для toast типа Success */ + .p-toast-message-success .p-toast-message-icon { + color: ${dt('toast.extend.extSuccess.color')}; + } + + .p-toast-message-success .p-toast-close-button { + color: ${dt('toast.extend.extSuccess.closeButton.color')}; + border-color: ${dt('toast.extend.extSuccess.closeButton.borderColor')}; + } + + .p-toast-message.p-toast-message-success .p-toast-close-button.p-button-text:not(:disabled):hover { + background: ${dt('toast.colorScheme.light.success.closeButton.hoverBackground')}; + border-color: ${dt('toast.extend.extSuccess.closeButton.borderColor')}; + color: ${dt('toast.extend.extSuccess.closeButton.color')}; + } + + .p-toast-message-success .p-toast-accent-line { + background: ${dt('toast.extend.extSuccess.color')}; + } + + /* Стили для toast типа Warn */ + .p-toast-message-warn .p-toast-message-icon { + color: ${dt('toast.extend.extWarn.color')}; + } + + .p-toast-message-warn .p-toast-close-button { + color: ${dt('toast.extend.extWarn.closeButton.color')}; + border-color: ${dt('toast.extend.extWarn.closeButton.borderColor')}; + } + + .p-toast-message.p-toast-message-warn .p-toast-close-button.p-button-text:not(:disabled):hover { + background: ${dt('toast.colorScheme.light.warn.closeButton.hoverBackground')}; + border-color: ${dt('toast.extend.extWarn.closeButton.borderColor')}; + color: ${dt('toast.extend.extWarn.closeButton.color')}; + } + + .p-toast-message-warn .p-toast-accent-line { + background: ${dt('toast.extend.extWarn.color')}; + } + + /* Стили для toast типа Error */ + .p-toast-message-error .p-toast-message-icon { + color: ${dt('toast.extend.extError.color')}; + } + + .p-toast-message-error .p-toast-close-button { + color: ${dt('toast.extend.extError.closeButton.color')}; + border-color: ${dt('toast.extend.extError.closeButton.borderColor')}; + } + + .p-toast-message.p-toast-message-error .p-toast-close-button.p-button-text:not(:disabled):hover { + background: ${dt('toast.colorScheme.light.error.closeButton.hoverBackground')}; + border-color: ${dt('toast.extend.extError.closeButton.borderColor')}; + color: ${dt('toast.extend.extError.closeButton.color')}; + } + + .p-toast-message-error .p-toast-accent-line { + background: ${dt('toast.extend.extError.color')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/toggleswitch.ts b/src/lib/providers/prime-preset/tokens/components/toggleswitch.ts new file mode 100644 index 00000000..8793c207 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/toggleswitch.ts @@ -0,0 +1,11 @@ +export const toggleswitchCss = ({ dt }: { dt: (token: string) => string }): string => ` + /* Focus ring для валидных состояний */ + .p-toggleswitch:not(.p-disabled):not(.p-invalid):has(.p-toggleswitch-input:focus-visible) .p-toggleswitch-slider { + box-shadow: 0 0 0 ${dt('toggleswitch.root.focusRing.width')} ${dt('focusRing.extend.success')}; + } + + /* Focus ring для состояния ошибки */ + .p-toggleswitch.p-invalid:not(.p-disabled):has(.p-toggleswitch-input:focus-visible) .p-toggleswitch-slider { + box-shadow: 0 0 0 ${dt('focusRing.width')} ${dt('focusRing.extend.invalid')}; + } +`; diff --git a/src/lib/providers/prime-preset/tokens/components/tooltip.ts b/src/lib/providers/prime-preset/tokens/components/tooltip.ts new file mode 100644 index 00000000..8bbc215f --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/components/tooltip.ts @@ -0,0 +1,9 @@ +export const tooltipCss = ({ dt }: { dt: (token: string) => string }): string => ` +/* Типографика для Tooltip */ +.p-tooltip .p-tooltip-text { + font-family: ${dt('fonts.fontFamily.base')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.regular')}; + line-height: ${dt('fonts.lineHeight.300')}; +} +`; diff --git a/src/lib/providers/prime-preset/tokens/tokens.json b/src/lib/providers/prime-preset/tokens/tokens.json new file mode 100644 index 00000000..5824c878 --- /dev/null +++ b/src/lib/providers/prime-preset/tokens/tokens.json @@ -0,0 +1,5061 @@ +{ + "primitive": { + "colors": { + "alpha": { + "white": { + "100": "rgba(255, 255, 255, 0.1000)", + "200": "rgba(255, 255, 255, 0.2000)", + "300": "rgba(255, 255, 255, 0.3000)", + "400": "rgba(255, 255, 255, 0.4000)", + "500": "rgba(255, 255, 255, 0.5000)", + "600": "rgba(255, 255, 255, 0.6000)", + "700": "rgba(255, 255, 255, 0.7000)", + "800": "rgba(255, 255, 255, 0.8000)", + "900": "rgba(255, 255, 255, 0.9000)", + "1000": "#ffffff" + }, + "black": { + "100": "rgba(0, 0, 0, 0.1000)", + "200": "rgba(0, 0, 0, 0.2000)", + "300": "rgba(0, 0, 0, 0.3000)", + "400": "rgba(0, 0, 0, 0.4000)", + "500": "rgba(0, 0, 0, 0.5000)", + "600": "rgba(0, 0, 0, 0.6000)", + "700": "rgba(0, 0, 0, 0.7000)", + "800": "rgba(0, 0, 0, 0.8000)", + "900": "rgba(0, 0, 0, 0.9000)", + "1000": "#000000" + } + }, + "solid": { + "purple": { + "50": "#faf5ff", + "100": "#f3e8ff", + "200": "#e9d5ff", + "300": "#d8b4fe", + "400": "#c084fc", + "500": "#a855f7", + "600": "#9333ea", + "700": "#7e22ce", + "800": "#6b21a8", + "900": "#581c87", + "950": "#3b0764" + }, + "fuchsia": { + "50": "#fdf4ff", + "100": "#fae8ff", + "200": "#f5d0fe", + "300": "#f0abfc", + "400": "#e879f9", + "500": "#d946ef", + "600": "#c026d3", + "700": "#a21caf", + "800": "#86198f", + "900": "#701a75", + "950": "#4a044e" + }, + "pink": { + "50": "#fdf2f8", + "100": "#fce7f3", + "200": "#fbcfe8", + "300": "#f9a8d4", + "400": "#f472b6", + "500": "#ec4899", + "600": "#db2777", + "700": "#be185d", + "800": "#9d174d", + "900": "#831843", + "950": "#500724" + }, + "rose": { + "50": "#fff1f2", + "100": "#ffe4e6", + "200": "#fecdd3", + "300": "#fda4af", + "400": "#fb7185", + "500": "#f43f5e", + "600": "#e11d48", + "700": "#be123c", + "800": "#9f1239", + "900": "#881337", + "950": "#4c0519" + }, + "teal": { + "50": "#f0fdfa", + "100": "#ccfbf1", + "200": "#99f6e4", + "300": "#5eead4", + "400": "#2dd4bf", + "500": "#14b8a6", + "600": "#0d9488", + "700": "#0f766e", + "800": "#115e59", + "900": "#134e4a", + "950": "#042f2e" + }, + "cyan": { + "50": "#ecfeff", + "100": "#cffafe", + "200": "#a5f3fc", + "300": "#67e8f9", + "400": "#22d3ee", + "500": "#06b6d4", + "600": "#0891b2", + "700": "#0e7490", + "800": "#155e75", + "900": "#164e63", + "950": "#013138" + }, + "sky": { + "50": "#f0f9ff", + "100": "#e0f2fe", + "200": "#bae6fd", + "300": "#7dd3fc", + "400": "#38bdf8", + "500": "#0ea5e9", + "600": "#0284c7", + "700": "#0369a1", + "800": "#075985", + "900": "#0c4a6e", + "950": "#082f49" + }, + "blue": { + "50": "#fafdff", + "100": "#f0f9ff", + "200": "#d4ecfe", + "300": "#aad7fb", + "400": "#77baf4", + "500": "#4496e8", + "600": "#1e76cd", + "700": "#18538d", + "800": "#123a61", + "900": "#0e2a45", + "950": "#0c243b" + }, + "indigo": { + "50": "#eef2ff", + "100": "#e0e7ff", + "200": "#c7d2fe", + "300": "#a5b4fc", + "400": "#818cf8", + "500": "#6366f1", + "600": "#4f46e5", + "700": "#4338ca", + "800": "#3730a3", + "900": "#312e81", + "950": "#1e1b4b" + }, + "violet": { + "50": "#fcfaff", + "100": "#f6f0ff", + "200": "#e5d4fe", + "300": "#cbaafb", + "400": "#b284f5", + "500": "#a265ec", + "600": "#9457ea", + "700": "#48188d", + "800": "#321261", + "900": "#240e45", + "950": "#1f0c3b" + }, + "emerald": { + "50": "#ecfdf5", + "100": "#d1fae5", + "200": "#a7f3d0", + "300": "#6ee7b7", + "400": "#34d399", + "500": "#10b981", + "600": "#059669", + "700": "#047857", + "800": "#065f46", + "900": "#064e3b", + "950": "#022c22" + }, + "green": { + "50": "#fafffb", + "100": "#f0fff3", + "200": "#d4fedc", + "300": "#aafbb7", + "400": "#77f48a", + "500": "#44e858", + "600": "#1dc831", + "700": "#168322", + "800": "#12611b", + "900": "#0e4514", + "950": "#0c3b11" + }, + "lime": { + "50": "#f7fee7", + "100": "#ecfccb", + "200": "#d9f99d", + "300": "#bef264", + "400": "#a3e635", + "500": "#84cc16", + "600": "#65a30d", + "700": "#4d7c0f", + "800": "#3f6212", + "900": "#365314", + "950": "#1a2e05" + }, + "red": { + "50": "#fffafa", + "100": "#fff0f0", + "200": "#fed4d4", + "300": "#fbacaa", + "400": "#f47f77", + "500": "#e85244", + "600": "#db3424", + "700": "#8d2218", + "800": "#611912", + "900": "#45120e", + "950": "#3b100c" + }, + "orange": { + "50": "#fffbfa", + "100": "#fff3f0", + "200": "#ffddd5", + "300": "#ffbca9", + "400": "#ff9273", + "500": "#fe6434", + "600": "#d53f0b", + "700": "#a83107", + "800": "#752506", + "900": "#561c05", + "950": "#4b1905" + }, + "amber": { + "50": "#fffbeb", + "100": "#fef3c7", + "200": "#fde68a", + "300": "#fcd34d", + "400": "#fbbf24", + "500": "#f59e0b", + "600": "#d97706", + "700": "#b45309", + "800": "#92400e", + "900": "#78350f", + "950": "#451a03" + }, + "yellow": { + "50": "#fffdfa", + "100": "#fff9f0", + "200": "#ffeed4", + "300": "#fddeaa", + "400": "#facb75", + "500": "#f5b83d", + "600": "#dc9710", + "700": "#9d6d0e", + "800": "#6d4c0b", + "900": "#4f3709", + "950": "#453008" + }, + "slate": { + "50": "#f8fafc", + "100": "#f1f5f9", + "200": "#e2e8f0", + "300": "#cbd5e1", + "400": "#94a3b8", + "500": "#64748b", + "600": "#475569", + "700": "#334155", + "800": "#1e293b", + "900": "#0f172a", + "950": "#020617" + }, + "gray": { + "50": "#f9fafb", + "100": "#f3f4f6", + "200": "#e5e7eb", + "300": "#d1d5db", + "400": "#9ca3af", + "500": "#6b7280", + "600": "#4b5563", + "700": "#374151", + "800": "#1f2937", + "900": "#111827", + "950": "#030712" + }, + "zinc": { + "50": "#fafafa", + "100": "#f0f0f1", + "200": "#e2e2e4", + "300": "#cecfd2", + "400": "#a2a5a9", + "500": "#85888e", + "600": "#6d7076", + "700": "#56595f", + "800": "#404348", + "900": "#2b2e33", + "950": "#181a1f" + }, + "neutral": { + "50": "#fafafa", + "100": "#f5f5f5", + "200": "#e5e5e5", + "300": "#d4d4d4", + "400": "#a3a3a3", + "500": "#737373", + "600": "#525252", + "700": "#404040", + "800": "#262626", + "900": "#171717", + "950": "#0a0a0a" + }, + "stone": { + "50": "#fafaf9", + "100": "#f5f5f4", + "200": "#e7e5e4", + "300": "#d6d3d1", + "400": "#a8a29e", + "500": "#78716c", + "600": "#57534e", + "700": "#44403c", + "800": "#292524", + "900": "#1c1917", + "950": "#0c0a09" + } + } + }, + "borderRadius": { + "100": "0.25rem", + "200": "0.5rem", + "300": "0.75rem", + "400": "1rem", + "500": "1.5rem", + "none": "0rem", + "max": "71.3571rem" + }, + "borderWidth": { + "100": "0.0714rem", + "200": "0.1429rem", + "300": "0.25rem", + "none": "0rem" + }, + "fonts": { + "fontFamily": { + "heading": "TT Fellows", + "base": "PT Sans" + }, + "fontWeight": { + "regular": "400", + "medium": "500", + "demibold": "600", + "bold": "700" + }, + "fontSize": { + "100": "0.75rem", + "200": "0.875rem", + "300": "1rem", + "400": "1.125rem", + "500": "1.25rem", + "600": "1.5rem", + "650": "1.875rem", + "700": "2.25rem", + "750": "3rem", + "800": "3.75rem", + "900": "4.5rem", + "1000": "6rem" + }, + "lineHeight": { + "100": "0.7857rem", + "150": "0.8571rem", + "200": "0.9286rem", + "250": "1rem", + "300": "1.0714rem", + "350": "1.1429rem", + "400": "1.2857rem", + "450": "1.4286rem", + "500": "1.5rem", + "550": "1.5714rem", + "600": "1.7143rem", + "700": "1.8571rem", + "800": "2.2857rem", + "850": "2.3571rem", + "900": "2.7857rem", + "1000": "3.3571rem", + "auto": "auto" + } + }, + "spacing": { + "none": "0rem", + "1x": "0.25rem", + "2x": "0.5rem", + "3x": "0.75rem", + "4x": "1rem", + "5x": "1.25rem", + "6x": "1.5rem", + "7x": "1.75rem", + "8x": "2rem", + "9x": "2.25rem", + "10x": "2.5rem", + "11x": "2.75rem", + "12x": "3rem", + "14x": "3.5rem", + "16x": "4rem", + "20x": "5rem", + "24x": "6rem", + "28x": "7rem", + "32x": "8rem", + "36x": "9rem", + "40x": "10rem" + }, + "sizing": { + "none": "0rem", + "min": "0.0714rem", + "1x": "0.25rem", + "2x": "0.5rem", + "3x": "0.75rem", + "4x": "1rem", + "5x": "1.25rem", + "6x": "1.5rem", + "7x": "1.75rem", + "8x": "2rem", + "9x": "2.25rem", + "10x": "2.5rem", + "11x": "2.75rem", + "12x": "3rem", + "14x": "3.5rem", + "16x": "4rem", + "20x": "5rem", + "24x": "6rem", + "28x": "7rem", + "32x": "8rem", + "36x": "9rem", + "40x": "10rem", + "44x": "11rem", + "48x": "12rem", + "52x": "13rem", + "56x": "14rem", + "60x": "15rem", + "64x": "16rem", + "68x": "17rem", + "72x": "18rem", + "76x": "19rem", + "80x": "20rem", + "84x": "21rem", + "88x": "22rem", + "92x": "23rem", + "96x": "24rem", + "100x": "25rem", + "104x": "26rem", + "108x": "27rem", + "112x": "28rem", + "116x": "29rem", + "120x": "30rem", + "124x": "34rem", + "128x": "45rem", + "132x": "50rem", + "136x": "54rem", + "140x": "58rem", + "144x": "60rem", + "max": "100%" + }, + "shadows": { + "100": "0 0 0.1rem {colors.alpha.black.200}", + "200": "0 0 0.25rem {colors.alpha.black.200}", + "300": "0 0.1rem 0.25rem {colors.alpha.black.200}", + "400": "0 0.25rem 0.5rem {colors.alpha.black.200}", + "500": "0 0.5rem 1rem 0 {colors.alpha.black.200}", + "none": "none" + }, + "transition": { + "easing": { + "linear": "linear", + "in": "cubic-bezier(0.55, 0.06, 0.7, 0.2)", + "out": "cubic-bezier(0.2, 0.6, 0.4, 1)", + "inOut": "cubic-bezier(0.65, 0.05, 0.35, 1)" + }, + "duration": { + "100": "140ms", + "200": "180ms", + "300": "240ms", + "400": "320ms", + "500": "400ms" + } + }, + "opacity": { + "250": "0.25", + "500": "0.5", + "1000": "1" + } + }, + "semantic": { + "list": { + "padding": "{spacing.1x}", + "gap": { + "100": "{spacing.1x}", + "200": "{spacing.2x}" + }, + "header": { + "padding": "{spacing.4x} {spacing.4x} 0 {spacing.4x}" + }, + "option": { + "padding": "{spacing.2x} {spacing.3x}", + "borderRadius": "{borderRadius.200}" + }, + "optionGroup": { + "padding": "{spacing.2x} {spacing.3x}", + "fontWeight": "{fonts.fontWeight.demibold}" + } + }, + "focusRing": { + "width": "{borderWidth.300}", + "style": "none", + "color": "{focusRing.extend.success}", + "offset": "0rem" + }, + "form": { + "padding": { + "100": "{spacing.1x}", + "200": "{spacing.2x}", + "300": "{spacing.3x}", + "400": "{spacing.4x}", + "500": "{spacing.5x}", + "600": "{spacing.6x}", + "700": "{spacing.7x}" + }, + "borderRadius": { + "100": "{borderRadius.200}", + "200": "{borderRadius.300}", + "300": "{borderRadius.max}" + }, + "borderWidth": "{borderWidth.100}", + "icon": { + "100": "{sizing.2x}", + "200": "{sizing.3x}", + "300": "{sizing.4x}", + "400": "{sizing.5x}", + "500": "{sizing.6x}" + }, + "transitionDuration": "{transition.duration.200}", + "size": { + "100": "{sizing.min}", + "150": "{sizing.1x}", + "200": "{sizing.2x}", + "250": "{sizing.3x}", + "300": "{sizing.4x}", + "350": "{sizing.5x}", + "400": "{sizing.6x}", + "500": "{sizing.8x}", + "600": "{sizing.10x}", + "700": "{sizing.12x}", + "800": "{sizing.16x}", + "900": "{sizing.20x}" + }, + "width": { + "100": "{sizing.6x}", + "200": "{sizing.8x}", + "300": "{sizing.10x}", + "350": "{sizing.11x}", + "400": "{sizing.12x}", + "500": "{sizing.60x}", + "full": "{sizing.max}" + }, + "gap": { + "100": "{spacing.1x}", + "200": "{spacing.2x}", + "300": "{spacing.3x}", + "400": "{spacing.4x}" + }, + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}" + }, + "sm": { + "width": "{sizing.60x}", + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{spacing.3x}", + "paddingY": "{spacing.3x}" + }, + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{spacing.4x}", + "paddingY": "{spacing.4x}", + "lg": { + "width": "{sizing.76x}", + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{spacing.5x}", + "paddingY": "{spacing.5x}" + }, + "xlg": { + "width": "{sizing.84x}", + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{spacing.6x}", + "paddingY": "{spacing.6x}" + } + }, + "content": { + "borderRadius": "{borderRadius.300}", + "padding": { + "100": "{spacing.1x}", + "200": "{spacing.2x}", + "300": "{spacing.4x}", + "400": "{spacing.6x}", + "500": "{spacing.7x}" + }, + "borderWidth": "{sizing.min}", + "gap": { + "100": "{spacing.1x}", + "200": "{spacing.2x}", + "300": "{spacing.3x}", + "400": "{spacing.4x}" + } + }, + "navigation": { + "width": { + "100": "{borderWidth.100}", + "200": "{borderWidth.300}" + }, + "borderRadius": "{borderRadius.100}", + "padding": { + "100": "{spacing.1x}", + "200": "{spacing.3x}", + "300": "{spacing.4x}", + "400": "{spacing.6x}" + }, + "size": { + "100": "{sizing.1x}", + "200": "{sizing.2x}", + "300": "{sizing.5x}", + "400": "{sizing.8x}", + "500": "{sizing.16x}" + }, + "submenu": { + "padding": "{spacing.5x}" + }, + "list": { + "padding": { + "100": "{spacing.1x}", + "200": "{spacing.2x}" + }, + "gap": "{spacing.1x}" + }, + "item": { + "padding": "{spacing.2x} {spacing.3x}", + "borderRadius": "{borderRadius.200}", + "gap": "{spacing.2x}" + }, + "submenuLabel": { + "padding": "{spacing.2x} {spacing.3x}", + "fontWeight": "{fonts.fontWeight.regular}" + }, + "submenuIcon": { + "size": "{fonts.fontSize.500}" + } + }, + "overlay": { + "mask": { + "transitionDuration": "{transition.duration.200}" + }, + "select": { + "borderRadius": "{borderRadius.300}", + "padding": "{spacing.1x}" + }, + "borderWidth": "{borderWidth.100}", + "icon": { + "size": { + "100": "{sizing.4x}", + "200": "{sizing.6x}", + "300": "{sizing.7x}", + "400": "{sizing.8x}", + "500": "{sizing.9x}" + } + }, + "popover": { + "borderRadius": "{borderRadius.200}", + "width": { + "100": "{sizing.2x}", + "200": "{sizing.3x}" + }, + "padding": { + "100": "{spacing.3x}", + "200": "{spacing.5x}" + } + }, + "modal": { + "borderRadius": "{borderRadius.500}", + "padding": { + "100": "{spacing.4x}", + "200": "{spacing.5x}", + "300": "{spacing.6x}" + } + }, + "gap": { + "100": "{spacing.1x}", + "200": "{spacing.2x}", + "300": "{spacing.3x}", + "400": "{spacing.4x}" + }, + "width": "{sizing.100x}", + "drawer": { + "padding": "{spacing.2x}" + }, + "sm": { + "width": "{sizing.80x}" + }, + "lg": { + "width": "{sizing.120x}" + }, + "xlg": { + "width": "{sizing.128x}" + } + }, + "feedback": { + "transitionDuration": "{transition.duration.200}", + "width": { + "100": "{sizing.min}", + "200": "{sizing.1x}", + "300": "{sizing.2x}", + "400": "{sizing.3x}", + "500": "{sizing.4x}", + "550": "{sizing.5x}", + "600": "{sizing.6x}", + "650": "{sizing.7x}", + "700": "{sizing.8x}", + "800": "{sizing.12x}", + "900": "{sizing.16x}" + }, + "icon": { + "size": { + "100": "{sizing.2x}", + "200": "{sizing.4x}", + "300": "{sizing.6x}", + "350": "{sizing.7x}", + "400": "{sizing.8x}", + "500": "{sizing.9x}" + } + }, + "padding": { + "100": "{spacing.2x}", + "200": "{spacing.4x}" + }, + "height": { + "100": "{sizing.2x}", + "200": "{sizing.3x}", + "300": "{sizing.4x}", + "400": "{sizing.5x}", + "500": "{sizing.6x}", + "600": "{sizing.7x}", + "650": "{sizing.8x}", + "700": "{sizing.9x}", + "750": "{sizing.10x}", + "800": "{sizing.11x}", + "850": "{sizing.12x}", + "900": "{sizing.16x}" + }, + "gap": { + "100": "{spacing.1x}", + "200": "{spacing.2x}", + "300": "{spacing.3x}", + "400": "{spacing.4x}" + } + }, + "data": { + "padding": { + "100": "{spacing.1x}", + "200": "{spacing.2x}", + "300": "{spacing.3x}", + "400": "{spacing.4x}", + "500": "{spacing.5x}" + }, + "icon": { + "size": { + "100": "{sizing.4x}", + "200": "{sizing.5x}", + "300": "{sizing.6x}", + "400": "{sizing.7x}", + "500": "{sizing.8x}", + "600": "{sizing.9x}", + "700": "{sizing.10x}" + } + }, + "transitionDuration": "{transition.duration.200}", + "borderWidth": "{borderWidth.none}", + "borderRadius": "{borderRadius.100}", + "gap": { + "100": "{spacing.1x}", + "200": "{spacing.2x}", + "300": "{spacing.3x}" + }, + "width": { + "100": "{sizing.min}", + "200": "{sizing.1x}", + "300": "{sizing.2x}", + "400": "{sizing.20x}" + } + }, + "media": { + "size": { + "100": "{sizing.1x}", + "200": "{sizing.2x}", + "300": "{sizing.8x}", + "400": "{sizing.10x}", + "500": "{sizing.14x}", + "600": "{sizing.16x}" + }, + "borderRadius": { + "100": "{borderRadius.200}", + "200": "{borderRadius.300}", + "300": "{borderRadius.400}", + "400": "{borderRadius.500}", + "max": "{borderRadius.max}" + }, + "icon": { + "size": { + "100": "{sizing.4x}", + "200": "{sizing.6x}", + "300": "{sizing.8x}" + } + }, + "transitionDuration": "{transition.duration.200}", + "padding": { + "100": "{spacing.1x}", + "200": "{spacing.2x}", + "300": "{spacing.3x}", + "400": "{spacing.4x}", + "500": "{spacing.5x}", + "600": "{spacing.6x}" + }, + "gap": { + "100": "{spacing.1x}", + "200": "{spacing.2x}" + } + }, + "controls": { + "iconOnly": { + "100": "{sizing.2x}", + "200": "{sizing.4x}", + "300": "{sizing.5x}", + "400": "{sizing.6x}", + "500": "{sizing.7x}", + "600": "{sizing.8x}", + "700": "{sizing.10x}", + "800": "{sizing.11x}", + "850": "{sizing.14x}", + "900": "{sizing.16x}" + }, + "borderRadius": { + "100": "{borderRadius.300}", + "200": "{borderRadius.400}", + "max": "{borderRadius.max}" + }, + "transitionDuration": "{transition.duration.200}", + "padding": { + "100": "{spacing.1x}", + "200": "{spacing.2x}", + "300": "{spacing.3x}", + "400": "{spacing.4x}", + "500": "{spacing.5x}", + "600": "{spacing.6x}" + }, + "gap": { + "100": "{spacing.2x}", + "200": "{spacing.3x}", + "300": "{spacing.4x}" + }, + "width": { + "100": "{sizing.min}" + } + }, + "colorScheme": { + "light": { + "success": { + "50": "{colors.solid.green.50}", + "100": "{colors.solid.green.100}", + "200": "{colors.solid.green.200}", + "300": "{colors.solid.green.300}", + "400": "{colors.solid.green.400}", + "500": "{colors.solid.green.500}", + "600": "{colors.solid.green.600}", + "700": "{colors.solid.green.700}", + "800": "{colors.solid.green.800}", + "900": "{colors.solid.green.900}", + "950": "{colors.solid.green.950}" + }, + "info": { + "50": "{colors.solid.blue.50}", + "100": "{colors.solid.blue.100}", + "200": "{colors.solid.blue.200}", + "300": "{colors.solid.blue.300}", + "400": "{colors.solid.blue.400}", + "500": "{colors.solid.blue.500}", + "600": "{colors.solid.blue.600}", + "700": "{colors.solid.blue.700}", + "800": "{colors.solid.blue.800}", + "900": "{colors.solid.blue.900}", + "950": "{colors.solid.blue.950}" + }, + "warn": { + "50": "{colors.solid.yellow.50}", + "100": "{colors.solid.yellow.100}", + "200": "{colors.solid.yellow.200}", + "300": "{colors.solid.yellow.300}", + "400": "{colors.solid.yellow.400}", + "500": "{colors.solid.yellow.500}", + "600": "{colors.solid.yellow.600}", + "700": "{colors.solid.yellow.700}", + "800": "{colors.solid.yellow.800}", + "900": "{colors.solid.yellow.900}", + "950": "{colors.solid.yellow.950}" + }, + "transparent": "rgba(255, 255, 255, 0.0001)", + "help": { + "50": "{colors.solid.purple.50}", + "100": "{colors.solid.purple.100}", + "200": "{colors.solid.purple.200}", + "300": "{colors.solid.purple.300}", + "400": "{colors.solid.purple.400}", + "500": "{colors.solid.purple.500}", + "600": "{colors.solid.purple.600}", + "700": "{colors.solid.purple.700}", + "800": "{colors.solid.purple.800}", + "900": "{colors.solid.purple.900}", + "950": "{colors.solid.purple.950}" + }, + "error": { + "50": "{colors.solid.red.50}", + "100": "{colors.solid.red.100}", + "200": "{colors.solid.red.200}", + "300": "{colors.solid.red.300}", + "400": "{colors.solid.red.400}", + "500": "{colors.solid.red.500}", + "600": "{colors.solid.red.600}", + "700": "{colors.solid.red.700}", + "800": "{colors.solid.red.800}", + "900": "{colors.solid.red.900}", + "950": "{colors.solid.red.950}" + }, + "surface": { + "0": "{colors.alpha.white.1000}", + "50": "{colors.solid.zinc.50}", + "100": "{colors.solid.zinc.100}", + "200": "{colors.solid.zinc.200}", + "300": "{colors.solid.zinc.300}", + "400": "{colors.solid.zinc.400}", + "500": "{colors.solid.zinc.500}", + "600": "{colors.solid.zinc.600}", + "700": "{colors.solid.zinc.700}", + "800": "{colors.solid.zinc.800}", + "900": "{colors.solid.zinc.900}", + "950": "{colors.solid.zinc.950}" + }, + "primary": { + "color": "{colors.solid.green.500}", + "contrastColor": "{colors.alpha.white.1000}", + "hoverColor": "{colors.solid.green.600}", + "activeColor": "{colors.solid.green.700}", + "hoverBackground": "{colors.solid.green.50}", + "activeBackground": "{colors.solid.green.100}", + "borderColor": "{colors.solid.green.200}", + "selectedBackground": "{colors.solid.green.500}", + "selectedHoverBackground": "{colors.solid.green.600}" + }, + "highlight": { + "background": "{colors.solid.zinc.900}", + "focusBackground": "{colors.solid.zinc.800}", + "color": "{colors.alpha.white.1000}", + "focusColor": "{colors.alpha.white.1000}" + }, + "focusRing": { + "shadow": "{shadows.200}", + "extend": { + "invalid": "{colors.solid.red.200}", + "success": "{colors.solid.green.200}", + "warning": "{colors.solid.yellow.200}", + "info": "{colors.solid.blue.200}" + } + }, + "mask": { + "background": "{colors.alpha.black.400}", + "color": "{surface.200}" + }, + "form": { + "background": "{colors.alpha.white.1000}", + "disabledBackground": "{colors.solid.zinc.200}", + "readonlyBackground": "{colors.solid.zinc.100}", + "filledBackground": "{colors.alpha.white.1000}", + "filledHoverBackground": "{colors.alpha.white.1000}", + "filledFocusBackground": "{colors.alpha.white.1000}", + "borderColor": "{colors.solid.zinc.300}", + "hoverBorderPrimaryColor": "{colors.solid.zinc.900}", + "focusBorderPrimaryColor": "{colors.solid.zinc.900}", + "hoverBorderSecondaryColor": "{colors.solid.green.600}", + "focusBorderSecondaryColor": "{colors.solid.green.600}", + "invalidBorderColor": "{colors.solid.red.400}", + "color": "{colors.solid.zinc.950}", + "disabledColor": "{colors.solid.zinc.500}", + "placeholderColor": "{colors.solid.zinc.500}", + "invalidPlaceholderColor": "{colors.solid.red.600}", + "floatLabelColor": "{colors.solid.zinc.500}", + "floatLabelFocusColor": "{colors.solid.zinc.500}", + "floatLabelActiveColor": "{colors.solid.zinc.500}", + "floatLabelInvalidColor": "{form.invalidPlaceholderColor}", + "iconColor": "{colors.solid.zinc.950}", + "backgroundHandler": "{colors.alpha.white.1000}", + "shadow": "{shadows.200}" + }, + "text": { + "color": "{colors.solid.zinc.900}", + "extend": { + "colorPrimaryStatic": "{colors.solid.zinc.900}", + "colorSecondaryStatic": "{colors.alpha.white.1000}", + "colorInverted": "{colors.alpha.white.1000}" + }, + "hoverColor": "{colors.solid.zinc.700}", + "primaryColor": "{colors.solid.green.600}", + "hoverPrimaryColor": "{colors.solid.green.700}", + "secondaryColor": "{colors.solid.zinc.600}", + "hoverSecondaryColor": "{colors.solid.zinc.400}", + "mutedColor": "{colors.solid.zinc.500}", + "hoverMutedColor": "{colors.solid.zinc.300}", + "disabledColor": "{colors.solid.zinc.300}", + "infoColor": "{colors.solid.blue.600}", + "successColor": "{colors.solid.green.700}", + "dangerColor": "{colors.solid.red.600}", + "warningColor": "{colors.solid.yellow.600}", + "helpColor": "{colors.solid.purple.600}" + }, + "content": { + "background": "{colors.alpha.white.1000}", + "hoverBackground": "{colors.solid.zinc.100}", + "borderColor": "{colors.solid.zinc.200}", + "activeBorderColor": "{colors.solid.zinc.800}", + "color": "{text.color}", + "hoverColor": "{text.hoverColor}", + "shadow": "{shadows.400}" + }, + "list": { + "option": { + "background": "{colors.alpha.white.1000}", + "focusBackground": "{colors.solid.zinc.100}", + "selectedBackground": "{colors.solid.zinc.900}", + "selectedFocusBackground": "{colors.solid.zinc.700}", + "color": "{text.color}", + "focusColor": "{text.color}", + "selectedColor": "{text.extend.colorInverted}", + "selectedFocusColor": "{text.extend.colorInverted}", + "icon": { + "color": "{text.color}", + "focusColor": "{text.color}" + } + }, + "optionGroup": { + "background": "{colors.alpha.white.1000}", + "color": "{text.mutedColor}" + } + }, + "overlay": { + "select": { + "background": "{colors.alpha.white.1000}", + "borderColor": "{colors.solid.zinc.200}", + "color": "{text.color}", + "shadow": "0 0.25rem 0.5rem {colors.alpha.black.200}" + }, + "popover": { + "background": "{colors.alpha.white.1000}", + "borderColor": "{form.borderColor}", + "color": "{text.color}", + "shadow": "{shadows.400}" + }, + "modal": { + "background": "{colors.alpha.white.1000}", + "backdrop": "{colors.alpha.black.300}", + "borderColor": "{colors.solid.zinc.200}", + "color": "{text.color}", + "shadow": "{shadows.200}" + } + }, + "navigation": { + "submenuLabel": { + "background": "rgba(255, 255, 255, 0.0000)", + "color": "{text.mutedColor}" + }, + "submenuIcon": { + "color": "{colors.solid.zinc.900}", + "focusColor": "{colors.solid.zinc.900}", + "activeColor": "{colors.alpha.white.1000}" + }, + "item": { + "focusBackground": "{colors.solid.zinc.100}", + "activeBackground": "{colors.solid.zinc.900}", + "color": "{colors.solid.zinc.900}", + "focusColor": "{colors.solid.zinc.900}", + "icon": { + "color": "{colors.solid.zinc.900}", + "focusColor": "{colors.solid.zinc.900}", + "activeColor": "{colors.alpha.white.1000}" + }, + "activeColor": "{colors.alpha.white.1000}" + }, + "shadow": "{shadows.400}" + } + }, + "dark": { + "success": { + "50": "{colors.solid.green.950}", + "100": "{colors.solid.green.900}", + "200": "{colors.solid.green.800}", + "300": "{colors.solid.green.700}", + "400": "{colors.solid.green.600}", + "500": "{colors.solid.green.500}", + "600": "{colors.solid.green.400}", + "700": "{colors.solid.green.300}", + "800": "{colors.solid.green.200}", + "900": "{colors.solid.green.100}", + "950": "{colors.solid.green.50}" + }, + "info": { + "50": "{colors.solid.blue.950}", + "100": "{colors.solid.blue.900}", + "200": "{colors.solid.blue.800}", + "300": "{colors.solid.blue.700}", + "400": "{colors.solid.blue.600}", + "500": "{colors.solid.blue.500}", + "600": "{colors.solid.blue.400}", + "700": "{colors.solid.blue.300}", + "800": "{colors.solid.blue.200}", + "900": "{colors.solid.blue.100}", + "950": "{colors.solid.blue.50}" + }, + "warn": { + "50": "{colors.solid.yellow.950}", + "100": "{colors.solid.yellow.900}", + "200": "{colors.solid.yellow.800}", + "300": "{colors.solid.yellow.700}", + "400": "{colors.solid.yellow.600}", + "500": "{colors.solid.yellow.500}", + "600": "{colors.solid.yellow.400}", + "700": "{colors.solid.yellow.300}", + "800": "{colors.solid.yellow.200}", + "900": "{colors.solid.yellow.100}", + "950": "{colors.solid.yellow.50}" + }, + "transparent": "rgba(0, 0, 0, 0.0001)", + "help": { + "50": "{colors.solid.purple.950}", + "100": "{colors.solid.purple.900}", + "200": "{colors.solid.purple.800}", + "300": "{colors.solid.purple.700}", + "400": "{colors.solid.purple.600}", + "500": "{colors.solid.purple.500}", + "600": "{colors.solid.purple.400}", + "700": "{colors.solid.purple.300}", + "800": "{colors.solid.purple.200}", + "900": "{colors.solid.purple.100}", + "950": "{colors.solid.purple.50}" + }, + "error": { + "50": "{colors.solid.red.950}", + "100": "{colors.solid.red.900}", + "200": "{colors.solid.red.800}", + "300": "{colors.solid.red.700}", + "400": "{colors.solid.red.600}", + "500": "{colors.solid.red.500}", + "600": "{colors.solid.red.400}", + "700": "{colors.solid.red.300}", + "800": "{colors.solid.red.200}", + "900": "{colors.solid.red.100}", + "950": "{colors.solid.red.50}" + }, + "surface": { + "0": "{colors.alpha.black.1000}", + "50": "{colors.solid.zinc.950}", + "100": "{colors.solid.zinc.900}", + "200": "{colors.solid.zinc.800}", + "300": "{colors.solid.zinc.700}", + "400": "{colors.solid.zinc.600}", + "500": "{colors.solid.zinc.500}", + "600": "{colors.solid.zinc.400}", + "700": "{colors.solid.zinc.300}", + "800": "{colors.solid.zinc.200}", + "900": "{colors.solid.zinc.100}", + "950": "{colors.solid.zinc.50}" + }, + "primary": { + "color": "{colors.solid.green.500}", + "contrastColor": "{colors.solid.zinc.900}", + "hoverColor": "{colors.solid.green.400}", + "activeColor": "{colors.solid.green.300}", + "hoverBackground": "{colors.solid.green.950}", + "activeBackground": "{colors.solid.green.900}", + "borderColor": "{colors.solid.green.800}", + "selectedBackground": "{colors.solid.green.500}", + "selectedHoverBackground": "{colors.solid.green.600}" + }, + "highlight": { + "background": "{colors.solid.zinc.100}", + "focusBackground": "{colors.solid.zinc.200}", + "color": "{colors.solid.zinc.900}", + "focusColor": "{colors.solid.zinc.900}" + }, + "focusRing": { + "shadow": "{shadows.200}", + "extend": { + "invalid": "{colors.solid.red.800}", + "success": "{colors.solid.green.800}", + "warning": "{colors.solid.yellow.800}", + "info": "{colors.solid.blue.800}" + } + }, + "mask": { + "background": "{colors.alpha.black.600}", + "color": "{surface.800}" + }, + "form": { + "background": "{colors.solid.zinc.950}", + "disabledBackground": "{colors.solid.zinc.800}", + "readonlyBackground": "{colors.solid.zinc.900}", + "filledBackground": "{colors.solid.zinc.950}", + "filledHoverBackground": "{colors.solid.zinc.950}", + "filledFocusBackground": "{colors.solid.zinc.950}", + "borderColor": "{colors.solid.zinc.700}", + "hoverBorderPrimaryColor": "{colors.solid.zinc.100}", + "focusBorderPrimaryColor": "{colors.solid.zinc.100}", + "hoverBorderSecondaryColor": "{colors.solid.green.400}", + "focusBorderSecondaryColor": "{colors.solid.green.400}", + "invalidBorderColor": "{colors.solid.red.600}", + "color": "{colors.alpha.white.1000}", + "disabledColor": "{colors.solid.zinc.500}", + "placeholderColor": "{colors.solid.zinc.500}", + "invalidPlaceholderColor": "{colors.solid.red.400}", + "floatLabelColor": "{colors.solid.zinc.500}", + "floatLabelFocusColor": "{colors.solid.zinc.500}", + "floatLabelActiveColor": "{colors.solid.zinc.500}", + "floatLabelInvalidColor": "{form.invalidPlaceholderColor}", + "iconColor": "{colors.alpha.white.1000}", + "backgroundHandler": "{colors.alpha.white.1000}", + "shadow": "{shadows.200}" + }, + "text": { + "color": "{colors.alpha.white.1000}", + "extend": { + "colorPrimaryStatic": "{colors.solid.zinc.900}", + "colorSecondaryStatic": "{colors.alpha.white.1000}", + "colorInverted": "{colors.solid.zinc.900}" + }, + "hoverColor": "{colors.solid.zinc.300}", + "primaryColor": "{colors.solid.green.400}", + "hoverPrimaryColor": "{colors.solid.green.300}", + "secondaryColor": "{colors.solid.zinc.400}", + "hoverSecondaryColor": "{colors.solid.zinc.600}", + "mutedColor": "{colors.solid.zinc.500}", + "hoverMutedColor": "{colors.solid.zinc.700}", + "disabledColor": "{colors.solid.zinc.700}", + "infoColor": "{colors.solid.blue.400}", + "successColor": "{colors.solid.green.400}", + "dangerColor": "{colors.solid.red.400}", + "warningColor": "{colors.solid.yellow.400}", + "helpColor": "{colors.solid.purple.400}" + }, + "content": { + "background": "{colors.solid.zinc.900}", + "hoverBackground": "{colors.solid.zinc.800}", + "borderColor": "{colors.solid.zinc.800}", + "activeBorderColor": "{colors.solid.zinc.200}", + "color": "{text.color}", + "hoverColor": "{text.hoverColor}", + "shadow": "{shadows.400}" + }, + "list": { + "option": { + "background": "{colors.solid.zinc.900}", + "focusBackground": "{colors.solid.zinc.800}", + "selectedBackground": "{colors.solid.zinc.100}", + "selectedFocusBackground": "{colors.solid.zinc.300}", + "color": "{text.color}", + "focusColor": "{text.color}", + "selectedColor": "{text.extend.colorInverted}", + "selectedFocusColor": "{text.extend.colorInverted}", + "icon": { + "color": "{text.color}", + "focusColor": "{text.color}" + } + }, + "optionGroup": { + "background": "{colors.solid.zinc.900}", + "color": "{text.mutedColor}" + } + }, + "overlay": { + "select": { + "background": "{colors.solid.zinc.900}", + "borderColor": "{colors.solid.zinc.800}", + "color": "{text.color}", + "shadow": "{shadows.400}" + }, + "popover": { + "background": "{colors.solid.zinc.900}", + "borderColor": "{form.borderColor}", + "color": "{text.color}", + "shadow": "{shadows.400}" + }, + "modal": { + "background": "{colors.solid.zinc.900}", + "backdrop": "{colors.alpha.black.300}", + "borderColor": "{colors.solid.zinc.800}", + "color": "{text.color}", + "shadow": "{shadows.200}" + } + }, + "navigation": { + "submenuLabel": { + "background": "rgba(255, 255, 255, 0.0000)", + "color": "{text.mutedColor}" + }, + "submenuIcon": { + "color": "{colors.solid.zinc.100}", + "focusColor": "{colors.solid.zinc.100}", + "activeColor": "{colors.solid.zinc.900}" + }, + "item": { + "focusBackground": "{colors.solid.zinc.900}", + "activeBackground": "{colors.solid.zinc.100}", + "color": "{colors.alpha.white.1000}", + "focusColor": "{colors.alpha.white.1000}", + "icon": { + "color": "{colors.alpha.white.1000}", + "focusColor": "{colors.alpha.white.1000}", + "activeColor": "{colors.solid.zinc.900}" + }, + "activeColor": "{colors.solid.zinc.900}" + }, + "shadow": "{shadows.400}" + } + } + } + }, + "components": { + "accordion": { + "extend": { + "extHeader": { + "iconSize": "{controls.iconOnly.300}", + "gap": "{controls.gap.100}" + } + }, + "colorScheme": { + "light": { + "header": { + "background": "{transparent}", + "hoverBackground": "{transparent}", + "activeBackground": "{transparent}", + "activeHoverBackground": "{transparent}" + } + } + }, + "header": { + "color": "{text.color}", + "hoverColor": "{text.hoverColor}", + "activeColor": "{text.color}", + "activeHoverColor": "{text.hoverColor}", + "borderColor": "{transparent}", + "padding": "{navigation.padding.300} 0 {navigation.padding.300} 0", + "fontWeight": "{fonts.fontWeight.bold}", + "borderRadius": "{borderRadius.none}", + "borderWidth": "{borderWidth.none}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "inset {focus.ring.shadow}" + }, + "toggleIcon": { + "color": "{text.color}", + "hoverColor": "{text.hoverColor}", + "activeColor": "{text.color}", + "activeHoverColor": "{text.hoverColor}" + }, + "last": { + "bottomBorderRadius": "{borderRadius.none}", + "activeBottomBorderRadius": "{borderRadius.none}" + }, + "first": { + "borderWidth": "{borderWidth.none}", + "topBorderRadius": "{borderRadius.none}" + } + }, + "root": { + "transitionDuration": "{controls.transitionDuration}" + }, + "panel": { + "borderWidth": "{borderWidth.none} {borderWidth.none} {navigation.width.200} {borderWidth.none}", + "borderColor": "{form.borderColor}" + }, + "content": { + "borderWidth": "{content.borderWidth} {borderWidth.none} {borderWidth.none} {borderWidth.none}", + "borderColor": "{transparent}", + "background": "{transparent}", + "color": "{text.color}", + "padding": "0 {content.padding.400} {content.padding.300} {content.padding.400}" + } + }, + "autocomplete": { + "extend": { + "extOption": { + "gap": "{form.gap.200}" + }, + "extOptionGroup": { + "gap": "{form.gap.200}" + } + }, + "colorScheme": { + "light": { + "chip": { + "focusBackground": "{chip.colorScheme.light.root.background}", + "focusColor": "{chip.colorScheme.light.root.color}" + }, + "dropdown": { + "background": "{form.background}", + "hoverBackground": "{form.background}", + "activeBackground": "{form.background}", + "color": "{form.color}", + "hoverColor": "{form.color}", + "activeColor": "{form.color}" + } + } + }, + "root": { + "background": "{form.background}", + "disabledBackground": "{form.disabledBackground}", + "filledBackground": "{form.filledBackground}", + "filledHoverBackground": "{form.filledHoverBackground}", + "filledFocusBackground": "{form.filledFocusBackground}", + "borderColor": "{form.borderColor}", + "hoverBorderColor": "{form.hoverBorderSecondaryColor}", + "focusBorderColor": "{form.focusBorderSecondaryColor}", + "invalidBorderColor": "{form.invalidBorderColor}", + "color": "{form.color}", + "disabledColor": "{form.disabledColor}", + "placeholderColor": "{form.placeholderColor}", + "invalidPlaceholderColor": "{form.invalidPlaceholderColor}", + "shadow": "0", + "paddingX": "{form.padding.300}", + "paddingY": "{form.padding.300}", + "borderRadius": "{form.borderRadius.200}", + "transitionDuration": "{form.transitionDuration}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{form.focusRing.style}", + "color": "{form.focusRing.color}", + "offset": "{form.focusRing.offset}", + "shadow": "0" + } + }, + "overlay": { + "background": "{overlay.select.background}", + "borderColor": "{overlay.select.borderColor}", + "borderRadius": "{overlay.select.borderRadius}", + "color": "{overlay.select.color}", + "shadow": "{form.shadow}" + }, + "list": { + "padding": "{list.padding}", + "gap": "{list.gap.100}" + }, + "option": { + "focusBackground": "{list.option.focusBackground}", + "selectedBackground": "{list.option.selectedBackground}", + "selectedFocusBackground": "{list.option.selectedFocusBackground}", + "color": "{list.option.color}", + "focusColor": "{list.option.focusColor}", + "selectedColor": "{list.option.selectedColor}", + "selectedFocusColor": "{list.option.selectedFocusColor}", + "padding": "{list.option.padding}", + "borderRadius": "{list.option.borderRadius}" + }, + "optionGroup": { + "background": "{list.optionGroup.background}", + "color": "{list.optionGroup.color}", + "fontWeight": "{fonts.fontWeight.demibold}", + "padding": "{list.optionGroup.padding}" + }, + "dropdown": { + "width": "{form.width.full}", + "borderColor": "{form.borderColor}", + "hoverBorderColor": "{form.hoverBorderSecondaryColor}", + "activeBorderColor": "{form.focusBorderSecondaryColor}", + "borderRadius": "{form.borderRadius.200}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{form.focusRing.style}", + "color": "{form.focusRing.color}", + "offset": "{form.focusRing.offset}", + "shadow": "0" + }, + "sm": { + "width": "{form.width.200}" + }, + "lg": { + "width": "{form.width.400}" + } + }, + "chip": { + "borderRadius": "{chip.root.borderRadius}" + }, + "emptyMessage": { + "padding": "{list.option.padding}" + } + }, + "avatar": { + "extend": { + "borderColor": "{form.borderColor}", + "circle": { + "borderRadius": "{media.borderRadius.max}" + } + }, + "root": { + "width": "{media.size.300}", + "height": "{media.size.300}", + "fontSize": "{fonts.fontSize.200}", + "color": "{text.extend.colorPrimaryStatic}", + "background": "{primary.color}", + "borderRadius": "{media.borderRadius.200}" + }, + "icon": { + "size": "{media.icon.size.100}" + }, + "group": { + "borderColor": "{content.background}", + "offset": "-{media.padding.300}" + }, + "lg": { + "width": "{media.size.400}", + "height": "{media.size.400}", + "fontSize": "{fonts.fontSize.300}", + "icon": { + "size": "{media.icon.size.100}" + }, + "group": { + "offset": "-{media.padding.300}" + } + }, + "xl": { + "width": "{media.size.500}", + "height": "{media.size.500}", + "icon": { + "size": "{media.icon.size.200}" + }, + "group": { + "offset": "-{media.padding.600}" + }, + "fontSize": "{fonts.fontSize.500}" + } + }, + "badge": { + "extend": { + "extDot": { + "success": { + "background": "{colors.solid.green.400}" + }, + "info": { + "background": "{info.400}" + }, + "warn": { + "background": "{warn.400}" + }, + "danger": { + "background": "{error.400}" + }, + "lg": { + "size": "{feedback.width.400}" + }, + "xlg": { + "size": "{feedback.width.500}" + } + }, + "ext": { + "padding": "0rem" + } + }, + "colorScheme": { + "light": { + "primary": { + "color": "{text.extend.colorPrimaryStatic}", + "background": "{primary.color}" + }, + "secondary": { + "color": "{text.extend.colorInverted}", + "background": "{surface.900}" + }, + "success": { + "color": "{success.900}", + "background": "{success.300}" + }, + "info": { + "color": "{info.900}", + "background": "{info.300}" + }, + "warn": { + "color": "{warn.900}", + "background": "{warn.300}" + }, + "danger": { + "color": "{error.900}", + "background": "{error.300}" + } + } + }, + "root": { + "borderRadius": "{feedback.width.300}", + "padding": "{feedback.padding.100}", + "fontSize": "{fonts.fontSize.100}", + "fontWeight": "{fonts.fontWeight.regular}", + "minWidth": "{feedback.width.600}", + "height": "{feedback.height.500}" + }, + "dot": { + "size": "{feedback.width.300}" + }, + "sm": { + "fontSize": "{fonts.fontSize.100}", + "minWidth": "0rem", + "height": "0rem" + }, + "lg": { + "fontSize": "{fonts.fontSize.100}", + "minWidth": "{feedback.width.650}", + "height": "{feedback.height.600}" + }, + "xl": { + "fontSize": "{fonts.fontSize.100}", + "minWidth": "{feedback.width.700}", + "height": "{feedback.height.650}" + } + }, + "breadcrumb": { + "extend": { + "hoverBackground": "{surface.100}", + "iconSize": "{navigation.size.300}", + "extItem": { + "padding": "{navigation.padding.100}" + } + }, + "root": { + "padding": "0rem", + "background": "{transparent}", + "gap": "0rem", + "transitionDuration": "{form.transitionDuration}" + }, + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + }, + "item": { + "color": "{text.color}", + "hoverColor": "{text.hoverColor}", + "borderRadius": "{navigation.borderRadius}", + "gap": "{navigation.item.gap}", + "icon": { + "color": "{text.color}", + "hoverColor": "{text.hoverColor}" + } + }, + "separator": { + "color": "{text.color}" + } + }, + "button": { + "extend": { + "disabledBackground": "{form.disabledBackground}", + "extOutlined": { + "danger": { + "focusBackground": "{transparent}" + }, + "warn": { + "focusBackground": "{transparent}" + }, + "info": { + "focusBackground": "{transparent}" + }, + "help": { + "focusBackground": "{transparent}" + }, + "success": { + "focusBackground": "{transparent}" + } + }, + "disabledColor": "{form.disabledColor}", + "extText": { + "danger": { + "focusBackground": "{transparent}" + }, + "warn": { + "focusBackground": "{transparent}" + }, + "info": { + "focusBackground": "{transparent}" + }, + "help": { + "focusBackground": "{transparent}" + }, + "success": { + "focusBackground": "{transparent}" + } + }, + "extLink": { + "background": "{transparent}", + "colorHover": "{text.hoverColor}", + "paddingX": "0rem", + "paddingY": "{controls.padding.100}", + "sm": { + "iconOnlyWidth": "{controls.iconOnly.200}" + }, + "base": { + "iconOnlyWidth": "{controls.iconOnly.400}" + }, + "lg": { + "iconOnlyWidth": "{controls.iconOnly.500}" + }, + "xlg": { + "iconOnlyWidth": "{controls.iconOnly.600}" + } + }, + "extSm": { + "borderRadius": "{controls.borderRadius.100}", + "gap": "{controls.gap.100}" + }, + "extLg": { + "borderRadius": "{controls.borderRadius.200}", + "gap": "{controls.gap.200}", + "height": "{controls.iconOnly.850}" + }, + "extXlg": { + "borderRadius": "{controls.borderRadius.200}", + "gap": "{controls.gap.200}", + "iconOnlyWidth": "{controls.iconOnly.900}", + "paddingX": "{controls.padding.600}", + "paddingY": "{controls.padding.500}", + "height": "{controls.iconOnly.900}" + }, + "borderWidth": "{controls.width.100}", + "iconSize": { + "sm": "{controls.iconOnly.200}", + "md": "{controls.iconOnly.300}", + "lg": "{controls.iconOnly.400}" + } + }, + "colorScheme": { + "light": { + "root": { + "primary": { + "background": "{primary.color}", + "hoverBackground": "{primary.hoverColor}", + "activeBackground": "{primary.activeColor}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{text.extend.colorPrimaryStatic}", + "hoverColor": "{text.extend.colorPrimaryStatic}", + "activeColor": "{text.extend.colorPrimaryStatic}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + }, + "secondary": { + "background": "{surface.900}", + "hoverBackground": "{surface.800}", + "activeBackground": "{surface.700}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{text.extend.colorInverted}", + "hoverColor": "{text.extend.colorInverted}", + "activeColor": "{text.extend.colorInverted}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + }, + "contrast": { + "background": "{surface.200}", + "hoverBackground": "{surface.300}", + "activeBackground": "{surface.400}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{text.color}", + "hoverColor": "{text.color}", + "activeColor": "{text.color}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + }, + "info": { + "background": "{info.300}", + "hoverBackground": "{info.400}", + "activeBackground": "{info.500}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{info.900}", + "hoverColor": "{info.950}", + "activeColor": "{info.900}" + }, + "success": { + "background": "{success.300}", + "hoverBackground": "{success.400}", + "activeBackground": "{success.500}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{success.900}", + "hoverColor": "{success.950}", + "activeColor": "{success.900}" + }, + "warn": { + "background": "{warn.300}", + "hoverBackground": "{warn.400}", + "activeBackground": "{warn.500}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{warn.900}", + "hoverColor": "{warn.950}", + "activeColor": "{warn.900}" + }, + "help": { + "background": "{help.300}", + "hoverBackground": "{help.400}", + "activeBackground": "{help.500}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{help.900}", + "hoverColor": "{help.950}", + "activeColor": "{help.900}" + }, + "danger": { + "background": "{error.300}", + "hoverBackground": "{error.400}", + "activeBackground": "{error.500}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{error.900}", + "hoverColor": "{error.950}", + "activeColor": "{error.900}" + } + }, + "outlined": { + "primary": { + "hoverBackground": "{primary.hoverBackground}", + "activeBackground": "{primary.activeBackground}", + "borderColor": "{primary.borderColor}", + "color": "{primary.color}" + }, + "success": { + "hoverBackground": "{success.100}", + "activeBackground": "{success.200}", + "borderColor": "{success.600}", + "color": "{success.600}" + }, + "info": { + "hoverBackground": "{info.100}", + "activeBackground": "{info.200}", + "borderColor": "{info.600}", + "color": "{info.600}" + }, + "warn": { + "hoverBackground": "{warn.100}", + "activeBackground": "{warn.200}", + "borderColor": "{warn.600}", + "color": "{warn.600}" + }, + "help": { + "hoverBackground": "{help.100}", + "activeBackground": "{help.200}", + "borderColor": "{help.600}", + "color": "{help.600}" + }, + "danger": { + "hoverBackground": "{error.100}", + "activeBackground": "{error.200}", + "borderColor": "{error.600}", + "color": "{error.600}" + } + }, + "text": { + "primary": { + "hoverBackground": "{surface.100}", + "activeBackground": "{surface.200}", + "color": "{text.color}" + }, + "success": { + "hoverBackground": "{success.100}", + "activeBackground": "{success.200}", + "color": "{success.600}" + }, + "info": { + "hoverBackground": "{info.100}", + "activeBackground": "{info.200}", + "color": "{info.600}" + }, + "warn": { + "hoverBackground": "{warn.100}", + "activeBackground": "{warn.200}", + "color": "{warn.600}" + }, + "help": { + "hoverBackground": "{help.100}", + "activeBackground": "{help.200}", + "color": "{help.600}" + }, + "danger": { + "hoverBackground": "{error.100}", + "activeBackground": "{error.200}", + "color": "{error.600}" + } + }, + "link": { + "color": "{text.color}", + "hoverColor": "{text.hoverColor}", + "activeColor": "{text.mutedColor}" + } + }, + "dark": { + "root": { + "primary": { + "background": "{primary.color}", + "hoverBackground": "{primary.hoverColor}", + "activeBackground": "{primary.activeColor}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{text.extend.colorPrimaryStatic}", + "hoverColor": "{text.extend.colorPrimaryStatic}", + "activeColor": "{text.extend.colorPrimaryStatic}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + }, + "secondary": { + "background": "{surface.200}", + "hoverBackground": "{surface.300}", + "activeBackground": "{surface.400}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{surface.950}", + "hoverColor": "{surface.950}", + "activeColor": "{surface.950}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + }, + "contrast": { + "background": "{surface.950}", + "hoverBackground": "{surface.900}", + "activeBackground": "{surface.800}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{surface.0}", + "hoverColor": "{surface.0}", + "activeColor": "{surface.0}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + }, + "info": { + "background": "{info.500}", + "hoverBackground": "{info.400}", + "activeBackground": "{info.300}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{text.extend.colorPrimaryStatic}", + "hoverColor": "{text.extend.colorPrimaryStatic}", + "activeColor": "{text.extend.colorPrimaryStatic}" + }, + "success": { + "background": "{success.500}", + "hoverBackground": "{success.400}", + "activeBackground": "{success.300}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{text.extend.colorPrimaryStatic}", + "hoverColor": "{text.extend.colorPrimaryStatic}", + "activeColor": "{text.extend.colorPrimaryStatic}" + }, + "warn": { + "background": "{warn.500}", + "hoverBackground": "{warn.400}", + "activeBackground": "{warn.300}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{text.extend.colorPrimaryStatic}", + "hoverColor": "{text.extend.colorPrimaryStatic}", + "activeColor": "{text.extend.colorPrimaryStatic}" + }, + "help": { + "background": "{help.500}", + "hoverBackground": "{help.400}", + "activeBackground": "{help.300}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{text.extend.colorPrimaryStatic}", + "hoverColor": "{text.extend.colorPrimaryStatic}", + "activeColor": "{text.extend.colorPrimaryStatic}" + }, + "danger": { + "background": "{error.500}", + "hoverBackground": "{error.400}", + "activeBackground": "{error.300}", + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "activeBorderColor": "{transparent}", + "color": "{text.extend.colorPrimaryStatic}", + "hoverColor": "{text.extend.colorPrimaryStatic}", + "activeColor": "{text.extend.colorPrimaryStatic}" + } + }, + "outlined": { + "primary": { + "hoverBackground": "{primary.hoverBackground}", + "activeBackground": "{primary.activeBackground}", + "borderColor": "{primary.borderColor}", + "color": "{primary.color}" + }, + "success": { + "hoverBackground": "{success.950}", + "activeBackground": "{success.900}", + "borderColor": "{success.500}", + "color": "{success.500}" + }, + "info": { + "hoverBackground": "{info.950}", + "activeBackground": "{info.900}", + "borderColor": "{info.500}", + "color": "{info.500}" + }, + "warn": { + "hoverBackground": "{warn.950}", + "activeBackground": "{warn.900}", + "borderColor": "{warn.500}", + "color": "{warn.500}" + }, + "help": { + "hoverBackground": "{help.950}", + "activeBackground": "{help.900}", + "borderColor": "{help.500}", + "color": "{help.500}" + }, + "danger": { + "hoverBackground": "{error.950}", + "activeBackground": "{error.900}", + "borderColor": "{error.500}", + "color": "{error.500}" + } + }, + "text": { + "primary": { + "hoverBackground": "{surface.800}", + "activeBackground": "{surface.700}", + "color": "{text.color}" + }, + "success": { + "hoverBackground": "{success.950}", + "activeBackground": "{success.900}", + "color": "{success.500}" + }, + "info": { + "hoverBackground": "{info.950}", + "activeBackground": "{info.900}", + "color": "{info.500}" + }, + "warn": { + "hoverBackground": "{warn.950}", + "activeBackground": "{warn.900}", + "color": "{warn.500}" + }, + "help": { + "hoverBackground": "{help.950}", + "activeBackground": "{help.900}", + "color": "{help.500}" + }, + "danger": { + "hoverBackground": "{error.950}", + "activeBackground": "{error.900}", + "color": "{error.500}" + } + }, + "link": { + "color": "{text.color}", + "hoverColor": "{text.hoverColor}", + "activeColor": "{text.mutedColor}" + } + } + }, + "root": { + "borderRadius": "{controls.borderRadius.100}", + "roundedBorderRadius": "{controls.borderRadius.max}", + "gap": "{controls.gap.100}", + "fontSize": "{fonts.fontSize.200}", + "paddingX": "{controls.padding.400}", + "paddingY": "{controls.padding.200}", + "iconOnlyWidth": "{controls.iconOnly.700}", + "raisedShadow": "none", + "badgeSize": "{feedback.width.500}", + "transitionDuration": "{controls.transitionDuration}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "offset": "{focusRing.offset}" + }, + "sm": { + "fontSize": "{fonts.fontSize.200}", + "iconOnlyWidth": "{controls.iconOnly.600}", + "paddingX": "{controls.padding.300}", + "paddingY": "{controls.padding.200}" + }, + "lg": { + "fontSize": "{fonts.fontSize.500}", + "iconOnlyWidth": "{controls.iconOnly.850}", + "paddingX": "{controls.padding.600}", + "paddingY": "{controls.padding.400}" + }, + "label": { + "fontWeight": "{fonts.fontWeight.demibold}" + } + } + }, + "card": { + "extend": { + "borderColor": "{content.borderColor}", + "borderWidth": "{content.borderWidth}" + }, + "root": { + "background": "{content.background}", + "borderRadius": "{content.gap.400}", + "color": "{content.color}" + }, + "body": { + "padding": "{content.padding.300}", + "gap": "{content.gap.400}" + }, + "caption": { + "gap": "{content.gap.100}" + }, + "title": { + "fontSize": "{fonts.fontSize.400}", + "fontWeight": "{fonts.fontWeight.demibold}" + }, + "subtitle": { + "color": "{text.mutedColor}" + } + }, + "carousel": { + "colorScheme": { + "light": { + "indicator": { + "background": "{surface.300}", + "hoverBackground": "{surface.400}", + "activeBackground": "{surface.900}" + } + } + }, + "root": { + "transitionDuration": "{media.transitionDuration}" + }, + "content": { + "gap": "{media.gap.200}" + }, + "indicatorList": { + "padding": "{media.padding.400}", + "gap": "{media.gap.200}" + }, + "indicator": { + "width": "{controls.iconOnly.100}", + "height": "{controls.iconOnly.100}", + "borderRadius": "{media.borderRadius.400}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + } + }, + "checkbox": { + "root": { + "borderRadius": "{form.borderRadius.100}", + "extend": { + "borderWidth": "{form.borderWidth}" + }, + "width": "{form.size.400}", + "height": "{form.size.400}", + "background": "{form.background}", + "checkedBackground": "{surface.900}", + "checkedHoverBackground": "{surface.800}", + "disabledBackground": "{form.disabledBackground}", + "filledBackground": "{form.filledBackground}", + "borderColor": "{form.borderColor}", + "hoverBorderColor": "{form.hoverBorderPrimaryColor}", + "focusBorderColor": "{form.focusBorderPrimaryColor}", + "checkedBorderColor": "{surface.900}", + "checkedHoverBorderColor": "{surface.800}", + "checkedFocusBorderColor": "{primary.color}", + "checkedDisabledBorderColor": "{form.borderColor}", + "invalidBorderColor": "{form.invalidBorderColor}", + "shadow": "0", + "focusRing": { + "focusRing": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + }, + "sm": { + "width": "{form.size.200}", + "height": "{form.size.200}" + }, + "lg": { + "width": "{form.size.350}", + "height": "{form.size.350}" + }, + "transitionDuration": "{form.transitionDuration}" + }, + "icon": { + "size": "{form.icon.300}", + "color": "{form.color}", + "checkedColor": "{primary.contrastColor}", + "checkedHoverColor": "{primary.contrastColor}", + "disabledColor": "{form.disabledColor}", + "sm": { + "size": "{form.icon.200}" + }, + "lg": { + "size": "{form.icon.400}" + } + } + }, + "chip": { + "extend": { + "borderColor": "{transparent}", + "borderWidth": "{controls.width.100}" + }, + "root": { + "borderRadius": "{media.borderRadius.100}", + "paddingX": "{media.padding.200}", + "paddingY": "{media.padding.100}", + "gap": "{media.gap.200}", + "transitionDuration": "{media.transitionDuration}" + }, + "colorScheme": { + "light": { + "root": { + "background": "{surface.200}", + "color": "{text.color}" + }, + "icon": { + "color": "{text.color}" + }, + "removeIcon": { + "color": "{text.color}" + } + } + }, + "image": { + "width": "0rem", + "height": "0rem" + }, + "icon": { + "size": "{media.icon.size.100}" + }, + "removeIcon": { + "size": "{media.icon.size.100}", + "focusRing": { + "width": "{form.focusRing.width}", + "style": "{form.focusRing.style}", + "color": "{focusRing.color}", + "offset": "{form.focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + } + }, + "confirmdialog": { + "extend": { + "extIcon": { + "success": "{success.500}", + "info": "{info.500}", + "help": "{help.500}", + "warn": "{warn.500}", + "danger": "{error.500}" + } + }, + "icon": { + "size": "{overlay.icon.size.200}", + "color": "{overlay.modal.color}" + }, + "content": { + "gap": "0rem" + } + }, + "confirmpopup": { + "root": { + "background": "{overlay.popover.background}", + "color": "{overlay.popover.color}", + "shadow": "{overlay.popover.shadow}", + "gutter": "{overlay.gap.300}", + "arrowOffset": "{overlay.modal.padding.200}" + }, + "content": { + "padding": "{overlay.popover.padding.100}", + "gap": "{overlay.gap.400}" + }, + "icon": { + "size": "{overlay.icon.size.200}", + "color": "{overlay.popover.color}" + }, + "footer": { + "gap": "{overlay.gap.200}", + "padding": "0 {overlay.popover.padding} {overlay.popover.padding} {overlay.popover.padding}" + } + }, + "contextmenu": { + "root": { + "background": "{content.background}", + "color": "{content.color}", + "shadow": "{navigation.shadow}" + }, + "list": { + "padding": "{navigation.list.padding.md} 0", + "gap": "{navigation.list.gap}" + }, + "item": { + "padding": "{navigation.item.padding}", + "gap": "{navigation.item.gap}" + }, + "submenu": { + "mobileIndent": "{navigation.submenu.padding}" + } + }, + "datatable": { + "colorScheme": { + "light": { + "root": { + "color": "{text.color}", + "borderColor": "{content.borderColor}" + }, + "header": { + "background": "{surface.50}", + "color": "{text.color}" + }, + "headerCell": { + "background": "{surface.50}", + "hoverBackground": "{surface.100}", + "color": "{text.color}" + }, + "footer": { + "background": "{surface.100}", + "color": "{text.color}" + }, + "footerCell": { + "background": "{content.hoverBackground}", + "color": "{text.color}" + }, + "row": { + "stripedBackground": "{content.hoverBackground}" + }, + "bodyCell": { + "selectedBorderColor": "{content.borderColor}" + } + } + }, + "extended": { + "extHeaderCell": { + "selectedHoverBackground": "{surface.800}" + }, + "extRow": { + "selectedHoverBackground": "{surface.800}", + "stripedHoverBackground": "{surface.100}" + } + }, + "root": { + "transitionDuration": "{data.transitionDuration}" + }, + "header": { + "borderColor": "{content.borderColor}", + "borderWidth": "{data.width.100} 0 {data.width.100} 0", + "padding": "{data.padding.400}", + "sm": { + "padding": "{data.padding.200}" + }, + "lg": { + "padding": "{data.padding.500}" + } + }, + "headerCell": { + "selectedBackground": "{highlight.background}", + "borderColor": "{content.borderColor}", + "hoverColor": "{text.extend.colorInverted}", + "selectedColor": "{highlight.color}", + "gap": "{data.gap.200}", + "padding": "{data.padding.400}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "inset {focus.ring.shadow}" + }, + "sm": { + "padding": "{data.padding.200}" + }, + "lg": { + "padding": "{data.padding.500}" + } + }, + "columnTitle": { + "fontWeight": "{fonts.fontWeight.bold}" + }, + "row": { + "background": "{content.background}", + "hoverBackground": "{content.hoverBackground}", + "selectedBackground": "{highlight.background}", + "color": "{content.color}", + "hoverColor": "{content.hoverColor}", + "selectedColor": "{highlight.color}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "inset {focus.ring.shadow}" + } + }, + "bodyCell": { + "borderColor": "{content.borderColor}", + "padding": "{data.padding.400}", + "sm": { + "padding": "{data.padding.200}" + }, + "lg": { + "padding": "{data.padding.500}" + } + }, + "footerCell": { + "borderColor": "{content.borderColor}", + "padding": "{data.padding.400}", + "sm": { + "padding": "{data.padding.200}" + }, + "lg": { + "padding": "{data.padding.500}" + } + }, + "columnFooter": { + "fontWeight": "{fonts.fontWeight.bold}" + }, + "dropPoint": { + "color": "{highlight.background}" + }, + "footer": { + "borderColor": "{content.borderColor}", + "borderWidth": "0 0 {data.width.100} 0", + "padding": "{data.padding.400}", + "sm": { + "padding": "{data.padding.200}" + }, + "lg": { + "padding": "{data.padding.500}" + } + }, + "columnResizer": { + "width": "{data.width.300}" + }, + "resizeIndicator": { + "width": "{data.width.100}", + "color": "{highlight.background}" + }, + "sortIcon": { + "color": "{text.color}", + "hoverColor": "{text.hoverColor}", + "size": "{data.icon.size.100}" + }, + "loadingIcon": { + "size": "{data.icon.size.500}" + }, + "rowToggleButton": { + "hoverBackground": "{content.hoverBackground}", + "selectedHoverBackground": "{content.hoverBackground}", + "color": "{text.color}", + "hoverColor": "{text.color}", + "selectedHoverColor": "{text.color}", + "size": "{data.icon.size.500}", + "borderRadius": "{content.borderRadius}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + }, + "filter": { + "inlineGap": "{data.gap.200}", + "rule": { + "borderColor": "{content.borderColor}" + }, + "constraintList": { + "padding": "{list.padding}", + "gap": "{list.gap.100}" + }, + "constraint": { + "focusBackground": "{list.option.focusBackground}", + "selectedBackground": "{list.option.selectedBackground}", + "selectedFocusBackground": "{list.option.selectedFocusBackground}", + "color": "{list.option.color}", + "focusColor": "{list.option.focusColor}", + "selectedColor": "{list.option.selectedColor}", + "selectedFocusColor": "{list.option.selectedFocusColor}", + "padding": "{list.option.padding}", + "borderRadius": "{list.option.borderRadius}", + "separator": { + "borderColor": "{content.borderColor}" + } + }, + "overlaySelect": { + "background": "{overlay.select.background}", + "color": "{overlay.select.color}", + "borderColor": "{overlay.select.borderColor}", + "borderRadius": "{overlay.select.borderRadius}", + "shadow": "{overlay.select.shadow}" + }, + "overlayPopover": { + "background": "{overlay.popover.background}", + "color": "{overlay.popover.color}", + "borderColor": "{overlay.select.borderColor}", + "borderRadius": "{overlay.select.borderRadius}", + "shadow": "{overlay.popover.shadow}", + "padding": "{overlay.popover.padding.100}", + "gap": "{list.gap.100}" + } + }, + "paginatorTop": { + "borderColor": "{form.borderColor}", + "borderWidth": "0 0 {data.width.100} 0" + }, + "paginatorBottom": { + "borderWidth": "0 0 {data.width.100} 0", + "borderColor": "{content.borderColor}" + } + }, + "dataview": { + "root": { + "borderWidth": "{data.width.100}", + "borderRadius": "{data.borderRadius}", + "padding": "0rem", + "borderColor": "{content.borderColor}" + }, + "header": { + "borderWidth": "0 0 {data.width.100} 0", + "padding": "{data.padding.200} {data.padding.300}", + "borderRadius": "0 0 0 0", + "color": "{text.color}" + }, + "content": { + "background": "{content.background}", + "color": "{content.color}", + "borderColor": "{content.borderColor}", + "borderWidth": "0rem", + "padding": "0rem", + "borderRadius": "0" + }, + "footer": { + "background": "{surface.100}", + "color": "{text.color}", + "borderWidth": "{data.width.100} 0 0 0", + "padding": "{data.padding.200} {data.padding.300}", + "borderRadius": "0 0 0 0" + }, + "paginatorTop": { + "borderWidth": "0 0 {data.width.100} 0" + }, + "paginatorBottom": { + "borderWidth": "{data.width.100} 0 0 0" + } + }, + "datepicker": { + "extend": { + "extDate": { + "selectedHoverBackground": "{surface.800}" + }, + "extToday": { + "hoverBackground": "{content.hoverBackground}", + "borderColor": "{content.activeBorderColor}" + }, + "extTitle": { + "width": "{form.width.500}" + }, + "extTimePicker": { + "minWidth": "{form.width.400}", + "color": "{content.color}" + } + }, + "colorScheme": { + "light": { + "dropdown": { + "background": "{content.background}", + "hoverBackground": "{navigation.item.focusBackground}", + "activeBackground": "{navigation.item.activeBackground}", + "color": "{navigation.item.color}", + "hoverColor": "{navigation.item.focusColor}", + "activeColor": "{navigation.item.activeColor}" + }, + "today": { + "background": "{transparent}", + "color": "{text.extend.colorPrimaryStatic}" + } + } + }, + "panel": { + "background": "{content.background}", + "borderColor": "{content.borderColor}", + "color": "{content.color}", + "borderRadius": "{content.borderRadius}", + "shadow": "{overlay.popover.shadow}", + "padding": "0rem" + }, + "header": { + "background": "{content.background}", + "borderColor": "{content.borderColor}", + "color": "{content.color}", + "padding": "{overlay.popover.padding.100}" + }, + "title": { + "gap": "{form.gap.200}", + "fontWeight": "{fonts.fontWeight.bold}" + }, + "selectMonth": { + "hoverBackground": "{content.hoverBackground}", + "color": "{content.color}", + "hoverColor": "{content.hoverColor}", + "borderRadius": "{content.borderRadius}", + "padding": "{form.padding.200}" + }, + "inputIcon": { + "color": "{form.floatLabelColor}" + }, + "dropdown": { + "width": "{form.width.300}", + "borderColor": "{form.borderColor}", + "hoverBorderColor": "{form.borderColor}", + "activeBorderColor": "{form.borderColor}", + "borderRadius": "{form.borderRadius.200}", + "focusRing": { + "width": "{form.focusRing.width}", + "style": "{form.focusRing.style}", + "color": "{form.focusRing.color}", + "offset": "{form.focusRing.offset}", + "shadow": "{focusRing.shadow}" + }, + "sm": { + "width": "0rem" + }, + "lg": { + "width": "0rem" + } + }, + "group": { + "borderColor": "{content.borderColor}", + "gap": "{overlay.popover.padding.100}" + }, + "selectYear": { + "hoverBackground": "{content.hoverBackground}", + "color": "{content.color}", + "hoverColor": "{content.hoverColor}", + "borderRadius": "{content.borderRadius}", + "padding": "{overlay.select.padding}" + }, + "dayView": { + "margin": "{overlay.popover.padding.100}" + }, + "weekDay": { + "padding": "{form.padding.100}", + "fontWeight": "{fonts.fontWeight.bold}", + "color": "{content.color}" + }, + "date": { + "hoverBackground": "{content.hoverBackground}", + "selectedBackground": "{highlight.background}", + "rangeSelectedBackground": "{list.option.focusBackground}", + "color": "{content.color}", + "hoverColor": "{content.color}", + "selectedColor": "{text.extend.colorInverted}", + "rangeSelectedColor": "{text.color}", + "width": "{form.size.500}", + "height": "{form.size.500}", + "borderRadius": "{form.borderRadius.100}", + "padding": "{form.padding.100}", + "focusRing": { + "width": "{form.focusRing.width}", + "style": "{form.focusRing.style}", + "color": "{form.focusRing.color}", + "offset": "{form.focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + }, + "monthView": { + "margin": "0 0 0 0" + }, + "month": { + "padding": "0", + "borderRadius": "0rem" + }, + "yearView": { + "margin": "0 0 0 0" + }, + "year": { + "padding": "0", + "borderRadius": "0rem" + }, + "buttonbar": { + "padding": "{overlay.popover.padding.100}", + "borderColor": "{content.borderColor}" + }, + "timePicker": { + "padding": "{form.padding.300}", + "borderColor": "{content.borderColor}", + "gap": "{form.gap.200}", + "buttonGap": "{form.gap.100}" + }, + "root": { + "transitionDuration": "{form.transitionDuration}" + } + }, + "dialog": { + "extend": { + "borderWidth": "{overlay.borderWidth}", + "backdrop": "{overlay.modal.backdrop}" + }, + "root": { + "background": "{overlay.modal.background}", + "borderColor": "{overlay.modal.borderColor}", + "color": "{overlay.modal.color}", + "borderRadius": "{overlay.modal.borderRadius}", + "shadow": "{overlay.popover.shadow}" + }, + "header": { + "padding": "{overlay.modal.padding.300} {overlay.modal.padding.300} 1rem {overlay.modal.padding.300}", + "gap": "{overlay.gap.200}" + }, + "title": { + "fontSize": "{fonts.fontSize.500}", + "fontWeight": "{fonts.fontWeight.demibold}" + }, + "content": { + "padding": "{content.padding.400}" + }, + "footer": { + "padding": "0 {overlay.modal.padding.300} {overlay.modal.padding.300} {overlay.modal.padding.300}", + "gap": "{content.gap.200}" + } + }, + "divider": { + "colorScheme": { + "light": { + "content": { + "background": "{content.background}", + "color": "{text.mutedColor}" + }, + "borderColor": "{content.borderColor}" + } + }, + "extend": { + "content": { + "gap": "{content.gap.200}" + }, + "iconSize": "{media.icon.size.100}" + }, + "horizontal": { + "margin": "{content.padding.300} 0", + "padding": "0 {content.padding.300}", + "content": { + "padding": "0 {content.padding.200}" + } + }, + "vertical": { + "margin": "0 {content.padding.300}", + "padding": "{content.padding.300} 0", + "content": { + "padding": "{content.padding.200} 0" + } + } + }, + "drawer": { + "extend": { + "borderRadius": "{overlay.popover.borderRadius}", + "borderWidth": "{overlay.borderWidth}", + "width": "{overlay.width}", + "extHeader": { + "gap": "{overlay.gap.200}", + "borderColor": "{drawer.root.borderColor}" + }, + "padding": "{overlay.drawer.padding}", + "scale": "0.125rem", + "backdrop": "{overlay.modal.backdrop}" + }, + "root": { + "background": "{overlay.modal.background}", + "borderColor": "{overlay.modal.borderColor}", + "color": "{overlay.modal.color}", + "shadow": "{overlay.modal.shadow}" + }, + "header": { + "padding": "{overlay.modal.padding.300} {overlay.modal.padding.300} {overlay.modal.padding.100} {overlay.modal.padding.300} " + }, + "title": { + "fontSize": "{fonts.fontSize.500}", + "fontWeight": "{fonts.fontWeight.demibold}" + }, + "content": { + "padding": "{overlay.modal.padding.300}" + }, + "footer": { + "padding": "0 {overlay.modal.padding.300} {overlay.modal.padding.300} {overlay.modal.padding.300} " + } + }, + "fileupload": { + "extend": { + "extDragNdrop": { + "background": "{surface.0}", + "borderRadius": "{form.borderRadius.200}", + "iconSize": "{form.size.500}", + "padding": "{form.padding.400}", + "info": { + "gap": "{form.gap.100}" + } + }, + "extFile": { + "iconSize": "{form.size.350}" + }, + "extContent": { + "borderRadius": "{content.borderRadius}", + "highlightBorderDefault": "{form.borderColor}" + } + }, + "colorScheme": { + "light": { + "header": { + "background": "{surface.0}", + "color": "{text.color}" + } + } + }, + "root": { + "background": "{content.background}", + "borderColor": "{content.borderColor}", + "color": "{content.color}", + "borderRadius": "{content.borderRadius}", + "transitionDuration": "{form.transitionDuration}" + }, + "header": { + "borderColor": "{content.borderColor}", + "borderWidth": "0rem", + "padding": "0rem", + "borderRadius": "0rem", + "gap": "{content.gap.200}" + }, + "content": { + "highlightBorderColor": "{surface.900}", + "padding": "0rem", + "gap": "{content.gap.200}" + }, + "file": { + "padding": "{content.padding.200}", + "gap": "{content.gap.200}", + "borderColor": "{form.borderColor}", + "info": { + "gap": "{content.gap.100}" + } + }, + "fileList": { + "gap": "{content.gap.200}" + }, + "progressbar": { + "height": "{feedback.height.100}" + }, + "basic": { + "gap": "{content.gap.200}" + } + }, + "floatlabel": { + "extend": { + "height": "{form.size.800}", + "iconSize": "{form.icon.400}" + }, + "root": { + "color": "{form.floatLabelColor}", + "focusColor": "{form.floatLabelFocusColor}", + "activeColor": "{form.floatLabelActiveColor}", + "invalidColor": "{form.floatLabelInvalidColor}", + "transitionDuration": "{form.transitionDuration}", + "positionX": "{form.padding.300}", + "positionY": "{form.padding.300}", + "fontWeight": "{fonts.fontWeight.regular}", + "active": { + "fontSize": "{fonts.fontSize.100}", + "fontWeight": "{fonts.fontWeight.regular}" + } + }, + "over": { + "active": { + "top": "{form.padding.400}" + } + }, + "in": { + "input": { + "paddingTop": "{form.padding.700}", + "paddingBottom": "{form.padding.300}" + }, + "active": { + "top": "{form.padding.300}" + } + }, + "on": { + "borderRadius": "0rem", + "active": { + "padding": "0 {form.padding.100}", + "background": "{form.background}" + } + } + }, + "galleria": { + "extend": { + "backdrop": "{overlay.modal.backdrop}" + }, + "colorScheme": { + "light": { + "thumbnailContent": { + "background": "{surface.100}" + }, + "thumbnailNavButton": { + "hoverBackground": "{colors.alpha.white.200}", + "color": "{text.color}", + "hoverColor": "{text.hoverColor}" + }, + "indicatorButton": { + "background": "{surface.300}", + "hoverBackground": "{surface.400}" + } + } + }, + "root": { + "borderWidth": "{content.borderWidth}", + "borderColor": "{content.borderColor}", + "borderRadius": "{content.borderRadius}", + "transitionDuration": "{media.transitionDuration}" + }, + "navButton": { + "background": "{transparent}", + "hoverBackground": "{colors.alpha.white.200}", + "color": "{text.extend.colorInverted}", + "hoverColor": "{text.extend.colorInverted}", + "size": "{media.size.600}", + "gutter": "{media.gap.200}", + "prev": { + "borderRadius": "{navigation.item.borderRadius}" + }, + "next": { + "borderRadius": "{navigation.item.borderRadius}" + }, + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + }, + "navIcon": { + "size": "{media.icon.size.300}" + }, + "thumbnailsContent": { + "padding": "{media.padding.100}" + }, + "thumbnailNavButton": { + "size": "{media.size.300}", + "borderRadius": "{content.borderRadius}", + "gutter": "{media.gap.200}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + }, + "thumbnailNavButtonIcon": { + "size": "{media.icon.size.100}" + }, + "caption": { + "background": "{colors.alpha.white.500}", + "color": "{text.color}", + "padding": "{media.gap.200}" + }, + "indicatorList": { + "gap": "{media.gap.200}", + "padding": "{media.padding.400}" + }, + "indicatorButton": { + "width": "{media.size.200}", + "height": "{media.size.200}", + "activeBackground": "{surface.900}", + "borderRadius": "{content.borderRadius}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + }, + "insetIndicatorList": { + "background": "{colors.alpha.black.500}" + }, + "insetIndicatorButton": { + "background": "{colors.alpha.white.100}", + "hoverBackground": "{colors.alpha.white.200}", + "activeBackground": "{colors.alpha.white.500}" + }, + "closeButton": { + "size": "{media.size.600}", + "gutter": "{media.gap.200}", + "background": "{colors.alpha.white.100}", + "hoverBackground": "{colors.alpha.white.200}", + "color": "{text.extend.colorInverted}", + "hoverColor": "{text.extend.colorInverted}", + "borderRadius": "{controls.borderRadius.200}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + }, + "closeButtonIcon": { + "size": "{media.icon.size.300}" + } + }, + "inputgroup": { + "extend": { + "borderWidth": "{form.borderWidth}", + "iconSize": "{form.icon.300}" + }, + "colorScheme": { + "light": { + "addon": { + "background": "{form.background}", + "borderColor": "{form.borderColor}", + "color": "{text.mutedColor}" + } + } + }, + "addon": { + "borderRadius": "{form.borderRadius.200}", + "padding": "{form.padding.300}", + "minWidth": "{form.width.300}" + } + }, + "inputnumber": { + "extend": { + "borderWidth": "{form.borderWidth}", + "extButton": { + "height": "{form.size.600}", + "iconSize": "{form.icon.300}" + } + }, + "colorScheme": { + "light": { + "button": { + "background": "{content.background}", + "hoverBackground": "{content.hoverBackground}", + "activeBackground": "{transparent}", + "borderColor": "{form.borderColor}", + "hoverBorderColor": "{form.borderColor}", + "activeBorderColor": "{form.borderColor}", + "color": "{text.color}", + "hoverColor": "{text.hoverColor}", + "activeColor": "{text.color}" + } + } + }, + "transitionDuration": { + "transitionDuration": "{form.transitionDuration}" + }, + "button": { + "width": "{form.size.600}", + "borderRadius": "{form.borderRadius.200}", + "verticalPadding": "{form.padding.300}" + } + }, + "inputotp": { + "extend": { + "height": "{form.size.600}", + "borderWidth": "{form.borderWidth}" + }, + "root": { + "gap": "{form.gap.200}" + }, + "input": { + "width": "{form.width.400}" + }, + "sm": { + "width": "0rem" + }, + "lg": { + "width": "0rem" + } + }, + "inputtext": { + "extend": { + "readonlyBackground": "{form.readonlyBackground}", + "iconSize": "{form.icon.300}", + "borderWidth": "{form.borderWidth}", + "extXlg": { + "fontSize": "{form.xlg.fontSize}", + "paddingX": "{form.padding.300}", + "paddingY": "{form.padding.600}" + } + }, + "root": { + "background": "{form.background}", + "disabledBackground": "{form.disabledBackground}", + "filledBackground": "{form.filledBackground}", + "filledHoverBackground": "{form.filledHoverBackground}", + "filledFocusBackground": "{form.filledFocusBackground}", + "borderColor": "{form.borderColor}", + "hoverBorderColor": "{form.hoverBorderSecondaryColor}", + "focusBorderColor": "{form.focusBorderSecondaryColor}", + "invalidBorderColor": "{form.invalidBorderColor}", + "color": "{text.color}", + "disabledColor": "{form.disabledColor}", + "placeholderColor": "{form.placeholderColor}", + "invalidPlaceholderColor": "{form.invalidPlaceholderColor}", + "shadow": "0", + "paddingX": "{form.padding.300}", + "paddingY": "{form.padding.300}", + "borderRadius": "{form.borderRadius.200}", + "transitionDuration": "{form.transitionDuration}", + "sm": { + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{form.padding.300}", + "paddingY": "{form.padding.200}" + }, + "lg": { + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{form.padding.300}", + "paddingY": "{form.padding.400}" + }, + "focusRing": { + "width": "{form.focusRing.width}", + "style": "{form.focusRing.style}", + "color": "{form.focusRing.color}", + "offset": "{form.focusRing.offset}", + "shadow": "0" + } + } + }, + "listbox": { + "extend": { + "extOption": { + "label": { + "gap": "{list.gap.100}" + }, + "caption": { + "color": "{text.mutedColor}", + "stripedColor": "{text.mutedColor}" + }, + "gap": "{list.gap.200}" + } + }, + "colorScheme": { + "light": { + "option": { + "stripedBackground": "{surface.50}" + } + } + }, + "root": { + "background": "{form.background}", + "disabledBackground": "{form.disabledBackground}", + "borderColor": "{form.borderColor}", + "invalidBorderColor": "{form.invalidBorderColor}", + "color": "{form.color}", + "disabledColor": "{form.disabledColor}", + "shadow": "0", + "borderRadius": "{form.borderRadius.200}", + "transitionDuration": "{form.transitionDuration}" + }, + "list": { + "padding": "{list.padding}", + "gap": "{list.gap.100}", + "header": { + "padding": "{list.header.padding}" + } + }, + "option": { + "focusBackground": "{list.option.focusBackground}", + "selectedBackground": "{list.option.selectedBackground}", + "selectedFocusBackground": "{list.option.selectedFocusBackground}", + "color": "{list.option.color}", + "focusColor": "{list.option.focusColor}", + "selectedColor": "{list.option.selectedColor}", + "selectedFocusColor": "{list.option.selectedFocusColor}", + "padding": "{list.option.padding}", + "borderRadius": "{list.option.borderRadius}" + }, + "optionGroup": { + "background": "{list.optionGroup.background}", + "color": "{list.optionGroup.color}", + "fontWeight": "{fonts.fontWeight.regular}", + "padding": "{list.option.padding}" + }, + "checkmark": { + "color": "{list.option.color}", + "gutterStart": "-{list.gap.200}", + "gutterEnd": "{list.gap.200}" + }, + "emptyMessage": { + "padding": "{list.option.padding}" + } + }, + "megamenu": { + "extend": { + "extItem": { + "caption": { + "color": "{text.mutedColor}", + "gap": "{content.gap.100}" + } + }, + "iconSize": "{navigation.submenuIcon.size}" + }, + "colorScheme": { + "light": { + "root": { + "background": "{transparent}" + } + } + }, + "root": { + "borderColor": "{transparent}", + "borderRadius": "{content.borderRadius}", + "color": "{content.color}", + "gap": "{content.gap.100}", + "transitionDuration": "{form.transitionDuration}", + "verticalOrientation": { + "padding": "{navigation.list.padding.100}", + "gap": "{navigation.list.gap}" + }, + "horizontalOrientation": { + "padding": "{navigation.list.padding.100}", + "gap": "{navigation.list.gap}" + } + }, + "baseItem": { + "borderRadius": "{content.borderRadius}", + "padding": "{navigation.item.padding}" + }, + "item": { + "focusBackground": "{navigation.item.focusBackground}", + "activeBackground": "{navigation.item.activeBackground}", + "color": "{navigation.item.color}", + "focusColor": "{navigation.item.focusColor}", + "activeColor": "{navigation.item.activeColor}", + "padding": "{navigation.item.padding}", + "borderRadius": "{navigation.item.borderRadius}", + "gap": "{navigation.item.gap}", + "icon": { + "color": "{navigation.item.icon.color}", + "focusColor": "{navigation.item.icon.focusColor}", + "activeColor": "{navigation.item.icon.activeColor}" + } + }, + "overlay": { + "padding": "{content.padding.100}", + "background": "{content.background}", + "borderColor": "{content.borderColor}", + "borderRadius": "{content.borderRadius}", + "color": "{content.color}", + "shadow": "{navigation.shadow}", + "gap": "0rem" + }, + "submenu": { + "padding": "{navigation.list.padding.100}", + "gap": "{navigation.list.gap}" + }, + "submenuLabel": { + "fontWeight": "{navigation.submenuLabel.fontWeight}", + "padding": "{navigation.submenuLabel.padding}", + "background": "{navigation.submenuLabel.background}", + "color": "{navigation.submenuLabel.color}" + }, + "submenuIcon": { + "size": "{navigation.submenuIcon.size}", + "color": "{navigation.submenuIcon.color}", + "focusColor": "{navigation.submenuIcon.focusColor}", + "activeColor": "{navigation.submenuIcon.activeColor}" + }, + "separator": { + "borderColor": "{content.borderColor}" + }, + "mobileButton": { + "borderRadius": "{navigation.item.borderRadius}", + "size": "{controls.iconOnly.600}", + "color": "{text.color}", + "hoverColor": "{text.hoverColor}", + "hoverBackground": "{content.hoverBackground}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + } + }, + "menu": { + "extend": { + "paddingX": "0.25rem", + "iconSize": "{navigation.submenuIcon.size}", + "paddingY": "0.25rem", + "extItem": { + "caption": { + "gap": "{content.gap.100}" + }, + "activeBackground": "{navigation.item.activeBackground}", + "activeColor": "{navigation.item.activeColor}" + } + }, + "colorScheme": { + "light": { + "extend": { + "extItem": { + "caption": { + "color": "{text.mutedColor}" + }, + "icon": { + "activeColor": "{navigation.item.icon.activeColor}" + } + } + }, + "root": { + "background": "{content.background}", + "borderColor": "{content.borderColor}", + "color": "{content.color}" + }, + "item": { + "focusBackground": "{navigation.item.focusBackground}", + "color": "{navigation.item.color}", + "focusColor": "{navigation.item.focusColor}", + "icon": { + "color": "{navigation.item.icon.color}", + "focusColor": "{navigation.item.icon.focusColor}" + } + } + } + }, + "root": { + "borderRadius": "{content.borderRadius}", + "shadow": "{navigation.shadow}", + "transitionDuration": "{form.transitionDuration}" + }, + "list": { + "padding": "{navigation.list.padding.100}", + "gap": "{navigation.list.gap}" + }, + "submenuLabel": { + "padding": "{navigation.submenuLabel.padding}", + "fontWeight": "{fonts.fontWeight.regular}", + "background": "{navigation.submenuLabel.background}", + "color": "{navigation.submenuLabel.color}" + }, + "separator": { + "borderColor": "{content.borderColor}" + }, + "item": { + "padding": "{navigation.item.padding}", + "borderRadius": "{navigation.item.borderRadius}", + "gap": "{navigation.item.gap}" + } + }, + "menubar": { + "extend": { + "iconSize": "{navigation.submenuIcon.size}", + "extItem": { + "caption": { + "color": "{text.mutedColor}", + "gap": "{content.padding.100}" + } + }, + "extSubmenuLabel": { + "padding": "{navigation.submenuLabel.padding}", + "fontWeight": "{fonts.fontWeight.demibold}", + "background": "{navigation.submenuLabel.background}", + "color": "{navigation.submenuLabel.color}" + } + }, + "colorScheme": { + "light": { + "root": { + "background": "{transparent}" + } + } + }, + "root": { + "borderColor": "{transparent}", + "borderRadius": "{navigation.item.borderRadius}", + "color": "{content.color}", + "gap": "{content.padding.100}", + "padding": "{navigation.list.padding.100}", + "transitionDuration": "{form.transitionDuration}" + }, + "baseItem": { + "borderRadius": "{navigation.item.borderRadius}", + "padding": "{navigation.item.padding}" + }, + "item": { + "focusBackground": "{navigation.item.focusBackground}", + "activeBackground": "{navigation.item.activeBackground}", + "color": "{navigation.item.color}", + "focusColor": "{navigation.item.focusColor}", + "activeColor": "{navigation.item.activeColor}", + "padding": "{navigation.item.padding}", + "borderRadius": "{navigation.item.borderRadius}", + "gap": "{navigation.item.gap}", + "icon": { + "color": "{navigation.item.icon.color}", + "focusColor": "{navigation.item.icon.focusColor}", + "activeColor": "{navigation.item.icon.activeColor}" + } + }, + "submenu": { + "padding": "{navigation.list.padding.100}", + "gap": "{navigation.list.gap}", + "background": "{content.background}", + "borderColor": "{content.borderColor}", + "borderRadius": "{content.borderRadius}", + "shadow": "{navigation.shadow}", + "mobileIndent": "{navigation.padding.200}", + "icon": { + "size": "{navigation.submenuIcon.size}", + "color": "{navigation.submenuIcon.color}", + "focusColor": "{navigation.submenuIcon.focusColor}", + "activeColor": "{navigation.submenuIcon.activeColor}" + } + }, + "separator": { + "borderColor": "{content.borderColor}" + }, + "mobileButton": { + "borderRadius": "{navigation.item.borderRadius}", + "size": "{controls.iconOnly.600}", + "color": "{text.color}", + "hoverColor": "{text.hoverColor}", + "hoverBackground": "{content.hoverBackground}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + } + }, + "message": { + "extend": { + "width": "{messages.width}", + "extText": { + "gap": "{feedback.gap.100}" + }, + "extInfo": { + "color": "{info.500}", + "closeButton": { + "color": "{info.500}", + "borderColor": "{info.500}" + }, + "caption": { + "color": "{text.color}" + } + }, + "extAccentLine": { + "width": "{feedback.width.200}" + }, + "extCloseButton": { + "width": "{feedback.width.100}" + }, + "extSuccess": { + "color": "{success.500}", + "closeButton": { + "color": "{success.500}", + "borderColor": "{success.500}" + }, + "caption": { + "color": "{text.color}" + } + }, + "extWarn": { + "color": "{warn.500}", + "closeButton": { + "color": "{warn.500}", + "borderColor": "{warn.500}" + }, + "caption": { + "color": "{text.color}" + } + }, + "extError": { + "color": "{error.500}", + "closeButton": { + "color": "{error.500}", + "borderColor": "{error.500}" + }, + "caption": { + "color": "{text.color}" + } + } + }, + "colorScheme": { + "light": { + "success": { + "background": "{success.50}", + "borderColor": "{success.500}", + "color": "{text.color}", + "shadow": "none", + "outlined": { + "color": "{text.color}", + "borderColor": "{success.500}" + }, + "closeButton": { + "hoverBackground": "{success.200}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + }, + "simple": { + "color": "{text.color}" + } + }, + "outlined": { + "root": { + "borderWidth": "0rem" + }, + "closeButton": { + "hoverBackground": "{transparent}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + }, + "outlined": { + "color": "{transparent}", + "borderColor": "{transparent}" + }, + "simple": { + "color": "{transparent}" + } + }, + "simple": { + "content": { + "padding": "0rem" + } + }, + "warn": { + "background": "{warn.50}", + "borderColor": "{warn.500}", + "color": "{text.color}", + "shadow": "none", + "outlined": { + "color": "{text.color}", + "borderColor": "{warn.500}" + }, + "closeButton": { + "hoverBackground": "{warn.200}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + }, + "simple": { + "color": "{text.color}" + } + }, + "error": { + "background": "{error.50}", + "borderColor": "{error.500}", + "color": "{text.color}", + "shadow": "none", + "outlined": { + "color": "{text.color}", + "borderColor": "{error.500}" + }, + "closeButton": { + "hoverBackground": "{error.200}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + }, + "simple": { + "color": "{text.color}" + } + }, + "secondary": { + "borderColor": "{transparent}", + "shadow": "none", + "closeButton": { + "hoverBackground": "{transparent}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + }, + "simple": { + "color": "{transparent}" + }, + "outlined": { + "color": "{transparent}", + "borderColor": "{transparent}" + } + }, + "contrast": { + "borderColor": "{transparent}", + "shadow": "none", + "closeButton": { + "hoverBackground": "{transparent}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + }, + "simple": { + "color": "{transparent}" + }, + "outlined": { + "color": "{transparent}", + "borderColor": "{transparent}" + } + }, + "info": { + "background": "{info.50}", + "borderColor": "{info.500}", + "color": "{text.color}", + "shadow": "none", + "closeButton": { + "hoverBackground": "{info.200}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + }, + "outlined": { + "color": "{text.color}", + "borderColor": "{info.500}" + }, + "simple": { + "color": "{text.color}" + } + } + } + }, + "root": { + "borderRadius": "{content.borderRadius}", + "borderWidth": "{feedback.width.100}", + "transitionDuration": "{feedback.transitionDuration}" + }, + "content": { + "padding": "{feedback.padding.200}", + "gap": "{feedback.gap.400}", + "sm": { + "padding": "{feedback.padding.200}" + }, + "lg": { + "padding": "{feedback.padding.200}" + } + }, + "text": { + "fontSize": "{fonts.fontSize.300}", + "fontWeight": "{fonts.fontWeight.bold}", + "sm": { + "fontSize": "{fonts.fontSize.300}" + }, + "lg": { + "fontSize": "{fonts.fontSize.300}" + } + }, + "icon": { + "size": "{feedback.icon.size.500}", + "sm": { + "size": "{feedback.icon.size.500}" + }, + "lg": { + "size": "{feedback.icon.size.500}" + } + }, + "closeButton": { + "width": "{controls.iconOnly.600}", + "height": "{controls.iconOnly.600}", + "borderRadius": "{controls.borderRadius.100}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "offset": "{focusRing.offset}" + } + }, + "closeIcon": { + "size": "{feedback.icon.size.200}", + "sm": { + "size": "{feedback.icon.size.200}" + }, + "lg": { + "size": "{feedback.icon.size.200}" + } + } + }, + "metergroup": { + "extend": { + "extLabel": { + "color": "{text.mutedColor}" + } + }, + "root": { + "borderRadius": "{content.borderRadius}", + "gap": "{feedback.gap.300}" + }, + "meters": { + "size": "{feedback.height.100}", + "background": "{content.borderColor}" + }, + "label": { + "gap": "{feedback.gap.100}" + }, + "labelMarker": { + "size": "{feedback.icon.size.100}" + }, + "labelIcon": { + "size": "{feedback.icon.size.200}" + }, + "labelList": { + "verticalGap": "{feedback.gap.200}", + "horizontalGap": "{feedback.gap.300}" + } + }, + "multiselect": { + "colorScheme": { + "overlay": { + "background": "{overlay.select.background}", + "borderColor": "{overlay.select.borderColor}", + "color": "{overlay.select.color}" + }, + "option": { + "focusBackground": "{list.option.focusBackground}", + "selectedBackground": "{list.option.selectedBackground}", + "selectedFocusBackground": "{list.option.selectedFocusBackground}", + "color": "{list.option.color}", + "focusColor": "{list.option.focusColor}", + "selectedColor": "{list.option.selectedColor}", + "selectedFocusColor": "{list.option.selectedFocusColor}" + }, + "root": { + "background": "{form.background}", + "disabledBackground": "{form.disabledBackground}", + "filledBackground": "{form.filledBackground}", + "filledHoverBackground": "{form.filledHoverBackground}", + "filledFocusBackground": "{form.filledFocusBackground}", + "borderColor": "{form.borderColor}", + "hoverBorderColor": "{form.hoverBorderSecondaryColor}", + "focusBorderColor": "{form.focusBorderSecondaryColor}", + "invalidBorderColor": "{form.invalidBorderColor}", + "color": "{form.color}", + "disabledColor": "{form.disabledColor}", + "placeholderColor": "{form.placeholderColor}", + "invalidPlaceholderColor": "{form.invalidPlaceholderColor}", + "focusRing": { + "color": "{form.focusRing.color}" + } + }, + "dropdown": { + "color": "{form.floatLabelColor}" + }, + "optionGroup": { + "background": "{list.optionGroup.background}", + "color": "{list.optionGroup.color}" + }, + "clearIcon": { + "color": "{form.floatLabelColor}" + } + }, + "extend": { + "paddingX": "0.3571rem", + "paddingY": "0.3571rem", + "borderWidth": "{form.borderWidth}", + "iconSize": "{form.icon.300}", + "width": "{form.width}", + "readonlyBackground": "{form.readonlyBackground}" + }, + "root": { + "shadow": "0", + "paddingX": "{form.paddingX}", + "paddingY": "{form.paddingY}", + "borderRadius": "{form.borderRadius.200}", + "transitionDuration": "{form.transitionDuration}", + "sm": { + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{form.padding.200}", + "paddingY": "{form.padding.200}" + }, + "lg": { + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{form.padding.400}", + "paddingY": "{form.padding.400}" + }, + "focusRing": { + "width": "{form.focusRing.width}", + "style": "{form.focusRing.style}", + "offset": "{form.focusRing.offset}", + "shadow": "0" + } + }, + "dropdown": { + "width": "{form.width.300}" + }, + "overlay": { + "borderRadius": "{overlay.select.borderRadius}", + "shadow": "{overlay.select.shadow}" + }, + "list": { + "padding": "{list.padding}", + "header": { + "padding": "{list.header.padding}" + }, + "gap": "{list.gap.100}" + }, + "chip": { + "borderRadius": "{form.borderRadius.100}" + }, + "option": { + "padding": "{list.option.padding}", + "borderRadius": "{list.option.borderRadius}", + "gap": "{list.gap.200}" + }, + "optionGroup": { + "fontWeight": "{fonts.fontWeight.demibold}", + "padding": "{list.optionGroup.padding}" + }, + "emptyMessage": { + "padding": "{list.option.padding}" + } + }, + "paginator": { + "root": { + "padding": "0 {data.padding.200}", + "gap": "{data.gap.200}", + "borderRadius": "{content.borderRadius}", + "background": "{transparent}", + "color": "{content.color}", + "transitionDuration": "{data.transitionDuration}" + }, + "currentPageReport": { + "color": "{text.mutedColor}" + }, + "navButton": { + "background": "{transparent}", + "hoverBackground": "{content.hoverBackground}", + "selectedBackground": "{highlight.background}", + "color": "{text.color}", + "hoverColor": "{text.hoverColor}", + "selectedColor": "{text.extend.colorInverted}", + "width": "{data.icon.size.700}", + "height": "{data.icon.size.700}", + "borderRadius": "{content.borderRadius}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "focus": "{focusRing.shadow}" + } + }, + "jumpToPageInput": { + "maxWidth": "{data.width.400}" + } + }, + "panelmenu": { + "extend": { + "extPanel": { + "gap": "{content.gap.100}" + }, + "iconSize": "{navigation.submenuIcon.size}", + "extItem": { + "activeBackground": "{navigation.item.activeBackground}", + "activeColor": "{navigation.item.activeColor}", + "caption": { + "color": "{text.mutedColor}", + "gap": "{content.gap.100}" + } + } + }, + "root": { + "gap": "{content.gap.100}", + "transitionDuration": "{form.transitionDuration}" + }, + "panel": { + "background": "{transparent}", + "borderColor": "{transparent}", + "borderWidth": "{navigation.width.100}", + "color": "{content.color}", + "padding": "{content.padding.100}", + "borderRadius": "{content.borderRadius}", + "first": { + "borderWidth": "{navigation.width.100} {navigation.width.100} 0 {navigation.width.100}", + "topBorderRadius": "{content.borderRadius}" + }, + "last": { + "borderWidth": "0 {navigation.width.100} {navigation.width.100} {navigation.width.100}", + "topBorderRadius": "{content.borderRadius}" + } + }, + "item": { + "focusBackground": "{navigation.item.focusBackground}", + "color": "{navigation.item.color}", + "focusColor": "{navigation.item.focusColor}", + "gap": "{navigation.item.gap}", + "padding": "{navigation.item.padding}", + "borderRadius": "{navigation.item.borderRadius}", + "icon": { + "color": "{navigation.item.icon.color}", + "focusColor": "{navigation.item.icon.focusColor}" + } + }, + "submenu": { + "indent": "{navigation.padding.400}" + }, + "separator": { + "borderColor": "{content.borderColor}" + }, + "submenuIcon": { + "color": "{navigation.submenuIcon.color}", + "focusColor": "{navigation.submenuIcon.focusColor}" + } + }, + "password": { + "extend": { + "borderWidth": "{form.borderWidth}" + }, + "colorScheme": { + "light": { + "strength": { + "weakBackground": "{error.500}", + "mediumBackground": "{warn.500}", + "strongBackground": "{success.600}" + }, + "icon": { + "color": "{form.placeholderColor}" + } + }, + "dark": { + "strength": { + "weakBackground": "{error.500}", + "mediumBackground": "{warn.500}", + "strongBackground": "{success.600}" + }, + "icon": { + "color": "{form.placeholderColor}" + } + } + }, + "meter": { + "background": "{content.borderColor}", + "borderRadius": "{content.borderRadius}", + "height": "{feedback.height.100}" + }, + "overlay": { + "background": "{overlay.popover.background}", + "borderColor": "{overlay.popover.borderColor}", + "borderRadius": "{overlay.popover.borderRadius}", + "color": "{overlay.popover.color}", + "padding": "{overlay.popover.padding.100}", + "shadow": "{overlay.popover.shadow}" + }, + "content": { + "gap": "{content.gap.200}" + } + }, + "popover": { + "extend": { + "borderWidth": "{overlay.borderWidth}", + "arrow": { + "width": "{overlay.popover.width.200}", + "height": "{overlay.popover.width.100}" + } + }, + "root": { + "background": "{overlay.popover.background}", + "borderColor": "{overlay.popover.borderColor}", + "color": "{overlay.popover.color}", + "borderRadius": "{overlay.popover.borderRadius}", + "shadow": "{overlay.popover.shadow}", + "gutter": "{overlay.gap.100}", + "arrowOffset": "{overlay.popover.padding.200}" + }, + "content": { + "padding": "{overlay.popover.padding.100}" + } + }, + "progressbar": { + "label": { + "color": "{text.extend.colorPrimaryStatic}", + "fontSize": "{fonts.fontSize.100}", + "fontWeight": "{fonts.fontWeight.regular}" + }, + "root": { + "background": "{content.borderColor}", + "borderRadius": "{content.borderRadius}", + "height": "{feedback.height.300}" + }, + "value": { + "background": "{primary.color}" + } + }, + "progressspinner": { + "extend": { + "small": "{feedback.width.500}", + "medium": "{feedback.width.700}", + "large": "{feedback.width.800}", + "xlarge": "{feedback.width.900}" + }, + "colorScheme": { + "light": { + "root": { + "colorOne": "{success.500}", + "colorTwo": "{info.500}", + "colorThree": "{error.500}", + "colorFour": "{warn.500}" + } + } + }, + "root": { + "borderWidth": "{feedback.width.200}" + } + }, + "radiobutton": { + "root": { + "width": "{form.size.400}", + "height": "{form.size.400}", + "background": "{form.background}", + "checkedBackground": "{surface.900}", + "checkedHoverBackground": "{surface.800}", + "disabledBackground": "{form.disabledBackground}", + "filledBackground": "{form.filledBackground}", + "borderColor": "{form.borderColor}", + "hoverBorderColor": "{form.hoverBorderPrimaryColor}", + "focusBorderColor": "{form.borderColor}", + "checkedBorderColor": "{surface.900}", + "checkedHoverBorderColor": "{form.hoverBorderPrimaryColor}", + "checkedFocusBorderColor": "{form.focusBorderPrimaryColor}", + "checkedDisabledBorderColor": "{form.borderColor}", + "invalidBorderColor": "{form.invalidBorderColor}", + "shadow": "0", + "transitionDuration": "{form.transitionDuration}" + }, + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + }, + "sm": { + "width": "{form.size.300}", + "height": "{form.size.300}" + }, + "lg": { + "width": "{form.size.350}", + "height": "{form.size.350}" + }, + "icon": { + "size": "0.7rem", + "checkedColor": "{text.extend.colorInverted}", + "checkedHoverColor": "{text.extend.colorInverted}", + "disabledColor": "{text.mutedColor}", + "sm": { + "size": "{form.icon.100}" + }, + "lg": { + "size": "{form.icon.300}" + } + } + }, + "rating": { + "root": { + "gap": "{form.gap.200}", + "transitionDuration": "{form.transitionDuration}" + }, + "focusRing": { + "width": "{form.focusRing.width}", + "style": "{form.focusRing.style}", + "color": "{form.focusRing.color}", + "offset": "{form.focusRing.offset}", + "shadow": "{focusRing.shadow}" + }, + "icon": { + "size": "{form.icon.500}", + "color": "{surface.500}", + "hoverColor": "{warn.500}", + "activeColor": "{warn.500}" + } + }, + "ripple": { + "colorScheme": { + "light": { + "root": { + "background": "rgba(255, 255, 255, 0.0100)" + } + } + } + }, + "scrollpanel": { + "colorScheme": { + "light": { + "bar": { + "background": "{surface.300}" + } + } + }, + "root": { + "transitionDuration": "{media.transitionDuration}" + }, + "bar": { + "size": "{media.size.200}", + "borderRadius": "{media.borderRadius.100}", + "focusRing": { + "width": "0rem", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + } + }, + "select": { + "extend": { + "extOption": { + "background": "{list.option.background}", + "gap": "{list.gap.200}" + }, + "extOptionGroup": { + "gap": "{list.gap.200}" + }, + "readonlyBackground": "{form.readonlyBackground}", + "borderWidth": "{form.borderWidth}", + "iconSize": "{form.icon.300}" + }, + "root": { + "background": "{form.background}", + "disabledBackground": "{form.disabledBackground}", + "filledBackground": "{form.filledBackground}", + "filledHoverBackground": "{form.filledHoverBackground}", + "filledFocusBackground": "{form.filledFocusBackground}", + "borderColor": "{form.borderColor}", + "hoverBorderColor": "{form.hoverBorderSecondaryColor}", + "focusBorderColor": "{form.focusBorderSecondaryColor}", + "invalidBorderColor": "{form.invalidBorderColor}", + "color": "{text.color}", + "disabledColor": "{form.disabledColor}", + "placeholderColor": "{form.placeholderColor}", + "invalidPlaceholderColor": "{form.invalidPlaceholderColor}", + "shadow": "0", + "paddingX": "{form.padding.300}", + "paddingY": "{form.padding.300}", + "borderRadius": "{form.borderRadius.200}", + "transitionDuration": "{form.transitionDuration}", + "sm": { + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{form.padding.300}", + "paddingY": "{form.padding.200}" + }, + "lg": { + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{form.padding.300}", + "paddingY": "{form.padding.400}" + }, + "focusRing": { + "width": "{form.focusRing.width}", + "style": "{form.focusRing.style}", + "color": "{form.focusRing.color}", + "offset": "{form.focusRing.offset}", + "shadow": "0" + } + }, + "dropdown": { + "width": "{form.width.300}", + "color": "{form.iconColor}" + }, + "overlay": { + "background": "{overlay.select.background}", + "borderColor": "{overlay.select.borderColor}", + "borderRadius": "{overlay.select.borderRadius}", + "color": "{overlay.select.color}", + "shadow": "{overlay.select.shadow}" + }, + "list": { + "padding": "{list.padding}", + "gap": "{list.gap.100}", + "header": { + "padding": "{list.header.padding}" + } + }, + "option": { + "focusBackground": "{list.option.focusBackground}", + "selectedBackground": "{list.option.selectedBackground}", + "selectedFocusBackground": "{list.option.selectedFocusBackground}", + "color": "{list.option.color}", + "focusColor": "{list.option.focusColor}", + "selectedColor": "{list.option.selectedColor}", + "selectedFocusColor": "{list.option.selectedFocusColor}", + "padding": "{list.option.padding}", + "borderRadius": "{list.option.borderRadius}" + }, + "optionGroup": { + "background": "{list.optionGroup.background}", + "color": "{list.optionGroup.color}", + "fontWeight": "{fonts.fontWeight.regular}", + "padding": "{list.option.padding}" + }, + "clearIcon": { + "color": "{form.iconColor}" + }, + "checkmark": { + "color": "{list.option.color}", + "gutterStart": "-{form.padding.200}", + "gutterEnd": "{form.padding.200}" + }, + "emptyMessage": { + "padding": "{list.option.padding}" + } + }, + "selectbutton": { + "extend": { + "gap": "{form.gap.100}", + "paddingX": "{controls.padding.100}", + "paddingY": "{controls.padding.100}", + "checkedBackground": "{form.background}", + "iconSize": { + "sm": "{controls.iconOnly.200}", + "md": "{controls.iconOnly.300}", + "lg": "{controls.iconOnly.400}", + "xlg": "{controls.iconOnly.500}" + }, + "checkedBorderColor": "{form.background}", + "checkedColor": "{form.color}", + "ext": { + "borderRadius": "{borderRadius.200}" + } + }, + "colorScheme": { + "light": { + "root": { + "invalidBorderColor": "{form.invalidBorderColor}" + }, + "extend": { + "background": "{surface.200}" + } + } + }, + "root": { + "borderRadius": "{form.borderRadius.200}" + } + }, + "skeleton": { + "extend": { + "minWidth": "{feedback.width.700}", + "height": "{feedback.height.650}" + }, + "colorScheme": { + "light": { + "root": { + "background": "{surface.200}", + "animationBackground": "{surface.100}" + } + } + }, + "root": { + "borderRadius": "{content.borderRadius}" + } + }, + "slider": { + "colorScheme": { + "handle": { + "content": { + "background": "{surface.0}" + } + } + }, + "root": { + "transitionDuration": "{form.transitionDuration}" + }, + "track": { + "background": "{content.borderColor}", + "borderRadius": "{content.borderRadius}", + "size": "{form.size.150}" + }, + "range": { + "background": "{surface.900}" + }, + "handle": { + "width": "{form.size.350}", + "height": "{form.size.350}", + "borderRadius": "{form.borderRadius.300}", + "background": "{surface.900}", + "hoverBackground": "{surface.900}", + "focusRing": { + "width": "{form.focusRing.width}", + "style": "{form.focusRing.style}", + "color": "{form.focusRing.color}", + "offset": "{form.focusRing.offset}", + "shadow": "{focusRing.shadow}" + }, + "content": { + "borderRadius": "{form.borderRadius.300}", + "hoverBackground": "{surface.900}", + "width": "{form.size.250}", + "height": "{form.size.250}", + "shadow": "none" + } + } + }, + "splitter": { + "colorScheme": { + "light": { + "handle": { + "background": "{surface.900}" + } + } + }, + "gutter": { + "background": "{surface.100}" + }, + "root": { + "background": "{content.background}", + "borderColor": "{content.borderColor}", + "color": "{content.color}", + "transitionDuration": "{controls.transitionDuration}" + }, + "handle": { + "size": "{form.size.150}", + "borderRadius": "{content.borderRadius}", + "focusRing": { + "width": "{form.focusRing.width}", + "style": "{form.focusRing.style}", + "color": "{form.focusRing.color}", + "offset": "{form.focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + } + }, + "stepper": { + "extend": { + "extCaption": { + "gap": "{feedback.gap.100}" + }, + "extStepNumber": { + "invalidBackground": "{error.400}", + "invalidColor": "{error.900}", + "invalidBorderColor": "{error.400}", + "borderWidth": "{feedback.width.100}", + "iconSize": "{feedback.icon.size.300}" + } + }, + "root": { + "transitionDuration": "{feedback.transitionDuration}" + }, + "separator": { + "background": "{content.borderColor}", + "activeBackground": "{form.focusBorderPrimaryColor}", + "margin": "0 0 0 1.625rem", + "size": "{form.size.100}" + }, + "step": { + "padding": "{feedback.padding.100}", + "gap": "{feedback.gap.200}" + }, + "stepHeader": { + "padding": "0rem", + "borderRadius": "0rem", + "gap": "{feedback.gap.200}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + }, + "stepTitle": { + "color": "{text.color}", + "activeColor": "{text.color}", + "fontWeight": "{fonts.fontWeight.regular}" + }, + "stepNumber": { + "background": "{content.background}", + "activeBackground": "{primary.color}", + "borderColor": "{content.borderColor}", + "activeBorderColor": "{primary.color}", + "color": "{text.color}", + "activeColor": "{text.extend.colorPrimaryStatic}", + "size": "{form.size.400}", + "fontSize": "{fonts.fontSize.300}", + "fontWeight": "{fonts.fontWeight.bold}", + "borderRadius": "{form.borderRadius.300}", + "shadow": "none" + }, + "steppanels": { + "padding": "{feedback.padding.200}" + }, + "steppanel": { + "background": "{content.background}", + "color": "{content.color}", + "padding": "0rem", + "indent": "0rem" + } + }, + "steps": { + "itemLink": { + "gap": "{form.gap.200}" + }, + "itemLabel": { + "fontWeight": "{fonts.fontWeight.regular}" + }, + "itemNumber": { + "background": "{content.background}", + "size": "{form.size.500}", + "fontSize": "{fonts.fontSize.300}", + "fontWeight": "{fonts.fontWeight.bold}", + "borderRadius": "{form.borderRadius.300}", + "shadow": "none" + } + }, + "tabs": { + "colorScheme": { + "light": { + "navButton": { + "shadow": "none" + }, + "tab": { + "background": "{transparent}", + "hoverBackground": "{transparent}", + "activeBackground": "{transparent}" + } + } + }, + "root": { + "transitionDuration": "{data.transitionDuration}" + }, + "tablist": { + "borderWidth": "0 0 {data.width.100} 0", + "background": "{transparent}", + "borderColor": "{content.borderColor}" + }, + "tab": { + "borderWidth": "0", + "borderColor": "{content.borderColor}", + "hoverBorderColor": "{content.borderColor}", + "activeBorderColor": "{content.activeBorderColor}", + "color": "{text.mutedColor}", + "hoverColor": "{text.color}", + "activeColor": "{text.color}", + "padding": "{content.padding.300}", + "fontWeight": "{fonts.fontWeight.demibold}", + "margin": "0", + "gap": "{content.gap.200}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + }, + "tabpanel": { + "background": "{transparent}", + "color": "{text.color}", + "padding": "{spacing.4x}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + }, + "navButton": { + "background": "{content.background}", + "color": "{content.color}", + "hoverColor": "{content.hoverColor}", + "width": "{controls.iconOnly.400}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "color": "{focusRing.color}", + "offset": "{focusRing.offset}", + "shadow": "{focusRing.shadow}" + } + }, + "activeBar": { + "height": "0.18rem", + "bottom": "-0.18rem", + "background": "{content.color}" + } + }, + "toast": { + "extend": { + "extInfo": { + "color": "{info.500}", + "closeButton": { + "color": "{info.500}", + "borderColor": "{info.500}" + }, + "caption": { + "color": "{text.color}" + } + }, + "extAccentLine": { + "width": "{feedback.width.200}" + }, + "extCloseButton": { + "width": "{feedback.width.100}" + }, + "extSuccess": { + "color": "{success.500}", + "closeButton": { + "color": "{success.500}", + "borderColor": "{success.500}" + }, + "caption": { + "color": "{text.color}" + } + }, + "extWarn": { + "color": "{warn.500}", + "closeButton": { + "color": "{warn.500}", + "borderColor": "{warn.500}" + }, + "caption": { + "color": "{text.color}" + } + }, + "extError": { + "color": "{error.500}", + "closeButton": { + "color": "{error.500}", + "borderColor": "{error.500}" + }, + "caption": { + "color": "{text.color}" + } + } + }, + "colorScheme": { + "light": { + "info": { + "background": "{info.50}", + "borderColor": "{info.500}", + "color": "{text.color}", + "detailColor": "{text.color}", + "shadow": "{overlay.popover.shadow}", + "closeButton": { + "hoverBackground": "{info.200}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + } + }, + "success": { + "background": "{success.50}", + "borderColor": "{success.500}", + "color": "{text.color}", + "detailColor": "{text.color}", + "shadow": "{overlay.popover.shadow}", + "closeButton": { + "hoverBackground": "{success.200}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + } + }, + "warn": { + "background": "{warn.50}", + "borderColor": "{warn.500}", + "color": "{text.color}", + "detailColor": "{text.color}", + "shadow": "{overlay.popover.shadow}", + "closeButton": { + "hoverBackground": "{warn.200}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "none" + } + } + }, + "error": { + "background": "{error.50}", + "borderColor": "{error.500}", + "color": "{text.color}", + "detailColor": "{text.color}", + "shadow": "{overlay.popover.shadow}", + "closeButton": { + "hoverBackground": "{error.200}", + "focusRing": { + "color": "{focusRing.color}", + "shadow": "{focusRing.shadow}" + } + } + }, + "secondary": { + "shadow": "{overlay.popover.shadow}" + }, + "contrast": { + "shadow": "{overlay.popover.shadow}" + } + } + }, + "root": { + "width": "{messages.width}", + "borderWidth": "{feedback.width.100}", + "borderRadius": "{content.borderRadius}", + "transitionDuration": "{feedback.transitionDuration}" + }, + "icon": { + "size": "{feedback.icon.size.500}" + }, + "content": { + "padding": "{feedback.padding.200}", + "gap": "{feedback.gap.400}" + }, + "text": { + "gap": "{feedback.gap.100}" + }, + "summary": { + "fontWeight": "{fonts.fontWeight.bold}", + "fontSize": "{fonts.fontSize.300}" + }, + "detail": { + "fontWeight": "{fonts.fontWeight.regular}", + "fontSize": "{fonts.fontSize.200}" + }, + "closeButton": { + "width": "{feedback.icon.size.400}", + "height": "{feedback.icon.size.400}", + "borderRadius": "{controls.borderRadius.100}", + "focusRing": { + "width": "{focusRing.width}", + "style": "{focusRing.style}", + "offset": "{focusRing.offset}" + } + }, + "closeIcon": { + "size": "{feedback.icon.size.200}" + } + }, + "tag": { + "colorScheme": { + "light": { + "primary": { + "background": "{primary.selectedBackground}", + "color": "{text.color}" + }, + "secondary": { + "background": "{surface.200}", + "color": "{text.color}" + }, + "success": { + "background": "{success.400}", + "color": "{success.900}" + }, + "info": { + "background": "{info.300}", + "color": "{info.900}" + }, + "warn": { + "background": "{warn.300}", + "color": "{warn.900}" + }, + "danger": { + "background": "{error.300}", + "color": "{error.900}" + } + } + }, + "root": { + "fontSize": "{fonts.fontSize.100}", + "fontWeight": "{fonts.fontWeight.regular}", + "padding": "{media.padding.100} {media.padding.200}", + "gap": "{media.gap.100}", + "borderRadius": "{media.size.200}", + "roundedBorderRadius": "{media.borderRadius.400}" + }, + "icon": { + "size": "{media.icon.size.100}" + } + }, + "textarea": { + "extend": { + "readonlyBackground": "{form.readonlyBackground}", + "borderWidth": "{form.borderWidth}", + "iconSize": "{form.icon.300}", + "minHeight": "{form.size.900}", + "extXlg": { + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{form.padding.300}", + "paddingY": "{form.padding.500}" + } + }, + "root": { + "background": "{form.background}", + "disabledBackground": "{form.disabledBackground}", + "filledBackground": "{form.filledBackground}", + "filledHoverBackground": "{form.filledHoverBackground}", + "filledFocusBackground": "{form.filledFocusBackground}", + "borderColor": "{form.borderColor}", + "hoverBorderColor": "{form.hoverBorderSecondaryColor}", + "focusBorderColor": "{form.focusBorderSecondaryColor}", + "invalidBorderColor": "{form.invalidBorderColor}", + "color": "{form.color}", + "disabledColor": "{form.disabledColor}", + "placeholderColor": "{form.placeholderColor}", + "invalidPlaceholderColor": "{form.invalidPlaceholderColor}", + "shadow": "0", + "paddingX": "{form.padding.300}", + "paddingY": "{form.padding.300}", + "borderRadius": "{form.borderRadius.200}", + "transitionDuration": "{form.transitionDuration}", + "focusRing": { + "width": "{form.focusRing.width}", + "style": "{form.focusRing.style}", + "color": "{form.focusRing.color}", + "offset": "{form.focusRing.offset}", + "shadow": "0" + }, + "sm": { + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{form.padding.300}", + "paddingY": "{form.padding.200}" + }, + "lg": { + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{form.padding.300}", + "paddingY": "{form.padding.400}" + } + } + }, + "tieredmenu": { + "extend": { + "extSubmenu": { + "borderColor": "{content.borderColor}", + "background": "{content.background}" + }, + "iconSize": "{navigation.submenuIcon.size}", + "extItem": { + "caption": { + "gap": "{content.gap.100}", + "color": "{text.mutedColor}" + } + } + }, + "root": { + "background": "{content.background}", + "borderColor": "{transparent}", + "color": "{content.color}", + "borderRadius": "{content.borderRadius}", + "shadow": "{navigation.shadow}", + "transitionDuration": "{feedback.transitionDuration}" + }, + "list": { + "padding": "{navigation.list.padding.100}", + "gap": "{navigation.list.gap}" + }, + "item": { + "focusBackground": "{navigation.item.focusBackground}", + "activeBackground": "{navigation.item.activeBackground}", + "color": "{navigation.item.color}", + "focusColor": "{navigation.item.focusColor}", + "activeColor": "{navigation.item.activeColor}", + "padding": "{navigation.item.padding}", + "borderRadius": "{navigation.item.borderRadius}", + "gap": "{navigation.item.gap}", + "icon": { + "color": "{navigation.item.icon.color}", + "focusColor": "{navigation.item.icon.focusColor}", + "activeColor": "{navigation.item.icon.activeColor}" + } + }, + "submenu": { + "mobileIndent": "{overlay.popover.padding.100}" + }, + "separator": { + "borderColor": "{content.borderColor}" + } + }, + "timeline": { + "extend": { + "extEvent": { + "gap": "{feedback.gap.100}" + } + }, + "event": { + "minHeight": "{feedback.height.900}" + }, + "vertical": { + "eventContent": { + "padding": "0 {feedback.padding.100}" + } + }, + "horizontal": { + "eventContent": { + "padding": "{feedback.padding.100} 0" + } + }, + "eventMarker": { + "size": "{feedback.width.500}", + "borderRadius": "{content.borderRadius}", + "borderWidth": "{feedback.width.200}", + "background": "{content.background}", + "borderColor": "{primary.color}", + "content": { + "borderRadius": "{content.borderRadius}", + "size": "{feedback.width.400}", + "background": "{transparent}", + "insetShadow": "none" + } + }, + "eventConnector": { + "color": "{content.borderColor}", + "size": "{feedback.width.100}" + }, + "colorScheme": { + "light": { + "eventMarker": { + "background": "{content.background}", + "borderColor": "{primary.color}" + } + }, + "dark": { + "eventMarker": { + "background": "{content.background}", + "borderColor": "{primary.color}" + } + } + } + }, + "togglebutton": { + "extend": { + "ext": { + "gap": "{form.gap.300}" + }, + "iconSize": { + "sm": "{controls.iconOnly.200}", + "md": "{controls.iconOnly.300}", + "lg": "{controls.iconOnly.400}" + }, + "iconOnlyWidth": "{form.size.600}", + "hoverBorderColor": "{surface.300}", + "checkedHoverColor": "{text.extend.colorInverted}", + "checkedHoverBackground": "{surface.800}", + "checkedHoverBorderColor": "{surface.800}", + "extXlg": { + "padding": "{form.padding.500} {form.padding.500}", + "iconOnlyWidth": "4.0714rem" + }, + "extSm": { + "iconOnlyWidth": "2.1429rem" + }, + "extLg": { + "iconOnlyWidth": "3.5714rem" + } + }, + "colorScheme": { + "light": { + "root": { + "background": "{surface.200}", + "hoverBackground": "{surface.300}", + "borderColor": "{surface.200}", + "color": "{text.color}", + "hoverColor": "{text.color}", + "checkedBackground": "{surface.900}", + "checkedColor": "{text.extend.colorInverted}", + "checkedBorderColor": "{surface.900}", + "disabledBackground": "{form.disabledBackground}", + "disabledBorderColor": "{form.disabledBackground}", + "disabledColor": "{form.disabledColor}", + "invalidBorderColor": "{form.invalidBorderColor}" + }, + "icon": { + "color": "{text.color}", + "hoverColor": "{text.color}", + "checkedColor": "{text.extend.colorInverted}", + "disabledColor": "{form.disabledColor}" + }, + "content": { + "checkedBackground": "{transparent}" + } + } + }, + "root": { + "padding": "{form.padding.200} {form.padding.400}", + "borderRadius": "{form.borderRadius.300}", + "gap": "{form.gap.200}", + "fontWeight": "{fonts.fontWeight.demibold}", + "focusRing": { + "width": "{form.focusRing.width}", + "style": "{form.focusRing.style}", + "color": "{form.focusRing.color}", + "offset": "{form.focusRing.offset}", + "shadow": "{focusRing.shadow}" + }, + "sm": { + "fontSize": "{fonts.fontSize.200}", + "padding": "{form.padding.100} {form.padding.300}" + }, + "lg": { + "fontSize": "{fonts.fontSize.500}", + "padding": "{form.padding.400} {form.padding.600}" + }, + "transitionDuration": "{form.transitionDuration}" + }, + "content": { + "checkedShadow": "none", + "padding": "0rem", + "borderRadius": "0rem", + "sm": { + "padding": "0rem" + }, + "lg": { + "padding": "0rem" + } + } + }, + "toggleswitch": { + "colorScheme": { + "light": { + "root": { + "background": "{surface.400}", + "hoverBackground": "{surface.500}", + "disabledBackground": "{form.disabledBackground}", + "checkedBackground": "{surface.900}", + "checkedHoverBackground": "{surface.800}" + }, + "handle": { + "background": "{form.backgroundHandler}", + "hoverBackground": "{form.backgroundHandler}", + "disabledBackground": "{form.disabledColor}", + "checkedBackground": "{surface.0}", + "checkedHoverBackground": "{surface.0}", + "color": "{text.color}", + "hoverColor": "{text.color}", + "checkedColor": "{text.color}", + "checkedHoverColor": "{text.color}" + } + } + }, + "root": { + "width": "{form.size.600}", + "height": "{form.size.400}", + "borderRadius": "{form.borderRadius.300}", + "gap": "{form.gap.100}", + "borderWidth": "{form.borderWidth}", + "shadow": "none", + "focusRing": { + "width": "{form.focusRing.width}", + "style": "{form.focusRing.style}", + "color": "{form.focusRing.color}", + "offset": "{form.focusRing.offset}", + "shadow": "0" + }, + "borderColor": "{transparent}", + "hoverBorderColor": "{transparent}", + "checkedBorderColor": "{transparent}", + "checkedHoverBorderColor": "{transparent}", + "invalidBorderColor": "{form.invalidBorderColor}", + "transitionDuration": "{form.transitionDuration}", + "slideDuration": "{form.transitionDuration}" + }, + "handle": { + "borderRadius": "{form.borderRadius.300}", + "size": "{form.size.300}" + } + }, + "tooltip": { + "colorScheme": { + "light": { + "root": { + "background": "{surface.900}", + "color": "{text.extend.colorInverted}" + } + } + }, + "root": { + "maxWidth": "{overlay.width}", + "gutter": "{feedback.gap.100}", + "shadow": "{overlay.popover.shadow}", + "padding": "{feedback.padding.100} {feedback.padding.200} ", + "borderRadius": "{overlay.popover.borderRadius}" + } + }, + "tree": { + "root": { + "background": "{content.background}", + "color": "{content.color}", + "padding": "{data.padding.400}", + "gap": "{data.gap.100}", + "indent": "{data.padding.400}" + }, + "node": { + "padding": "{data.padding.200} {data.padding.300}", + "color": "{text.color}", + "selectedColor": "{text.extend.colorInverted}", + "gap": "{data.gap.100}" + }, + "nodeIcon": { + "selectedColor": "{text.extend.colorInverted}" + }, + "nodeToggleButton": { + "borderRadius": "{data.borderRadius}", + "size": "{data.icon.size.400}", + "selectedHoverBackground": "{surface.900}" + }, + "loadingIcon": { + "size": "{data.icon.size.100}" + }, + "filter": { + "margin": "0 0 {data.padding.200} 0" + } + }, + "overlaybadge": { + "root": { + "outline": { + "width": "0rem", + "color": "{transparent}" + } + } + } + } +} diff --git a/src/lib/providers/public_api.ts b/src/lib/providers/public_api.ts new file mode 100644 index 00000000..a642deab --- /dev/null +++ b/src/lib/providers/public_api.ts @@ -0,0 +1,4 @@ +export * from './theme-preset'; + + + diff --git a/src/lib/providers/theme-preset.ts b/src/lib/providers/theme-preset.ts new file mode 100644 index 00000000..0c70c99b --- /dev/null +++ b/src/lib/providers/theme-preset.ts @@ -0,0 +1,17 @@ +import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core'; +import { providePrimeNG } from 'primeng/config'; +import Preset from './prime-preset/theme.preset'; + +export function provideExtraThemes(): EnvironmentProviders { + return makeEnvironmentProviders([ + providePrimeNG({ + theme: { + preset: Preset, + options: { + darkModeSelector: false, + cssLayer: false + } + } + }) + ]); +} diff --git a/src/lib/public_api.ts b/src/lib/public_api.ts new file mode 100644 index 00000000..ff8b4c56 --- /dev/null +++ b/src/lib/public_api.ts @@ -0,0 +1 @@ +export default {}; diff --git a/src/lib/tsconfig.lib.json b/src/lib/tsconfig.lib.json new file mode 100644 index 00000000..011665c6 --- /dev/null +++ b/src/lib/tsconfig.lib.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "declaration": true, + "module": "es2022", + "target": "es2022", + "baseUrl": "./features", + "stripInternal": true, + "emitDecoratorMetadata": false, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "moduleResolution": "node", + "outDir": "../build", + "rootDir": ".", + "lib": ["es2022", "dom"], + "skipLibCheck": true, + "types": ["node"], + "paths": { + "@cdek-it/angular-ui-kit/*": ["./*/public_api"] + } + }, + "angularCompilerOptions": { + "annotateForClosureCompiler": false, + "strictMetadataEmit": true, + "skipTemplateCodegen": true, + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true + }, + "files": ["./public_api.ts"] +} diff --git a/src/lib/tsconfig.lib.prod.json b/src/lib/tsconfig.lib.prod.json new file mode 100644 index 00000000..06de549e --- /dev/null +++ b/src/lib/tsconfig.lib.prod.json @@ -0,0 +1,10 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, + "angularCompilerOptions": { + "compilationMode": "partial" + } +} diff --git a/src/lib/tsconfig.spec.json b/src/lib/tsconfig.spec.json new file mode 100644 index 00000000..ce7048bc --- /dev/null +++ b/src/lib/tsconfig.spec.json @@ -0,0 +1,14 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "include": [ + "**/*.spec.ts", + "**/*.d.ts" + ] +} diff --git a/src/prime-preset/map-tokens.ts b/src/prime-preset/map-tokens.ts deleted file mode 100644 index 4957dfd4..00000000 --- a/src/prime-preset/map-tokens.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Preset } from '@primeuix/themes/types'; -import type { AuraBaseDesignTokens } from '@primeuix/themes/aura/base'; - -import primitive from './tokens/primitive-default.json'; -import semantic from './tokens/semantic-default.json'; -import components from './tokens/components-default.json'; -import themeLight from './tokens/theme-light.json'; -import themeDark from './tokens/theme-dark.json'; -import sizingBase from './tokens/sizing-base.json'; -import sizingSm from './tokens/sizing-sm.json'; -import sizingLg from './tokens/sizing-lg.json'; -import sizingXlg from './tokens/sizing-xlg.json'; - -const presetTokens: Preset = { - primitive, - semantic, - components -}; - -if (presetTokens?.semantic) { - presetTokens.semantic.colorScheme = { - light: themeLight, - dark: themeDark - }; -} - -presetTokens.semantic = { ...presetTokens.semantic, ...sizingBase }; - -const semanticLink: Record = presetTokens.semantic; - -function applySizing(semantic: Record, sizing: Record, sizeKey: 'sm' | 'lg' | 'xlg') { - Object.keys(sizing).forEach((key) => { - if (semantic[key]) { - semantic[key][sizeKey] = sizing[key]?.root ?? sizing[key]; - } - }); -} - -applySizing(semanticLink, sizingSm, 'sm'); -applySizing(semanticLink, sizingLg, 'lg'); -applySizing(semanticLink, sizingXlg, 'xlg'); - -export default presetTokens; diff --git a/src/prime-preset/tokens/components-default.json b/src/prime-preset/tokens/components-default.json deleted file mode 100644 index 5d8c8cee..00000000 --- a/src/prime-preset/tokens/components-default.json +++ /dev/null @@ -1,3362 +0,0 @@ -{ - "accordion": { - "header": { - "color": "{text.color}", - "hoverColor": "{text.hoverColor}", - "activeColor": "{text.color}", - "activeHoverColor": "{text.hoverColor}", - "borderColor": "{transparent}", - "padding": "1rem 0 1rem 0", - "fontWeight": "{fonts.fontWeight.bold}", - "borderRadius": "0", - "borderWidth": "0 0 0 0", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "inset {focus.ring.shadow}" - }, - "toggleIcon": { - "color": "{text.color}", - "hoverColor": "{text.hoverColor}", - "activeColor": "{text.color}", - "activeHoverColor": "{text.hoverColor}" - }, - "last": { - "bottomBorderRadius": "{content.borderRadius}", - "activeBottomBorderRadius": "0" - }, - "first": { - "borderWidth": "0", - "topBorderRadius": "{content.borderRadius}" - } - }, - "root": { - "transitionDuration": "{formField.transitionDuration}" - }, - "panel": { - "borderWidth": "0.0625rem", - "borderColor": "{formField.borderColor}" - }, - "colorScheme": { - "light": { - "header": { - "background": "{transparent}", - "hoverBackground": "{transparent}", - "activeBackground": "{transparent}", - "activeHoverBackground": "{transparent}" - } - } - }, - "content": { - "borderWidth": "1px 0 0 0", - "borderColor": "{transparent}", - "background": "{transparent}", - "color": "{text.color}", - "padding": "0 0 1rem 1.75rem" - } - }, - "autocomplete": { - "colorScheme": { - "light": { - "chip": { - "focusBackground": "{chip.colorScheme.light.root.background}", - "focusColor": "{chip.colorScheme.light.root.color}" - }, - "dropdown": { - "background": "{formField.background}", - "hoverBackground": "{formField.background}", - "activeBackground": "{formField.background}", - "color": "{formField.color}", - "hoverColor": "{formField.color}", - "activeColor": "{formField.color}" - } - } - }, - "extend": { - "extOption": { - "gap": "0.4375rem" - }, - "extOptionGroup": { - "gap": "0.4375rem" - } - }, - "root": { - "background": "{formField.background}", - "disabledBackground": "{formField.disabledBackground}", - "filledBackground": "{formField.filledBackground}", - "filledHoverBackground": "{formField.filledHoverBackground}", - "filledFocusBackground": "{formField.filledFocusBackground}", - "borderColor": "{formField.borderColor}", - "hoverBorderColor": "{formField.hoverBorderSecondaryColor}", - "focusBorderColor": "{formField.focusBorderSecondaryColor}", - "invalidBorderColor": "{formField.invalidBorderColor}", - "color": "{formField.color}", - "disabledColor": "{formField.disabledColor}", - "placeholderColor": "{formField.placeholderColor}", - "invalidPlaceholderColor": "{formField.invalidPlaceholderColor}", - "shadow": "{formField.shadow}", - "paddingX": "{formField.paddingX}", - "paddingY": "{formField.paddingY}", - "borderRadius": "{formField.borderRadius}", - "transitionDuration": "{formField.transitionDuration}" - }, - "overlay": { - "background": "{overlay.select.background}", - "borderColor": "{overlay.select.borderColor}", - "borderRadius": "{overlay.select.borderRadius}", - "color": "{overlay.select.color}", - "shadow": "{overlay.select.shadow}" - }, - "list": { - "padding": "{list.padding}", - "gap": "{list.gap}" - }, - "option": { - "focusBackground": "{list.option.focusBackground}", - "selectedBackground": "{list.option.selectedBackground}", - "selectedFocusBackground": "{list.option.selectedFocusBackground}", - "color": "{list.option.color}", - "focusColor": "{list.option.focusColor}", - "selectedColor": "{list.option.selectedColor}", - "selectedFocusColor": "{list.option.selectedFocusColor}", - "padding": "{list.option.padding}", - "borderRadius": "{list.option.borderRadius}" - }, - "optionGroup": { - "background": "{list.optionGroup.background}", - "color": "{list.optionGroup.color}", - "fontWeight": "{fonts.fontWeight.demibold}", - "padding": "{list.optionGroup.padding}" - }, - "dropdown": { - "width": "100%", - "borderColor": "{formField.borderColor}", - "hoverBorderColor": "{formField.hoverBorderSecondaryColor}", - "activeBorderColor": "{formField.focusBorderSecondaryColor}", - "borderRadius": "{formField.borderRadius}", - "focusRing": { - "width": "{focusRing.width}", - "style": "{formField.focusRing.style}", - "color": "{formField.focusRing.color}", - "offset": "{formField.focusRing.offset}", - "shadow": "{formField.focusRing.shadow}" - }, - "sm": { - "width": "1.75rem" - }, - "lg": { - "width": "2.625rem" - } - }, - "chip": { - "borderRadius": "{chip.root.borderRadius}" - }, - "emptyMessage": { - "padding": "{list.option.padding}" - } - }, - "avatar": { - "extend": { - "borderColor": "{formField.borderColor}" - }, - "root": { - "width": "1.75rem", - "height": "1.75rem", - "fontSize": "{fonts.fontSize.base}", - "color": "{text.extend.colorPrimaryStatic}", - "background": "{primary.color}", - "borderRadius": "{borderRadius.md}" - }, - "icon": { - "size": "0.875rem" - }, - "group": { - "borderColor": "{content.background}", - "offset": "-0.75rem" - }, - "lg": { - "width": "2.1875rem", - "height": "2.1875rem", - "fontSize": "{fonts.fontSize.base}", - "icon": { - "size": "0.875rem" - }, - "group": { - "offset": "-1rem" - } - }, - "xl": { - "width": "3.0625rem", - "height": "3.0625rem", - "icon": { - "size": "1.3125rem" - }, - "group": { - "offset": "-1.5rem" - }, - "fontSize": "{fonts.fontSize.base}" - } - }, - "badge": { - "colorScheme": { - "light": { - "primary": { - "color": "{text.extend.colorPrimaryStatic}", - "background": "{primary.color}" - }, - "secondary": { - "color": "{text.extend.colorInverted}", - "background": "{surface.900}" - }, - "success": { - "color": "{success.900}", - "background": "{success.300}" - }, - "info": { - "color": "{info.900}", - "background": "{info.300}" - }, - "warn": { - "color": "{warn.900}", - "background": "{warn.300}" - }, - "danger": { - "color": "{error.900}", - "background": "{error.300}" - } - } - }, - "extend": { - "extDot": { - "success": { - "background": "{colors.solid.green.400}" - }, - "info": { - "background": "{info.400}" - }, - "warn": { - "background": "{warn.400}" - }, - "danger": { - "background": "{error.400}" - }, - "lg": { - "size": "0.65625rem" - }, - "xlg": { - "size": "0.875rem" - } - } - }, - "root": { - "borderRadius": "{borderRadius.xl}", - "padding": "0.46875rem", - "fontSize": "{fonts.fontSize.xs}", - "fontWeight": "{fonts.fontWeight.regular}", - "minWidth": "1.3125rem", - "height": "1.3125rem" - }, - "dot": { - "size": "0.4375rem" - }, - "sm": { - "fontSize": "{fonts.fontSize.xs}", - "minWidth": "0", - "height": "0" - }, - "lg": { - "fontSize": "{fonts.fontSize.xs}", - "minWidth": "1.53125rem", - "height": "1.53125rem" - }, - "xl": { - "fontSize": "{fonts.fontSize.xs}", - "minWidth": "1.75rem", - "height": "1.75rem" - } - }, - "breadcrumb": { - "extend": { - "hoverBackground": "{surface.100}" - }, - "root": { - "padding": "0.21875rem", - "background": "{transparent}", - "gap": "0", - "transitionDuration": "{formField.transitionDuration}" - }, - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - }, - "item": { - "color": "{text.color}", - "hoverColor": "{text.hoverColor}", - "borderRadius": "{borderRadius.xs}", - "gap": "{navigation.item.gap}", - "icon": { - "color": "{text.color}", - "hoverColor": "{text.hoverColor}" - } - }, - "separator": { - "color": "{text.color}" - } - }, - "button": { - "extend": { - "disabledBackground": "{formField.disabledBackground}", - "extOutlined": { - "danger": { - "focusBackground": "{transparent}" - }, - "warn": { - "focusBackground": "{transparent}" - }, - "info": { - "focusBackground": "{transparent}" - }, - "help": { - "focusBackground": "{transparent}" - }, - "success": { - "focusBackground": "{transparent}" - } - }, - "disabledColor": "{formField.disabledColor}", - "extText": { - "danger": { - "focusBackground": "{transparent}" - }, - "warn": { - "focusBackground": "{transparent}" - }, - "info": { - "focusBackground": "{transparent}" - }, - "help": { - "focusBackground": "{transparent}" - }, - "success": { - "focusBackground": "{transparent}" - } - }, - "extLink": { - "background": "{transparent}", - "colorHover": "{text.hoverColor}", - "paddingX": "0", - "paddingY": "0.21875rem", - "sm": { - "iconOnlyWidth": "0.875rem" - }, - "base": { - "iconOnlyWidth": "1.34375rem" - }, - "lg": { - "iconOnlyWidth": "1.53125rem" - }, - "xlg": { - "iconOnlyWidth": "1.75rem" - } - }, - "extSm": { - "borderRadius": "{borderRadius.md}", - "gap": "0.4375rem" - }, - "extLg": { - "borderRadius": "{borderRadius.lg}", - "gap": "0.65625rem" - }, - "extXlg": { - "borderRadius": "{borderRadius.lg}", - "gap": "0.65625rem", - "iconOnlyWidth": "3.5625rem", - "paddingX": "1.3125rem", - "paddingY": "1.09375rem" - }, - "borderWidth": "0.0625rem" - }, - "colorScheme": { - "light": { - "root": { - "primary": { - "background": "{primary.color}", - "hoverBackground": "{primary.hoverColor}", - "activeBackground": "{primary.color}", - "borderColor": "{transparent}", - "hoverBorderColor": "{transparent}", - "activeBorderColor": "{transparent}", - "color": "{text.extend.colorPrimaryStatic}", - "hoverColor": "{text.extend.colorPrimaryStatic}", - "activeColor": "{text.extend.colorPrimaryStatic}", - "focusRing": { - "color": "{primary.200}", - "shadow": "{focusRing.shadow}" - } - }, - "secondary": { - "background": "{surface.900}", - "hoverBackground": "{surface.800}", - "activeBackground": "{surface.900}", - "borderColor": "{transparent}", - "hoverBorderColor": "{transparent}", - "activeBorderColor": "{transparent}", - "color": "{text.extend.colorInverted}", - "hoverColor": "{text.extend.colorInverted}", - "activeColor": "{text.extend.colorInverted}", - "focusRing": { - "color": "{primary.200}", - "shadow": "{focusRing.shadow}" - } - }, - "contrast": { - "background": "{surface.200}", - "hoverBackground": "{surface.300}", - "activeBackground": "{surface.200}", - "borderColor": "{transparent}", - "hoverBorderColor": "{transparent}", - "activeBorderColor": "{transparent}", - "color": "{text.color}", - "hoverColor": "{text.color}", - "activeColor": "{text.color}", - "focusRing": { - "color": "{primary.200}", - "shadow": "{focusRing.shadow}" - } - }, - "info": { - "background": "{info.300}", - "hoverBackground": "{info.400}", - "activeBackground": "{info.300}", - "borderColor": "{transparent}", - "hoverBorderColor": "{transparent}", - "activeBorderColor": "{transparent}", - "color": "{info.900}", - "hoverColor": "{info.950}", - "activeColor": "{info.900}" - }, - "success": { - "background": "{success.300}", - "hoverBackground": "{success.400}", - "activeBackground": "{success.300}", - "borderColor": "{transparent}", - "hoverBorderColor": "{transparent}", - "activeBorderColor": "{transparent}", - "color": "{success.900}", - "hoverColor": "{success.950}", - "activeColor": "{success.900}" - }, - "warn": { - "background": "{warn.300}", - "hoverBackground": "{warn.400}", - "activeBackground": "{warn.300}", - "borderColor": "{transparent}", - "hoverBorderColor": "{transparent}", - "activeBorderColor": "{transparent}", - "color": "{warn.900}", - "hoverColor": "{warn.950}", - "activeColor": "{warn.900}" - }, - "help": { - "background": "{help.300}", - "hoverBackground": "{help.400}", - "activeBackground": "{help.300}", - "borderColor": "{transparent}", - "hoverBorderColor": "{transparent}", - "activeBorderColor": "{transparent}", - "color": "{help.900}", - "hoverColor": "{help.950}", - "activeColor": "{help.900}" - }, - "danger": { - "background": "{error.300}", - "hoverBackground": "{error.400}", - "activeBackground": "{error.300}", - "borderColor": "{transparent}", - "hoverBorderColor": "{transparent}", - "activeBorderColor": "{transparent}", - "color": "{error.900}", - "hoverColor": "{error.950}", - "activeColor": "{error.900}" - } - }, - "outlined": { - "primary": { - "hoverBackground": "{primary.50}", - "activeBackground": "{primary.100}", - "borderColor": "{primary.200}", - "color": "{colors.solid.green.500}" - }, - "success": { - "hoverBackground": "{success.100}", - "activeBackground": "{transparent}", - "borderColor": "{success.600}", - "color": "{success.600}" - }, - "info": { - "hoverBackground": "{info.100}", - "activeBackground": "{transparent}", - "borderColor": "{info.600}", - "color": "{info.600}" - }, - "warn": { - "hoverBackground": "{warn.100}", - "activeBackground": "{transparent}", - "borderColor": "{warn.600}", - "color": "{warn.600}" - }, - "help": { - "hoverBackground": "{help.100}", - "activeBackground": "{transparent}", - "borderColor": "{help.600}", - "color": "{help.600}" - }, - "danger": { - "hoverBackground": "{error.100}", - "activeBackground": "{transparent}", - "borderColor": "{error.600}", - "color": "{error.600}" - } - }, - "text": { - "primary": { - "hoverBackground": "{surface.100}", - "activeBackground": "{transparent}", - "color": "{text.color}" - }, - "success": { - "hoverBackground": "{success.100}", - "activeBackground": "{transparent}", - "color": "{success.600}" - }, - "info": { - "hoverBackground": "{info.100}", - "activeBackground": "{transparent}", - "color": "{info.600}" - }, - "warn": { - "hoverBackground": "{warn.100}", - "activeBackground": "{transparent}", - "color": "{warn.600}" - }, - "help": { - "hoverBackground": "{help.100}", - "activeBackground": "{transparent}", - "color": "{help.600}" - }, - "danger": { - "hoverBackground": "{error.100}", - "activeBackground": "{transparent}", - "color": "{error.600}" - } - }, - "link": { - "color": "{text.color}", - "hoverColor": "{text.hoverColor}", - "activeColor": "{text.color}" - } - } - }, - "root": { - "borderRadius": "{borderRadius.md}", - "roundedBorderRadius": "1.75rem", - "gap": "0.4375rem", - "paddingX": "0.875rem", - "paddingY": "0.4375rem", - "iconOnlyWidth": "2.1875rem", - "raisedShadow": "0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12)", - "badgeSize": "1rem", - "transitionDuration": "{formField.transitionDuration}", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "offset": "{focusRing.offset}" - }, - "sm": { - "fontSize": "{fonts.fontSize.sm}", - "iconOnlyWidth": "1.75rem", - "paddingX": "0.65625rem", - "paddingY": "0.4375rem" - }, - "lg": { - "fontSize": "{fonts.fontSize.xl}", - "iconOnlyWidth": "3.125rem", - "paddingX": "1.3125rem", - "paddingY": "0.875rem" - }, - "label": { - "fontWeight": "{fonts.fontWeight.demibold}" - } - } - }, - "card": { - "extend": { - "borderColor": "{content.borderColor}" - }, - "root": { - "background": "{content.background}", - "borderRadius": "{borderRadius.lg}", - "color": "{content.color}", - "shadow": "0 .125rem .25rem rgba(0,0,0,.075)" - }, - "body": { - "padding": "0.875rem", - "gap": "0.875rem" - }, - "caption": { - "gap": "0.21875rem" - }, - "title": { - "fontSize": "{fonts.fontSize.lg}", - "fontWeight": "{fonts.fontWeight.demibold}" - }, - "subtitle": { - "color": "{text.mutedColor}" - } - }, - "carousel": { - "colorScheme": { - "light": { - "indicator": { - "background": "{surface.300}", - "hoverBackground": "{surface.400}", - "activeBackground": "{surface.900}" - } - } - }, - "root": { - "transitionDuration": "{transitionDuration}" - }, - "content": { - "gap": "0.4375rem" - }, - "indicatorList": { - "padding": "0.875rem", - "gap": "0.4375rem" - }, - "indicator": { - "width": "0.4375rem", - "height": "0.4375rem", - "borderRadius": "{borderRadius.xl}", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{rating.focusRing.shadow}" - } - } - }, - "checkbox": { - "root": { - "borderRadius": "{borderRadius.sm}", - "extend": { - "borderWidth": "0.0625rem" - }, - "width": "1.3125rem", - "height": "1.3125rem", - "background": "{formField.background}", - "checkedBackground": "{surface.900}", - "checkedHoverBackground": "{surface.800}", - "disabledBackground": "{formField.disabledBackground}", - "filledBackground": "{formField.filledBackground}", - "borderColor": "{formField.borderColor}", - "hoverBorderColor": "{formField.hoverBorderPrimaryColor}", - "focusBorderColor": "{formField.focusBorderPrimaryColor}", - "checkedBorderColor": "{surface.900}", - "checkedHoverBorderColor": "{surface.800}", - "checkedFocusBorderColor": "{primary.color}", - "checkedDisabledBorderColor": "{formField.borderColor}", - "invalidBorderColor": "{formField.invalidBorderColor}", - "shadow": "{formField.shadow}", - "focusRing": { - "focusRing": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - }, - "sm": { - "width": "0.875rem", - "height": "0.875rem" - }, - "lg": { - "width": "1.09375rem", - "height": "1.09375rem" - }, - "transitionDuration": "{formField.transitionDuration}" - }, - "icon": { - "size": "0.875rem", - "color": "{formField.color}", - "checkedColor": "{primary.contrastColor}", - "checkedHoverColor": "{primary.contrastColor}", - "disabledColor": "{formField.disabledColor}", - "sm": { - "size": "0.65625rem" - }, - "lg": { - "size": "1.09375rem" - } - } - }, - "chip": { - "extend": { - "borderColor": "{transparent}" - }, - "root": { - "borderRadius": "{borderRadius.sm}", - "paddingX": "0.4375rem", - "paddingY": "0.21875rem", - "gap": "0.4375rem", - "transitionDuration": "{formField.transitionDuration}" - }, - "colorScheme": { - "light": { - "root": { - "background": "{surface.200}", - "color": "{text.color}" - }, - "icon": { - "color": "{text.color}" - }, - "removeIcon": { - "color": "{text.color}" - } - } - }, - "image": { - "width": "0", - "height": "0" - }, - "icon": { - "size": "0.875rem" - }, - "removeIcon": { - "size": "0.875rem", - "focusRing": { - "width": "{formField.focusRing.width}", - "style": "{formField.focusRing.style}", - "color": "{primary.200}", - "offset": "{formField.focusRing.offset}", - "shadow": "{formField.focusRing.shadow}" - } - } - }, - "confirmdialog": { - "extend": { - "extIcon": { - "success": "{success.500}", - "info": "{info.500}", - "help": "{help.500}", - "warn": "{warn.500}", - "danger": "{error.500}" - } - }, - "icon": { - "size": "1.3125rem", - "color": "{overlay.modal.color}" - }, - "content": { - "gap": "0" - } - }, - "confirmpopup": { - "root": { - "background": "{overlay.popover.background}", - "color": "{overlay.popover.color}", - "shadow": "{overlay.popover.shadow}", - "gutter": "10px", - "arrowOffset": "1.25rem" - }, - "content": { - "padding": "{overlay.popover.padding}", - "gap": "1rem" - }, - "icon": { - "size": "1.5rem", - "color": "{overlay.popover.color}" - }, - "footer": { - "gap": "0.5rem", - "padding": "0 {overlay.popover.padding} {overlay.popover.padding} {overlay.popover.padding}" - } - }, - "contextmenu": { - "root": { - "background": "{content.background}", - "color": "{content.color}", - "shadow": "{overlay.navigation.shadow}" - }, - "list": { - "padding": "{navigation.list.padding}", - "gap": "{navigation.list.gap}" - }, - "item": { - "padding": "{navigation.item.padding}", - "gap": "{navigation.item.gap}" - }, - "submenu": { - "mobileIndent": "1.25rem" - } - }, - "datatable": { - "colorScheme": { - "light": { - "root": { - "color": "{text.color}", - "borderColor": "{content.borderColor}" - }, - "header": { - "background": "{surface.50}", - "color": "{text.color}" - }, - "headerCell": { - "background": "{surface.50}", - "hoverBackground": "{surface.100}", - "color": "{text.color}" - }, - "footer": { - "background": "{surface.100}", - "color": "{text.color}" - }, - "footerCell": { - "background": "{content.hoverBackground}", - "color": "{text.color}" - }, - "row": { - "stripedBackground": "{content.hoverBackground}" - }, - "bodyCell": { - "selectedBorderColor": "{content.borderColor}" - } - } - }, - "extended": { - "extHeaderCell": { - "selectedHoverBackground": "{surface.800}" - }, - "extRow": { - "selectedHoverBackground": "{surface.800}", - "stripedHoverBackground": "{surface.100}" - } - }, - "root": { - "transitionDuration": "{transitionDuration}" - }, - "header": { - "borderColor": "{content.borderColor}", - "borderWidth": "1px 0 1px 0", - "padding": "0.875rem", - "sm": { - "padding": "0.4375rem" - }, - "lg": { - "padding": "1.09375rem" - } - }, - "headerCell": { - "selectedBackground": "{highlight.background}", - "borderColor": "{content.borderColor}", - "hoverColor": "{text.hoverColor}", - "selectedColor": "{highlight.color}", - "gap": "0.4375rem", - "padding": "0.875rem", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "inset {focus.ring.shadow}" - }, - "sm": { - "padding": "0.4375rem" - }, - "lg": { - "padding": "1.09375rem" - } - }, - "columnTitle": { - "fontWeight": "{fonts.fontWeight.bold}" - }, - "row": { - "background": "{content.background}", - "hoverBackground": "{content.hoverBackground}", - "selectedBackground": "{highlight.background}", - "color": "{content.color}", - "hoverColor": "{content.hoverColor}", - "selectedColor": "{highlight.color}", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "inset {focus.ring.shadow}" - } - }, - "bodyCell": { - "borderColor": "{content.borderColor}", - "padding": "0.875rem", - "sm": { - "padding": "0.4375rem" - }, - "lg": { - "padding": "1.09375rem" - } - }, - "footerCell": { - "borderColor": "{content.borderColor}", - "padding": "0.875rem", - "sm": { - "padding": "0.4375rem" - }, - "lg": { - "padding": "1.09375rem" - } - }, - "columnFooter": { - "fontWeight": "{fonts.fontWeight.bold}" - }, - "dropPoint": { - "color": "{highlight.background}" - }, - "footer": { - "borderColor": "{content.borderColor}", - "borderWidth": "0 0 1px 0", - "padding": "1rem", - "sm": { - "padding": "0.5rem" - }, - "lg": { - "padding": "1.25rem" - } - }, - "columnResizer": { - "width": "0.4375rem" - }, - "resizeIndicator": { - "width": "1px", - "color": "{highlight.background}" - }, - "sortIcon": { - "color": "{text.color}", - "hoverColor": "{text.hoverColor}", - "size": "0.875rem" - }, - "loadingIcon": { - "size": "1.75rem" - }, - "rowToggleButton": { - "hoverBackground": "{content.hoverBackground}", - "selectedHoverBackground": "{content.hoverBackground}", - "color": "{text.color}", - "hoverColor": "{text.color}", - "selectedHoverColor": "{text.color}", - "size": "1.75rem", - "borderRadius": "{content.borderRadius}", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - } - }, - "filter": { - "inlineGap": "0.4375rem", - "rule": { - "borderColor": "{content.borderColor}" - }, - "constraintList": { - "padding": "{list.padding}", - "gap": "{list.gap}" - }, - "constraint": { - "focusBackground": "{list.option.focusBackground}", - "selectedBackground": "{list.option.selectedBackground}", - "selectedFocusBackground": "{list.option.selectedFocusBackground}", - "color": "{list.option.color}", - "focusColor": "{list.option.focusColor}", - "selectedColor": "{list.option.selectedColor}", - "selectedFocusColor": "{list.option.selectedFocusColor}", - "padding": "{list.option.padding}", - "borderRadius": "{list.option.borderRadius}", - "separator": { - "borderColor": "{content.borderColor}" - } - }, - "overlaySelect": { - "background": "{overlay.select.background}", - "color": "{overlay.select.color}", - "borderColor": "{overlay.select.borderColor}", - "borderRadius": "{overlay.select.borderRadius}", - "shadow": "{overlay.select.shadow}" - }, - "overlayPopover": { - "background": "{overlay.popover.background}", - "color": "{overlay.popover.color}", - "borderColor": "{overlay.select.borderColor}", - "borderRadius": "{overlay.select.borderRadius}", - "shadow": "{overlay.popover.shadow}", - "padding": "{overlay.popover.padding}", - "gap": "{list.gap}" - } - }, - "paginatorTop": { - "borderColor": "{formField.borderColor}", - "borderWidth": "0 0 1px 0" - }, - "paginatorBottom": { - "borderWidth": "0 0 1px 0", - "borderColor": "{content.borderColor}" - } - }, - "dataview": { - "root": { - "borderWidth": "1px", - "borderRadius": "4px", - "padding": "0", - "borderColor": "#ffffff" - }, - "header": { - "borderWidth": "0 0 1px 0", - "padding": "0.875rem 1.125rem", - "borderRadius": "5px 5px 0 0", - "color": "{text.color}" - }, - "content": { - "background": "{content.background}", - "color": "{content.color}", - "borderColor": "#ffffff", - "borderWidth": "0", - "padding": "0", - "borderRadius": "5px" - }, - "footer": { - "background": "{content.background}", - "color": "{content.color}", - "borderWidth": "1px 0 0 0", - "padding": "0.875rem 1.125rem", - "borderRadius": "0 0 5px 5px" - }, - "paginatorTop": { - "borderWidth": "0 0 1px 0" - }, - "paginatorBottom": { - "borderWidth": "1px 0 0 0" - } - }, - "datepicker": { - "colorScheme": { - "light": { - "dropdown": { - "background": "{content.background}", - "hoverBackground": "{navigation.item.focusBackground}", - "activeBackground": "{navigation.item.activeBackground}", - "color": "{navigation.item.color}", - "hoverColor": "{navigation.item.focusColor}", - "activeColor": "{navigation.item.activeColor}" - }, - "today": { - "background": "{content.background}", - "color": "{text.color}" - } - } - }, - "extend": { - "extDate": { - "selectedHoverBackground": "{primary.600}" - }, - "extToday": { - "borderColor": "{content.borderColor}", - "hoverBackground": "{content.hoverBackground}" - }, - "extTimePicker": { - "minWidth": "2.5rem", - "color": "{content.color}" - }, - "extTitle": { - "width": "13.125rem" - } - }, - "panel": { - "background": "{content.background}", - "borderColor": "{content.borderColor}", - "color": "{content.color}", - "borderRadius": "{content.borderRadius}", - "shadow": "{overlay.popover.shadow}", - "padding": "{overlay.popover.padding}" - }, - "header": { - "background": "{content.background}", - "borderColor": "{content.borderColor}", - "color": "{content.color}", - "padding": "0 0 0.5rem 0" - }, - "title": { - "gap": "0.4375rem", - "fontWeight": "{fonts.fontWeight.bold}" - }, - "selectMonth": { - "hoverBackground": "{content.hoverBackground}", - "color": "{content.color}", - "hoverColor": "{content.hoverColor}", - "borderRadius": "{content.borderRadius}", - "padding": "0.375rem 0.625rem" - }, - "dropdown": { - "width": "2.5rem", - "borderColor": "{formField.borderColor}", - "hoverBorderColor": "{formField.borderColor}", - "activeBorderColor": "{formField.borderColor}", - "borderRadius": "{formField.borderRadius}", - "focusRing": { - "width": "{formField.focusRing.width}", - "style": "{formField.focusRing.style}", - "color": "{formField.focusRing.color}", - "offset": "{formField.focusRing.offset}", - "shadow": "{formField.shadow}" - }, - "sm": { - "width": "0" - }, - "lg": { - "width": "0" - } - }, - "inputIcon": { - "color": "{formField.iconColor}" - }, - "group": { - "borderColor": "{content.borderColor}", - "gap": "{overlay.popover.padding}" - }, - "selectYear": { - "hoverBackground": "{content.hoverBackground}", - "color": "{content.color}", - "hoverColor": "{content.hoverColor}", - "borderRadius": "{content.borderRadius}", - "padding": "0.375rem 0.625rem" - }, - "dayView": { - "margin": "0 0 0 0" - }, - "weekDay": { - "padding": "0.21875rem", - "fontWeight": "{fonts.fontWeight.bold}", - "color": "{content.color}" - }, - "date": { - "hoverBackground": "{content.hoverBackground}", - "selectedBackground": "{primary.500}", - "rangeSelectedBackground": "{highlight.background}", - "color": "{content.color}", - "hoverColor": "{content.color}", - "selectedColor": "{text.extend.colorPrimaryStatic}", - "rangeSelectedColor": "{text.extend.colorSecondaryStatic}", - "width": "1.75rem", - "height": "1.75rem", - "borderRadius": "0.328125rem", - "padding": "0.21875rem", - "focusRing": { - "width": "{formField.focusRing.width}", - "style": "{formField.focusRing.style}", - "color": "{formField.focusRing.color}", - "offset": "{formField.focusRing.offset}", - "shadow": "{formField.shadow}" - } - }, - "monthView": { - "margin": "0 0 0 0" - }, - "month": { - "padding": "0", - "borderRadius": "0" - }, - "yearView": { - "margin": "0 0 0 0" - }, - "year": { - "padding": "0", - "borderRadius": "0" - }, - "buttonbar": { - "padding": "0 0 0 0", - "borderColor": "{content.borderColor}" - }, - "timePicker": { - "padding": "1.5rem 0.75rem 0.75rem 0.75rem", - "borderColor": "{content.borderColor}", - "gap": "0.4375rem", - "buttonGap": "0.21875rem" - }, - "root": { - "transitionDuration": "{transitionDuration}" - } - }, - "dialog": { - "root": { - "background": "{overlay.modal.background}", - "borderColor": "{overlay.modal.borderColor}", - "color": "{overlay.modal.color}", - "borderRadius": "{overlay.modal.borderRadius}", - "shadow": "{overlay.popover.shadow}" - }, - "header": { - "padding": "{overlay.modal.padding} {overlay.modal.padding} 1rem {overlay.modal.padding}", - "gap": "0" - }, - "title": { - "fontSize": "{fonts.fontSize.xl}", - "fontWeight": "{fonts.fontWeight.demibold}" - }, - "content": { - "padding": "1.3125rem" - }, - "footer": { - "padding": "0 {overlay.modal.padding} {overlay.modal.padding} {overlay.modal.padding}", - "gap": "0.4375rem" - } - }, - "divider": { - "root": { - "borderColor": "{content.borderColor}" - }, - "content": { - "background": "{content.background}", - "color": "{text.mutedColor}" - }, - "horizontal": { - "margin": "1rem 0", - "padding": "0 1rem", - "content": { - "padding": "0 0.5rem" - } - }, - "vertical": { - "margin": "0 1rem", - "padding": "1rem 0", - "content": { - "padding": "0.5rem 0" - } - } - }, - "drawer": { - "extend": { - "borderRadius": "{overlay.popover.borderRadius}", - "extHeader": { - "gap": "0.4375rem", - "borderColor": "{drawer.root.borderColor}" - }, - "width": "{sizingDrawer.width}" - }, - "root": { - "background": "{overlay.modal.background}", - "borderColor": "{overlay.modal.borderColor}", - "color": "{overlay.modal.color}", - "shadow": "{overlay.modal.shadow}" - }, - "header": { - "padding": "{overlay.modal.padding} {overlay.modal.padding} 14 {overlay.modal.padding} " - }, - "title": { - "fontSize": "{fonts.fontSize.xl}", - "fontWeight": "{fonts.fontWeight.demibold}" - }, - "content": { - "padding": "{overlay.modal.padding}" - }, - "footer": { - "padding": "0 {overlay.modal.padding} {overlay.modal.padding} {overlay.modal.padding} " - } - }, - "fileupload": { - "colorScheme": { - "light": { - "header": { - "background": "{surface.0}", - "color": "{text.color}" - } - } - }, - "extend": { - "extDragNdrop": { - "background": "{surface.0}", - "padding": "0.875rem", - "borderRadius": "{formField.borderRadius}", - "gap": "0.4375rem", - "info": { - "gap": "0.21875rem" - } - }, - "extContent": { - "borderRadius": "{borderRadius.md}", - "highlightBorderDefault": "{formField.borderColor}" - } - }, - "root": { - "background": "{content.background}", - "borderColor": "{content.borderColor}", - "color": "{content.color}", - "borderRadius": "{content.borderRadius}", - "transitionDuration": "{transitionDuration}" - }, - "header": { - "borderColor": "{content.borderColor}", - "borderWidth": "0", - "padding": "0", - "borderRadius": "0", - "gap": "0.4375rem" - }, - "content": { - "highlightBorderColor": "{surface.900}", - "padding": "0", - "gap": "0.4375rem" - }, - "file": { - "padding": "0.4375rem", - "gap": "0.4375rem", - "borderColor": "{formField.borderColor}", - "info": { - "gap": "0.21875rem" - } - }, - "fileList": { - "gap": "0.4375rem" - }, - "progressbar": { - "height": "0.4375rem" - }, - "basic": { - "gap": "0.5rem" - } - }, - "floatlabel": { - "extend": { - "height": "3.5rem", - "iconSize": "{iconSizeLarge}" - }, - "root": { - "color": "{formField.floatLabelColor}", - "focusColor": "{formField.floatLabelFocusColor}", - "activeColor": "{formField.floatLabelActiveColor}", - "invalidColor": "{formField.floatLabelInvalidColor}", - "transitionDuration": "{formField.transitionDuration}", - "positionX": "{formField.paddingX}", - "positionY": "{formField.paddingY}", - "fontWeight": "{fonts.fontWeight.regular}", - "active": { - "fontSize": "{fonts.fontSize.sm}", - "fontWeight": "{fonts.fontWeight.regular}" - } - }, - "over": { - "active": { - "top": "0.5rem" - } - }, - "inside": { - "input": { - "paddingTop": "1.640625rem", - "paddingBottom": "{formField.paddingY}" - }, - "active": { - "top": "{formField.paddingY}" - } - }, - "on": { - "borderRadius": "0", - "active": { - "padding": "0 0.125rem", - "background": "#ffffff" - } - } - }, - "galleria": { - "colorScheme": { - "light": { - "thumbnailContent": { - "background": "{surface.100}" - }, - "thumbnailNavButton": { - "hoverBackground": "{colors.alpha.white.20}", - "color": "{text.color}", - "hoverColor": "{text.hoverColor}" - }, - "indicatorButton": { - "background": "{surface.300}", - "hoverBackground": "{surface.400}" - } - } - }, - "root": { - "borderWidth": "1px", - "borderColor": "{content.borderColor}", - "borderRadius": "{content.borderRadius}", - "transitionDuration": "{transitionDuration}" - }, - "navButton": { - "background": "{transparent}", - "hoverBackground": "{colors.alpha.white.20}", - "color": "{text.extend.colorInverted}", - "hoverColor": "{text.extend.colorInverted}", - "size": "3.5rem", - "gutter": "0.4375rem", - "prev": { - "borderRadius": "{navigation.item.borderRadius}" - }, - "next": { - "borderRadius": "{navigation.item.borderRadius}" - }, - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - } - }, - "navIcon": { - "size": "1.75rem" - }, - "thumbnailsContent": { - "padding": "0.21875rem" - }, - "thumbnailNavButton": { - "size": "1.75rem", - "borderRadius": "{content.borderRadius}", - "gutter": "0.4375rem", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - } - }, - "thumbnailNavButtonIcon": { - "size": "0.875rem" - }, - "caption": { - "background": "{colors.alpha.white.50}", - "color": "{text.color}", - "padding": "0.4375rem" - }, - "indicatorList": { - "gap": "0.4375rem", - "padding": "0.875rem" - }, - "indicatorButton": { - "width": "0.4375rem", - "height": "0.4375rem", - "activeBackground": "{surface.900}", - "borderRadius": "{content.borderRadius}", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - } - }, - "insetIndicatorList": { - "background": "{colors.alpha.black.50}" - }, - "insetIndicatorButton": { - "background": "{colors.alpha.white.10}", - "hoverBackground": "{colors.alpha.white.20}", - "activeBackground": "{colors.alpha.white.50}" - }, - "closeButton": { - "size": "3.5rem", - "gutter": "0.4375rem", - "background": "{colors.alpha.white.10}", - "hoverBackground": "{colors.alpha.white.20}", - "color": "{text.extend.colorInverted}", - "hoverColor": "{text.extend.colorInverted}", - "borderRadius": "{borderRadius.lg}", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - } - }, - "closeButtonIcon": { - "size": "1.75rem" - } - }, - "message": { - "colorScheme": { - "light": { - "success": { - "background": "{success.50}", - "borderColor": "{success.500}", - "color": "{text.color}", - "shadow": "none", - "outlined": { - "color": "{text.color}", - "borderColor": "{success.500}" - }, - "closeButton": { - "hoverBackground": "{success.200}", - "focusRing": { - "color": "{focusRing.color}", - "shadow": "none" - } - }, - "simple": { - "color": "{text.color}" - } - }, - "outlined": { - "root": { - "borderWidth": "0" - }, - "closeButton": { - "hoverBackground": "#ffffff", - "focusRing": { - "color": "{focusRing.color}", - "shadow": "none" - } - }, - "outlined": { - "color": "#ffffff", - "borderColor": "#ffffff" - }, - "simple": { - "color": "#ffffff" - } - }, - "simple": { - "content": { - "padding": "0" - } - }, - "warn": { - "background": "{warn.50}", - "borderColor": "{warn.500}", - "color": "{text.color}", - "shadow": "none", - "outlined": { - "color": "{text.color}", - "borderColor": "{warn.500}" - }, - "closeButton": { - "hoverBackground": "{warn.200}", - "focusRing": { - "color": "{focusRing.color}", - "shadow": "none" - } - }, - "simple": { - "color": "{text.color}" - } - }, - "error": { - "background": "{error.50}", - "borderColor": "{error.500}", - "color": "{text.color}", - "shadow": "none", - "outlined": { - "color": "{text.color}", - "borderColor": "{error.500}" - }, - "closeButton": { - "hoverBackground": "{error.200}", - "focusRing": { - "color": "{focusRing.color}", - "shadow": "none" - } - }, - "simple": { - "color": "{text.color}" - } - }, - "secondary": { - "borderColor": "#ffffff", - "shadow": "none", - "closeButton": { - "hoverBackground": "#ffffff", - "focusRing": { - "color": "{focusRing.color}", - "shadow": "none" - } - }, - "simple": { - "color": "#ffffff" - }, - "outlined": { - "color": "#ffffff", - "borderColor": "#ffffff" - } - }, - "contrast": { - "borderColor": "#ffffff", - "shadow": "none", - "closeButton": { - "hoverBackground": "#ffffff", - "focusRing": { - "color": "{focusRing.color}", - "shadow": "none" - } - }, - "simple": { - "color": "#ffffff" - }, - "outlined": { - "color": "#ffffff", - "borderColor": "#ffffff" - } - }, - "info": { - "background": "{info.50}", - "borderColor": "{info.500}", - "color": "{text.color}", - "shadow": "none", - "outlined": { - "color": "{text.color}", - "borderColor": "{info.500}" - }, - "closeButton": { - "hoverBackground": "{info.200}", - "focusRing": { - "color": "{focusRing.color}", - "shadow": "none" - } - }, - "simple": { - "color": "{text.color}" - } - } - } - }, - "extend": { - "width": "{sizingMessage.width}", - "extText": { - "gap": "0.21875rem" - }, - "extInfo": { - "color": "{info.500}", - "closeButton": { - "color": "{info.500}", - "borderColor": "{info.500}" - }, - "caption": { - "color": "{text.color}" - } - }, - "extAccentLine": { - "width": "0.21875rem" - }, - "extCloseButton": { - "width": "0.0625rem" - }, - "extSuccess": { - "color": "{success.500}", - "closeButton": { - "color": "{success.500}", - "borderColor": "{success.500}" - }, - "caption": { - "color": "{text.color}" - } - }, - "extWarn": { - "color": "{warn.500}", - "closeButton": { - "color": "{warn.500}", - "borderColor": "{warn.500}" - }, - "caption": { - "color": "{text.color}" - } - }, - "extError": { - "color": "{error.500}", - "closeButton": { - "color": "{error.500}", - "borderColor": "{error.500}" - }, - "caption": { - "color": "{text.color}" - } - } - }, - "root": { - "borderRadius": "{content.borderRadius}", - "borderWidth": "0.0625rem", - "transitionDuration": "{transitionDuration}" - }, - "content": { - "padding": "0.875rem", - "gap": "0.875rem", - "sm": { - "padding": "0.875rem" - }, - "lg": { - "padding": "0.875rem" - } - }, - "text": { - "fontSize": "{fonts.fontSize.base}", - "fontWeight": "{fonts.fontWeight.bold}", - "sm": { - "fontSize": "{fonts.fontSize.base}" - }, - "lg": { - "fontSize": "{fonts.fontSize.base}" - } - }, - "icon": { - "size": "1.96875rem", - "sm": { - "size": "1.96875rem" - }, - "lg": { - "size": "1.96875rem" - } - }, - "closeButton": { - "width": "1.75rem", - "height": "1.75rem", - "borderRadius": "0.65625rem", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "offset": "{focusRing.offset}" - } - }, - "closeIcon": { - "size": "0.875rem", - "sm": { - "size": "0.875rem" - }, - "lg": { - "size": "0.875rem" - } - } - }, - "inputgroup": { - "colorScheme": { - "light": { - "addon": { - "background": "{transparent}", - "borderColor": "{formField.borderColor}", - "color": "{text.mutedColor}" - } - } - }, - "addon": { - "borderRadius": "{formField.borderRadius}", - "padding": "0.65625rem", - "minWidth": "2.1875rem" - } - }, - "inputnumber": { - "colorScheme": { - "light": { - "button": { - "background": "{transparent}", - "hoverBackground": "{content.hoverBackground}", - "activeBackground": "{transparent}", - "borderColor": "{formField.borderColor}", - "hoverBorderColor": "{formField.borderColor}", - "activeBorderColor": "{formField.borderColor}", - "color": "{text.color}", - "hoverColor": "{text.hoverColor}", - "activeColor": "{text.color}" - } - } - }, - "extend": { - "extButton": { - "height": "2.1875rem" - } - }, - "transitionDuration": { - "transitionDuration": "{formField.transitionDuration}" - }, - "button": { - "width": "2.1875rem", - "borderRadius": "{formField.borderRadius}", - "verticalPadding": "{formField.paddingY}" - } - }, - "inputotp": { - "extend": { - "height": "2.1875rem" - }, - "root": { - "gap": "0.4375rem" - }, - "input": { - "width": "2.1875rem" - }, - "sm": { - "width": "0" - }, - "lg": { - "width": "0" - } - }, - "inputtext": { - "extend": { - "readonlyBackground": "{formField.readonlyBackground}", - "iconSize": "{iconSizeMedium}", - "extXlg": { - "fontSize": "{sizingInputtext.root.fontSize}", - "paddingX": "{sizingInputtext.root.paddingX}", - "paddingY": "{sizingInputtext.root.paddingY}" - } - }, - "root": { - "background": "{formField.background}", - "disabledBackground": "{formField.disabledBackground}", - "filledBackground": "{formField.filledBackground}", - "filledHoverBackground": "{formField.filledHoverBackground}", - "filledFocusBackground": "{formField.filledFocusBackground}", - "borderColor": "{formField.borderColor}", - "hoverBorderColor": "{formField.hoverBorderSecondaryColor}", - "focusBorderColor": "{formField.focusBorderSecondaryColor}", - "invalidBorderColor": "{formField.invalidBorderColor}", - "color": "{text.color}", - "disabledColor": "{formField.disabledColor}", - "placeholderColor": "{formField.placeholderColor}", - "invalidPlaceholderColor": "{formField.invalidPlaceholderColor}", - "shadow": "{formField.shadow}", - "paddingX": "{sizingInputtext.root.paddingX}", - "paddingY": "{sizingInputtext.root.paddingY}", - "borderRadius": "{formField.borderRadius}", - "transitionDuration": "{formField.transitionDuration}", - "sm": { - "fontSize": "{sizingInputtext.root.fontSize}", - "paddingX": "{sizingInputtext.root.paddingX}", - "paddingY": "{sizingInputtext.root.paddingY}" - }, - "lg": { - "fontSize": "{sizingInputtext.root.fontSize}", - "paddingX": "{sizingInputtext.root.paddingX}", - "paddingY": "{sizingInputtext.root.paddingY}" - }, - "focusRing": { - "width": "{formField.focusRing.width}", - "style": "{formField.focusRing.style}", - "color": "{formField.focusRing.color}", - "offset": "{formField.focusRing.offset}", - "shadow": "{formField.shadow}" - } - } - }, - "listbox": { - "colorScheme": { - "light": { - "option": { - "stripedBackground": "{surface.50}" - } - } - }, - "extend": { - "extOption": { - "label": { - "gap": "0.21875rem" - }, - "caption": { - "color": "{text.mutedColor}", - "stripedColor": "{text.mutedColor}" - } - } - }, - "root": { - "background": "{formField.background}", - "disabledBackground": "{formField.disabledBackground}", - "borderColor": "{formField.borderColor}", - "invalidBorderColor": "{formField.invalidBorderColor}", - "color": "{formField.color}", - "disabledColor": "{formField.disabledColor}", - "shadow": "{formField.shadow}", - "borderRadius": "{formField.borderRadius}", - "transitionDuration": "{formField.transitionDuration}" - }, - "list": { - "padding": "{list.padding}", - "gap": "{list.gap}", - "header": { - "padding": "{list.header.padding}" - } - }, - "option": { - "focusBackground": "{list.option.focusBackground}", - "selectedBackground": "{list.option.selectedBackground}", - "selectedFocusBackground": "{list.option.selectedFocusBackground}", - "color": "{list.option.color}", - "focusColor": "{list.option.focusColor}", - "selectedColor": "{list.option.selectedColor}", - "selectedFocusColor": "{list.option.selectedFocusColor}", - "padding": "{list.option.padding}", - "borderRadius": "{list.option.borderRadius}" - }, - "optionGroup": { - "background": "{list.optionGroup.background}", - "color": "{list.optionGroup.color}", - "fontWeight": "{fonts.fontWeight.demibold}", - "padding": "{list.option.padding}" - }, - "checkmark": { - "color": "{list.option.color}", - "gutterStart": "-0.5rem", - "gutterEnd": "0.5rem" - }, - "emptyMessage": { - "padding": "{list.option.padding}" - } - }, - "megamenu": { - "colorScheme": { - "light": { - "root": { - "background": "{transparent}" - } - } - }, - "extend": { - "extItem": { - "caption": { - "color": "{text.mutedColor}", - "gap": "0.21875rem" - } - } - }, - "root": { - "borderColor": "{transparent}", - "borderRadius": "{content.borderRadius}", - "color": "{content.color}", - "gap": "0.21875rem", - "transitionDuration": "{transitionDuration}", - "verticalOrientation": { - "padding": "{navigation.list.padding}", - "gap": "{navigation.list.gap}" - }, - "horizontalOrientation": { - "padding": "{navigation.list.padding}", - "gap": "{navigation.list.gap}" - } - }, - "baseItem": { - "borderRadius": "{content.borderRadius}", - "padding": "{navigation.item.padding}" - }, - "item": { - "focusBackground": "{navigation.item.focusBackground}", - "activeBackground": "{navigation.item.activeBackground}", - "color": "{navigation.item.color}", - "focusColor": "{navigation.item.focusColor}", - "activeColor": "{navigation.item.activeColor}", - "padding": "{navigation.item.padding}", - "borderRadius": "{navigation.item.borderRadius}", - "gap": "{navigation.item.gap}", - "icon": { - "color": "{navigation.item.icon.color}", - "focusColor": "{navigation.item.icon.focusColor}", - "activeColor": "{navigation.item.icon.activeColor}" - } - }, - "overlay": { - "padding": "0.21875rem", - "background": "{content.background}", - "borderColor": "{content.borderColor}", - "borderRadius": "{content.borderRadius}", - "color": "{content.color}", - "shadow": "{overlay.navigation.shadow}", - "gap": "0" - }, - "submenu": { - "padding": "{navigation.list.padding}", - "gap": "{navigation.list.gap}" - }, - "submenuLabel": { - "padding": "{navigation.submenuLabel.padding}", - "background": "{navigation.submenuLabel.background}", - "color": "{navigation.submenuLabel.color}", - "Number": "{fonts.fontWeight.demibold}" - }, - "submenuIcon": { - "size": "{navigation.submenuIcon.size}", - "color": "{navigation.submenuIcon.color}", - "focusColor": "{navigation.submenuIcon.focusColor}", - "activeColor": "{navigation.submenuIcon.activeColor}" - }, - "separator": { - "borderColor": "{content.borderColor}" - }, - "mobileButton": { - "borderRadius": "{navigation.item.borderRadius}", - "size": "1.75rem", - "color": "{text.color}", - "hoverColor": "{text.hoverColor}", - "hoverBackground": "{content.hoverBackground}", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - } - } - }, - "menu": { - "extend": { - "paddingX": "0.21875rem", - "paddingY": "0.21875rem", - "extItem": { - "caption": { - "color": "{text.mutedColor}", - "gap": "0.21875rem" - } - } - }, - "root": { - "background": "{content.background}", - "borderColor": "{content.borderColor}", - "color": "{content.color}", - "borderRadius": "{content.borderRadius}", - "shadow": "{overlay.navigation.shadow}", - "transitionDuration": "{transitionDuration}" - }, - "list": { - "padding": "{navigation.list.padding}", - "gap": "{navigation.list.gap}" - }, - "item": { - "focusBackground": "{navigation.item.focusBackground}", - "color": "{navigation.item.color}", - "focusColor": "{navigation.item.focusColor}", - "padding": "{navigation.item.padding}", - "borderRadius": "{navigation.item.borderRadius}", - "gap": "{navigation.item.gap}", - "icon": { - "color": "{navigation.item.icon.color}", - "focusColor": "{navigation.item.icon.focusColor}" - } - }, - "submenuLabel": { - "padding": "{navigation.submenuLabel.padding}", - "fontWeight": "{fonts.fontWeight.demibold}", - "background": "{navigation.submenuLabel.background}", - "color": "{navigation.submenuLabel.color}" - }, - "separator": { - "borderColor": "{content.borderColor}" - } - }, - "menubar": { - "extend": { - "extItem": { - "caption": { - "color": "{text.mutedColor}", - "gap": "0.21875rem" - } - }, - "extSubmenuLabel": { - "padding": "{navigation.submenuLabel.padding}", - "fontWeight": "{fonts.fontWeight.demibold}", - "background": "{navigation.submenuLabel.background}", - "color": "{navigation.submenuLabel.color}" - } - }, - "colorScheme": { - "light": { - "root": { - "background": "{transparent}" - } - } - }, - "root": { - "borderColor": "{transparent}", - "borderRadius": "{navigation.item.borderRadius}", - "color": "{content.color}", - "gap": "0.21875rem", - "padding": "{navigation.list.padding}", - "transitionDuration": "{transitionDuration}" - }, - "baseItem": { - "borderRadius": "{navigation.item.borderRadius}", - "padding": "0.5rem 0.75rem" - }, - "item": { - "focusBackground": "{navigation.item.focusBackground}", - "activeBackground": "{navigation.item.activeBackground}", - "color": "{navigation.item.color}", - "focusColor": "{navigation.item.focusColor}", - "activeColor": "{navigation.item.activeColor}", - "padding": "{navigation.item.padding}", - "borderRadius": "{navigation.item.borderRadius}", - "gap": "{navigation.item.gap}", - "icon": { - "color": "{navigation.item.icon.color}", - "focusColor": "{navigation.item.icon.focusColor}", - "activeColor": "{navigation.item.icon.activeColor}" - } - }, - "submenu": { - "padding": "{navigation.list.padding}", - "gap": "{navigation.list.gap}", - "background": "{content.background}", - "borderColor": "{content.borderColor}", - "borderRadius": "{content.borderRadius}", - "shadow": "{overlay.navigation.shadow}", - "mobileIndent": "0.65625rem", - "icon": { - "size": "{navigation.submenuIcon.size}", - "color": "{navigation.submenuIcon.color}", - "focusColor": "{navigation.submenuIcon.focusColor}", - "activeColor": "{navigation.submenuIcon.activeColor}" - } - }, - "separator": { - "borderColor": "{content.borderColor}" - }, - "mobileButton": { - "borderRadius": "{navigation.item.borderRadius}", - "size": "1.75rem", - "color": "{text.color}", - "hoverColor": "{text.hoverColor}", - "hoverBackground": "{content.hoverBackground}", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - } - } - }, - "metergroup": { - "extend": { - "extLabel": { - "color": "{text.mutedColor}" - } - }, - "root": { - "borderRadius": "{content.borderRadius}", - "gap": "0.65625rem" - }, - "meters": { - "size": "0.4375rem", - "background": "{content.borderColor}" - }, - "label": { - "gap": "0.4375rem" - }, - "labelMarker": { - "size": "0.4375rem" - }, - "labelIcon": { - "size": "0.875rem" - }, - "labelList": { - "verticalGap": "0.4375rem", - "horizontalGap": "0.65625rem" - } - }, - "multiselect": { - "extend": { - "paddingX": "0.3125rem", - "paddingY": "0.3125rem" - }, - "root": { - "background": "{formField.background}", - "disabledBackground": "{formField.disabledBackground}", - "filledBackground": "{formField.filledBackground}", - "filledHoverBackground": "{formField.filledHoverBackground}", - "filledFocusBackground": "{formField.filledFocusBackground}", - "borderColor": "{formField.borderColor}", - "hoverBorderColor": "{formField.hoverBorderSecondaryColor}", - "focusBorderColor": "{formField.focusBorderSecondaryColor}", - "invalidBorderColor": "{formField.invalidBorderColor}", - "color": "{formField.color}", - "disabledColor": "{formField.disabledColor}", - "placeholderColor": "{formField.placeholderColor}", - "invalidPlaceholderColor": "{formField.invalidPlaceholderColor}", - "shadow": "{formField.shadow}", - "paddingX": "{formField.paddingX}", - "paddingY": "{formField.paddingY}", - "borderRadius": "{formField.borderRadius}", - "transitionDuration": "{formField.transitionDuration}", - "sm": { - "fontSize": "{formField.sm.fontSize}", - "paddingX": "{formField.sm.paddingY}", - "paddingY": "{formField.sm.paddingY}" - }, - "lg": { - "fontSize": "{formField.lg.fontSize}", - "paddingX": "{formField.lg.paddingX}", - "paddingY": "{formField.lg.paddingY}" - }, - "focusRing": { - "width": "{formField.focusRing.width}", - "style": "{formField.focusRing.style}", - "color": "{formField.focusRing.color}", - "offset": "{formField.focusRing.offset}", - "shadow": "{formField.shadow}" - } - }, - "dropdown": { - "width": "0.75rem", - "color": "{formField.iconColor}" - }, - "overlay": { - "background": "{datatable.filter.overlaySelect.background}", - "borderColor": "{overlay.select.borderColor}", - "borderRadius": "{datatable.filter.overlaySelect.borderRadius}", - "color": "{datatable.filter.overlaySelect.color}", - "shadow": "{overlay.select.shadow}" - }, - "readonlyBackground": "{formField.readonlyBackground}", - "list": { - "padding": "{list.padding}", - "header": { - "padding": "{list.header.padding}" - }, - "gap": "{list.gap}" - }, - "option": { - "focusBackground": "{list.option.focusBackground}", - "selectedBackground": "{list.option.selectedBackground}", - "selectedFocusBackground": "{list.option.selectedFocusBackground}", - "color": "{list.option.color}", - "focusColor": "{list.option.focusColor}", - "selectedColor": "{list.option.selectedColor}", - "selectedFocusColor": "{list.option.selectedFocusColor}", - "padding": "{list.option.padding}", - "borderRadius": "{list.option.borderRadius}", - "gap": "0.4375rem" - }, - "optionGroup": { - "background": "{list.optionGroup.background}", - "color": "{list.optionGroup.color}", - "fontWeight": "{fonts.fontWeight.demibold}", - "padding": "{list.optionGroup.padding}" - }, - "clearIcon": { - "color": "{formField.iconColor}" - }, - "chip": { - "borderRadius": "{borderRadius.sm}" - }, - "emptyMessage": { - "padding": "{list.option.padding}" - } - }, - "paginator": { - "root": { - "padding": "0 0.5rem", - "gap": "0.4375rem", - "borderRadius": "{content.borderRadius}", - "background": "{transparent}", - "color": "{content.color}", - "transitionDuration": "{transitionDuration}" - }, - "currentPageReport": { - "color": "{text.mutedColor}" - }, - "navButton": { - "background": "{transparent}", - "hoverBackground": "{content.hoverBackground}", - "selectedBackground": "{highlight.background}", - "color": "{text.color}", - "hoverColor": "{text.hoverColor}", - "selectedColor": "{text.extend.colorInverted}", - "width": "2.1875rem", - "height": "2.1875rem", - "borderRadius": "{content.borderRadius}", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "focus": "{focusRing.shadow}" - } - }, - "jumpToPageInput": { - "maxWidth": "4.375rem" - } - }, - "panelmenu": { - "extend": { - "extPanel": { - "gap": "0.21875rem" - }, - "extItem": { - "activeBackground": "{navigation.item.activeBackground}", - "activeColor": "{navigation.item.activeColor}", - "caption": { - "color": "{text.mutedColor}", - "gap": "0.21875rem" - } - } - }, - "root": { - "gap": "0.21875rem", - "transitionDuration": "{transitionDuration}" - }, - "panel": { - "background": "{transparent}", - "borderColor": "{transparent}", - "borderWidth": "0.0625rem", - "color": "{content.color}", - "padding": "0.21875rem", - "borderRadius": "{content.borderRadius}", - "first": { - "borderWidth": "1px 1px 0 1px", - "topBorderRadius": "{content.borderRadius}" - }, - "last": { - "borderWidth": "0 1px 1px 1px", - "topBorderRadius": "{content.borderRadius}" - } - }, - "item": { - "focusBackground": "{navigation.item.focusBackground}", - "color": "{navigation.item.color}", - "focusColor": "{navigation.item.focusColor}", - "gap": "0.4375rem", - "padding": "{navigation.item.padding}", - "borderRadius": "{navigation.item.borderRadius}", - "icon": { - "color": "{navigation.item.icon.color}", - "focusColor": "{navigation.item.icon.focusColor}" - } - }, - "submenu": { - "indent": "0.65625rem" - }, - "separator": { - "borderColor": "{content.borderColor}" - }, - "submenuIcon": { - "color": "{navigation.submenuIcon.color}", - "focusColor": "{navigation.submenuIcon.focusColor}" - } - }, - "password": { - "colorScheme": { - "light": { - "strength": { - "weakBackground": "{error.500}", - "mediumBackground": "{warn.500}", - "strongBackground": "{success.600}" - } - } - }, - "meter": { - "background": "{content.borderColor}", - "borderRadius": "{content.borderRadius}", - "height": "0.4375rem" - }, - "icon": { - "color": "{text.color}" - }, - "overlay": { - "background": "{overlay.popover.background}", - "borderColor": "{overlay.popover.borderColor}", - "borderRadius": "{overlay.popover.borderRadius}", - "color": "{overlay.popover.color}", - "padding": "{overlay.popover.padding}", - "shadow": "{overlay.popover.shadow}" - }, - "content": { - "gap": "0.4375rem" - } - }, - "popover": { - "root": { - "background": "{overlay.popover.background}", - "borderColor": "{datatable.filter.overlayPopover.borderColor}", - "color": "{overlay.popover.color}", - "borderRadius": "{overlay.popover.borderRadius}", - "shadow": "{overlay.popover.shadow}", - "gutter": "0.21875rem", - "arrowOffset": "1.25rem" - }, - "content": { - "padding": "{overlay.popover.padding}" - } - }, - "progressbar": { - "label": { - "color": "{text.extend.colorPrimaryStatic}", - "fontSize": "{fonts.fontSize.xs}", - "fontWeight": "{fonts.fontWeight.regular}" - }, - "root": { - "background": "{content.borderColor}", - "borderRadius": "{content.borderRadius}", - "height": "0.875rem" - }, - "value": { - "background": "{primary.color}" - } - }, - "progressspinner": { - "colorScheme": { - "light": { - "root": { - "colorOne": "{success.500}", - "colorTwo": "{info.500}", - "colorThree": "{error.500}", - "colorFour": "{warn.500}" - } - } - } - }, - "radiobutton": { - "root": { - "width": "1.3125rem", - "height": "1.3125rem", - "background": "{formField.background}", - "checkedBackground": "{surface.900}", - "checkedHoverBackground": "{surface.800}", - "disabledBackground": "{formField.disabledBackground}", - "filledBackground": "{formField.filledBackground}", - "borderColor": "{formField.borderColor}", - "hoverBorderColor": "{formField.hoverBorderPrimaryColor}", - "focusBorderColor": "{formField.borderColor}", - "checkedBorderColor": "{surface.900}", - "checkedHoverBorderColor": "{formField.hoverBorderPrimaryColor}", - "checkedFocusBorderColor": "{formField.focusBorderPrimaryColor}", - "checkedDisabledBorderColor": "{formField.borderColor}", - "invalidBorderColor": "{formField.invalidBorderColor}", - "shadow": "{formField.shadow}", - "transitionDuration": "{formField.transitionDuration}" - }, - "focusRing": { - "width": "0.21875rem", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{formField.focusRing.shadow}" - }, - "sm": { - "width": "0.875rem", - "height": "0.875rem" - }, - "lg": { - "width": "1.09375rem", - "height": "1.09375rem" - }, - "icon": { - "size": "0.75rem", - "checkedColor": "{text.extend.colorInverted}", - "checkedHoverColor": "{text.extend.colorInverted}", - "disabledColor": "{text.mutedColor}", - "sm": { - "size": "0" - }, - "lg": { - "size": "0" - } - } - }, - "rating": { - "root": { - "gap": "0.4375rem", - "transitionDuration": "{formField.transitionDuration}" - }, - "focusRing": { - "width": "{formField.focusRing.width}", - "style": "{formField.focusRing.style}", - "color": "{formField.focusRing.color}", - "offset": "{formField.focusRing.offset}", - "shadow": "{formField.shadow}" - }, - "icon": { - "size": "1.3125rem", - "color": "{surface.500}", - "hoverColor": "{warn.500}", - "activeColor": "{warn.500}" - } - }, - "ripple": { - "colorScheme": { - "light": { - "root": { - "background": "rgba(255, 255, 255, 0.0100)" - } - } - } - }, - "scrollpanel": { - "colorScheme": { - "light": { - "bar": { - "background": "{surface.300}" - } - } - }, - "root": { - "transitionDuration": "{transitionDuration}" - }, - "bar": { - "size": "0.4375rem", - "borderRadius": "{borderRadius.sm}", - "focusRing": { - "width": "0", - "style": "{focusRing.style}", - "color": "#ffffff", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - } - } - }, - "select": { - "extend": { - "extOption": { - "background": "{list.option.background}", - "gap": "0.4375rem" - }, - "extOptionGroup": { - "gap": "0.4375rem" - }, - "readonlyBackground": "{formField.readonlyBackground}" - }, - "root": { - "background": "{formField.background}", - "disabledBackground": "{formField.disabledBackground}", - "filledBackground": "{formField.filledBackground}", - "filledHoverBackground": "{formField.filledHoverBackground}", - "filledFocusBackground": "{formField.filledFocusBackground}", - "borderColor": "{formField.borderColor}", - "hoverBorderColor": "{formField.hoverBorderSecondaryColor}", - "focusBorderColor": "{formField.focusBorderSecondaryColor}", - "invalidBorderColor": "{formField.invalidBorderColor}", - "color": "{text.color}", - "disabledColor": "{formField.disabledColor}", - "placeholderColor": "{formField.placeholderColor}", - "invalidPlaceholderColor": "{formField.invalidPlaceholderColor}", - "shadow": "{formField.shadow}", - "paddingX": "{sizingSelect.root.paddingX}", - "paddingY": "{sizingSelect.root.paddingY}", - "borderRadius": "{formField.borderRadius}", - "transitionDuration": "{formField.transitionDuration}", - "sm": { - "fontSize": "{sizingSelect.root.fontSize}", - "paddingX": "{sizingSelect.root.paddingX}", - "paddingY": "{sizingSelect.root.paddingY}" - }, - "lg": { - "fontSize": "{sizingSelect.root.fontSize}", - "paddingX": "{sizingSelect.root.paddingX}", - "paddingY": "{sizingSelect.root.paddingY}" - }, - "focusRing": { - "width": "{formField.focusRing.width}", - "style": "{formField.focusRing.style}", - "color": "{formField.focusRing.color}", - "offset": "{formField.focusRing.offset}", - "shadow": "{formField.shadow}" - } - }, - "dropdown": { - "width": "2.5rem", - "color": "{formField.iconColor}" - }, - "overlay": { - "background": "{overlay.select.background}", - "borderColor": "{overlay.select.borderColor}", - "borderRadius": "{overlay.select.borderRadius}", - "color": "{overlay.select.color}", - "shadow": "{overlay.select.shadow}" - }, - "list": { - "padding": "{list.padding}", - "gap": "{list.gap}", - "header": { - "padding": "{list.header.padding}" - } - }, - "option": { - "focusBackground": "{list.option.focusBackground}", - "selectedBackground": "{list.option.selectedBackground}", - "selectedFocusBackground": "{list.option.selectedFocusBackground}", - "color": "{list.option.color}", - "focusColor": "{list.option.focusColor}", - "selectedColor": "{list.option.selectedColor}", - "selectedFocusColor": "{list.option.selectedFocusColor}", - "padding": "{list.option.padding}", - "borderRadius": "{list.option.borderRadius}" - }, - "optionGroup": { - "background": "{list.optionGroup.background}", - "color": "{list.optionGroup.color}", - "fontWeight": "{fonts.fontWeight.demibold}", - "padding": "{list.option.padding}" - }, - "clearIcon": { - "color": "{formField.iconColor}" - }, - "checkmark": { - "color": "{list.option.color}", - "gutterStart": "-0.5rem", - "gutterEnd": "0.5rem" - }, - "emptyMessage": { - "padding": "{list.option.padding}" - } - }, - "selectbutton": { - "colorScheme": { - "light": { - "root": { - "invalidBorderColor": "{formField.invalidBorderColor}" - } - } - }, - "extend": { - "background": "{surface.200}" - }, - "root": { - "borderRadius": "{borderRadius.rounded}" - } - }, - "skeleton": { - "colorScheme": { - "light": { - "root": { - "background": "{surface.200}", - "animationBackground": "{surface.100}" - } - } - }, - "root": { - "borderRadius": "{content.borderRadius}" - } - }, - "slider": { - "colorScheme": { - "light": { - "handle": { - "content": { - "background": "{surface.0}" - } - } - } - }, - "root": { - "transitionDuration": "{formField.transitionDuration}" - }, - "track": { - "background": "{content.borderColor}", - "borderRadius": "{content.borderRadius}", - "size": "0.21875rem" - }, - "range": { - "background": "{surface.900}" - }, - "handle": { - "width": "1.09375rem", - "height": "1.09375rem", - "borderRadius": "{borderRadius.xl}", - "background": "{surface.900}", - "hoverBackground": "{surface.900}", - "focusRing": { - "width": "{formField.focusRing.width}", - "style": "{formField.focusRing.style}", - "color": "{formField.focusRing.color}", - "offset": "{formField.focusRing.offset}", - "shadow": "{formField.shadow}" - }, - "content": { - "borderRadius": "{borderRadius.xl}", - "hoverBackground": "{surface.900}", - "width": "0.65625rem", - "height": "0.65625rem", - "shadow": "none" - } - } - }, - "splitter": { - "colorScheme": { - "light": { - "handle": { - "background": "{surface.900}" - } - } - }, - "gutter": { - "background": "{surface.100}" - }, - "root": { - "background": "{content.background}", - "borderColor": "{content.borderColor}", - "color": "{content.color}", - "transitionDuration": "{transitionDuration}" - }, - "handle": { - "size": "0.21875rem", - "borderRadius": "{content.borderRadius}", - "focusRing": { - "width": "{formField.focusRing.width}", - "style": "{formField.focusRing.style}", - "color": "{formField.focusRing.color}", - "offset": "{formField.focusRing.offset}", - "shadow": "{formField.shadow}" - } - } - }, - "stepper": { - "extend": { - "extCaption": { - "gap": "0.21875rem" - }, - "extStepNumber": { - "invalidBackground": "{error.400}", - "invalidColor": "{error.900}", - "invalidBorderColor": "{error.400}" - } - }, - "root": { - "transitionDuration": "{transitionDuration}" - }, - "separator": { - "background": "{content.borderColor}", - "activeBackground": "{formField.focusBorderPrimaryColor}", - "margin": "0 0 0 1.625rem", - "size": "0.0625rem" - }, - "step": { - "padding": "0.4375rem", - "gap": "0.4375rem" - }, - "stepHeader": { - "padding": "0", - "borderRadius": "0", - "gap": "0.4375rem", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - } - }, - "stepTitle": { - "color": "{text.color}", - "activeColor": "{text.color}", - "fontWeight": "{fonts.fontWeight.regular}" - }, - "stepNumber": { - "background": "{content.background}", - "activeBackground": "{primary.color}", - "borderColor": "{content.borderColor}", - "activeBorderColor": "{primary.color}", - "color": "{text.color}", - "activeColor": "{text.extend.colorPrimaryStatic}", - "size": "1.3125rem", - "fontSize": "{fonts.fontSize.base}", - "fontWeight": "{fonts.fontWeight.bold}", - "borderRadius": "{content.borderRadius}", - "shadow": "none" - }, - "steppanels": { - "padding": "0.875rem" - }, - "steppanel": { - "background": "{content.background}", - "color": "{content.color}", - "padding": "0", - "indent": "0" - } - }, - "steps": { - "itemLink": { - "gap": "0.5rem" - }, - "itemLabel": { - "fontWeight": "{fonts.fontWeight.regular}" - }, - "itemNumber": { - "background": "{content.background}", - "size": "2.25rem", - "fontSize": "{fonts.fontSize.base}", - "fontWeight": "{fonts.fontWeight.bold}", - "borderRadius": "50%", - "shadow": "none" - } - }, - "tabs": { - "colorScheme": { - "light": { - "navButton": { - "shadow": "0px 0px 10px 50px rgba(255, 255, 255, 0.6)" - }, - "tab": { - "background": "{transparent}", - "hoverBackground": "{transparent}", - "activeBackground": "{transparent}" - } - } - }, - "root": { - "transitionDuration": "{transitionDuration}" - }, - "tablist": { - "borderWidth": "0 0 2px 0", - "background": "{transparent}", - "borderColor": "{content.borderColor}" - }, - "tab": { - "borderWidth": "0", - "borderColor": "{content.borderColor}", - "hoverBorderColor": "{content.borderColor}", - "activeBorderColor": "{formField.focusBorderPrimaryColor}", - "color": "{text.mutedColor}", - "hoverColor": "{text.color}", - "activeColor": "{text.color}", - "padding": "0.875rem", - "fontWeight": "{fonts.fontWeight.demibold}", - "margin": "0 0 -1px 0", - "gap": "0.4375rem", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - } - }, - "tabpanel": { - "background": "{transparent}", - "color": "{text.color}", - "padding": "0.875rem", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - } - }, - "navButton": { - "background": "{content.background}", - "color": "{content.color}", - "hoverColor": "{content.hoverColor}", - "width": "1.3125rem", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - } - }, - "activeBar": { - "height": "0.125rem", - "bottom": "-1", - "background": "{content.color}" - } - }, - "toast": { - "colorScheme": { - "light": { - "info": { - "background": "{info.50}", - "borderColor": "{info.500}", - "color": "{text.color}", - "detailColor": "{text.color}", - "shadow": "{overlay.popover.shadow}", - "closeButton": { - "hoverBackground": "{info.200}", - "focusRing": { - "color": "{focusRing.color}", - "shadow": "none" - } - } - }, - "success": { - "background": "{success.50}", - "borderColor": "{success.500}", - "color": "{text.color}", - "detailColor": "{text.color}", - "shadow": "{overlay.popover.shadow}", - "closeButton": { - "hoverBackground": "{success.200}", - "focusRing": { - "color": "{focusRing.color}", - "shadow": "none" - } - } - }, - "warn": { - "background": "{warn.50}", - "borderColor": "{warn.500}", - "color": "{text.color}", - "detailColor": "{text.color}", - "shadow": "{overlay.popover.shadow}", - "closeButton": { - "hoverBackground": "{warn.200}", - "focusRing": { - "color": "{focusRing.color}", - "shadow": "none" - } - } - }, - "error": { - "background": "{error.50}", - "borderColor": "{error.500}", - "color": "{text.color}", - "detailColor": "{text.color}", - "shadow": "{overlay.popover.shadow}", - "closeButton": { - "hoverBackground": "{error.200}", - "focusRing": { - "color": "{focusRing.color}", - "shadow": "none" - } - } - }, - "secondary": { - "shadow": "{overlay.popover.shadow}" - }, - "contrast": { - "shadow": "{overlay.popover.shadow}" - } - } - }, - "extend": { - "extInfo": { - "color": "{info.500}", - "closeButton": { - "color": "{info.500}", - "borderColor": "{info.500}" - }, - "caption": { - "color": "{text.color}" - } - }, - "extAccentLine": { - "width": "0.21875rem" - }, - "extCloseButton": { - "width": "0.0625rem" - }, - "extSuccess": { - "color": "{success.500}", - "closeButton": { - "color": "{success.500}", - "borderColor": "{success.500}" - }, - "caption": { - "color": "{text.color}" - } - }, - "extWarn": { - "color": "{warn.500}", - "closeButton": { - "color": "{warn.500}", - "borderColor": "{warn.500}" - }, - "caption": { - "color": "{text.color}" - } - }, - "extError": { - "color": "{error.500}", - "closeButton": { - "color": "{error.500}", - "borderColor": "{error.500}" - }, - "caption": { - "color": "{text.color}" - } - } - }, - "root": { - "width": "{sizingToast.width}", - "borderWidth": "0.0625rem", - "borderRadius": "{content.borderRadius}", - "transitionDuration": "{transitionDuration}" - }, - "icon": { - "size": "1.96875rem" - }, - "content": { - "padding": "0.875rem", - "gap": "0.875rem" - }, - "text": { - "gap": "0.21875rem" - }, - "summary": { - "fontWeight": "{fonts.fontWeight.bold}", - "fontSize": "{fonts.fontSize.base}" - }, - "detail": { - "fontWeight": "{fonts.fontWeight.regular}", - "fontSize": "{fonts.fontSize.sm}" - }, - "closeButton": { - "width": "1.75rem", - "height": "1.75rem", - "borderRadius": "{borderRadius.md}", - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "offset": "{focusRing.offset}" - } - }, - "closeIcon": { - "size": "0.875rem" - } - }, - "tag": { - "colorScheme": { - "light": { - "primary": { - "background": "{primary.500}", - "color": "{text.color}" - }, - "secondary": { - "background": "{surface.200}", - "color": "{text.color}" - }, - "success": { - "background": "{success.400}", - "color": "{success.900}" - }, - "info": { - "background": "{info.300}", - "color": "{info.900}" - }, - "warn": { - "background": "{warn.300}", - "color": "{warn.900}" - }, - "danger": { - "background": "{error.300}", - "color": "{error.900}" - } - } - }, - "root": { - "fontSize": "{fonts.fontSize.xs}", - "fontWeight": "{fonts.fontWeight.regular}", - "padding": "0.285rem 0.5rem", - "gap": "0.21875rem", - "borderRadius": "{borderRadius.sm}", - "roundedBorderRadius": "{borderRadius.xl}" - }, - "icon": { - "size": "0.765625rem" - } - }, - "textarea": { - "extend": { - "readonlyBackground": "{formField.readonlyBackground}" - }, - "root": { - "background": "{formField.background}", - "disabledBackground": "{formField.disabledBackground}", - "filledBackground": "{formField.filledBackground}", - "filledHoverBackground": "{formField.filledHoverBackground}", - "filledFocusBackground": "{formField.filledFocusBackground}", - "borderColor": "{formField.borderColor}", - "hoverBorderColor": "{formField.hoverBorderSecondaryColor}", - "focusBorderColor": "{formField.focusBorderSecondaryColor}", - "invalidBorderColor": "{formField.invalidBorderColor}", - "color": "{formField.color}", - "disabledColor": "{formField.disabledColor}", - "placeholderColor": "{formField.placeholderColor}", - "invalidPlaceholderColor": "{formField.invalidPlaceholderColor}", - "shadow": "{formField.shadow}", - "paddingX": "{formField.paddingX}", - "paddingY": "{formField.paddingY}", - "borderRadius": "{formField.borderRadius}", - "transitionDuration": "{formField.transitionDuration}", - "focusRing": { - "width": "{formField.focusRing.width}", - "style": "{formField.focusRing.style}", - "color": "{formField.focusRing.color}", - "offset": "{formField.focusRing.offset}", - "shadow": "{formField.shadow}" - } - }, - "sm": { - "fontSize": "{fonts.fontSize.base}", - "paddingX": "{formField.sm.paddingX}", - "paddingY": "{formField.sm.paddingY}" - }, - "lg": { - "fontSize": "{fonts.fontSize.base}", - "paddingX": "{formField.lg.paddingX}", - "paddingY": "{formField.lg.paddingY}" - } - }, - "tieredmenu": { - "extend": { - "extSubmenu": { - "borderColor": "{content.borderColor}", - "background": "{content.background}" - }, - "extItem": { - "caption": { - "gap": "0.21875rem", - "color": "{text.mutedColor}" - } - } - }, - "root": { - "background": "{transparent}", - "borderColor": "{transparent}", - "color": "{content.color}", - "borderRadius": "{content.borderRadius}", - "shadow": "{overlay.navigation.shadow}", - "transitionDuration": "{transitionDuration}" - }, - "list": { - "padding": "{navigation.list.padding}", - "gap": "{navigation.list.gap}" - }, - "item": { - "focusBackground": "{navigation.item.focusBackground}", - "activeBackground": "{navigation.item.activeBackground}", - "color": "{navigation.item.color}", - "focusColor": "{navigation.item.focusColor}", - "activeColor": "{navigation.item.activeColor}", - "padding": "{navigation.item.padding}", - "borderRadius": "{navigation.item.borderRadius}", - "gap": "{navigation.item.gap}", - "icon": { - "color": "{navigation.item.icon.color}", - "focusColor": "{navigation.item.icon.focusColor}", - "activeColor": "{navigation.item.icon.activeColor}" - } - }, - "submenu": { - "mobileIndent": "0.65625rem" - }, - "separator": { - "borderColor": "{content.borderColor}" - } - }, - "timeline": { - "event": { - "minHeight": "2.65625rem" - }, - "vertical": { - "eventContent": { - "padding": "0 1rem" - } - }, - "horizontal": { - "eventContent": { - "padding": "1rem 0" - } - }, - "eventMarker": { - "size": "0.875rem", - "borderRadius": "{content.borderRadius}", - "borderWidth": "0.21875rem", - "background": "{content.background}", - "borderColor": "{primary.color}", - "content": { - "borderRadius": "{content.borderRadius}", - "size": "0.65625rem", - "background": "{transparent}", - "insetShadow": "none" - } - }, - "eventConnector": { - "color": "{content.borderColor}", - "size": "0.0625rem" - } - }, - "togglebutton": { - "colorScheme": { - "light": { - "root": { - "background": "{surface.200}" - } - } - }, - "extend": { - "gap": "0.65625rem", - "extXlg": { - "padding": "1.25rem 1.5rem", - "iconOnlyWidth": "3.5625rem" - }, - "iconOnlyWidth": "2.1875rem", - "extSm": { - "iconOnlyWidth": "1.875rem" - }, - "hoverBorderColor": "{surface.300}", - "checkedHoverColor": "{text.extend.colorInverted}", - "checkedHoverBackground": "{surface.800}", - "checkedHoverBorderColor": "{surface.800}", - "extLg": { - "iconOnlyWidth": "3.125rem" - } - }, - "root": { - "padding": "0.5rem 1rem", - "borderRadius": "{borderRadius.rounded}", - "gap": "0.4375rem", - "fontWeight": "{fonts.fontWeight.demibold}", - "hoverBackground": "{surface.300}", - "borderColor": "{surface.200}", - "color": "{text.color}", - "hoverColor": "{text.color}", - "checkedBackground": "{surface.900}", - "checkedColor": "{text.extend.colorInverted}", - "checkedBorderColor": "{surface.900}", - "disabledBackground": "{formField.disabledBackground}", - "disabledBorderColor": "{formField.disabledBackground}", - "disabledColor": "{formField.disabledColor}", - "invalidBorderColor": "{formField.invalidBorderColor}", - "focusRing": { - "width": "{formField.focusRing.width}", - "style": "{formField.focusRing.style}", - "color": "{formField.focusRing.color}", - "offset": "{formField.focusRing.offset}", - "shadow": "{formField.shadow}" - }, - "sm": { - "fontSize": "{formField.sm.fontSize}", - "padding": "0.75rem 0.25rem" - }, - "lg": { - "fontSize": "{formField.sm.fontSize}", - "padding": "1rem 1.5rem" - }, - "transitionDuration": "{formField.transitionDuration}" - }, - "icon": { - "color": "{text.color}", - "hoverColor": "{text.color}", - "checkedColor": "{text.extend.colorInverted}", - "disabledColor": "{formField.disabledColor}" - }, - "content": { - "checkedBackground": "{transparent}", - "checkedShadow": "none", - "padding": "0", - "borderRadius": "0", - "sm": { - "padding": "0" - }, - "lg": { - "padding": "0" - } - } - }, - "toggleswitch": { - "colorScheme": { - "light": { - "root": { - "background": "{surface.400}", - "hoverBackground": "{surface.500}", - "disabledBackground": "{formField.disabledBackground}", - "checkedBackground": "{surface.900}", - "checkedHoverBackground": "{surface.500}" - }, - "handle": { - "background": "{formField.backgroundHandler}", - "hoverBackground": "{formField.backgroundHandler}", - "disabledBackground": "{formField.disabledColor}", - "checkedBackground": "{surface.0}", - "checkedHoverBackground": "{surface.0}", - "color": "{text.color}", - "hoverColor": "{text.color}", - "checkedColor": "{text.color}", - "checkedHoverColor": "{text.color}" - } - } - }, - "root": { - "width": "2.1875rem", - "height": "1.3125rem", - "borderRadius": "{borderRadius.xl}", - "gap": "0.125rem", - "borderWidth": "0", - "shadow": "none", - "focusRing": { - "width": "{formField.focusRing.width}", - "style": "{formField.focusRing.style}", - "color": "{primary.200}", - "offset": "{formField.focusRing.offset}", - "shadow": "{formField.shadow}" - }, - "borderColor": "{transparent}", - "hoverBorderColor": "{transparent}", - "checkedBorderColor": "{transparent}", - "checkedHoverBorderColor": "{transparent}", - "invalidBorderColor": "{formField.invalidBorderColor}", - "transitionDuration": "{formField.transitionDuration}", - "slideDuration": "0.2s" - }, - "handle": { - "borderRadius": "6.25rem", - "size": "1.09375rem" - } - }, - "tooltip": { - "colorScheme": { - "light": { - "root": { - "background": "{surface.900}", - "color": "{text.extend.colorInverted}" - } - } - }, - "root": { - "maxWidth": "14.875rem", - "gutter": "0.21875rem", - "shadow": "{overlay.popover.shadow}", - "padding": "0.5rem 1rem", - "borderRadius": "{overlay.popover.borderRadius}" - } - }, - "tree": { - "root": { - "background": "{content.background}", - "color": "{content.color}", - "padding": "1rem", - "gap": "2px", - "indent": "1rem" - }, - "node": { - "padding": "0.375rem 0.625rem", - "color": "{text.color}", - "selectedColor": "{text.extend.colorInverted}", - "gap": "0.25rem" - }, - "nodeIcon": { - "selectedColor": "{text.extend.colorInverted}" - }, - "nodeToggleButton": { - "borderRadius": "50%", - "size": "1.75rem", - "selectedHoverBackground": "{surface.900}" - }, - "loadingIcon": { - "size": "2rem" - }, - "filter": { - "margin": "0 0 0.5rem 0" - } - }, - "overlaybadge": { - "root": { - "outline": { - "width": "0", - "color": "{transparent}" - } - } - } -} diff --git a/src/prime-preset/tokens/primitive-default.json b/src/prime-preset/tokens/primitive-default.json deleted file mode 100644 index 6dc1c0fe..00000000 --- a/src/prime-preset/tokens/primitive-default.json +++ /dev/null @@ -1,377 +0,0 @@ -{ - "colors": { - "alpha": { - "white": { - "10": "rgba(255, 255, 255, 0.1000)", - "20": "rgba(255, 255, 255, 0.2000)", - "30": "rgba(255, 255, 255, 0.3000)", - "40": "rgba(255, 255, 255, 0.4000)", - "50": "rgba(255, 255, 255, 0.5000)", - "60": "rgba(255, 255, 255, 0.6000)", - "70": "rgba(255, 255, 255, 0.7000)", - "80": "rgba(255, 255, 255, 0.8000)", - "90": "rgba(255, 255, 255, 0.9000)", - "100": "#ffffff" - }, - "black": { - "10": "rgba(0, 0, 0, 0.1000)", - "20": "rgba(0, 0, 0, 0.2000)", - "30": "rgba(0, 0, 0, 0.3000)", - "40": "rgba(0, 0, 0, 0.4000)", - "50": "rgba(0, 0, 0, 0.5000)", - "60": "rgba(0, 0, 0, 0.6000)", - "70": "rgba(0, 0, 0, 0.7000)", - "80": "rgba(0, 0, 0, 0.8000)", - "90": "rgba(0, 0, 0, 0.9000)", - "100": "#000000" - } - }, - "solid": { - "purple": { - "50": "#faf5ff", - "100": "#f3e8ff", - "200": "#e9d5ff", - "300": "#d8b4fe", - "400": "#c084fc", - "500": "#a855f7", - "600": "#9333ea", - "700": "#7e22ce", - "800": "#6b21a8", - "900": "#581c87", - "950": "#3b0764" - }, - "fuchsia": { - "50": "#fdf4ff", - "100": "#fae8ff", - "200": "#f5d0fe", - "300": "#f0abfc", - "400": "#e879f9", - "500": "#d946ef", - "600": "#c026d3", - "700": "#a21caf", - "800": "#86198f", - "900": "#701a75", - "950": "#4a044e" - }, - "pink": { - "50": "#fdf2f8", - "100": "#fce7f3", - "200": "#fbcfe8", - "300": "#f9a8d4", - "400": "#f472b6", - "500": "#ec4899", - "600": "#db2777", - "700": "#be185d", - "800": "#9d174d", - "900": "#831843", - "950": "#500724" - }, - "rose": { - "50": "#fff1f2", - "100": "#ffe4e6", - "200": "#fecdd3", - "300": "#fda4af", - "400": "#fb7185", - "500": "#f43f5e", - "600": "#e11d48", - "700": "#be123c", - "800": "#9f1239", - "900": "#881337", - "950": "#4c0519" - }, - "teal": { - "50": "#f0fdfa", - "100": "#ccfbf1", - "200": "#99f6e4", - "300": "#5eead4", - "400": "#2dd4bf", - "500": "#14b8a6", - "600": "#0d9488", - "700": "#0f766e", - "800": "#115e59", - "900": "#134e4a", - "950": "#042f2e" - }, - "cyan": { - "50": "#ecfeff", - "100": "#cffafe", - "200": "#a5f3fc", - "300": "#67e8f9", - "400": "#22d3ee", - "500": "#06b6d4", - "600": "#0891b2", - "700": "#0e7490", - "800": "#155e75", - "900": "#164e63", - "950": "#083344" - }, - "sky": { - "50": "#f0f9ff", - "100": "#e0f2fe", - "200": "#bae6fd", - "300": "#7dd3fc", - "400": "#38bdf8", - "500": "#0ea5e9", - "600": "#0284c7", - "700": "#0369a1", - "800": "#075985", - "900": "#0c4a6e", - "950": "#082f49" - }, - "blue": { - "50": "#fafdff", - "100": "#f0f9ff", - "200": "#d4ecfe", - "300": "#aad7fb", - "400": "#77baf4", - "500": "#4496e8", - "600": "#1e76cd", - "700": "#18538d", - "800": "#123a61", - "900": "#0e2a45", - "950": "#0c243b" - }, - "indigo": { - "50": "#eef2ff", - "100": "#e0e7ff", - "200": "#c7d2fe", - "300": "#a5b4fc", - "400": "#818cf8", - "500": "#6366f1", - "600": "#4f46e5", - "700": "#4338ca", - "800": "#3730a3", - "900": "#312e81", - "950": "#1e1b4b" - }, - "violet": { - "50": "#fcfaff", - "100": "#f6f0ff", - "200": "#e5d4fe", - "300": "#cbaafb", - "400": "#b284f5", - "500": "#a265ec", - "600": "#9457ea", - "700": "#48188d", - "800": "#321261", - "900": "#240e45", - "950": "#1f0c3b" - }, - "emerald": { - "50": "#ecfdf5", - "100": "#d1fae5", - "200": "#a7f3d0", - "300": "#6ee7b7", - "400": "#34d399", - "500": "#10b981", - "600": "#059669", - "700": "#047857", - "800": "#065f46", - "900": "#064e3b", - "950": "#022c22" - }, - "green": { - "50": "#fafffb", - "100": "#f0fff3", - "200": "#d4fedc", - "300": "#aafbb7", - "400": "#77f48a", - "500": "#44e858", - "600": "#1dc831", - "700": "#168322", - "800": "#12611b", - "900": "#0e4514", - "950": "#0c3b11" - }, - "lime": { - "50": "#f7fee7", - "100": "#ecfccb", - "200": "#d9f99d", - "300": "#bef264", - "400": "#a3e635", - "500": "#84cc16", - "600": "#65a30d", - "700": "#4d7c0f", - "800": "#3f6212", - "900": "#365314", - "950": "#1a2e05" - }, - "red": { - "50": "#fffafa", - "100": "#fff0f0", - "200": "#fed4d4", - "300": "#fbacaa", - "400": "#f47f77", - "500": "#e85244", - "600": "#db3424", - "700": "#8d2218", - "800": "#611912", - "900": "#45120e", - "950": "#3b100c" - }, - "orange": { - "50": "#fffbfa", - "100": "#fff3f0", - "200": "#ffddd5", - "300": "#ffbca9", - "400": "#ff9273", - "500": "#fe6434", - "600": "#d53f0b", - "700": "#a83107", - "800": "#752506", - "900": "#561c05", - "950": "#4b1905" - }, - "amber": { - "50": "#fffbeb", - "100": "#fef3c7", - "200": "#fde68a", - "300": "#fcd34d", - "400": "#fbbf24", - "500": "#f59e0b", - "600": "#d97706", - "700": "#b45309", - "800": "#92400e", - "900": "#78350f", - "950": "#451a03" - }, - "yellow": { - "50": "#fffdfa", - "100": "#fff9f0", - "200": "#ffeed4", - "300": "#fddeaa", - "400": "#facb75", - "500": "#f5b83d", - "600": "#dc9710", - "700": "#9d6d0e", - "800": "#6d4c0b", - "900": "#4f3709", - "950": "#453008" - }, - "slate": { - "50": "#f8fafc", - "100": "#f1f5f9", - "200": "#e2e8f0", - "300": "#cbd5e1", - "400": "#94a3b8", - "500": "#64748b", - "600": "#475569", - "700": "#334155", - "800": "#1e293b", - "900": "#0f172a", - "950": "#020617" - }, - "gray": { - "50": "#f9fafb", - "100": "#f3f4f6", - "200": "#e5e7eb", - "300": "#d1d5db", - "400": "#9ca3af", - "500": "#6b7280", - "600": "#4b5563", - "700": "#374151", - "800": "#1f2937", - "900": "#111827", - "950": "#030712" - }, - "zinc": { - "50": "#fafafa", - "100": "#f0f0f1", - "200": "#e2e2e4", - "300": "#cecfd2", - "400": "#a2a5a9", - "500": "#85888e", - "600": "#6d7076", - "700": "#56595f", - "800": "#404348", - "900": "#2b2e33", - "950": "#181a1f" - }, - "neutral": { - "50": "#fafafa", - "100": "#f5f5f5", - "200": "#e5e5e5", - "300": "#d4d4d4", - "400": "#a3a3a3", - "500": "#737373", - "600": "#525252", - "700": "#404040", - "800": "#262626", - "900": "#171717", - "950": "#0a0a0a" - }, - "stone": { - "50": "#fafaf9", - "100": "#f5f5f4", - "200": "#e7e5e4", - "300": "#d6d3d1", - "400": "#a8a29e", - "500": "#78716c", - "600": "#57534e", - "700": "#44403c", - "800": "#292524", - "900": "#1c1917", - "950": "#0c0a09" - } - } - }, - "borderRadius": { - "none": "0", - "xs": "0.21875rem", - "sm": "0.4375rem", - "md": "0.65625rem", - "lg": "0.875rem", - "xl": "1.3125rem", - "rounded": "62.4375rem" - }, - "fonts": { - "fontFamily": { - "heading": "TT Fellows", - "base": "PT Sans" - }, - "fontWeight": { - "regular": "25rem", - "medium": "31.25rem", - "demibold": "37.5rem", - "bold": "43.75rem" - }, - "fontSize": { - "xs": "0.65625rem", - "sm": "0.765625rem", - "base": "0.875rem", - "lg": "0.984375rem", - "xl": "1.09375rem", - "2xl": "1.3125rem", - "3xl": "1.640625rem", - "4xl": "1.96875rem", - "5xl": "2.625rem", - "6xl": "3.28125rem", - "7xl": "3.9375rem", - "8xl": "5.25rem" - }, - "lineHeight": { - "10": "0.6875rem", - "15": "0.75rem", - "20": "0.8125rem", - "25": "0.875rem", - "30": "0.9375rem", - "35": "1rem", - "40": "1.125rem", - "45": "1.25rem", - "50": "1.3125rem", - "55": "1.375rem", - "60": "1.5rem", - "65": "1.625rem", - "70": "2rem", - "75": "2.0625rem", - "80": "2.4375rem", - "85": "2.9375rem", - "auto": "auto" - } - }, - "opacity": { - "0": "0", - "50": "0.03125rem", - "100": "0.0625rem" - } -} diff --git a/src/prime-preset/tokens/semantic-default.json b/src/prime-preset/tokens/semantic-default.json deleted file mode 100644 index 2167ac22..00000000 --- a/src/prime-preset/tokens/semantic-default.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "list": { - "padding": "0.21875rem", - "gap": "0.21875rem", - "header": { - "padding": "1rem 1rem 0 1rem" - }, - "option": { - "padding": "0.5rem 0.75rem", - "borderRadius": "0.4375rem" - }, - "optionGroup": { - "padding": "0.5rem 0.75rem", - "fontWeight": "{fonts.fontWeight.demibold}" - } - }, - "focusRing": { - "width": "0.21875rem", - "style": "none", - "color": "#ffffff", - "offset": "0", - "shadow": "0 0 0 0.25rem {primary.200}" - }, - "primary": { - "50": "{colors.solid.green.50}", - "100": "{colors.solid.green.100}", - "200": "{colors.solid.green.200}", - "300": "{colors.solid.green.300}", - "400": "{colors.solid.green.400}", - "500": "{colors.solid.green.500}", - "600": "{colors.solid.green.600}", - "700": "{colors.solid.green.700}", - "800": "{colors.solid.green.800}", - "900": "{colors.solid.green.900}", - "950": "{colors.solid.green.950}" - }, - "formField": { - "paddingX": "0.65625rem", - "paddingY": "0.65625rem", - "borderRadius": "{borderRadius.md}", - "transitionDuration": "{transitionDuration}", - "sm": { - "fontSize": "{fonts.fontSize.base}", - "paddingX": "0.0390625rem", - "paddingY": "0.03125rem" - }, - "lg": { - "fontSize": "{fonts.fontSize.base}", - "paddingX": "0.0546875rem", - "paddingY": "0.046875rem" - }, - "focusRing": { - "width": "{focusRing.width}", - "style": "{focusRing.style}", - "color": "{focusRing.color}", - "offset": "{focusRing.offset}", - "shadow": "{focusRing.shadow}" - } - }, - "content": { - "borderRadius": "{borderRadius.md}" - }, - "mask": { - "transitionDuration": "{transitionDuration}" - }, - "navigation": { - "list": { - "padding": "0.21875rem", - "gap": "0.21875rem" - }, - "item": { - "padding": "0.625rem 1rem", - "borderRadius": "{borderRadius.sm}", - "gap": "0.4375rem" - }, - "submenuLabel": { - "padding": "0.625rem 1rem", - "fontWeight": "{fonts.fontWeight.demibold}" - }, - "submenuIcon": { - "size": "1.09375rem" - } - }, - "overlay": { - "select": { - "borderRadius": "{borderRadius.md}", - "shadow": "0 3.5px 7px 0 rgba(0, 0, 0, 0.2)" - }, - "popover": { - "borderRadius": "{borderRadius.sm}", - "padding": "0.65625rem", - "shadow": "0 1px 3px rgba(0, 0, 0, 0.1)" - }, - "modal": { - "borderRadius": "{borderRadius.xl}", - "padding": "1.3125rem", - "shadow": "0 1px 3px rgba(0, 0, 0, 0.3)" - }, - "navigation": { - "shadow": "0 2px 12px 0 rgba(0, 0, 0, 0.1)" - } - }, - "transitionDuration": "0.2s", - "iconSizeMedium": "0.875rem", - "iconSizeLarge": "1.09375rem", - "anchorGutter": "0.125rem", - "opacity": { - "default": "{opacity.100}", - "muted": "{opacity.50}", - "disabled": "{opacity.0}" - } -} \ No newline at end of file diff --git a/src/prime-preset/tokens/sizing-base.json b/src/prime-preset/tokens/sizing-base.json deleted file mode 100644 index cb9c0f35..00000000 --- a/src/prime-preset/tokens/sizing-base.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "sizingInputtext": { - "root": { - "fontSize": "0.875rem", - "paddingX": "0.875rem", - "paddingY": "0.875rem" - } - }, - "sizingSelect": { - "root": { - "fontSize": "0.875rem", - "paddingX": "0.875rem", - "paddingY": "0.875rem" - } - }, - "sizingDialog": { - "extra": { - "minWidth": "21.875rem" - } - }, - "sizingMessage": { - "width": "21.875rem" - }, - "sizingToast": { - "width": "21.875rem" - }, - "sizingDrawer": { - "width": "21.875rem" - } -} \ No newline at end of file diff --git a/src/prime-preset/tokens/sizing-lg.json b/src/prime-preset/tokens/sizing-lg.json deleted file mode 100644 index 9e2f0d67..00000000 --- a/src/prime-preset/tokens/sizing-lg.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "sizingInputtext": { - "root": { - "fontSize": "0.875rem", - "paddingX": "1.09375rem", - "paddingY": "1.09375rem" - } - }, - "sizingSelect": { - "root": { - "fontSize": "0.875rem", - "paddingX": "1.09375rem", - "paddingY": "1.09375rem" - } - }, - "sizingDialog": { - "extra": { - "minWidth": "26.25rem" - } - }, - "sizingMessage": { - "width": "26.25rem" - }, - "sizingToast": { - "width": "26.25rem" - }, - "sizingDrawer": { - "width": "26.25rem" - } -} \ No newline at end of file diff --git a/src/prime-preset/tokens/sizing-sm.json b/src/prime-preset/tokens/sizing-sm.json deleted file mode 100644 index 8118b77d..00000000 --- a/src/prime-preset/tokens/sizing-sm.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "sizingInputtext": { - "root": { - "fontSize": "0.875rem", - "paddingX": "0.65625rem", - "paddingY": "0.65625rem" - } - }, - "sizingSelect": { - "root": { - "fontSize": "0.875rem", - "paddingX": "0.65625rem", - "paddingY": "0.65625rem" - } - }, - "sizingDialog": { - "extra": { - "minWidth": "17.5rem" - } - }, - "sizingMessage": { - "width": "17.5rem" - }, - "sizingToast": { - "width": "17.5rem" - }, - "sizingDrawer": { - "width": "17.5rem" - } -} \ No newline at end of file diff --git a/src/prime-preset/tokens/sizing-xlg.json b/src/prime-preset/tokens/sizing-xlg.json deleted file mode 100644 index 8dc35163..00000000 --- a/src/prime-preset/tokens/sizing-xlg.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "sizingInputtext": { - "root": { - "fontSize": "0.875rem", - "paddingX": "1.3125rem", - "paddingY": "1.3125rem" - } - }, - "sizingSelect": { - "root": { - "fontSize": "0.875rem", - "paddingX": "1.3125rem", - "paddingY": "1.3125rem" - } - }, - "sizingDialog": { - "extra": { - "minWidth": "39.375rem" - } - }, - "sizingMessage": { - "width": "39.375rem" - }, - "sizingToast": { - "width": "39.375rem" - }, - "sizingDrawer": { - "width": "39.375rem" - } -} \ No newline at end of file diff --git a/src/prime-preset/tokens/theme-dark.json b/src/prime-preset/tokens/theme-dark.json deleted file mode 100644 index 9e1b7947..00000000 --- a/src/prime-preset/tokens/theme-dark.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "success": { - "50": "{colors.solid.green.950}", - "100": "{colors.solid.green.900}", - "200": "{colors.solid.green.800}", - "300": "{colors.solid.green.700}", - "400": "{colors.solid.green.600}", - "500": "{colors.solid.green.500}", - "600": "{colors.solid.green.400}", - "700": "{colors.solid.green.300}", - "800": "{colors.solid.green.200}", - "900": "{colors.solid.green.100}", - "950": "{colors.solid.green.50}" - }, - "info": { - "50": "{colors.solid.blue.950}", - "100": "{colors.solid.blue.900}", - "200": "{colors.solid.blue.800}", - "300": "{colors.solid.blue.700}", - "400": "{colors.solid.blue.600}", - "500": "{colors.solid.blue.500}", - "600": "{colors.solid.blue.400}", - "700": "{colors.solid.blue.300}", - "800": "{colors.solid.blue.200}", - "900": "{colors.solid.blue.100}", - "950": "{colors.solid.blue.50}" - }, - "warn": { - "50": "{colors.solid.yellow.950}", - "100": "{colors.solid.yellow.900}", - "200": "{colors.solid.yellow.800}", - "300": "{colors.solid.yellow.700}", - "400": "{colors.solid.yellow.600}", - "500": "{colors.solid.yellow.500}", - "600": "{colors.solid.yellow.400}", - "700": "{colors.solid.yellow.300}", - "800": "{colors.solid.yellow.200}", - "900": "{colors.solid.yellow.100}", - "950": "{colors.solid.yellow.50}" - }, - "transparent": "rgba(0, 0, 0, 0.0001)", - "help": { - "50": "{colors.solid.purple.950}", - "100": "{colors.solid.purple.900}", - "200": "{colors.solid.purple.800}", - "300": "{colors.solid.purple.700}", - "400": "{colors.solid.purple.600}", - "500": "{colors.solid.purple.500}", - "600": "{colors.solid.purple.400}", - "700": "{colors.solid.purple.300}", - "800": "{colors.solid.purple.200}", - "900": "{colors.solid.purple.100}", - "950": "{colors.solid.purple.50}" - }, - "error": { - "50": "{colors.solid.red.950}", - "100": "{colors.solid.red.900}", - "200": "{colors.solid.red.800}", - "300": "{colors.solid.red.700}", - "400": "{colors.solid.red.600}", - "500": "{colors.solid.red.500}", - "600": "{colors.solid.red.400}", - "700": "{colors.solid.red.300}", - "800": "{colors.solid.red.200}", - "900": "{colors.solid.red.100}", - "950": "{colors.solid.red.50}" - }, - "surface": { - "0": "{colors.alpha.black.100}", - "50": "{colors.solid.zinc.950}", - "100": "{colors.solid.zinc.900}", - "200": "{colors.solid.zinc.800}", - "300": "{colors.solid.zinc.700}", - "400": "{colors.solid.zinc.600}", - "500": "{colors.solid.zinc.500}", - "600": "{colors.solid.zinc.400}", - "700": "{colors.solid.zinc.300}", - "800": "{colors.solid.zinc.200}", - "900": "{colors.solid.zinc.100}", - "950": "{colors.solid.zinc.50}" - }, - "primary": { - "color": "{primary.500}", - "contrastColor": "{colors.solid.zinc.900}", - "hoverColor": "{primary.400}", - "activeColor": "{primary.300}" - }, - "highlight": { - "background": "{colors.solid.zinc.100}", - "focusBackground": "{colors.solid.zinc.200}", - "color": "{colors.solid.zinc.900}", - "focusColor": "{colors.solid.zinc.900}" - }, - "focusRing": { - "shadow": "0 0 0 0.2rem {primary.800}", - "extend": { - "invalid": "{colors.solid.red.800}", - "success": "{colors.solid.green.800}", - "warning": "{colors.solid.yellow.800}", - "info": "{colors.solid.blue.800}" - } - }, - "mask": { - "background": "{colors.alpha.black.60}", - "color": "{surface.800}" - }, - "formField": { - "background": "{colors.solid.zinc.950}", - "disabledBackground": "{colors.solid.zinc.800}", - "readonlyBackground": "{colors.solid.zinc.900}", - "filledBackground": "{colors.solid.zinc.950}", - "filledHoverBackground": "{colors.solid.zinc.950}", - "filledFocusBackground": "{colors.solid.zinc.950}", - "borderColor": "{colors.solid.zinc.700}", - "hoverBorderPrimaryColor": "{colors.solid.zinc.100}", - "focusBorderPrimaryColor": "{colors.solid.zinc.100}", - "hoverBorderSecondaryColor": "{colors.solid.green.400}", - "focusBorderSecondaryColor": "{colors.solid.green.400}", - "invalidBorderColor": "{colors.solid.red.600}", - "color": "{colors.alpha.white.100}", - "disabledColor": "{colors.solid.zinc.500}", - "placeholderColor": "{colors.solid.zinc.500}", - "invalidPlaceholderColor": "{colors.solid.red.400}", - "floatLabelColor": "{colors.solid.zinc.500}", - "floatLabelFocusColor": "{colors.solid.zinc.500}", - "floatLabelActiveColor": "{colors.solid.zinc.500}", - "floatLabelInvalidColor": "{formField.invalidPlaceholderColor}", - "iconColor": "{colors.alpha.white.100}", - "shadow": "rgba(255, 255, 255, 0.0000)", - "backgroundHandler": "{colors.alpha.white.100}" - }, - "text": { - "color": "{colors.alpha.white.100}", - "extend": { - "colorPrimaryStatic": "{colors.solid.zinc.900}", - "colorSecondaryStatic": "{colors.alpha.white.100}", - "colorInverted": "{colors.solid.zinc.900}" - }, - "hoverColor": "{colors.solid.zinc.300}", - "mutedColor": "{colors.solid.zinc.500}", - "hoverMutedColor": "{colors.solid.zinc.700}" - }, - "content": { - "background": "{colors.solid.zinc.900}", - "hoverBackground": "{colors.solid.zinc.800}", - "borderColor": "{colors.solid.zinc.800}", - "color": "{text.color}", - "hoverColor": "{text.hoverColor}" - }, - "overlay": { - "select": { - "background": "{colors.solid.zinc.900}", - "borderColor": "{colors.solid.zinc.800}", - "color": "{text.color}" - }, - "popover": { - "background": "{colors.solid.zinc.900}", - "borderColor": "{formField.borderColor}", - "color": "{text.color}", - "shadow": "rgba(24, 26, 31, 0.2000)" - }, - "modal": { - "background": "{colors.solid.zinc.900}", - "borderColor": "{colors.solid.zinc.800}", - "color": "{text.color}" - } - }, - "list": { - "option": { - "background": "{colors.solid.zinc.900}", - "focusBackground": "{colors.solid.zinc.800}", - "selectedBackground": "{colors.solid.zinc.100}", - "selectedFocusBackground": "{colors.solid.zinc.300}", - "color": "{text.color}", - "focusColor": "{text.color}", - "selectedColor": "{text.extend.colorInverted}", - "selectedFocusColor": "{text.extend.colorInverted}", - "icon": { - "color": "{text.color}", - "focusColor": "{text.color}" - } - }, - "surface": "#ffffff", - "optionGroup": { - "background": "{colors.solid.zinc.900}", - "color": "{text.mutedColor}" - } - }, - "navigation": { - "submenuLabel": { - "background": "rgba(255, 255, 255, 0.0000)", - "color": "{text.mutedColor}" - }, - "submenuIcon": { - "color": "{colors.solid.zinc.100}", - "focusColor": "{colors.solid.zinc.100}", - "activeColor": "{colors.solid.zinc.900}" - }, - "item": { - "focusBackground": "{colors.solid.zinc.900}", - "activeBackground": "{colors.solid.zinc.100}", - "color": "{colors.alpha.white.100}", - "focusColor": "{colors.alpha.white.100}", - "activeColor": "{colors.solid.zinc.900}", - "icon": { - "color": "{colors.alpha.white.100}", - "focusColor": "{colors.alpha.white.100}", - "activeColor": "{colors.solid.zinc.900}" - } - } - } -} \ No newline at end of file diff --git a/src/prime-preset/tokens/theme-light.json b/src/prime-preset/tokens/theme-light.json deleted file mode 100644 index d06ada7f..00000000 --- a/src/prime-preset/tokens/theme-light.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "success": { - "50": "{colors.solid.green.50}", - "100": "{colors.solid.green.100}", - "200": "{colors.solid.green.200}", - "300": "{colors.solid.green.300}", - "400": "{colors.solid.green.400}", - "500": "{colors.solid.green.500}", - "600": "{colors.solid.green.600}", - "700": "{colors.solid.green.700}", - "800": "{colors.solid.green.800}", - "900": "{colors.solid.green.900}", - "950": "{colors.solid.green.950}" - }, - "info": { - "50": "{colors.solid.blue.50}", - "100": "{colors.solid.blue.100}", - "200": "{colors.solid.blue.200}", - "300": "{colors.solid.blue.300}", - "400": "{colors.solid.blue.400}", - "500": "{colors.solid.blue.500}", - "600": "{colors.solid.blue.600}", - "700": "{colors.solid.blue.700}", - "800": "{colors.solid.blue.800}", - "900": "{colors.solid.blue.900}", - "950": "{colors.solid.blue.950}" - }, - "warn": { - "50": "{colors.solid.yellow.50}", - "100": "{colors.solid.yellow.100}", - "200": "{colors.solid.yellow.200}", - "300": "{colors.solid.yellow.300}", - "400": "{colors.solid.yellow.400}", - "500": "{colors.solid.yellow.500}", - "600": "{colors.solid.yellow.600}", - "700": "{colors.solid.yellow.700}", - "800": "{colors.solid.yellow.800}", - "900": "{colors.solid.yellow.900}", - "950": "{colors.solid.yellow.950}" - }, - "transparent": "rgba(255, 255, 255, 0.0001)", - "help": { - "50": "{colors.solid.purple.50}", - "100": "{colors.solid.purple.100}", - "200": "{colors.solid.purple.200}", - "300": "{colors.solid.purple.300}", - "400": "{colors.solid.purple.400}", - "500": "{colors.solid.purple.500}", - "600": "{colors.solid.purple.600}", - "700": "{colors.solid.purple.700}", - "800": "{colors.solid.purple.800}", - "900": "{colors.solid.purple.900}", - "950": "{colors.solid.purple.950}" - }, - "error": { - "50": "{colors.solid.red.50}", - "100": "{colors.solid.red.100}", - "200": "{colors.solid.red.200}", - "300": "{colors.solid.red.300}", - "400": "{colors.solid.red.400}", - "500": "{colors.solid.red.500}", - "600": "{colors.solid.red.600}", - "700": "{colors.solid.red.700}", - "800": "{colors.solid.red.800}", - "900": "{colors.solid.red.900}", - "950": "{colors.solid.red.950}" - }, - "surface": { - "0": "{colors.alpha.white.100}", - "50": "{colors.solid.zinc.50}", - "100": "{colors.solid.zinc.100}", - "200": "{colors.solid.zinc.200}", - "300": "{colors.solid.zinc.300}", - "400": "{colors.solid.zinc.400}", - "500": "{colors.solid.zinc.500}", - "600": "{colors.solid.zinc.600}", - "700": "{colors.solid.zinc.700}", - "800": "{colors.solid.zinc.800}", - "900": "{colors.solid.zinc.900}", - "950": "{colors.solid.zinc.950}" - }, - "primary": { - "color": "{primary.500}", - "contrastColor": "{surface.0}", - "hoverColor": "{primary.600}", - "activeColor": "{primary.700}" - }, - "highlight": { - "background": "{colors.solid.zinc.900}", - "focusBackground": "{colors.solid.zinc.800}", - "color": "{colors.alpha.white.100}", - "focusColor": "{colors.alpha.white.100}" - }, - "focusRing": { - "shadow": "0 0 0 0.2rem {primary.200}", - "extend": { - "invalid": "{colors.solid.red.200}", - "success": "{colors.solid.green.200}", - "warning": "{colors.solid.yellow.200}", - "info": "{colors.solid.blue.200}" - } - }, - "mask": { - "background": "{colors.alpha.black.40}", - "color": "{surface.200}" - }, - "formField": { - "background": "{colors.alpha.white.100}", - "disabledBackground": "{colors.solid.zinc.200}", - "readonlyBackground": "{colors.solid.zinc.100}", - "filledBackground": "{colors.alpha.white.100}", - "filledHoverBackground": "{colors.alpha.white.100}", - "filledFocusBackground": "{colors.alpha.white.100}", - "borderColor": "{colors.solid.zinc.300}", - "hoverBorderPrimaryColor": "{colors.solid.zinc.900}", - "focusBorderPrimaryColor": "{colors.solid.zinc.900}", - "hoverBorderSecondaryColor": "{colors.solid.green.600}", - "focusBorderSecondaryColor": "{colors.solid.green.600}", - "invalidBorderColor": "{colors.solid.red.400}", - "color": "{colors.solid.zinc.950}", - "disabledColor": "{colors.solid.zinc.500}", - "placeholderColor": "{colors.solid.zinc.500}", - "invalidPlaceholderColor": "{colors.solid.red.600}", - "floatLabelColor": "{colors.solid.zinc.500}", - "floatLabelFocusColor": "{colors.solid.zinc.500}", - "floatLabelActiveColor": "{colors.solid.zinc.500}", - "floatLabelInvalidColor": "{formField.invalidPlaceholderColor}", - "iconColor": "{colors.solid.zinc.950}", - "shadow": "rgba(0, 0, 0, 0.0000)", - "backgroundHandler": "{colors.alpha.white.100}" - }, - "text": { - "color": "{colors.solid.zinc.900}", - "extend": { - "colorPrimaryStatic": "{colors.solid.zinc.900}", - "colorSecondaryStatic": "{colors.alpha.white.100}", - "colorInverted": "{colors.alpha.white.100}" - }, - "hoverColor": "{colors.solid.zinc.700}", - "mutedColor": "{colors.solid.zinc.500}", - "hoverMutedColor": "{colors.solid.zinc.300}" - }, - "content": { - "background": "{colors.alpha.white.100}", - "hoverBackground": "{colors.solid.zinc.100}", - "borderColor": "{colors.solid.zinc.200}", - "color": "{text.color}", - "hoverColor": "{text.hoverColor}" - }, - "overlay": { - "select": { - "background": "{colors.alpha.white.100}", - "borderColor": "{colors.solid.zinc.200}", - "color": "{text.color}" - }, - "popover": { - "background": "{colors.alpha.white.100}", - "borderColor": "{formField.borderColor}", - "color": "{text.color}", - "shadow": "rgba(24, 26, 31, 0.2000)" - }, - "modal": { - "background": "{colors.alpha.white.100}", - "borderColor": "{colors.solid.zinc.200}", - "color": "{text.color}" - } - }, - "list": { - "option": { - "background": "{colors.alpha.white.100}", - "focusBackground": "{colors.solid.zinc.100}", - "selectedBackground": "{colors.solid.zinc.900}", - "selectedFocusBackground": "{colors.solid.zinc.700}", - "color": "{text.color}", - "focusColor": "{text.color}", - "selectedColor": "{text.extend.colorInverted}", - "selectedFocusColor": "{text.extend.colorInverted}", - "icon": { - "color": "{text.color}", - "focusColor": "{text.color}" - } - }, - "surface": "#ffffff", - "optionGroup": { - "background": "{colors.alpha.white.100}", - "color": "{text.mutedColor}" - } - }, - "navigation": { - "submenuLabel": { - "background": "rgba(255, 255, 255, 0.0000)", - "color": "{text.mutedColor}" - }, - "submenuIcon": { - "color": "{colors.solid.zinc.900}", - "focusColor": "{colors.solid.zinc.900}", - "activeColor": "{colors.alpha.white.100}" - }, - "item": { - "focusBackground": "{colors.solid.zinc.100}", - "activeBackground": "{colors.solid.zinc.900}", - "color": "{colors.solid.zinc.900}", - "focusColor": "{colors.solid.zinc.900}", - "activeColor": "{colors.alpha.white.100}", - "icon": { - "color": "{colors.solid.zinc.900}", - "focusColor": "{colors.solid.zinc.900}", - "activeColor": "{colors.alpha.white.100}" - } - } - } -} \ No newline at end of file diff --git a/src/stories/components/avatar/avatar.stories.ts b/src/stories/components/avatar/avatar.stories.ts new file mode 100644 index 00000000..367c3f42 --- /dev/null +++ b/src/stories/components/avatar/avatar.stories.ts @@ -0,0 +1,297 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { OverlayBadge } from 'primeng/overlaybadge'; +import { ExtraAvatarComponent, ExtraAvatarGroupComponent } from '../../../lib/components/avatar/avatar.component'; + +const meta: Meta = { + title: 'Components/Misc/Avatar', + component: ExtraAvatarComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ExtraAvatarComponent, ExtraAvatarGroupComponent, OverlayBadge], + }), + ], + parameters: { + docs: { + description: { + component: `Аватар представляет пользователя или сущность. Может содержать текст, иконку или изображение. [PrimeNG Avatar](https://primeng.org/avatar). + +\`\`\`typescript +import { ExtraAvatarComponent, ExtraAvatarGroupComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-avatar' }, + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + label: { + control: 'text', + description: 'Текст внутри аватара', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + icon: { + control: 'text', + description: 'CSS-класс иконки (например: ti ti-user)', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + image: { + control: 'text', + description: 'URL изображения', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + size: { + control: 'select', + options: ['normal', 'large', 'xlarge'], + description: 'Размер аватара', + table: { + category: 'Props', + defaultValue: { summary: 'normal' }, + type: { summary: "'normal' | 'large' | 'xlarge'" }, + }, + }, + shape: { + control: 'select', + options: ['square', 'circle'], + description: 'Форма аватара', + table: { + category: 'Props', + defaultValue: { summary: 'square' }, + type: { summary: "'square' | 'circle'" }, + }, + }, + }, +}; + +const commonTemplate = ` + +`; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.label) parts.push(`label="${args.label}"`); + if (args.icon) parts.push(`icon="${args.icon}"`); + if (args.image) parts.push(`image="${args.image}"`); + if (args.size && args.size !== 'normal') parts.push(`size="${args.size}"`); + if (args.shape && args.shape !== 'square') parts.push(`shape="${args.shape}"`); + + const template = parts.length + ? `` + : ``; + + return { props: args, template }; + }, + args: { + label: 'A', + size: 'normal', + shape: 'square', + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Label ──────────────────────────────────────────────────────────────────── + +export const Label: Story = { + render: (args) => ({ props: args, template: commonTemplate }), + args: { label: 'A', size: 'normal', shape: 'square' }, + parameters: { + docs: { + description: { story: 'Аватар с текстовой меткой.' }, + source: { + code: ``, + }, + }, + }, +}; + +// ── Icon ───────────────────────────────────────────────────────────────────── + +export const Icon: Story = { + render: (args) => ({ props: args, template: commonTemplate }), + args: { icon: 'ti ti-user', size: 'normal', shape: 'square' }, + parameters: { + docs: { + description: { story: 'Аватар с иконкой.' }, + source: { + code: ``, + }, + }, + }, +}; + +// ── Image ──────────────────────────────────────────────────────────────────── + +export const Image: Story = { + render: (args) => ({ props: args, template: commonTemplate }), + args: { image: '/assets/images/avatar/avatar.png', size: 'normal', shape: 'square' }, + parameters: { + docs: { + description: { story: 'Аватар с изображением. shape="square" — без обрезки, shape="circle" — с обрезкой по кругу.' }, + source: { + code: ``, + }, + }, + }, +}; + +// ── Sizes ──────────────────────────────────────────────────────────────────── + +export const Sizes: Story = { + render: (args) => ({ props: args, template: commonTemplate }), + args: { label: 'L', size: 'large', shape: 'square' }, + parameters: { + docs: { + description: { story: 'Размер аватара. Доступны: normal, large, xlarge.' }, + source: { + code: ``, + }, + }, + }, +}; + +// ── Shapes ─────────────────────────────────────────────────────────────────── + +export const Shapes: Story = { + render: (args) => ({ props: args, template: commonTemplate }), + args: { label: 'C', size: 'normal', shape: 'circle' }, + parameters: { + docs: { + description: { story: 'Форма аватара. circle — круглый, square — квадратный (по умолчанию).' }, + source: { + code: ``, + }, + }, + }, +}; + +// ── Group ──────────────────────────────────────────────────────────────────── +// Исключение: avatar-group — составной компонент, +// дочерние элементы — это его суть, не дублирование. + +export const Group: Story = { + render: () => ({ + template: ` + + + + + + + + + `, + }), + parameters: { + docs: { + description: { story: 'Группа аватаров с перекрытием.' }, + source: { + code: ` + + + +`, + }, + }, + }, +}; + +// ── LabelWithBadge ─────────────────────────────────────────────────────────── + +export const LabelWithBadge: Story = { + render: (args) => ({ + props: args, + template: ` + + + + `, + }), + parameters: { + docs: { + description: { story: 'Аватар с текстовой меткой и бейджем через OverlayBadge.' }, + source: { + code: ` + +`, + }, + }, + }, +}; + +// ── IconWithBadge ──────────────────────────────────────────────────────────── + +export const IconWithBadge: Story = { + render: (args) => ({ + props: args, + template: ` + + + + `, + }), + parameters: { + docs: { + description: { story: 'Аватар с иконкой и бейджем через OverlayBadge.' }, + source: { + code: ` + +`, + }, + }, + }, +}; + +// ── ImageWithBadge ─────────────────────────────────────────────────────────── + +export const ImageWithBadge: Story = { + render: (args) => ({ + props: args, + template: ` + + + + `, + }), + parameters: { + docs: { + description: { story: 'Аватар с изображением и бейджем через OverlayBadge.' }, + source: { + code: ` + +`, + }, + }, + }, +}; diff --git a/src/stories/components/avatar/examples/avatar-group.component.ts b/src/stories/components/avatar/examples/avatar-group.component.ts new file mode 100644 index 00000000..ebd968e3 --- /dev/null +++ b/src/stories/components/avatar/examples/avatar-group.component.ts @@ -0,0 +1,61 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraAvatarComponent, ExtraAvatarGroupComponent } from '../../../../lib/components/avatar/avatar.component'; + +const template = ` +
+ + + + + + + + +
+`; +const styles = ''; + +@Component({ + selector: 'app-avatar-group', + standalone: true, + imports: [ExtraAvatarComponent, ExtraAvatarGroupComponent], + template, + styles, +}) +export class AvatarGroupExampleComponent {} + +export const Group: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { + story: 'Группа аватаров с перекрытием.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraAvatarComponent, ExtraAvatarGroupComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-avatar-group', + standalone: true, + imports: [ExtraAvatarComponent, ExtraAvatarGroupComponent], + template: \` + + + + + + + \`, +}) +export class AvatarGroupExampleComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/avatar/examples/avatar-icon-badge.component.ts b/src/stories/components/avatar/examples/avatar-icon-badge.component.ts new file mode 100644 index 00000000..9beedc02 --- /dev/null +++ b/src/stories/components/avatar/examples/avatar-icon-badge.component.ts @@ -0,0 +1,65 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { OverlayBadge } from 'primeng/overlaybadge'; +import { ExtraAvatarComponent } from '../../../../lib/components/avatar/avatar.component'; + +const template = ` +
+
+ + + + + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-avatar-icon-badge', + standalone: true, + imports: [ExtraAvatarComponent, OverlayBadge], + template, + styles, +}) +export class AvatarIconBadgeComponent {} + +export const IconWithBadge: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { + story: 'Аватары с иконкой и бейджем через OverlayBadge.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { OverlayBadge } from 'primeng/overlaybadge'; +import { ExtraAvatarComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-avatar-icon-badge', + standalone: true, + imports: [ExtraAvatarComponent, OverlayBadge], + template: \` +
+ + + + + + +
+ \`, +}) +export class AvatarIconBadgeComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/avatar/examples/avatar-icon.component.ts b/src/stories/components/avatar/examples/avatar-icon.component.ts new file mode 100644 index 00000000..d5170612 --- /dev/null +++ b/src/stories/components/avatar/examples/avatar-icon.component.ts @@ -0,0 +1,57 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraAvatarComponent } from '../../../../lib/components/avatar/avatar.component'; + +const template = ` +
+
+ + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-avatar-icon', + standalone: true, + imports: [ExtraAvatarComponent], + template, + styles, +}) +export class AvatarIconComponent {} + +export const Icon: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { + story: 'Аватары с иконкой разных размеров.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraAvatarComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-avatar-icon', + standalone: true, + imports: [ExtraAvatarComponent], + template: \` +
+ + + +
+ \`, +}) +export class AvatarIconComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/avatar/examples/avatar-image-badge.component.ts b/src/stories/components/avatar/examples/avatar-image-badge.component.ts new file mode 100644 index 00000000..30538bee --- /dev/null +++ b/src/stories/components/avatar/examples/avatar-image-badge.component.ts @@ -0,0 +1,65 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { OverlayBadge } from 'primeng/overlaybadge'; +import { ExtraAvatarComponent } from '../../../../lib/components/avatar/avatar.component'; + +const template = ` +
+
+ + + + + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-avatar-image-badge', + standalone: true, + imports: [ExtraAvatarComponent, OverlayBadge], + template, + styles, +}) +export class AvatarImageBadgeComponent {} + +export const ImageWithBadge: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { + story: 'Аватары с изображением и бейджем через OverlayBadge.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { OverlayBadge } from 'primeng/overlaybadge'; +import { ExtraAvatarComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-avatar-image-badge', + standalone: true, + imports: [ExtraAvatarComponent, OverlayBadge], + template: \` +
+ + + + + + +
+ \`, +}) +export class AvatarImageBadgeComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/avatar/examples/avatar-image.component.ts b/src/stories/components/avatar/examples/avatar-image.component.ts new file mode 100644 index 00000000..b5b3c3ee --- /dev/null +++ b/src/stories/components/avatar/examples/avatar-image.component.ts @@ -0,0 +1,57 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraAvatarComponent } from '../../../../lib/components/avatar/avatar.component'; + +const template = ` +
+
+ + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-avatar-image', + standalone: true, + imports: [ExtraAvatarComponent], + template, + styles, +}) +export class AvatarImageComponent {} + +export const Image: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { + story: 'Аватары с изображением разных размеров.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraAvatarComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-avatar-image', + standalone: true, + imports: [ExtraAvatarComponent], + template: \` +
+ + + +
+ \`, +}) +export class AvatarImageComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/avatar/examples/avatar-label-badge.component.ts b/src/stories/components/avatar/examples/avatar-label-badge.component.ts new file mode 100644 index 00000000..9904edd1 --- /dev/null +++ b/src/stories/components/avatar/examples/avatar-label-badge.component.ts @@ -0,0 +1,65 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { OverlayBadge } from 'primeng/overlaybadge'; +import { ExtraAvatarComponent } from '../../../../lib/components/avatar/avatar.component'; + +const template = ` +
+
+ + + + + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-avatar-label-badge', + standalone: true, + imports: [ExtraAvatarComponent, OverlayBadge], + template, + styles, +}) +export class AvatarLabelBadgeComponent {} + +export const LabelWithBadge: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { + story: 'Аватары с текстом и бейджем через OverlayBadge.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { OverlayBadge } from 'primeng/overlaybadge'; +import { ExtraAvatarComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-avatar-label-badge', + standalone: true, + imports: [ExtraAvatarComponent, OverlayBadge], + template: \` +
+ + + + + + +
+ \`, +}) +export class AvatarLabelBadgeComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/avatar/examples/avatar-label.component.ts b/src/stories/components/avatar/examples/avatar-label.component.ts new file mode 100644 index 00000000..5475254a --- /dev/null +++ b/src/stories/components/avatar/examples/avatar-label.component.ts @@ -0,0 +1,57 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraAvatarComponent } from '../../../../lib/components/avatar/avatar.component'; + +const template = ` +
+
+ + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-avatar-label', + standalone: true, + imports: [ExtraAvatarComponent], + template, + styles, +}) +export class AvatarLabelComponent {} + +export const Label: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { + story: 'Аватары с текстовой меткой разных размеров.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraAvatarComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-avatar-label', + standalone: true, + imports: [ExtraAvatarComponent], + template: \` +
+ + + +
+ \`, +}) +export class AvatarLabelComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/avatar/examples/avatar-shapes.component.ts b/src/stories/components/avatar/examples/avatar-shapes.component.ts new file mode 100644 index 00000000..2ecd333b --- /dev/null +++ b/src/stories/components/avatar/examples/avatar-shapes.component.ts @@ -0,0 +1,55 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraAvatarComponent } from '../../../../lib/components/avatar/avatar.component'; + +const template = ` +
+
+ + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-avatar-shapes', + standalone: true, + imports: [ExtraAvatarComponent], + template, + styles, +}) +export class AvatarShapesComponent {} + +export const Shapes: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { + story: 'Формы аватара: square (по умолчанию) и circle.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraAvatarComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-avatar-shapes', + standalone: true, + imports: [ExtraAvatarComponent], + template: \` +
+ + +
+ \`, +}) +export class AvatarShapesComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/avatar/examples/avatar-sizes.component.ts b/src/stories/components/avatar/examples/avatar-sizes.component.ts new file mode 100644 index 00000000..97a71047 --- /dev/null +++ b/src/stories/components/avatar/examples/avatar-sizes.component.ts @@ -0,0 +1,57 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraAvatarComponent } from '../../../../lib/components/avatar/avatar.component'; + +const template = ` +
+
+ + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-avatar-sizes', + standalone: true, + imports: [ExtraAvatarComponent], + template, + styles, +}) +export class AvatarSizesComponent {} + +export const Sizes: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { + story: 'Все доступные размеры аватара: normal, large, xlarge.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraAvatarComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-avatar-sizes', + standalone: true, + imports: [ExtraAvatarComponent], + template: \` +
+ + + +
+ \`, +}) +export class AvatarSizesComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/badge/badge.stories.ts b/src/stories/components/badge/badge.stories.ts new file mode 100644 index 00000000..d1d30ab4 --- /dev/null +++ b/src/stories/components/badge/badge.stories.ts @@ -0,0 +1,92 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraBadgeComponent } from '../../../lib/components/badge/badge.component'; +import { BadgeSeverityComponent } from './examples/badge-severity.component'; +import { BadgeSizesComponent } from './examples/badge-sizes.component'; +import { BadgeDotComponent } from './examples/badge-dot.component'; + +export { Severity } from './examples/badge-severity.component'; +export { Sizes } from './examples/badge-sizes.component'; +export { Dot } from './examples/badge-dot.component'; + +const meta: Meta = { + title: 'Components/Misc/Badge', + component: ExtraBadgeComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ExtraBadgeComponent, BadgeSeverityComponent, BadgeSizesComponent, BadgeDotComponent] + }) + ], + parameters: { + docs: { + description: { + component: `Компактный индикатор статуса или числового значения. Используется для отображения счётчиков, меток и статусов. + +\`\`\`typescript +import { BadgeModule } from 'primeng/badge'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-badge' }, + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + value: { + control: 'text', + description: 'Отображаемое значение бейджа', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string | number' }, + }, + }, + severity: { + control: 'select', + options: ['primary', 'success', 'info', 'warning', 'danger'], + description: 'Цветовая схема бейджа', + table: { + category: 'Props', + defaultValue: { summary: "'primary'" }, + type: { summary: "'primary' | 'success' | 'info' | 'warning' | 'danger'" }, + }, + }, + size: { + control: 'select', + options: ['base', 'large', 'xlarge'], + description: 'Размер бейджа', + table: { + category: 'Props', + defaultValue: { summary: "'base'" }, + type: { summary: "'base' | 'large' | 'xlarge'" }, + }, + }, + }, + args: { + value: '8', + severity: 'primary', + size: 'base', + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => ({ + props: args, + template: ``, + }), + args: { + value: '8', + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; diff --git a/src/stories/components/badge/examples/badge-dot.component.ts b/src/stories/components/badge/examples/badge-dot.component.ts new file mode 100644 index 00000000..d29b8210 --- /dev/null +++ b/src/stories/components/badge/examples/badge-dot.component.ts @@ -0,0 +1,62 @@ +import { Component, Input } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraBadgeComponent, BadgeSeverity, BadgeSize } from '../../../../lib/components/badge/badge.component'; + +const template = ` +
+ +
+`; + +const styles = ''; + +@Component({ + selector: 'app-badge-dot', + standalone: true, + imports: [ExtraBadgeComponent], + template, + styles +}) +export class BadgeDotComponent { + @Input() severity: BadgeSeverity = 'primary'; + @Input() size: BadgeSize = 'base'; +} + +export const Dot: StoryObj = { + render: (args) => ({ + props: args, + template: `` + }), + args: { + severity: 'primary' + }, + argTypes: { + severity: { + control: 'select', + options: ['primary', 'success', 'info', 'warning', 'danger'], + description: 'Цветовая схема бейджа' + } + }, + parameters: { + docs: { + description: { + story: 'Без значения — отображается как точка-индикатор.' + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraBadgeComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-badge-dot', + standalone: true, + imports: [BadgeComponent], + template: \`${template}\` +}) +export class BadgeDotComponent {} + ` + } + } + } +}; diff --git a/src/stories/components/badge/examples/badge-severity.component.ts b/src/stories/components/badge/examples/badge-severity.component.ts new file mode 100644 index 00000000..95d0e4f4 --- /dev/null +++ b/src/stories/components/badge/examples/badge-severity.component.ts @@ -0,0 +1,68 @@ +import { Component, Input } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraBadgeComponent, BadgeSeverity, BadgeSize } from '../../../../lib/components/badge/badge.component'; + +const template = ` +
+ +
+`; + +const styles = ''; + +@Component({ + selector: 'app-badge-severity', + standalone: true, + imports: [ExtraBadgeComponent], + template, + styles +}) +export class BadgeSeverityComponent { + @Input() value: string | number = '8'; + @Input() severity: BadgeSeverity = 'success'; + @Input() size: BadgeSize = 'base'; +} + +export const Severity: StoryObj = { + render: (args) => ({ + props: args, + template: `` + }), + args: { + value: '8', + severity: 'success' + }, + argTypes: { + severity: { + control: 'select', + options: ['primary', 'success', 'info', 'warning', 'danger'], + description: 'Цветовая схема бейджа' + }, + value: { + control: 'text', + description: 'Отображаемое значение бейджа' + } + }, + parameters: { + docs: { + description: { + story: 'Цветовые схемы: primary, success, info, warning, danger.' + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraBadgeComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-badge-severity', + standalone: true, + imports: [BadgeComponent], + template: \`${template}\` +}) +export class BadgeSeverityComponent {} + ` + } + } + } +}; diff --git a/src/stories/components/badge/examples/badge-sizes.component.ts b/src/stories/components/badge/examples/badge-sizes.component.ts new file mode 100644 index 00000000..ac656ece --- /dev/null +++ b/src/stories/components/badge/examples/badge-sizes.component.ts @@ -0,0 +1,67 @@ +import { Component, Input } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraBadgeComponent, BadgeSize } from '../../../../lib/components/badge/badge.component'; + +const template = ` +
+ +
+`; + +const styles = ''; + +@Component({ + selector: 'app-badge-sizes', + standalone: true, + imports: [ExtraBadgeComponent], + template, + styles +}) +export class BadgeSizesComponent { + @Input() value: string | number = '8'; + @Input() size: BadgeSize = 'large'; +} + +export const Sizes: StoryObj = { + render: (args) => ({ + props: args, + template: `` + }), + args: { + value: '8', + size: 'large' + }, + argTypes: { + size: { + control: 'select', + options: ['base', 'large', 'xlarge'], + description: 'Размер бейджа' + }, + value: { + control: 'text', + description: 'Отображаемое значение бейджа' + } + }, + parameters: { + docs: { + description: { + story: 'Все доступные размеры: base, large, xlarge.' + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraBadgeComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-badge-sizes', + standalone: true, + imports: [BadgeComponent], + template: \`${template}\` +}) +export class BadgeSizesComponent {} + ` + } + } + } +}; diff --git a/src/stories/components/breadcrumb/breadcrumb.data.ts b/src/stories/components/breadcrumb/breadcrumb.data.ts new file mode 100644 index 00000000..df815a46 --- /dev/null +++ b/src/stories/components/breadcrumb/breadcrumb.data.ts @@ -0,0 +1,15 @@ +import { MenuItem } from 'primeng/api'; + +export const commonHome: MenuItem = { icon: 'ti ti-home', url: '#' }; + +export const commonItems: MenuItem[] = [ + { label: 'Электроника', icon: 'ti ti-device-laptop', url: '#' }, + { label: 'Компьютеры', icon: 'ti ti-cpu', url: '#' }, + { label: 'Ноутбуки' }, +]; + +export const iconOnlyItems: MenuItem[] = [ + { icon: 'ti ti-device-laptop', url: '#' }, + { icon: 'ti ti-cpu', url: '#' }, + { icon: 'ti ti-book' }, +]; diff --git a/src/stories/components/breadcrumb/breadcrumb.stories.ts b/src/stories/components/breadcrumb/breadcrumb.stories.ts new file mode 100644 index 00000000..938cfbbb --- /dev/null +++ b/src/stories/components/breadcrumb/breadcrumb.stories.ts @@ -0,0 +1,75 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraBreadcrumbComponent as BreadcrumbComponent } from '../../../lib/components/breadcrumb/breadcrumb.component'; +import { BreadcrumbBasicComponent, Basic } from './examples/breadcrumb-basic.component'; +import { BreadcrumbIconsOnlyComponent, IconsOnly } from './examples/breadcrumb-icons-only.component'; +import { commonHome, commonItems } from './breadcrumb.data'; + +type BreadcrumbArgs = BreadcrumbComponent; + +const meta: Meta = { + title: 'Components/Menu/Breadcrumb', + component: BreadcrumbComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + BreadcrumbComponent, + BreadcrumbBasicComponent, + BreadcrumbIconsOnlyComponent, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-breadcrumb' }, + docs: { + description: { + component: `Компонент навигации, показывающий путь к текущей странице.`, + }, + }, + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + model: { + control: 'object', + description: 'Массив элементов меню', + table: { + category: 'Props', + type: { summary: 'MenuItem[]' }, + }, + }, + home: { + control: 'object', + description: 'Элемент для иконки «Домой»', + table: { + category: 'Props', + type: { summary: 'MenuItem' }, + }, + }, + }, + args: { + model: commonItems, + home: commonHome, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => ({ + props: args, + template: ``, + }), + parameters: { + docs: { + description: { + story: 'Базовый пример компонента.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { Basic, IconsOnly }; diff --git a/src/stories/components/breadcrumb/examples/breadcrumb-basic.component.ts b/src/stories/components/breadcrumb/examples/breadcrumb-basic.component.ts new file mode 100644 index 00000000..cfb582e8 --- /dev/null +++ b/src/stories/components/breadcrumb/examples/breadcrumb-basic.component.ts @@ -0,0 +1,56 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraBreadcrumbComponent } from '../../../../lib/components/breadcrumb/breadcrumb.component'; +import { commonHome, commonItems } from '../breadcrumb.data'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-breadcrumb-basic', + standalone: true, + imports: [ExtraBreadcrumbComponent], + template, +}) +export class BreadcrumbBasicComponent { + home = commonHome; + model = commonItems; +} + +export const Basic: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Хлебные крошки с текстом и иконками.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraBreadcrumbComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-breadcrumb-basic', + standalone: true, + imports: [BreadcrumbComponent], + template: \` + + \`, +}) +export class BreadcrumbBasicComponent { + home = { icon: 'ti ti-home', url: '#' }; + model = [ + { label: 'Электроника', icon: 'ti ti-device-laptop', url: '#' }, + { label: 'Компьютеры', icon: 'ti ti-cpu', url: '#' }, + { label: 'Ноутбуки' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/breadcrumb/examples/breadcrumb-icons-only.component.ts b/src/stories/components/breadcrumb/examples/breadcrumb-icons-only.component.ts new file mode 100644 index 00000000..b8ea75b2 --- /dev/null +++ b/src/stories/components/breadcrumb/examples/breadcrumb-icons-only.component.ts @@ -0,0 +1,56 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraBreadcrumbComponent } from '../../../../lib/components/breadcrumb/breadcrumb.component'; +import { commonHome, iconOnlyItems } from '../breadcrumb.data'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-breadcrumb-icons-only', + standalone: true, + imports: [ExtraBreadcrumbComponent], + template, +}) +export class BreadcrumbIconsOnlyComponent { + home = commonHome; + model = iconOnlyItems; +} + +export const IconsOnly: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Хлебные крошки только с иконками, без текста.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraBreadcrumbComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-breadcrumb-icons-only', + standalone: true, + imports: [BreadcrumbComponent], + template: \` + + \`, +}) +export class BreadcrumbIconsOnlyComponent { + home = { icon: 'ti ti-home', url: '#' }; + model = [ + { icon: 'ti ti-device-laptop', url: '#' }, + { icon: 'ti ti-cpu', url: '#' }, + { icon: 'ti ti-book' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/button/button.stories.ts b/src/stories/components/button/button.stories.ts index 80c11661..95eae973 100644 --- a/src/stories/components/button/button.stories.ts +++ b/src/stories/components/button/button.stories.ts @@ -1,48 +1,301 @@ -import { Meta, moduleMetadata } from '@storybook/angular'; - -import { ButtonModule } from 'primeng/button'; - -import { ButtonBaseComponent, Base } from './examples/button-base.component'; +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraButtonComponent } from '../../../lib/components/button/button.component'; import { ButtonSizesComponent, Sizes } from './examples/button-sizes.component'; +import { ButtonTextComponent, Text } from './examples/button-text.component'; +import { ButtonSeverityComponent, Severity } from './examples/button-severity.component'; import { ButtonRoundedComponent, Rounded } from './examples/button-rounded.component'; import { ButtonOutlinedComponent, Outlined } from './examples/button-outlined.component'; -import { ButtonTextComponent, Text } from './examples/button-text.component'; +import { ButtonLoadingComponent, Loading } from './examples/button-loading.component'; import { ButtonIconComponent, Icon } from './examples/button-icon.component'; +import { Extra } from './examples/button-extra.component'; import { ButtonDisabledComponent, Disabled } from './examples/button-disabled.component'; -import { ButtonLoadingComponent, Loading } from './examples/button-loading.component'; -import { ButtonBadgeComponent, Badge } from './examples/button-badge.component'; -import { ButtonSeverityComponent, Severity } from './examples/button-severity.component'; -import { CommonModule } from '@angular/common'; +import { Base, ButtonBaseComponent } from './examples/button-base.component'; +import { Badge, ButtonBadgeComponent } from './examples/button-badge.component'; + +type ButtonArgs = ExtraButtonComponent & { onClick?: (event: MouseEvent) => void }; -const meta: Meta = { - title: 'PrimeNG/Button', +const meta: Meta = { + title: 'Components/Button', + component: ExtraButtonComponent, tags: ['autodocs'], decorators: [ moduleMetadata({ imports: [ - CommonModule, - ButtonModule, - ButtonBaseComponent, + ExtraButtonComponent, ButtonSizesComponent, - ButtonRoundedComponent, - ButtonOutlinedComponent, - ButtonTextComponent, - ButtonIconComponent, + ButtonBadgeComponent, + ButtonBaseComponent, ButtonDisabledComponent, + ButtonIconComponent, ButtonLoadingComponent, - ButtonBadgeComponent, - ButtonSeverityComponent + ButtonOutlinedComponent, + ButtonRoundedComponent, + ButtonSeverityComponent, + ButtonSizesComponent, + ButtonTextComponent ] }) ], parameters: { docs: { description: { - component: 'Компонент кнопки с различными стилями, состояниями и иконками' + component: `Интерактивный элемент интерфейса. Используется для инициации действий, отправки форм и навигации. + +\`\`\`typescript +import { ExtraButtonComponent } from '@cdek-it/angular-ui-kit'; +\`\`\`` } } + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + label: { + control: 'text', + description: 'Текст кнопки', + table: { + category: 'Props', + defaultValue: { summary: 'Button' }, + type: { summary: 'string' } + } + }, + severity: { + control: 'select', + options: [null, 'success', 'info', 'warning', 'danger'], + description: 'Семантический вариант кнопки', + table: { + category: 'Props', + defaultValue: { summary: 'null' }, + type: { summary: "'success' | 'info' | 'warning' | 'danger' | null" } + } + }, + variant: { + control: 'select', + options: ['primary', 'secondary', 'outlined', 'text', 'link'], + description: 'Вариант отображения кнопки', + table: { + category: 'Props', + defaultValue: { summary: 'primary' }, + type: { summary: "'primary' | 'secondary' | 'outlined' | 'text' | 'link'" } + } + }, + size: { + control: 'select', + options: ['small', 'base', 'large', 'xlarge'], + description: 'Размер кнопки', + table: { + category: 'Props', + defaultValue: { summary: 'base' }, + type: { summary: "'small' | 'base' | 'large' | 'xlarge'" } + } + }, + icon: { + control: 'text', + description: 'CSS-класс иконки (например: ti ti-check)', + table: { + category: 'Props', + defaultValue: { summary: '' }, + type: { summary: 'string' } + } + }, + iconPos: { + control: 'select', + options: [null, 'prefix', 'postfix'], + description: 'Позиция иконки относительно текста', + table: { + category: 'Props', + defaultValue: { summary: 'null' }, + type: { summary: "'prefix' | 'postfix' | null" } + } + }, + iconOnly: { + control: 'boolean', + description: 'Только иконка, без текста', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + rounded: { + control: 'boolean', + description: 'Скруглённая форма кнопки', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + disabled: { + control: 'boolean', + description: 'Отключённое состояние', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + loading: { + control: 'boolean', + description: 'Состояние загрузки с индикатором', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + fluid: { + control: 'boolean', + description: 'Растягивать ли кнопку на всю ширину контейнера', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + ariaLabel: { + control: 'text', + description: 'Метка для экранных дикторов', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'string' } + } + }, + autofocus: { + control: 'boolean', + description: 'Автофокус при загрузке', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + tabindex: { + control: 'number', + description: 'Порядок фокуса', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'number' } + } + }, + text: { + control: 'boolean', + description: 'Текстовый вариант кнопки', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + // ── Badge ──────────────────────────────────────────────── + badge: { + control: 'text', + description: 'Значение бейджа', + table: { + category: 'Badge', + defaultValue: { summary: '' }, + type: { summary: 'string' } + } + }, + badgeSeverity: { + control: 'select', + options: [null, 'success', 'info', 'warning', 'danger', 'secondary', 'contrast'], + description: 'Цветовая схема бейджа', + table: { + category: 'Badge', + defaultValue: { summary: 'null' }, + type: { summary: "'success' | 'info' | 'warning' | 'danger' | 'secondary' | 'contrast' | null" } + } + }, + showBadge: { + control: 'boolean', + description: 'Показывать ли бейдж', + table: { + category: 'Badge', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + // ── Events ─────────────────────────────────────────────── + onClick: { + control: false, + description: 'Событие клика по кнопке', + table: { + category: 'Events', + type: { summary: 'EventEmitter' } + } + } + }, + args: { + showBadge: false, + badge: '', + badgeSeverity: null, + fluid: false, + autofocus: false, + text: false } }; + +const commonTemplate = ` + +`; + export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.label != null && args.label !== '') parts.push(`label="${args.label}"`); + if (args.severity != null) parts.push(`severity="${args.severity}"`); + if (args.variant != null) parts.push(`variant="${args.variant}"`); + if (args.size != null) parts.push(`size="${args.size}"`); + if (args.icon != null && (args.icon as string) !== '') parts.push(`icon="${args.icon}"`); + if (args.iconPos != null) parts.push(`iconPos="${args.iconPos}"`); + if (args.rounded) parts.push(`[rounded]="true"`); + if (args.disabled) parts.push(`[disabled]="true"`); + if (args.loading) parts.push(`[loading]="true"`); + + const template = parts.length + ? `` + : ``; + + return { props: args, template }; + }, + args: { + label: 'Button' + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.' + } + } + } +}; -export { Base, Disabled, Loading, Sizes, Rounded, Outlined, Text, Icon, Badge, Severity }; +export { Sizes, Text, Severity, Rounded, Outlined, Loading, Icon, Extra, Disabled, Base, Badge }; diff --git a/src/stories/components/button/examples/button-extra.component.ts b/src/stories/components/button/examples/button-extra.component.ts new file mode 100644 index 00000000..2bba46a4 --- /dev/null +++ b/src/stories/components/button/examples/button-extra.component.ts @@ -0,0 +1,109 @@ +import { StoryObj } from '@storybook/angular'; + +export { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; + +export const Extra: StoryObj = { + render: (args) => ({ + props: args, + template: ` +` + }), + args: { + label: 'Button', + showBadge: false, + fluid: false, + autofocus: false, + text: false + }, + argTypes: { + label: { control: 'text' }, + variant: { + control: 'select', + options: ['primary', 'secondary', 'outlined', 'text', 'link'] + }, + severity: { + control: 'select', + options: [null, 'success', 'warning', 'danger', 'info'] + }, + size: { + control: 'select', + options: ['small', 'base', 'large', 'xlarge'] + }, + rounded: { control: 'boolean' }, + iconPos: { + control: 'select', + options: [null, 'prefix', 'postfix'] + }, + iconOnly: { control: 'boolean' }, + icon: { control: 'text' }, + disabled: { control: 'boolean' }, + loading: { control: 'boolean' }, + badge: { control: 'text' }, + badgeSeverity: { + control: 'select', + options: [null, 'success', 'warning', 'danger', 'info', 'secondary', 'contrast'] + }, + showBadge: { control: 'boolean' }, + fluid: { control: 'boolean' }, + ariaLabel: { control: 'text' }, + autofocus: { control: 'boolean' }, + tabindex: { control: 'number' }, + text: { control: 'boolean' } + }, + parameters: { + + docs: { + description: { + story: 'Интерактивный пример с пропсами, соответствующими Figma-компоненту Button.' + } + } + } +}; + +export const Badge: StoryObj = { + render: (args) => ({ + props: args, + template: ` +` + }), + args: { + label: 'Emails', + badge: '8', + badgeSeverity: 'danger', + showBadge: true, + severity: 'success' + }, + parameters: { + docs: { + description: { + story: 'Пример кнопки с бейджем для отображения уведомлений или счётчиков.' + } + } + } +}; + diff --git a/src/stories/components/button/examples/button-sizes.component.ts b/src/stories/components/button/examples/button-sizes.component.ts index 7b5b9a77..9581bda4 100644 --- a/src/stories/components/button/examples/button-sizes.component.ts +++ b/src/stories/components/button/examples/button-sizes.component.ts @@ -1,14 +1,14 @@ import { Component } from '@angular/core'; import { StoryObj } from '@storybook/angular'; -import { Button } from 'primeng/button'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; const template = `
- - - - + + + +
`; @@ -18,7 +18,7 @@ const styles = ''; @Component({ selector: 'app-button-sizes', standalone: true, - imports: [Button], + imports: [ExtraButtonComponent], template, styles }) diff --git a/src/stories/components/card/card.stories.ts b/src/stories/components/card/card.stories.ts new file mode 100644 index 00000000..8752c91d --- /dev/null +++ b/src/stories/components/card/card.stories.ts @@ -0,0 +1,251 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { SharedModule } from 'primeng/api'; +import { ExtraCardComponent as CardComponent } from '../../../lib/components/card/card.component'; +import { ExtraButtonComponent as ButtonComponent } from '../../../lib/components/button/button.component'; +import { CardOverlayComponent } from './examples/card-overlay.component'; +import { CardWithoutHeaderComponent } from './examples/card-without-header.component'; +import { CardWithoutFooterComponent } from './examples/card-without-footer.component'; +import { CardWithoutSubtitleComponent } from './examples/card-without-subtitle.component'; + +type CardArgs = CardComponent; + +const meta: Meta = { + title: 'Components/Panel/Card', + component: CardComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + CardComponent, + ButtonComponent, + SharedModule, + CardOverlayComponent, + CardWithoutHeaderComponent, + CardWithoutFooterComponent, + CardWithoutSubtitleComponent, + ] + }) + ], + parameters: { + docs: { + description: { + component: `Гибкий контейнер для группировки контента с заголовком, подзаголовком, основным содержимым и действиями. + +\`\`\`typescript +import { CardModule } from 'primeng/card'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-card' }, + }, + argTypes: { + title: { + control: 'text', + description: 'Заголовок карточки', + table: { + category: 'Props', + defaultValue: { summary: '' }, + type: { summary: 'string' }, + }, + }, + subtitle: { + control: 'text', + description: 'Подзаголовок карточки', + table: { + category: 'Props', + defaultValue: { summary: '' }, + type: { summary: 'string' }, + }, + }, + overlay: { + control: 'boolean', + description: 'Тень вокруг карточки (shadow-md)', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.title) parts.push(`title="${args.title}"`); + if (args.subtitle) parts.push(`subtitle="${args.subtitle}"`); + if (args.overlay) parts.push(`[overlay]="true"`); + + const attrs = parts.length ? `\n ${parts.join('\n ')}` : ''; + const template = `
+ + + Заголовок + + +

Контент карточки. Гибкая область для любого содержимого.

+
+ + + + +
`; + + return { props: args, template }; + }, + args: { + title: 'Заголовок', + subtitle: 'Подзаголовок', + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Overlay ─────────────────────────────────────────────────────────────────── + +export const Overlay: Story = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Карточка с тенью (overlay).' }, + source: { + language: 'ts', + code: ` + import { Component } from '@angular/core'; + import { SharedModule } from 'primeng/api'; + import { ExtraCardComponent, ExtraButtonComponent } from '@cdek-it/angular-ui-kit'; + + @Component({ + selector: 'app-card-without-header', + standalone: true, + imports: [ExtraCardComponent, ExtraButtonComponent, SharedModule], + template: \` + + +

Карточка без изображения в шапке.

+
+ + + +
+ \`, + }) + export class CardWithoutHeaderComponent {} + `, + selector: 'app-card-without-header', + standalone: true, + imports: [CardComponent, ButtonComponent, SharedModule], + template: ` + + +

Карточка без изображения в шапке.

+
+ + + +
+ \`, +}) +export class CardWithoutHeaderComponent {} + `, + }, + }, + }, +}; + +// ── WithoutFooter ───────────────────────────────────────────────────────────── + +export const WithoutFooter: Story = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Карточка без футера с действиями.' }, + source: { + language: 'ts', + code: ` + import { Component } from '@angular/core'; + import { SharedModule } from 'primeng/api'; + import { ExtraCardComponent } from '@cdek-it/angular-ui-kit'; + + @Component({ + selector: 'app-card-without-footer', + standalone: true, + imports: [ExtraCardComponent, SharedModule], + template: \` + + +
+ +
+
+ +

Карточка без футера.

+
+
+ \`, + }) + export class CardWithoutFooterComponent {} + `, + }, + }, + }, +}; + +// ── WithoutSubtitle ─────────────────────────────────────────────────────────── + +export const WithoutSubtitle: Story = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Карточка без подзаголовка.' }, + source: { + language: 'ts', + code: ` + import { Component } from '@angular/core'; + import { SharedModule } from 'primeng/api'; + import { ExtraCardComponent, ExtraButtonComponent } from '@cdek-it/angular-ui-kit'; + + @Component({ + selector: 'app-card-without-subtitle', + standalone: true, + imports: [ExtraCardComponent, ExtraButtonComponent, SharedModule], + template: \` + + +
+ +
+
+ +

Карточка без подзаголовка.

+
+ + + +
+ \`, + }) + export class CardWithoutSubtitleComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/card/examples/card-overlay.component.ts b/src/stories/components/card/examples/card-overlay.component.ts new file mode 100644 index 00000000..cd02e1c0 --- /dev/null +++ b/src/stories/components/card/examples/card-overlay.component.ts @@ -0,0 +1,70 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { SharedModule } from 'primeng/api'; +import { ExtraCardComponent } from '../../../../lib/components/card/card.component'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; + +const template = ` +
+ + + Заголовок + + +

Карточка с тенью.

+
+ + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-card-overlay', + standalone: true, + imports: [ExtraCardComponent, ExtraButtonComponent, SharedModule], + template, + styles, +}) +export class CardOverlayComponent {} + +export const Overlay: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Карточка с тенью (overlay).' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { SharedModule } from 'primeng/api'; +import { ExtraCardComponent, ExtraButtonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-card-overlay', + standalone: true, + imports: [CardComponent, ButtonComponent, SharedModule], + template: \` + + + Заголовок + + +

Карточка с тенью.

+
+ + + +
+ \`, +}) +export class CardOverlayComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/card/examples/card-without-footer.component.ts b/src/stories/components/card/examples/card-without-footer.component.ts new file mode 100644 index 00000000..6db1baa1 --- /dev/null +++ b/src/stories/components/card/examples/card-without-footer.component.ts @@ -0,0 +1,63 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { SharedModule } from 'primeng/api'; +import { ExtraCardComponent } from '../../../../lib/components/card/card.component'; + +const template = ` +
+ + + Заголовок + + +

Карточка без футера.

+
+
+
+`; +const styles = ''; + +@Component({ + selector: 'app-card-without-footer', + standalone: true, + imports: [ExtraCardComponent, SharedModule], + template, + styles, +}) +export class CardWithoutFooterComponent {} + +export const WithoutFooter: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Карточка без футера с действиями.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { SharedModule } from 'primeng/api'; +import { ExtraCardComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-card-without-footer', + standalone: true, + imports: [CardComponent, SharedModule], + template: \` + + + Заголовок + + +

Карточка без футера.

+
+
+ \`, +}) +export class CardWithoutFooterComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/card/examples/card-without-header.component.ts b/src/stories/components/card/examples/card-without-header.component.ts new file mode 100644 index 00000000..8a9ad93c --- /dev/null +++ b/src/stories/components/card/examples/card-without-header.component.ts @@ -0,0 +1,64 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { SharedModule } from 'primeng/api'; +import { ExtraCardComponent } from '../../../../lib/components/card/card.component'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; + +const template = ` +
+ + +

Карточка без изображения в шапке.

+
+ + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-card-without-header', + standalone: true, + imports: [ExtraCardComponent, ExtraButtonComponent, SharedModule], + template, + styles, +}) +export class CardWithoutHeaderComponent {} + +export const WithoutHeader: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Карточка без изображения в шапке.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { SharedModule } from 'primeng/api'; +import { ExtraCardComponent, ExtraButtonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-card-without-header', + standalone: true, + imports: [CardComponent, ButtonComponent, SharedModule], + template: \` + + +

Карточка без изображения в шапке.

+
+ + + +
+ \`, +}) +export class CardWithoutHeaderComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/card/examples/card-without-subtitle.component.ts b/src/stories/components/card/examples/card-without-subtitle.component.ts new file mode 100644 index 00000000..09198187 --- /dev/null +++ b/src/stories/components/card/examples/card-without-subtitle.component.ts @@ -0,0 +1,70 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { SharedModule } from 'primeng/api'; +import { ExtraCardComponent } from '../../../../lib/components/card/card.component'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; + +const template = ` +
+ + + Заголовок + + +

Карточка без подзаголовка.

+
+ + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-card-without-subtitle', + standalone: true, + imports: [ExtraCardComponent, ExtraButtonComponent, SharedModule], + template, + styles, +}) +export class CardWithoutSubtitleComponent {} + +export const WithoutSubtitle: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Карточка без подзаголовка.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { SharedModule } from 'primeng/api'; +import { ExtraCardComponent, ExtraButtonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-card-without-subtitle', + standalone: true, + imports: [CardComponent, ButtonComponent, SharedModule], + template: \` + + + Заголовок + + +

Карточка без подзаголовка.

+
+ + + +
+ \`, +}) +export class CardWithoutSubtitleComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/checkbox/checkbox.stories.ts b/src/stories/components/checkbox/checkbox.stories.ts new file mode 100644 index 00000000..5d6a8249 --- /dev/null +++ b/src/stories/components/checkbox/checkbox.stories.ts @@ -0,0 +1,146 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraCheckboxComponent } from '../../../lib/components/checkbox/checkbox.component'; +import { FormsModule } from '@angular/forms'; +import { CheckboxGroupComponent, Group } from './examples/checkbox-group.component'; +import { CheckboxIndeterminateComponent, Indeterminate } from './examples/checkbox-indeterminate.component'; +import { CheckboxDisabledComponent, Disabled } from './examples/checkbox-disabled.component'; +import { CheckboxInvalidComponent, Invalid } from './examples/checkbox-invalid.component'; +import { CheckboxLabelComponent, Label } from './examples/checkbox-label.component'; +import { CheckboxCustomLabelComponent, CustomLabel } from './examples/checkbox-custom-label.component'; + +type CheckboxArgs = ExtraCheckboxComponent & { label?: string }; + +const meta: Meta = { + title: 'Components/Form/Checkbox', + component: ExtraCheckboxComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraCheckboxComponent, + FormsModule, + CheckboxGroupComponent, + CheckboxIndeterminateComponent, + CheckboxDisabledComponent, + CheckboxInvalidComponent, + CheckboxLabelComponent, + CheckboxCustomLabelComponent + ] + }) + ], + parameters: { + designTokens: { prefix: '--p-checkbox' }, + docs: { + description: { + component: `Компонент для выбора одного или нескольких вариантов.` + } + } + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + binary: { table: { disable: true } }, + invalid: { + control: 'boolean', + description: 'Подсвечивает поле как невалидное', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + disabled: { + control: 'boolean', + description: 'Отключает возможность взаимодействия', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + indeterminate: { + control: 'boolean', + description: 'Устанавливает неопределенное состояние', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + // Hidden props + size: { table: { disable: true } }, + readonly: { table: { disable: true } }, + checkboxIcon: { table: { disable: true } }, + ariaLabel: { table: { disable: true } }, + ariaLabelledBy: { table: { disable: true } }, + tabindex: { table: { disable: true } }, + inputId: { table: { disable: true } }, + trueValue: { table: { disable: true } }, + falseValue: { table: { disable: true } }, + autofocus: { table: { disable: true } }, + variant: { table: { disable: true } }, + value: { table: { disable: true } }, + label: { table: { disable: true } }, + + // ── Events ─────────────────────────────────────────────── + onChange: { + control: false, + description: 'Событие изменения значения', + table: { + category: 'Events', + type: { summary: 'EventEmitter' } + } + }, + onFocus: { + control: false, + description: 'Событие фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' } + } + }, + onBlur: { + control: false, + description: 'Событие потери фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' } + } + } + }, + args: { + binary: true, + disabled: false, + invalid: false, + indeterminate: false + } +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + if (args.binary) parts.push(`[binary]="true"`); + if (args.disabled) parts.push(`[disabled]="true"`); + if (args.invalid) parts.push(`[invalid]="true"`); + if (args.indeterminate) parts.push(`[indeterminate]="true"`); + parts.push(`[(ngModel)]="checked"`); + + const template = ``; + + return { props: { ...args, checked: false }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { Invalid, Disabled, Indeterminate, Group, Label, CustomLabel }; diff --git a/src/stories/components/checkbox/examples/checkbox-custom-label.component.ts b/src/stories/components/checkbox/examples/checkbox-custom-label.component.ts new file mode 100644 index 00000000..4fa63f2a --- /dev/null +++ b/src/stories/components/checkbox/examples/checkbox-custom-label.component.ts @@ -0,0 +1,159 @@ +import { Component, Input } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraCheckboxComponent } from '../../../../lib/components/checkbox/checkbox.component'; + +const styles = ''; + +@Component({ + selector: 'app-checkbox-custom-label', + standalone: true, + imports: [ExtraCheckboxComponent, ReactiveFormsModule], + styles, + template: ` +
+ @if (labelPosition === 'left') { + + } +
+ + @if (caption) { + {{ caption }} + } +
+ @if (labelPosition === 'right') { + + } +
+ `, +}) +export class CheckboxCustomLabelComponent { + @Input() label = 'Checkbox'; + @Input() caption = 'caption'; + @Input() labelPosition: 'left' | 'right' = 'left'; + @Input() invalid = false; + @Input() disabled = false; + @Input() inputId = 'custom-checkbox'; + + formControl = new FormControl(false); + + get labelClass(): string { + return this.disabled ? 'checkbox-label checkbox-label--disabled' : 'checkbox-label'; + } + + get captionClass(): string { + return this.disabled ? 'checkbox-caption checkbox-caption--disabled' : 'checkbox-caption'; + } + + ngOnChanges(): void { + if (this.disabled) { + this.formControl.disable(); + } else { + this.formControl.enable(); + } + } +} + +export const CustomLabel: StoryObj = { + render: (args) => ({ + props: { ...args, checked: false }, + template: ` + + `, + }), + args: { + label: 'Checkbox', + caption: 'caption', + labelPosition: 'left', + invalid: false, + disabled: false, + }, + argTypes: { + label: { + control: 'text', + description: 'Текст метки', + table: { category: 'Props' }, + }, + caption: { + control: 'text', + description: 'Подпись под меткой', + table: { category: 'Props' }, + }, + labelPosition: { + control: 'select', + options: ['left', 'right'], + description: 'Позиция чекбокса относительно метки', + table: { category: 'Props', defaultValue: { summary: 'left' } }, + }, + }, + parameters: { + docs: { + description: { + story: 'Чекбокс с label и caption. Управляйте состоянием через Controls.', + }, + source: { + language: 'ts', + code: ` +import { Component, Input, OnChanges } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraCheckboxComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-checkbox-custom-label', + standalone: true, + imports: [ExtraCheckboxComponent, ReactiveFormsModule], + template: \` +
+ @if (labelPosition === 'left') { + + } +
+ + @if (caption) { + {{ caption }} + } +
+ @if (labelPosition === 'right') { + + } +
+ \`, +}) +export class CheckboxCustomLabelComponent implements OnChanges { + @Input() label = 'Checkbox'; + @Input() caption = 'caption'; + @Input() labelPosition: 'left' | 'right' = 'left'; + @Input() invalid = false; + @Input() disabled = false; + @Input() inputId = 'custom-checkbox'; + + formControl = new FormControl(false); + + get labelClass(): string { + return this.disabled ? 'checkbox-label checkbox-label--disabled' : 'checkbox-label'; + } + + get captionClass(): string { + return this.disabled ? 'checkbox-caption checkbox-caption--disabled' : 'checkbox-caption'; + } + + ngOnChanges(): void { + if (this.disabled) { + this.formControl.disable(); + } else { + this.formControl.enable(); + } + } +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/checkbox/examples/checkbox-disabled.component.ts b/src/stories/components/checkbox/examples/checkbox-disabled.component.ts new file mode 100644 index 00000000..7f8beca4 --- /dev/null +++ b/src/stories/components/checkbox/examples/checkbox-disabled.component.ts @@ -0,0 +1,52 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraCheckboxComponent } from '../../../../lib/components/checkbox/checkbox.component'; + +const styles = ''; + +@Component({ + selector: 'app-checkbox-disabled', + standalone: true, + imports: [ExtraCheckboxComponent, FormsModule], + styles, + template: ` + + `, +}) +export class CheckboxDisabledComponent { + checked = true; +} + +export const Disabled: StoryObj = { + render: (args) => ({ + props: { ...args, checked: true }, + template: ``, + }), + args: { disabled: true }, + parameters: { + docs: { + description: { story: 'Заблокированное состояние чекбокса.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraCheckboxComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-checkbox-disabled', + standalone: true, + imports: [ExtraCheckboxComponent, ReactiveFormsModule], + template: \` + + \`, +}) +export class CheckboxDisabledComponent { + control = new FormControl({ value: true, disabled: true }); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/checkbox/examples/checkbox-group.component.ts b/src/stories/components/checkbox/examples/checkbox-group.component.ts new file mode 100644 index 00000000..61db85ad --- /dev/null +++ b/src/stories/components/checkbox/examples/checkbox-group.component.ts @@ -0,0 +1,57 @@ +import { Component } from '@angular/core'; +import { JsonPipe } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraCheckboxComponent } from '../../../../lib/components/checkbox/checkbox.component'; + +const template = ` +
+ + + +
+`; + +@Component({ + selector: 'app-checkbox-group', + standalone: true, + imports: [ExtraCheckboxComponent, FormsModule, JsonPipe], + template, +}) +export class CheckboxGroupComponent { + selectedItems: string[] = ['Pizza']; +} + +export const Group: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Использование нескольких чекбоксов для выбора нескольких значений из массива.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraCheckboxComponent } from '@cdek-it/angular-ui-kit'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-checkbox-group', + standalone: true, + imports: [ExtraCheckboxComponent, FormsModule], + template: \` + + + + \`, +}) +export class CheckboxGroupComponent { + selectedItems: string[] = ['Pizza']; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/checkbox/examples/checkbox-indeterminate.component.ts b/src/stories/components/checkbox/examples/checkbox-indeterminate.component.ts new file mode 100644 index 00000000..acb35a25 --- /dev/null +++ b/src/stories/components/checkbox/examples/checkbox-indeterminate.component.ts @@ -0,0 +1,52 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraCheckboxComponent } from '../../../../lib/components/checkbox/checkbox.component'; + +const styles = ''; + +@Component({ + selector: 'app-checkbox-indeterminate', + standalone: true, + imports: [ExtraCheckboxComponent, FormsModule], + styles, + template: ` + + `, +}) +export class CheckboxIndeterminateComponent { + checked = false; +} + +export const Indeterminate: StoryObj = { + render: (args) => ({ + props: { ...args, checked: false }, + template: ``, + }), + args: { indeterminate: true }, + parameters: { + docs: { + description: { story: 'Неопределённое состояние чекбокса.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraCheckboxComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-checkbox-indeterminate', + standalone: true, + imports: [ExtraCheckboxComponent, FormsModule], + template: \` + + \`, +}) +export class CheckboxIndeterminateComponent { + checked = false; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/checkbox/examples/checkbox-invalid.component.ts b/src/stories/components/checkbox/examples/checkbox-invalid.component.ts new file mode 100644 index 00000000..351d56f5 --- /dev/null +++ b/src/stories/components/checkbox/examples/checkbox-invalid.component.ts @@ -0,0 +1,52 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraCheckboxComponent } from '../../../../lib/components/checkbox/checkbox.component'; + +const styles = ''; + +@Component({ + selector: 'app-checkbox-invalid', + standalone: true, + imports: [ExtraCheckboxComponent, FormsModule], + styles, + template: ` + + `, +}) +export class CheckboxInvalidComponent { + checked = false; +} + +export const Invalid: StoryObj = { + render: (args) => ({ + props: { ...args, checked: false }, + template: ``, + }), + args: { invalid: true }, + parameters: { + docs: { + description: { story: 'Невалидное состояние чекбокса.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ExtraCheckboxComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-checkbox-invalid', + standalone: true, + imports: [ExtraCheckboxComponent, ReactiveFormsModule], + template: \` + + \`, +}) +export class CheckboxInvalidComponent { + control = new FormControl(false, [Validators.requiredTrue]); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/checkbox/examples/checkbox-label.component.ts b/src/stories/components/checkbox/examples/checkbox-label.component.ts new file mode 100644 index 00000000..4bb3cb75 --- /dev/null +++ b/src/stories/components/checkbox/examples/checkbox-label.component.ts @@ -0,0 +1,62 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraCheckboxComponent } from '../../../../lib/components/checkbox/checkbox.component'; + +const styles = ''; + +@Component({ + selector: 'app-checkbox-label', + standalone: true, + imports: [ExtraCheckboxComponent, FormsModule], + styles, + template: ` +
+ + +
+ `, +}) +export class CheckboxLabelComponent { + checked = false; +} + +export const Label: StoryObj = { + render: (args) => ({ + props: { ...args, checked: false }, + template: ` +
+ + +
+ `, + }), + parameters: { + docs: { + description: { story: 'Чекбокс с привязанным label через inputId.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraCheckboxComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-checkbox-label', + standalone: true, + imports: [ExtraCheckboxComponent, ReactiveFormsModule], + template: \` +
+ + +
+ \`, +}) +export class CheckboxLabelComponent { + control = new FormControl(false); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/chip/chip.stories.ts b/src/stories/components/chip/chip.stories.ts new file mode 100644 index 00000000..dd44e9d8 --- /dev/null +++ b/src/stories/components/chip/chip.stories.ts @@ -0,0 +1,144 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraChipComponent as ChipComponent } from '../../../lib/components/chip/chip.component'; +import { ChipWithIconComponent, WithIcon as WithIconStory } from './examples/chip-with-icon.component'; +import { ChipRemovableComponent, Removable as RemovableStory } from './examples/chip-removable.component'; +import { ChipRemovableWithIconComponent, RemovableWithIcon as RemovableWithIconStory } from './examples/chip-removable-with-icon.component'; +import { ChipDisabledComponent, Disabled as DisabledStory } from './examples/chip-disabled.component'; + +type ChipArgs = ChipComponent & { onRemove?: (event: MouseEvent) => void }; + +const meta: Meta = { + title: 'Components/Misc/Chip', + component: ChipComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ChipComponent, + ChipWithIconComponent, + ChipRemovableComponent, + ChipRemovableWithIconComponent, + ChipDisabledComponent, + ], + }), + ], + parameters: { + docs: { + description: { + component: `Чип — небольшой интерактивный элемент с текстом, иконкой и опциональной кнопкой удаления. + +\`\`\`typescript +import { ChipComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-chip' }, + }, + argTypes: { + label: { + control: 'text', + description: 'Текст внутри чипа', + table: { + category: 'Props', + defaultValue: { summary: '' }, + type: { summary: 'string' }, + }, + }, + icon: { + control: 'text', + description: 'Иконка чипа (класс Tabler Icons, например "ti ti-map-pin")', + table: { + category: 'Props', + defaultValue: { summary: '' }, + type: { summary: 'string' }, + }, + }, + removable: { + control: 'boolean', + description: 'Отображает кнопку удаления', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + disabled: { + control: 'boolean', + description: 'Отключает чип', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + onRemove: { + control: false, + description: 'Событие удаления чипа', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, +}; + +const commonTemplate = ` + +`; + +export default meta; +type Story = StoryObj; + +// ── Default ─────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.label) parts.push(`label="${args.label}"`); + if (args.icon) parts.push(`icon="${args.icon}"`); + if (args.removable) parts.push(`[removable]="true"`); + if (args.disabled) parts.push(`[disabled]="true"`); + + const template = parts.length + ? `` + : ``; + + return { props: args, template }; + }, + args: { + label: 'В пути', + icon: '', + removable: false, + disabled: false, + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── WithIcon ────────────────────────────────────────────────────────────────── + +export const WithIcon: Story = WithIconStory; + +// ── Removable ───────────────────────────────────────────────────────────────── + +export const Removable: Story = RemovableStory; + +// ── RemovableWithIcon ───────────────────────────────────────────────────────── + +export const RemovableWithIcon: Story = RemovableWithIconStory; + +// ── Disabled ────────────────────────────────────────────────────────────────── + +export const Disabled: Story = DisabledStory; diff --git a/src/stories/components/chip/examples/chip-disabled.component.ts b/src/stories/components/chip/examples/chip-disabled.component.ts new file mode 100644 index 00000000..c235ca30 --- /dev/null +++ b/src/stories/components/chip/examples/chip-disabled.component.ts @@ -0,0 +1,46 @@ +import { Component } from '@angular/core'; +import { ExtraChipComponent } from '../../../../lib/components/chip/chip.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-chip-disabled', + standalone: true, + imports: [ExtraChipComponent], + template, + styles, +}) +export class ChipDisabledComponent {} + +export const Disabled = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Отключённый чип.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraChipComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-chip-disabled', + standalone: true, + imports: [ExtraChipComponent], + template: \` + + \`, +}) +export class ChipDisabledComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/chip/examples/chip-removable-with-icon.component.ts b/src/stories/components/chip/examples/chip-removable-with-icon.component.ts new file mode 100644 index 00000000..b0d8254a --- /dev/null +++ b/src/stories/components/chip/examples/chip-removable-with-icon.component.ts @@ -0,0 +1,46 @@ +import { Component } from '@angular/core'; +import { ExtraChipComponent } from '../../../../lib/components/chip/chip.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-chip-removable-with-icon', + standalone: true, + imports: [ExtraChipComponent], + template, + styles, +}) +export class ChipRemovableWithIconComponent {} + +export const RemovableWithIcon = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Чип с иконкой и кнопкой удаления.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraChipComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-chip-removable-with-icon', + standalone: true, + imports: [ExtraChipComponent], + template: \` + + \`, +}) +export class ChipRemovableWithIconComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/chip/examples/chip-removable.component.ts b/src/stories/components/chip/examples/chip-removable.component.ts new file mode 100644 index 00000000..6e9627c8 --- /dev/null +++ b/src/stories/components/chip/examples/chip-removable.component.ts @@ -0,0 +1,46 @@ +import { Component } from '@angular/core'; +import { ExtraChipComponent } from '../../../../lib/components/chip/chip.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-chip-removable', + standalone: true, + imports: [ExtraChipComponent], + template, + styles, +}) +export class ChipRemovableComponent {} + +export const Removable = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Чип с кнопкой удаления.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraChipComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-chip-removable', + standalone: true, + imports: [ExtraChipComponent], + template: \` + + \`, +}) +export class ChipRemovableComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/chip/examples/chip-with-icon.component.ts b/src/stories/components/chip/examples/chip-with-icon.component.ts new file mode 100644 index 00000000..820ac811 --- /dev/null +++ b/src/stories/components/chip/examples/chip-with-icon.component.ts @@ -0,0 +1,46 @@ +import { Component } from '@angular/core'; +import { ExtraChipComponent } from '../../../../lib/components/chip/chip.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-chip-with-icon', + standalone: true, + imports: [ExtraChipComponent], + template, + styles, +}) +export class ChipWithIconComponent {} + +export const WithIcon = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Чип с иконкой.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraChipComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-chip-with-icon', + standalone: true, + imports: [ExtraChipComponent], + template: \` + + \`, +}) +export class ChipWithIconComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/dialog/dialog.stories.ts b/src/stories/components/dialog/dialog.stories.ts new file mode 100644 index 00000000..2f540ed8 --- /dev/null +++ b/src/stories/components/dialog/dialog.stories.ts @@ -0,0 +1,365 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraDialogComponent } from '../../../lib/components/dialog/dialog.component'; +import { DialogDefaultComponent, template as dialogDefaultTemplate } from './examples/dialog-default.component'; +import { DialogSmallComponent, template as dialogSmallTemplate } from './examples/dialog-small.component'; +import { DialogLargeComponent, template as dialogLargeTemplate } from './examples/dialog-large.component'; +import { DialogExtraLargeComponent, template as dialogExtraLargeTemplate } from './examples/dialog-extra-large.component'; +import { DialogNoModalComponent, template as dialogNoModalTemplate } from './examples/dialog-no-modal.component'; +import { DialogNoHeaderComponent, template as dialogNoHeaderTemplate } from './examples/dialog-no-header.component'; +import { DialogDynamicComponent } from './examples/dialog-dynamic.component'; + +const meta: Meta = { + title: 'Components/Overlay/Dialog', + component: ExtraDialogComponent, + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: `Dialog (модальное окно) — контейнер, отображающийся поверх основного содержимого страницы. + + \`\`\`typescript + import { ExtraDialogComponent } from '@cdek-it/angular-ui-kit'; + \`\`\``, + }, + }, + designTokens: { prefix: '--p-dialog' }, + }, + argTypes: { + header: { + control: 'text', + description: 'Заголовок окна', + table: { + category: 'Props', + defaultValue: { summary: '' }, + type: { summary: 'string' }, + }, + }, + headerTemplate: { + control: false, + description: 'Кастомный шаблон заголовка. При наличии заменяет строковый header', + table: { + category: 'Props', + defaultValue: { summary: 'null' }, + type: { summary: 'TemplateRef | null' }, + }, + }, + size: { + control: 'select', + options: ['sm', 'default', 'lg', 'xlg'], + description: 'Размер диалога', + table: { + category: 'Props', + defaultValue: { summary: 'default' }, + type: { summary: "'sm' | 'default' | 'lg' | 'xlg'" }, + }, + }, + modal: { + control: 'boolean', + description: 'Должно ли окно быть модальным (блокировать фон)', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + dismissableMask: { + control: 'boolean', + description: 'Закрывать ли окно при клике на маску', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + closeOnEscape: { + control: 'boolean', + description: 'Закрывать ли окно по нажатию Escape', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + showHeader: { + control: 'boolean', + description: 'Отображать ли заголовок', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + focusOnShow: { + control: 'boolean', + description: 'Фокус на первый элемент при открытии', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + appendTo: { + control: 'text', + description: 'Элемент, к которому прикрепляется диалог (например body или CSS-селектор)', + table: { + category: 'Props', + defaultValue: { summary: "'body'" }, + type: { summary: 'string' }, + }, + }, + visibleChange: { + control: false, + description: 'Изменение видимости диалога', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Basic ───────────────────────────────────────────────────────────────────── + +export const Basic: Story = { + name: 'Basic', + decorators: [moduleMetadata({ imports: [DialogDefaultComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Базовый пример диалогового окна с заголовком, контентом и кнопками действий.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraDialogComponent, ExtraButtonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-dialog-basic', + standalone: true, + imports: [ExtraDialogComponent, ExtraButtonComponent], + template: \`${dialogDefaultTemplate}\`, +}) +export class DialogBasicComponent { + visible = false; +} + `, + }, + }, + }, +}; + +// ── Small ───────────────────────────────────────────────────────────────────── + +export const Small: Story = { + name: 'Small', + decorators: [moduleMetadata({ imports: [DialogSmallComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { story: 'Уменьшенный размер диалога (SM).' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraDialogComponent, ExtraButtonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-dialog-small', + standalone: true, + imports: [ExtraDialogComponent, ExtraButtonComponent], + template: \`${dialogSmallTemplate}\`, +}) +export class DialogSmallComponent { + visible = false; +} + `, + }, + }, + }, +}; + +// ── Large ───────────────────────────────────────────────────────────────────── + +export const Large: Story = { + name: 'Large', + decorators: [moduleMetadata({ imports: [DialogLargeComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { story: 'Увеличенный размер диалога (LG).' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraDialogComponent, ExtraButtonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-dialog-large', + standalone: true, + imports: [ExtraDialogComponent, ExtraButtonComponent], + template: \`${dialogLargeTemplate}\`, +}) +export class DialogLargeComponent { + visible = false; +} + `, + }, + }, + }, +}; + +// ── Extra Large ─────────────────────────────────────────────────────────────── + +export const ExtraLarge: Story = { + name: 'Extra Large', + decorators: [moduleMetadata({ imports: [DialogExtraLargeComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { story: 'Максимальный размер диалога (XLG).' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraDialogComponent, ExtraButtonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-dialog-extra-large', + standalone: true, + imports: [ExtraDialogComponent, ExtraButtonComponent], + template: \`${dialogExtraLargeTemplate}\`, +}) +export class DialogExtraLargeComponent { + visible = false; +} + `, + }, + }, + }, +}; + +// ── No Modal ────────────────────────────────────────────────────────────────── + +export const NoModal: Story = { + name: 'No Modal', + decorators: [moduleMetadata({ imports: [DialogNoModalComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { story: 'Окно не блокирует фон страницы.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraDialogComponent, ExtraButtonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-dialog-no-modal', + standalone: true, + imports: [ExtraDialogComponent, ExtraButtonComponent], + template: \`${dialogNoModalTemplate}\`, +}) +export class DialogNoModalComponent { + visible = false; +} + `, + }, + }, + }, +}; + +// ── Show Header ─────────────────────────────────────────────────────────────── + +export const NoHeader: Story = { + name: 'Show Header', + decorators: [moduleMetadata({ imports: [DialogNoHeaderComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { story: 'Заголовок можно скрыть с помощью пропса showHeader: false.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraDialogComponent, ExtraButtonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-dialog-no-header', + standalone: true, + imports: [ExtraDialogComponent, ExtraButtonComponent], + template: \`${dialogNoHeaderTemplate}\`, +}) +export class DialogNoHeaderComponent { + visible = false; +} + `, + }, + }, + }, +}; + +// ── Dynamic ─────────────────────────────────────────────────────────────────── + +export const Dynamic: Story = { + name: 'Dynamic', + decorators: [moduleMetadata({ imports: [DialogDynamicComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Программное открытие диалога через `ExtraDialogService`. Содержимое — любой Angular-компонент, получающий `DynamicDialogRef` для закрытия.', + }, + source: { + language: 'ts', + code: ` +import { Component, Injector } from '@angular/core'; +import { ExtraButtonComponent, DynamicDialogRef, ExtraDialogService } from '@cdek-it/angular-ui-kit'; + +// Содержимое диалога +@Component({ + selector: 'app-dialog-dynamic-content', + standalone: true, + imports: [ExtraButtonComponent], + template: \` +

Заявка на доставку груза №CDEK-2025-00478312 готова к оформлению.

+
+ + +
+ \`, +}) +export class DialogDynamicContentComponent { + constructor(readonly ref: DynamicDialogRef) {} +} + +// Компонент-триггер +@Component({ + selector: 'app-dialog-dynamic', + standalone: true, + imports: [ButtonComponent], + template: \` + + \`, +}) +export class DialogDynamicComponent { + constructor( + private readonly dialogService: ExtraDialogService, + private readonly injector: Injector, + ) {} + + open(): void { + this.dialogService.open(DialogDynamicContentComponent, this.injector, { + header: 'Подтверждение заявки', + modal: true, + }); + } +}`, + }, + }, + }, +}; diff --git a/src/stories/components/dialog/examples/dialog-default.component.ts b/src/stories/components/dialog/examples/dialog-default.component.ts new file mode 100644 index 00000000..c85128c1 --- /dev/null +++ b/src/stories/components/dialog/examples/dialog-default.component.ts @@ -0,0 +1,33 @@ +import { Component } from '@angular/core'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraDialogComponent } from '../../../../lib/components/dialog/dialog.component'; + +export const template = ` +
+ + + + + + + + +

Заявка на доставку груза №CDEK-2025-00478312 готова к оформлению. Вес отправления: 3,5 кг, габариты: 40×30×20 см. Ориентировочный срок доставки — 3 рабочих дня.

+
+
+`; + +@Component({ + selector: 'app-dialog-basic', + standalone: true, + imports: [ExtraDialogComponent, ExtraButtonComponent], + template, +}) +export class DialogDefaultComponent { + visible = false; +} diff --git a/src/stories/components/dialog/examples/dialog-dynamic.component.ts b/src/stories/components/dialog/examples/dialog-dynamic.component.ts new file mode 100644 index 00000000..12f46615 --- /dev/null +++ b/src/stories/components/dialog/examples/dialog-dynamic.component.ts @@ -0,0 +1,49 @@ +import { Component, Injector } from '@angular/core'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { DynamicDialogRef, ExtraDialogService } from '../../../../lib/components/dialog/dialog-open.service'; + +// ── Содержимое диалога ──────────────────────────────────────────────────────── + +@Component({ + selector: 'app-dialog-dynamic-content', + standalone: true, + imports: [ExtraButtonComponent], + template: ` +

Заявка на доставку груза №CDEK-2025-00478312 готова к оформлению.

+

Вес отправления: 3,5 кг, габариты: 40×30×20 см. Ориентировочный срок — 3 рабочих дня.

+
+ + +
+ `, +}) +export class DialogDynamicContentComponent { + constructor(readonly ref: DynamicDialogRef) {} +} + +// ── Компонент-триггер ───────────────────────────────────────────────────────── + +export const template = ` +
+ +
+`; + +@Component({ + selector: 'app-dialog-dynamic', + standalone: true, + imports: [ExtraButtonComponent], + template, +}) +export class DialogDynamicComponent { + constructor( + private readonly dialogService: ExtraDialogService, + ) {} + + open(): void { + this.dialogService.open(DialogDynamicContentComponent, { + header: 'Подтверждение заявки', + modal: true, + }); + } +} diff --git a/src/stories/components/dialog/examples/dialog-extra-large.component.ts b/src/stories/components/dialog/examples/dialog-extra-large.component.ts new file mode 100644 index 00000000..89d5be8e --- /dev/null +++ b/src/stories/components/dialog/examples/dialog-extra-large.component.ts @@ -0,0 +1,34 @@ +import { Component } from '@angular/core'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraDialogComponent } from '../../../../lib/components/dialog/dialog.component'; + +export const template = ` +
+ + + + + + + + +

За апрель 2025 года обработано 4 872 отправления. Успешно доставлено — 4 641 (95,3%). Возвраты — 112 (2,3%). В пути — 119 (2,4%). Средний срок доставки по России составил 2,7 рабочего дня. Наиболее загруженные направления: Москва — Санкт-Петербург, Москва — Новосибирск, Москва — Екатеринбург.

+
+
+`; + +@Component({ + selector: 'app-dialog-extra-large', + standalone: true, + imports: [ExtraDialogComponent, ExtraButtonComponent], + template, +}) +export class DialogExtraLargeComponent { + visible = false; +} diff --git a/src/stories/components/dialog/examples/dialog-large.component.ts b/src/stories/components/dialog/examples/dialog-large.component.ts new file mode 100644 index 00000000..ecee283c --- /dev/null +++ b/src/stories/components/dialog/examples/dialog-large.component.ts @@ -0,0 +1,34 @@ +import { Component } from '@angular/core'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraDialogComponent } from '../../../../lib/components/dialog/dialog.component'; + +export const template = ` +
+ + + + + + + + +

Отправление CDEK-2025-00478312 передано курьеру для доставки до двери получателя. Последнее обновление: 09.04.2025, 14:35. Адрес доставки: г. Новосибирск, ул. Ленина, 42, кв. 8. Получатель: Иванов И.И., +7 913 000-00-00.

+
+
+`; + +@Component({ + selector: 'app-dialog-large', + standalone: true, + imports: [ExtraDialogComponent, ExtraButtonComponent], + template, +}) +export class DialogLargeComponent { + visible = false; +} diff --git a/src/stories/components/dialog/examples/dialog-no-header.component.ts b/src/stories/components/dialog/examples/dialog-no-header.component.ts new file mode 100644 index 00000000..ea2cdd8b --- /dev/null +++ b/src/stories/components/dialog/examples/dialog-no-header.component.ts @@ -0,0 +1,35 @@ +import { Component } from '@angular/core'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraDialogComponent } from '../../../../lib/components/dialog/dialog.component'; + +export const template = ` +
+ + + +
+ +
+
+ + +

Заявка на доставку принята в обработку. Трек-номер будет присвоен в течение 15 минут и отправлен на указанный email.

+
+
+`; + +@Component({ + selector: 'app-dialog-no-header', + standalone: true, + imports: [ExtraDialogComponent, ExtraButtonComponent], + template, +}) +export class DialogNoHeaderComponent { + visible = false; +} diff --git a/src/stories/components/dialog/examples/dialog-no-modal.component.ts b/src/stories/components/dialog/examples/dialog-no-modal.component.ts new file mode 100644 index 00000000..03d643e7 --- /dev/null +++ b/src/stories/components/dialog/examples/dialog-no-modal.component.ts @@ -0,0 +1,34 @@ +import { Component } from '@angular/core'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraDialogComponent } from '../../../../lib/components/dialog/dialog.component'; + +export const template = ` +
+ + + + + + + + +

Маршрут отправления CDEK-2025-00478312: Москва (склад) → Новосибирск (сортировочный центр) → Новосибирск (пункт выдачи). Это окно не блокирует основной контент страницы.

+
+
+`; + +@Component({ + selector: 'app-dialog-no-modal', + standalone: true, + imports: [ExtraDialogComponent, ExtraButtonComponent], + template, +}) +export class DialogNoModalComponent { + visible = false; +} diff --git a/src/stories/components/dialog/examples/dialog-small.component.ts b/src/stories/components/dialog/examples/dialog-small.component.ts new file mode 100644 index 00000000..79257d6f --- /dev/null +++ b/src/stories/components/dialog/examples/dialog-small.component.ts @@ -0,0 +1,34 @@ +import { Component } from '@angular/core'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraDialogComponent } from '../../../../lib/components/dialog/dialog.component'; + +export const template = ` +
+ + + + + + + + +

Отправление CDEK-2025-00478312 прибыло на сортировочный центр г. Новосибирск и готово к передаче курьеру.

+
+
+`; + +@Component({ + selector: 'app-dialog-small', + standalone: true, + imports: [ExtraDialogComponent, ExtraButtonComponent], + template, +}) +export class DialogSmallComponent { + visible = false; +} diff --git a/src/stories/components/divider/divider.stories.ts b/src/stories/components/divider/divider.stories.ts new file mode 100644 index 00000000..2bb45e1b --- /dev/null +++ b/src/stories/components/divider/divider.stories.ts @@ -0,0 +1,176 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraDividerComponent as DividerComponent } from '../../../lib/components/divider/divider.component'; +import { DividerWithContentComponent, WithContent as WithContentStory } from './examples/divider-with-content.component'; +import { DividerWithIconComponent, WithIcon as WithIconStory } from './examples/divider-with-icon.component'; +import { DividerAlignLeftComponent, AlignLeft as AlignLeftStory } from './examples/divider-align-left.component'; + +const meta: Meta = { + title: 'Components/Panel/Divider', + component: DividerComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + DividerComponent, + DividerWithContentComponent, + DividerWithIconComponent, + DividerAlignLeftComponent, + ], + }), + ], + parameters: { + docs: { + description: { + component: `Разделитель для визуального разделения контента. Поддерживает горизонтальную и вертикальную ориентацию, различные стили линии и выравнивание. + +\`\`\`typescript +import { DividerModule } from 'primeng/divider'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-divider' }, + }, + argTypes: { + layout: { + control: 'select', + options: ['horizontal', 'vertical'], + description: 'Ориентация разделителя', + table: { + category: 'Props', + defaultValue: { summary: 'horizontal' }, + type: { summary: "'horizontal' | 'vertical'" }, + }, + }, + type: { + control: 'select', + options: ['solid', 'dashed', 'dotted'], + description: 'Стиль линии разделителя', + table: { + category: 'Props', + defaultValue: { summary: 'solid' }, + type: { summary: "'solid' | 'dashed' | 'dotted'" }, + }, + }, + align: { + control: 'select', + options: ['left', 'center', 'right', 'top', 'bottom'], + description: 'Выравнивание контента внутри разделителя', + table: { + category: 'Props', + defaultValue: { summary: 'center' }, + type: { summary: "'left' | 'center' | 'right' | 'top' | 'bottom'" }, + }, + }, + }, +}; + +const commonTemplate = ` + +`; + +export default meta; +type Story = StoryObj; + +// ── Default ─────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.layout && args.layout !== 'horizontal') parts.push(`layout="${args.layout}"`); + if (args.type && args.type !== 'solid') parts.push(`type="${args.type}"`); + if (args.align && args.align !== 'center') parts.push(`align="${args.align}"`); + + const template = parts.length + ? `` + : ``; + + return { props: args, template }; + }, + args: { + layout: 'horizontal', + type: 'solid', + align: 'center', + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── WithContent ─────────────────────────────────────────────────────────────── + +export const WithContent: Story = WithContentStory; + +// ── WithIcon ────────────────────────────────────────────────────────────────── + +export const WithIcon: Story = WithIconStory; + +// ── Vertical ────────────────────────────────────────────────────────────────── + +export const Vertical: Story = { + render: (args) => ({ props: args, template: commonTemplate }), + args: { + layout: 'vertical', + type: 'solid', + align: 'center', + }, + parameters: { + docs: { + description: { story: 'Вертикальный разделитель для разделения контента по горизонтали.' }, + source: { + code: ``, + }, + }, + }, +}; + +// ── Type ────────────────────────────────────────────────────────────────────── + +export const TypeDashed: Story = { + name: 'Dashed', + render: (args) => ({ props: args, template: commonTemplate }), + args: { + layout: 'horizontal', + type: 'dashed', + align: 'center', + }, + parameters: { + docs: { + description: { story: 'Разделитель с пунктирной линией.' }, + source: { + code: ``, + }, + }, + }, +}; + +export const TypeDotted: Story = { + name: 'Dotted', + render: (args) => ({ props: args, template: commonTemplate }), + args: { + layout: 'horizontal', + type: 'dotted', + align: 'center', + }, + parameters: { + docs: { + description: { story: 'Разделитель с точечной линией.' }, + source: { + code: ``, + }, + }, + }, +}; + +// ── Align ───────────────────────────────────────────────────────────────────── + +export const AlignLeft: Story = AlignLeftStory; diff --git a/src/stories/components/divider/examples/divider-align-left.component.ts b/src/stories/components/divider/examples/divider-align-left.component.ts new file mode 100644 index 00000000..4348d483 --- /dev/null +++ b/src/stories/components/divider/examples/divider-align-left.component.ts @@ -0,0 +1,50 @@ +import { Component } from '@angular/core'; +import { ExtraDividerComponent } from '../../../../lib/components/divider/divider.component'; + +const template = ` +
+ + Отправитель + +
+`; +const styles = ''; + +@Component({ + selector: 'app-divider-align-left', + standalone: true, + imports: [ExtraDividerComponent], + template, + styles, +}) +export class DividerAlignLeftComponent {} + +export const AlignLeft = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Контент разделителя выровнен по левому краю.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraDividerComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-divider-align-left', + standalone: true, + imports: [ExtraDividerComponent], + template: \` + + Отправитель + + \`, +}) +export class DividerAlignLeftComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/divider/examples/divider-with-content.component.ts b/src/stories/components/divider/examples/divider-with-content.component.ts new file mode 100644 index 00000000..8daf3b8b --- /dev/null +++ b/src/stories/components/divider/examples/divider-with-content.component.ts @@ -0,0 +1,50 @@ +import { Component } from '@angular/core'; +import { ExtraDividerComponent } from '../../../../lib/components/divider/divider.component'; + +const template = ` +
+ + Москва → Новосибирск + +
+`; +const styles = ''; + +@Component({ + selector: 'app-divider-with-content', + standalone: true, + imports: [ExtraDividerComponent], + template, + styles, +}) +export class DividerWithContentComponent {} + +export const WithContent = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Разделитель с текстовым контентом по центру.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraDividerComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-divider-with-content', + standalone: true, + imports: [ExtraDividerComponent], + template: \` + + Москва → Новосибирск + + \`, +}) +export class DividerWithContentComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/divider/examples/divider-with-icon.component.ts b/src/stories/components/divider/examples/divider-with-icon.component.ts new file mode 100644 index 00000000..013ae2e0 --- /dev/null +++ b/src/stories/components/divider/examples/divider-with-icon.component.ts @@ -0,0 +1,50 @@ +import { Component } from '@angular/core'; +import { ExtraDividerComponent } from '../../../../lib/components/divider/divider.component'; + +const template = ` +
+ + + +
+`; +const styles = ''; + +@Component({ + selector: 'app-divider-with-icon', + standalone: true, + imports: [ExtraDividerComponent], + template, + styles, +}) +export class DividerWithIconComponent {} + +export const WithIcon = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Разделитель с иконкой.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraDividerComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-divider-with-icon', + standalone: true, + imports: [ExtraDividerComponent], + template: \` + + + + \`, +}) +export class DividerWithIconComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputgroup/examples/inputgroup-addon-both.component.ts b/src/stories/components/inputgroup/examples/inputgroup-addon-both.component.ts new file mode 100644 index 00000000..1ab9e802 --- /dev/null +++ b/src/stories/components/inputgroup/examples/inputgroup-addon-both.component.ts @@ -0,0 +1,63 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { FormsModule } from '@angular/forms'; +import { ExtraInputGroupComponent } from '../../../../lib/components/inputgroup/input-group.component'; +import { ExtraInputGroupAddonComponent } from '../../../../lib/components/inputgroup/input-group-addon.component'; +import { ExtraInputTextComponent } from '../../../../lib/components/inputtext/inputtext.component'; + +const template = ` +
+ + + + + +
+`; +const styles = ''; + +@Component({ + selector: 'app-inputgroup-addon-both', + standalone: true, + imports: [ExtraInputGroupComponent, ExtraInputGroupAddonComponent, ExtraInputTextComponent, FormsModule], + template, + styles +}) +export class InputGroupAddonBothComponent { + value = ''; +} + +export const AddonBoth: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Аддоны расположены с обеих сторон — например, иконка-префикс и кнопка поиска.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraInputGroupComponent, ExtraInputGroupAddonComponent, InputTextComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-inputgroup-addon-both', + standalone: true, + imports: [ExtraInputGroupComponent, ExtraInputGroupAddonComponent, InputTextComponent, FormsModule], + template: \` + + + + + + \`, +}) +export class InputGroupAddonBothComponent { + value = ''; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputgroup/examples/inputgroup-addon-right.component.ts b/src/stories/components/inputgroup/examples/inputgroup-addon-right.component.ts new file mode 100644 index 00000000..84098c30 --- /dev/null +++ b/src/stories/components/inputgroup/examples/inputgroup-addon-right.component.ts @@ -0,0 +1,61 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { FormsModule } from '@angular/forms'; +import { ExtraInputGroupComponent } from '../../../../lib/components/inputgroup/input-group.component'; +import { ExtraInputGroupAddonComponent } from '../../../../lib/components/inputgroup/input-group-addon.component'; +import { ExtraInputTextComponent } from '../../../../lib/components/inputtext/inputtext.component'; + +const template = ` +
+ + + руб. + +
+`; +const styles = ''; + +@Component({ + selector: 'app-inputgroup-addon-right', + standalone: true, + imports: [ExtraInputGroupComponent, ExtraInputGroupAddonComponent, ExtraInputTextComponent, FormsModule], + template, + styles +}) +export class InputGroupAddonRightComponent { + value = ''; +} + +export const AddonRight: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Аддон расположен справа — используется для единиц измерения, валюты или суффиксов.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraInputGroupComponent, ExtraInputGroupAddonComponent, InputTextComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-inputgroup-addon-right', + standalone: true, + imports: [ExtraInputGroupComponent, ExtraInputGroupAddonComponent, InputTextComponent, FormsModule], + template: \` + + + руб. + + \`, +}) +export class InputGroupAddonRightComponent { + value = ''; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputgroup/examples/inputgroup-disabled.component.ts b/src/stories/components/inputgroup/examples/inputgroup-disabled.component.ts new file mode 100644 index 00000000..ce45d214 --- /dev/null +++ b/src/stories/components/inputgroup/examples/inputgroup-disabled.component.ts @@ -0,0 +1,61 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { FormsModule } from '@angular/forms'; +import { ExtraInputGroupComponent } from '../../../../lib/components/inputgroup/input-group.component'; +import { ExtraInputGroupAddonComponent } from '../../../../lib/components/inputgroup/input-group-addon.component'; +import { ExtraInputTextComponent } from '../../../../lib/components/inputtext/inputtext.component'; + +const template = ` +
+ + + + +
+`; +const styles = ''; + +@Component({ + selector: 'app-inputgroup-disabled', + standalone: true, + imports: [ExtraInputGroupComponent, ExtraInputGroupAddonComponent, ExtraInputTextComponent, FormsModule], + template, + styles +}) +export class InputGroupDisabledComponent { + value = ''; +} + +export const Disabled: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Отключённое состояние — аддоны автоматически получают стили disabled вместе с полем ввода.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraInputGroupComponent, ExtraInputGroupAddonComponent, InputTextComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-inputgroup-disabled', + standalone: true, + imports: [ExtraInputGroupComponent, ExtraInputGroupAddonComponent, InputTextComponent, FormsModule], + template: \` + + + + + \`, +}) +export class InputGroupDisabledComponent { + value = ''; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputgroup/examples/inputgroup-with-text.component.ts b/src/stories/components/inputgroup/examples/inputgroup-with-text.component.ts new file mode 100644 index 00000000..fbb887de --- /dev/null +++ b/src/stories/components/inputgroup/examples/inputgroup-with-text.component.ts @@ -0,0 +1,61 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { FormsModule } from '@angular/forms'; +import { ExtraInputGroupComponent } from '../../../../lib/components/inputgroup/input-group.component'; +import { ExtraInputGroupAddonComponent } from '../../../../lib/components/inputgroup/input-group-addon.component'; +import { ExtraInputTextComponent } from '../../../../lib/components/inputtext/inputtext.component'; + +const template = ` +
+ + @ + + +
+`; +const styles = ''; + +@Component({ + selector: 'app-inputgroup-with-text', + standalone: true, + imports: [ExtraInputGroupComponent, ExtraInputGroupAddonComponent, ExtraInputTextComponent, FormsModule], + template, + styles +}) +export class InputGroupWithTextComponent { + value = ''; +} + +export const WithText: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'В качестве наполнения аддона можно использовать обычный текст — например, символ валюты или префикс.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraInputGroupComponent, ExtraInputGroupAddonComponent, InputTextComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-inputgroup-with-text', + standalone: true, + imports: [ExtraInputGroupComponent, ExtraInputGroupAddonComponent, InputTextComponent, FormsModule], + template: \` + + @ + + + \`, +}) +export class InputGroupWithTextComponent { + value = ''; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputgroup/examples/inputgroup-xlarge.component.ts b/src/stories/components/inputgroup/examples/inputgroup-xlarge.component.ts new file mode 100644 index 00000000..0eef686a --- /dev/null +++ b/src/stories/components/inputgroup/examples/inputgroup-xlarge.component.ts @@ -0,0 +1,61 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { FormsModule } from '@angular/forms'; +import { ExtraInputGroupComponent } from '../../../../lib/components/inputgroup/input-group.component'; +import { ExtraInputGroupAddonComponent } from '../../../../lib/components/inputgroup/input-group-addon.component'; +import { ExtraInputTextComponent } from '../../../../lib/components/inputtext/inputtext.component'; + +const template = ` +
+ + + + +
+`; +const styles = ''; + +@Component({ + selector: 'app-inputgroup-xlarge', + standalone: true, + imports: [ExtraInputGroupComponent, ExtraInputGroupAddonComponent, ExtraInputTextComponent, FormsModule], + template, + styles +}) +export class InputGroupXlargeComponent { + value = ''; +} + +export const Sizes: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Увеличенный размер группы ввода — для акцентных форм и поисковых строк.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraInputGroupComponent, ExtraInputGroupAddonComponent, InputTextComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-inputgroup-xlarge', + standalone: true, + imports: [ExtraInputGroupComponent, ExtraInputGroupAddonComponent, InputTextComponent, FormsModule], + template: \` + + + + + \`, +}) +export class InputGroupXlargeComponent { + value = ''; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputgroup/inputgroup.stories.ts b/src/stories/components/inputgroup/inputgroup.stories.ts new file mode 100644 index 00000000..5ba75c71 --- /dev/null +++ b/src/stories/components/inputgroup/inputgroup.stories.ts @@ -0,0 +1,92 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormsModule } from '@angular/forms'; +import { ExtraInputGroupComponent } from '../../../lib/components/inputgroup/input-group.component'; +import { ExtraInputGroupAddonComponent } from '../../../lib/components/inputgroup/input-group-addon.component'; +import { ExtraInputTextComponent } from '../../../lib/components/inputtext/inputtext.component'; +import { InputGroupWithTextComponent, WithText } from './examples/inputgroup-with-text.component'; +import { InputGroupDisabledComponent, Disabled } from './examples/inputgroup-disabled.component'; +import { InputGroupXlargeComponent, Sizes } from './examples/inputgroup-xlarge.component'; +import { InputGroupAddonRightComponent, AddonRight } from './examples/inputgroup-addon-right.component'; +import { InputGroupAddonBothComponent, AddonBoth } from './examples/inputgroup-addon-both.component'; + +const meta: Meta = { + title: 'Components/Form/InputGroup', + component: ExtraInputGroupComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraInputGroupComponent, + ExtraInputGroupAddonComponent, + ExtraInputTextComponent, + FormsModule, + InputGroupWithTextComponent, + InputGroupDisabledComponent, + InputGroupXlargeComponent, + InputGroupAddonRightComponent, + InputGroupAddonBothComponent + ] + }) + ], + parameters: { + docs: { + description: { + component: `Группа полей ввода для объединения с аддонами (иконками или текстом). + +\`\`\`typescript +import { ExtraInputGroupComponent, ExtraInputGroupAddonComponent } from '@cdek-it/angular-ui-kit'; +\`\`\`` + } + }, + designTokens: { prefix: '--p-inputgroup' } + }, + argTypes: { + size: { + control: 'select', + options: ['small', 'base', 'large', 'xlarge'], + description: 'Размер группы (влияет на паддинги и шрифты аддонов)', + table: { + category: 'Props', + defaultValue: { summary: "'base'" }, + type: { summary: "'small' | 'base' | 'large' | 'xlarge'" } + } + } + }, + args: { + size: 'base' + } +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + name: 'Default', + render: (args) => { + const groupParts: string[] = []; + if (args.size && args.size !== 'base') groupParts.push(`size="${args.size}"`); + + const groupOpen = groupParts.length + ? `` + : ``; + + const inputSize = args.size && args.size !== 'base' ? ` size="${args.size}"` : ''; + + const template = ` +${groupOpen} + + +`; + + return { props: { ...args, value: '' }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример группы ввода с иконкой. Используйте Controls для изменения размера.', + }, + }, + }, +}; + +export { WithText, AddonRight, AddonBoth, Disabled, Sizes }; diff --git a/src/stories/components/inputmask/examples/inputmask-disabled.component.ts b/src/stories/components/inputmask/examples/inputmask-disabled.component.ts new file mode 100644 index 00000000..22a08a8b --- /dev/null +++ b/src/stories/components/inputmask/examples/inputmask-disabled.component.ts @@ -0,0 +1,47 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputMaskComponent } from '../../../../lib/components/inputmask/inputmask.component'; + +export const Disabled: StoryObj = { + name: 'Disabled', + render: (args) => { + const control = new FormControl({ value: '12-34-56', disabled: true }); + return { + props: { ...args, control }, + template: ``, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraInputMaskComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Отключённое состояние — управляется через `FormControl`.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputMaskComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputMaskComponent, ReactiveFormsModule], + template: \`\`, +}) +export class DisabledExample { + control = new FormControl({ value: '12-34-56', disabled: true }); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputmask/examples/inputmask-float-label.component.ts b/src/stories/components/inputmask/examples/inputmask-float-label.component.ts new file mode 100644 index 00000000..8cd181b8 --- /dev/null +++ b/src/stories/components/inputmask/examples/inputmask-float-label.component.ts @@ -0,0 +1,67 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { InputMask } from 'primeng/inputmask'; +import { FloatLabel } from 'primeng/floatlabel'; + +export const template = ` +
+ + + + +
+`; +const styles = ''; + +@Component({ + selector: 'app-inputmask-float-label', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [InputMask, FloatLabel, ReactiveFormsModule], + template, + styles, +}) +export class InputMaskFloatLabelComponent { + readonly control = new FormControl(''); +} + +export const FloatLabelStory: StoryObj = { + name: 'FloatLabel', + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: + 'Интеграция с `p-floatlabel` — плавающая метка внутри поля. Кликните на поле чтобы увидеть анимацию. Требует нативный `` как прямой дочерний элемент `p-floatlabel`.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { InputMask } from 'primeng/inputmask'; +import { FloatLabel } from 'primeng/floatlabel'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-inputmask-float-label', + standalone: true, + imports: [InputMask, FloatLabel, ReactiveFormsModule], + template: \` + + + + + \`, +}) +export class InputMaskFloatLabelComponent { + readonly control = new FormControl(''); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputmask/examples/inputmask-invalid.component.ts b/src/stories/components/inputmask/examples/inputmask-invalid.component.ts new file mode 100644 index 00000000..c183d3b8 --- /dev/null +++ b/src/stories/components/inputmask/examples/inputmask-invalid.component.ts @@ -0,0 +1,47 @@ +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputMaskComponent } from '../../../../lib/components/inputmask/inputmask.component'; + +export const Invalid: StoryObj = { + name: 'Invalid', + render: (args) => { + const control = new FormControl('', Validators.required); + return { + props: { ...args, control }, + template: ``, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraInputMaskComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Невалидное состояние — определяется через валидаторы `FormControl`.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, Validators, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputMaskComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputMaskComponent, ReactiveFormsModule], + template: \`\`, +}) +export class InvalidExample { + control = new FormControl('', Validators.required); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputmask/examples/inputmask-readonly.component.ts b/src/stories/components/inputmask/examples/inputmask-readonly.component.ts new file mode 100644 index 00000000..f3d12028 --- /dev/null +++ b/src/stories/components/inputmask/examples/inputmask-readonly.component.ts @@ -0,0 +1,47 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputMaskComponent } from '../../../../lib/components/inputmask/inputmask.component'; + +export const Readonly: StoryObj = { + name: 'Readonly', + render: (args) => { + const control = new FormControl('12-34-56'); + return { + props: { ...args, control }, + template: ``, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraInputMaskComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Режим только для чтения — поле отображает значение, но недоступно для редактирования.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputMaskComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputMaskComponent, ReactiveFormsModule], + template: \`\`, +}) +export class ReadonlyExample { + control = new FormControl('12-34-56'); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputmask/examples/inputmask-sizes.component.ts b/src/stories/components/inputmask/examples/inputmask-sizes.component.ts new file mode 100644 index 00000000..470d8bc0 --- /dev/null +++ b/src/stories/components/inputmask/examples/inputmask-sizes.component.ts @@ -0,0 +1,55 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputMaskComponent } from '../../../../lib/components/inputmask/inputmask.component'; + +type Story = StoryObj; + +export const Sizes: Story = { + name: 'Sizes', + render: (args) => ({ + props: { ...args, control: new FormControl('') }, + template: ` + + `, + }), + args: { + mask: '99-99-99', + size: 'small', + placeholder: '99-99-99', + }, + parameters: { + docs: { + description: { + story: 'Размеры поля: small, base, large, xlarge. Переключайте через Controls.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputMaskComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputMaskComponent, ReactiveFormsModule], + template: \`\`, +}) +export class SizesExample { + control = new FormControl(''); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputmask/inputmask.stories.ts b/src/stories/components/inputmask/inputmask.stories.ts new file mode 100644 index 00000000..4fcd0308 --- /dev/null +++ b/src/stories/components/inputmask/inputmask.stories.ts @@ -0,0 +1,231 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputMaskComponent } from '../../../lib/components/inputmask/inputmask.component'; +import { InputMaskFloatLabelComponent, FloatLabelStory } from './examples/inputmask-float-label.component'; +import { Sizes } from './examples/inputmask-sizes.component'; +import { Disabled } from './examples/inputmask-disabled.component'; +import { Readonly } from './examples/inputmask-readonly.component'; +import { Invalid } from './examples/inputmask-invalid.component'; + +type InputMaskArgs = ExtraInputMaskComponent; + +const meta: Meta = { + title: 'Components/Form/InputMask', + component: ExtraInputMaskComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraInputMaskComponent, + FormsModule, + ReactiveFormsModule, + InputMaskFloatLabelComponent, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-inputmask' }, + docs: { + description: { + component: `Компонент текстового ввода по маске. Используется для ввода данных в определённом формате: дата, телефон, серийный номер и т.д. + +\`\`\`typescript +import { ExtraInputMaskComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + }, + argTypes: { + mask: { + control: 'text', + description: 'Маска ввода (9 — цифра, a — буква, * — любой символ)', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + slotChar: { + control: 'text', + description: 'Символ-заполнитель для пустых позиций маски', + table: { + category: 'Props', + defaultValue: { summary: "'_'" }, + type: { summary: 'string' }, + }, + }, + unmask: { + control: 'boolean', + description: 'Возвращать чистое значение без символов маски', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + autoClear: { + control: 'boolean', + description: 'Очищать незавершённое значение при потере фокуса', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + showClear: { + control: 'boolean', + description: 'Показывает иконку очистки при наличии значения', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + placeholder: { + control: 'text', + description: 'Подсказка при пустом поле', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + size: { + control: 'select', + options: ['small', 'base', 'large', 'xlarge'] as const, + description: 'Размер поля', + table: { + category: 'Props', + defaultValue: { summary: "'base'" }, + type: { summary: "'small' | 'base' | 'large' | 'xlarge'" }, + }, + }, + readonly: { + control: 'boolean', + description: 'Только для чтения', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + fluid: { + control: 'boolean', + description: 'Растягивает поле на всю ширину контейнера', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + characterPattern: { + control: 'text', + description: 'Регулярное выражение для символов типа a в маске', + table: { + category: 'Props', + defaultValue: { summary: "'[A-Za-z]'" }, + type: { summary: 'string' }, + }, + }, + keepBuffer: { + control: 'boolean', + description: 'Сохранять введённые символы при очистке маски', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + autocomplete: { + control: 'text', + description: 'Значение атрибута autocomplete для input', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + control: { table: { disable: true } }, + invalid: { table: { disable: true } }, + primeSize: { table: { disable: true } }, + writeValue: { table: { disable: true } }, + registerOnChange: { table: { disable: true } }, + registerOnTouched: { table: { disable: true } }, + setDisabledState: { table: { disable: true } }, + onComplete: { + control: false, + description: 'Событие завершения ввода маски', + table: { category: 'Events', type: { summary: 'EventEmitter' } }, + }, + onFocusEvent: { + control: false, + description: 'Событие фокуса', + table: { category: 'Events', type: { summary: 'EventEmitter' } }, + }, + onBlurEvent: { + control: false, + description: 'Событие потери фокуса', + table: { category: 'Events', type: { summary: 'EventEmitter' } }, + }, + onInputEvent: { + control: false, + description: 'Событие ввода', + table: { category: 'Events', type: { summary: 'EventEmitter' } }, + }, + onKeydownEvent: { + control: false, + description: 'Событие нажатия клавиши', + table: { category: 'Events', type: { summary: 'EventEmitter' } }, + }, + onClearEvent: { + control: false, + description: 'Событие очистки поля', + table: { category: 'Events', type: { summary: 'EventEmitter' } }, + }, + }, + args: { + mask: '99-99-99', + slotChar: '_', + unmask: false, + autoClear: true, + showClear: false, + placeholder: '99-99-99', + size: 'base', + readonly: false, + fluid: false, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.mask) parts.push(`mask="${args.mask}"`); + if (args.slotChar && args.slotChar !== '_') parts.push(`slotChar="${args.slotChar}"`); + if (args.unmask) parts.push(`[unmask]="true"`); + if (!args.autoClear) parts.push(`[autoClear]="false"`); + if (args.showClear) parts.push(`[showClear]="true"`); + if (args.placeholder) parts.push(`placeholder="${args.placeholder}"`); + if (args.size && args.size !== 'base') parts.push(`size="${args.size}"`); + if (args.readonly) parts.push(`[readonly]="true"`); + if (args.fluid) parts.push(`[fluid]="true"`); + parts.push(`[formControl]="control"`); + + const template = ``; + + return { props: { ...args, control: new FormControl('') }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +export { Sizes, FloatLabelStory as FloatLabel, Disabled, Readonly, Invalid }; diff --git a/src/stories/components/inputnumber/examples/inputnumber-buttons.component.ts b/src/stories/components/inputnumber/examples/inputnumber-buttons.component.ts new file mode 100644 index 00000000..74b29b56 --- /dev/null +++ b/src/stories/components/inputnumber/examples/inputnumber-buttons.component.ts @@ -0,0 +1,55 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputNumberComponent } from '../../../../lib/components/inputnumber/inputnumber.component'; + +type Story = StoryObj; + +export const Buttons: Story = { + name: 'Buttons', + render: () => { + const control = new FormControl(null); + return { + props: { control }, + template: ` + + `, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraInputNumberComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Числовое поле с кнопками увеличения/уменьшения в горизонтальной раскладке. Кастомные SVG-иконки +/− используются по умолчанию.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputNumberComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputNumberComponent, ReactiveFormsModule], + template: \`\`, +}) +export class ButtonsExample { + control = new FormControl(null); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputnumber/examples/inputnumber-currency.component.ts b/src/stories/components/inputnumber/examples/inputnumber-currency.component.ts new file mode 100644 index 00000000..dad66ae0 --- /dev/null +++ b/src/stories/components/inputnumber/examples/inputnumber-currency.component.ts @@ -0,0 +1,55 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputNumberComponent } from '../../../../lib/components/inputnumber/inputnumber.component'; + +type Story = StoryObj; + +export const Currency: Story = { + name: 'Currency', + render: () => { + const control = new FormControl(null); + return { + props: { control }, + template: ` + + `, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraInputNumberComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Форматирование значения как валюты через `suffix`. Режим `mode="currency"` не используется из-за известного бага PrimeNG с кареткой.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputNumberComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputNumberComponent, ReactiveFormsModule], + template: \`\`, +}) +export class CurrencyExample { + control = new FormControl(null); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputnumber/examples/inputnumber-disabled.component.ts b/src/stories/components/inputnumber/examples/inputnumber-disabled.component.ts new file mode 100644 index 00000000..3ffa1903 --- /dev/null +++ b/src/stories/components/inputnumber/examples/inputnumber-disabled.component.ts @@ -0,0 +1,55 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputNumberComponent } from '../../../../lib/components/inputnumber/inputnumber.component'; + +type Story = StoryObj; + +export const Disabled: Story = { + name: 'Disabled', + render: () => { + const control = new FormControl({ value: 42, disabled: true }); + return { + props: { control }, + template: ` + + `, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraInputNumberComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Отключённое состояние — поле и кнопки недоступны для взаимодействия.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputNumberComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputNumberComponent, ReactiveFormsModule], + template: \`\`, +}) +export class DisabledExample { + control = new FormControl({ value: 42, disabled: true }); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputnumber/examples/inputnumber-float-label.component.ts b/src/stories/components/inputnumber/examples/inputnumber-float-label.component.ts new file mode 100644 index 00000000..532093fb --- /dev/null +++ b/src/stories/components/inputnumber/examples/inputnumber-float-label.component.ts @@ -0,0 +1,82 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { InputNumber } from 'primeng/inputnumber'; +import { FloatLabel } from 'primeng/floatlabel'; +import { SharedModule } from 'primeng/api'; + +const template = ` +
+ + + + + + + + + + + +
+`; +const styles = ''; + +@Component({ + selector: 'app-inputnumber-float-label', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [InputNumber, FloatLabel, ReactiveFormsModule, SharedModule], + template, + styles, +}) +export class InputNumberFloatLabelComponent { + control = new FormControl(null); +} + +export const FloatLabelStory: StoryObj = { + name: 'FloatLabel', + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: + 'Интеграция с `p-floatlabel` — плавающая метка внутри поля. Требует нативный `p-inputNumber` как прямой дочерний элемент `p-floatlabel`.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { InputNumber } from 'primeng/inputnumber'; +import { FloatLabel } from 'primeng/floatlabel'; +import { SharedModule } from 'primeng/api'; + +@Component({ + standalone: true, + imports: [InputNumber, FloatLabel, ReactiveFormsModule, SharedModule], + template: \` + + + + + + + + + + + + \`, +}) +export class InputNumberFloatLabelExample { + control = new FormControl(null); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputnumber/inputnumber.stories.ts b/src/stories/components/inputnumber/inputnumber.stories.ts new file mode 100644 index 00000000..9c171e80 --- /dev/null +++ b/src/stories/components/inputnumber/inputnumber.stories.ts @@ -0,0 +1,288 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ExtraInputNumberComponent } from '../../../lib/components/inputnumber/inputnumber.component'; +import { InputNumberFloatLabelComponent, FloatLabelStory } from './examples/inputnumber-float-label.component'; +import { Currency } from './examples/inputnumber-currency.component'; +import { Buttons } from './examples/inputnumber-buttons.component'; +import { Disabled } from './examples/inputnumber-disabled.component'; + +type InputNumberArgs = ExtraInputNumberComponent & { disabled: boolean; invalid: boolean }; + +const meta: Meta = { + title: 'Components/Form/InputNumber', + component: ExtraInputNumberComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraInputNumberComponent, + ReactiveFormsModule, + InputNumberFloatLabelComponent, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-inputnumber' }, + docs: { + description: { + component: `Числовое поле ввода с поддержкой форматирования, валюты и кнопок увеличения/уменьшения. + +\`\`\`typescript +import { ExtraInputNumberComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + size: { + control: 'select', + options: ['small', 'base', 'large', 'xlarge'], + description: 'Размер компонента', + table: { + category: 'Props', + defaultValue: { summary: "'base'" }, + type: { summary: "'small' | 'base' | 'large' | 'xlarge'" }, + }, + }, + placeholder: { + control: 'text', + description: 'Подсказка при пустом поле', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + showButtons: { + control: 'boolean', + description: 'Отображает кнопки увеличения/уменьшения', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + buttonLayout: { + control: 'select', + options: ['stacked', 'horizontal', 'vertical'], + description: 'Расположение кнопок', + table: { + category: 'Props', + defaultValue: { summary: "'stacked'" }, + type: { summary: "'stacked' | 'horizontal' | 'vertical'" }, + }, + }, + mode: { + control: 'select', + options: ['decimal', 'currency'], + description: 'Режим форматирования', + table: { + category: 'Props', + defaultValue: { summary: "'decimal'" }, + type: { summary: "'decimal' | 'currency'" }, + }, + }, + currency: { + control: 'text', + description: 'ISO 4217 код валюты (при `mode="currency"`)', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'string' }, + }, + }, + locale: { + control: 'text', + description: 'Локаль для форматирования', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'string' }, + }, + }, + disabled: { + control: 'boolean', + description: 'Отключает взаимодействие — управляется через FormControl', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + invalid: { + control: 'boolean', + description: 'Невалидное состояние — управляется через FormControl', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + readonly: { + control: 'boolean', + description: 'Только для чтения', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + fluid: { + control: 'boolean', + description: 'Растягивает поле на всю ширину контейнера', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + min: { + control: 'number', + description: 'Минимальное значение', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'number' }, + }, + }, + max: { + control: 'number', + description: 'Максимальное значение', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'number' }, + }, + }, + step: { + control: 'number', + description: 'Шаг изменения значения', + table: { + category: 'Props', + defaultValue: { summary: '1' }, + type: { summary: 'number' }, + }, + }, + useGrouping: { + control: 'boolean', + description: 'Использовать разделитель групп разрядов', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + prefix: { + control: 'text', + description: 'Текст перед значением', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'string' }, + }, + }, + suffix: { + control: 'text', + description: 'Текст после значения', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'string' }, + }, + }, + minFractionDigits: { + control: 'number', + description: 'Минимальное количество знаков после запятой', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'number' }, + }, + }, + maxFractionDigits: { + control: 'number', + description: 'Максимальное количество знаков после запятой', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'number' }, + }, + }, + // Hidden computed props + modelValue: { table: { disable: true } }, + inputSizeClass: { table: { disable: true } }, + sizeClass: { table: { disable: true } }, + + // ── Events ─────────────────────────────────────────────── + onInput: { + control: false, + description: 'Событие при изменении значения', + table: { + category: 'Events', + type: { summary: 'EventEmitter<{ value: number | null }>' }, + }, + }, + }, + args: { + size: 'base', + placeholder: 'Введите число...', + showButtons: false, + buttonLayout: 'stacked', + mode: 'decimal', + disabled: false, + invalid: false, + readonly: false, + fluid: false, + step: 1, + useGrouping: true, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.size && args.size !== 'base') parts.push(`size="${args.size}"`); + if (args.placeholder) parts.push(`placeholder="${args.placeholder}"`); + if (args.showButtons) parts.push(`[showButtons]="true"`); + if (args.buttonLayout && args.buttonLayout !== 'stacked') parts.push(`buttonLayout="${args.buttonLayout}"`); + if (args.mode && args.mode !== 'decimal') parts.push(`mode="${args.mode}"`); + if (args.currency) parts.push(`currency="${args.currency}"`); + if (args.locale) parts.push(`locale="${args.locale}"`); + if (args.readonly) parts.push(`[readonly]="true"`); + if (args.fluid) parts.push(`[fluid]="true"`); + if (args.min != null) parts.push(`[min]="${args.min}"`); + if (args.max != null) parts.push(`[max]="${args.max}"`); + if (args.step && args.step !== 1) parts.push(`[step]="${args.step}"`); + if (args.prefix) parts.push(`prefix="${args.prefix}"`); + if (args.suffix) parts.push(`suffix="${args.suffix}"`); + if (args.minFractionDigits != null) parts.push(`[minFractionDigits]="${args.minFractionDigits}"`); + if (args.maxFractionDigits != null) parts.push(`[maxFractionDigits]="${args.maxFractionDigits}"`); + if (!args.useGrouping) parts.push(`[useGrouping]="false"`); + + const validators = []; + if (args.invalid) validators.push(Validators.required); + + const control = new FormControl({ value: null, disabled: args.disabled }, validators); + + const template = ``; + + return { props: { ...args, control }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { Currency, Buttons, Disabled, FloatLabelStory as FloatLabel }; diff --git a/src/stories/components/inputotp/examples/inputotp-disabled.component.ts b/src/stories/components/inputotp/examples/inputotp-disabled.component.ts new file mode 100644 index 00000000..d0dcffa5 --- /dev/null +++ b/src/stories/components/inputotp/examples/inputotp-disabled.component.ts @@ -0,0 +1,47 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputOtpComponent } from '../../../../lib/components/inputotp/inputotp.component'; + +export const Disabled: StoryObj = { + name: 'Disabled', + render: (args) => { + const control = new FormControl({ value: '1234', disabled: true }); + return { + props: { ...args, control }, + template: ``, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraInputOtpComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Заблокированное состояние — управляется через `FormControl`.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputOtpComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputOtpComponent, ReactiveFormsModule], + template: \`\`, +}) +export class DisabledExample { + control = new FormControl({ value: '1234', disabled: true }); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputotp/examples/inputotp-integeronly.component.ts b/src/stories/components/inputotp/examples/inputotp-integeronly.component.ts new file mode 100644 index 00000000..462dc73c --- /dev/null +++ b/src/stories/components/inputotp/examples/inputotp-integeronly.component.ts @@ -0,0 +1,47 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputOtpComponent } from '../../../../lib/components/inputotp/inputotp.component'; + +export const IntegerOnly: StoryObj = { + name: 'IntegerOnly', + render: (args) => { + const control = new FormControl(null); + return { + props: { ...args, control }, + template: ``, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraInputOtpComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Ввод только цифр.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputOtpComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputOtpComponent, ReactiveFormsModule], + template: \`\`, +}) +export class IntegerOnlyExample { + control = new FormControl(null); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputotp/examples/inputotp-invalid.component.ts b/src/stories/components/inputotp/examples/inputotp-invalid.component.ts new file mode 100644 index 00000000..1d0e89ca --- /dev/null +++ b/src/stories/components/inputotp/examples/inputotp-invalid.component.ts @@ -0,0 +1,47 @@ +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputOtpComponent } from '../../../../lib/components/inputotp/inputotp.component'; + +export const Invalid: StoryObj = { + name: 'Invalid', + render: (args) => { + const control = new FormControl('', Validators.required); + return { + props: { ...args, control }, + template: ``, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraInputOtpComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Невалидное состояние — определяется через валидаторы `FormControl`.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, Validators, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputOtpComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputOtpComponent, ReactiveFormsModule], + template: \`\`, +}) +export class InvalidExample { + control = new FormControl('', Validators.required); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputotp/examples/inputotp-mask.component.ts b/src/stories/components/inputotp/examples/inputotp-mask.component.ts new file mode 100644 index 00000000..3f3a5f83 --- /dev/null +++ b/src/stories/components/inputotp/examples/inputotp-mask.component.ts @@ -0,0 +1,47 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputOtpComponent } from '../../../../lib/components/inputotp/inputotp.component'; + +export const Mask: StoryObj = { + name: 'Mask', + render: (args) => { + const control = new FormControl('1234'); + return { + props: { ...args, control }, + template: ``, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraInputOtpComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Маскированный ввод — символы скрыты.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputOtpComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputOtpComponent, ReactiveFormsModule], + template: \`\`, +}) +export class MaskExample { + control = new FormControl('1234'); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputotp/examples/inputotp-readonly.component.ts b/src/stories/components/inputotp/examples/inputotp-readonly.component.ts new file mode 100644 index 00000000..7fea5939 --- /dev/null +++ b/src/stories/components/inputotp/examples/inputotp-readonly.component.ts @@ -0,0 +1,47 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputOtpComponent } from '../../../../lib/components/inputotp/inputotp.component'; + +export const Readonly: StoryObj = { + name: 'Readonly', + render: (args) => { + const control = new FormControl('1234'); + return { + props: { ...args, control }, + template: ``, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraInputOtpComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Режим только для чтения — поле отображает значение, но недоступно для редактирования.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputOtpComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputOtpComponent, ReactiveFormsModule], + template: \`\`, +}) +export class ReadonlyExample { + control = new FormControl('1234'); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputotp/inputotp.stories.ts b/src/stories/components/inputotp/inputotp.stories.ts new file mode 100644 index 00000000..3d74f486 --- /dev/null +++ b/src/stories/components/inputotp/inputotp.stories.ts @@ -0,0 +1,181 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputOtpComponent } from '../../../lib/components/inputotp/inputotp.component'; +import { Disabled } from './examples/inputotp-disabled.component'; +import { Invalid } from './examples/inputotp-invalid.component'; +import { Mask } from './examples/inputotp-mask.component'; +import { Readonly } from './examples/inputotp-readonly.component'; +import { IntegerOnly } from './examples/inputotp-integeronly.component'; + +type InputOtpArgs = ExtraInputOtpComponent; + +const meta: Meta = { + title: 'Components/Form/InputOtp', + component: ExtraInputOtpComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraInputOtpComponent, + ReactiveFormsModule, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-inputotp' }, + docs: { + description: { + component: `Компонент для ввода одноразовых паролей (OTP). + +\`\`\`typescript +import { ExtraInputOtpComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + }, + argTypes: { + length: { + control: 'number', + description: 'Количество символов', + table: { + category: 'Props', + defaultValue: { summary: '4' }, + type: { summary: 'number' }, + }, + }, + mask: { + control: 'boolean', + description: 'Маскирует введённые символы', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + integerOnly: { + control: 'boolean', + description: 'Разрешает ввод только цифр', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + size: { + control: 'select', + options: ['small', 'base', 'large', 'xlarge'] as const, + description: 'Размер поля', + table: { + category: 'Props', + defaultValue: { summary: "'base'" }, + type: { summary: "'small' | 'base' | 'large' | 'xlarge'" }, + }, + }, + + readonly: { + control: 'boolean', + description: 'Только для чтения', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + tabindex: { + control: 'number', + description: 'Значение атрибута tabindex', + table: { + category: 'Props', + defaultValue: { summary: 'null' }, + type: { summary: 'number | null' }, + }, + }, + autofocus: { + control: 'boolean', + description: 'Автоматический фокус при загрузке', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + + // Hidden props + control: { table: { disable: true } }, + disabled: { table: { disable: true } }, + invalid: { table: { disable: true } }, + primeSize: { table: { disable: true } }, + sizeClass: { table: { disable: true } }, + writeValue: { table: { disable: true } }, + registerOnChange: { table: { disable: true } }, + registerOnTouched: { table: { disable: true } }, + setDisabledState: { table: { disable: true } }, + + // Events + onChange: { + control: false, + description: 'Событие изменения значения', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + onFocus: { + control: false, + description: 'Событие фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + onBlur: { + control: false, + description: 'Событие потери фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, + args: { + length: 4, + mask: false, + integerOnly: false, + readonly: false, + autofocus: false, + tabindex: null, + size: 'base' as const, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.length !== 4) parts.push(`[length]="${args.length}"`); + if (args.mask) parts.push(`[mask]="true"`); + if (args.integerOnly) parts.push(`[integerOnly]="true"`); + if (args.size && args.size !== 'base') parts.push(`size="${args.size}"`); + if (args.readonly) parts.push(`[readonly]="true"`); + if (args.autofocus) parts.push(`[autofocus]="true"`); + if (args.tabindex != null) parts.push(`[tabindex]="${args.tabindex}"`); + parts.push(`[formControl]="control"`); + + const template = ``; + + return { props: { ...args, control: new FormControl('') }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +export { Disabled, Readonly, Invalid, Mask, IntegerOnly }; diff --git a/src/stories/components/inputtext/examples/inputtext-clear.component.ts b/src/stories/components/inputtext/examples/inputtext-clear.component.ts new file mode 100644 index 00000000..42937835 --- /dev/null +++ b/src/stories/components/inputtext/examples/inputtext-clear.component.ts @@ -0,0 +1,54 @@ +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputTextComponent } from '../../../../lib/components/inputtext/inputtext.component'; + +@Component({ + selector: 'app-inputtext-clear', + standalone: true, + imports: [ExtraInputTextComponent, ReactiveFormsModule], + template: `` +}) +export class InputTextClearComponent { + control = new FormControl(''); +} + +export const ClearButton: StoryObj = { + name: 'ClearButton', + render: () => ({ + template: ``, + }), + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [InputTextClearComponent], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Поле с кнопкой очистки через `showClear`. Иконка × появляется при вводе первого символа.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { InputTextComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [InputTextComponent, ReactiveFormsModule], + template: \`\`, +}) +export class ClearButtonExample { + control = new FormControl(''); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputtext/examples/inputtext-disabled.component.ts b/src/stories/components/inputtext/examples/inputtext-disabled.component.ts new file mode 100644 index 00000000..53d03a57 --- /dev/null +++ b/src/stories/components/inputtext/examples/inputtext-disabled.component.ts @@ -0,0 +1,45 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputTextComponent } from '../../../../lib/components/inputtext/inputtext.component'; + +export const Disabled: StoryObj = { + name: 'Disabled', + render: (args) => { + const control = new FormControl({ value: '', disabled: true }); + return { + props: { ...args, control }, + template: ``, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraInputTextComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Отключённое состояние — управляется через FormControl.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputTextComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputTextComponent, ReactiveFormsModule], + template: \`\`, +}) +export class DisabledExample { + control = new FormControl({ value: '', disabled: true }); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputtext/examples/inputtext-float-label-invalid.component.ts b/src/stories/components/inputtext/examples/inputtext-float-label-invalid.component.ts new file mode 100644 index 00000000..3be8f15c --- /dev/null +++ b/src/stories/components/inputtext/examples/inputtext-float-label-invalid.component.ts @@ -0,0 +1,79 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { FloatLabel } from 'primeng/floatlabel'; +import { ExtraInputTextComponent } from '../../../../lib/components/inputtext/inputtext.component'; + +@Component({ + selector: 'app-inputtext-float-label-invalid', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ExtraInputTextComponent, FloatLabel, ReactiveFormsModule], + template: ` +
+ + + + +
+ ` +}) +export class InputTextFloatLabelInvalidComponent { + control = new FormControl('', Validators.required); + @Input() required = false; +} + +export const FloatLabelInvalid: StoryObj = { + name: 'FloatLabel + Invalid', + render: (args) => ({ + template: ``, + props: { required: args['required'] }, + }), + args: { required: true }, + argTypes: { + required: { + control: 'boolean', + description: 'Показывает маркер обязательного поля `*` рядом с меткой', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + }, + parameters: { + docs: { + description: { + story: 'FloatLabel с невалидным состоянием — демонстрирует стилизацию ошибки в комбинации с плавающей меткой.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, Validators, ReactiveFormsModule } from '@angular/forms'; +import { InputTextComponent } from '@cdek-it/angular-ui-kit'; +import { FloatLabel } from 'primeng/floatlabel'; + +@Component({ + standalone: true, + imports: [InputTextComponent, FloatLabel, ReactiveFormsModule], + template: \` + + + + + \`, +}) +export class FloatLabelInvalidExample { + control = new FormControl('', Validators.required); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputtext/examples/inputtext-float-label.component.ts b/src/stories/components/inputtext/examples/inputtext-float-label.component.ts new file mode 100644 index 00000000..717eff65 --- /dev/null +++ b/src/stories/components/inputtext/examples/inputtext-float-label.component.ts @@ -0,0 +1,79 @@ +import { Component, Input } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { FloatLabel } from 'primeng/floatlabel'; +import { ExtraInputTextComponent } from '../../../../lib/components/inputtext/inputtext.component'; + +@Component({ + selector: 'app-inputtext-float-label', + standalone: true, + imports: [ExtraInputTextComponent, FloatLabel, ReactiveFormsModule], + template: ` +
+ + + + +
+ ` +}) +export class InputTextFloatLabelComponent { + control = new FormControl(''); + @Input() required = false; +} + +export const FloatLabelStory: StoryObj = { + name: 'FloatLabel', + render: (args) => ({ + template: ``, + props: { required: args['required'] }, + }), + args: { required: true }, + argTypes: { + required: { + control: 'boolean', + description: 'Показывает маркер обязательного поля `*` рядом с меткой', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + }, + parameters: { + docs: { + description: { + story: + 'Интеграция с `p-floatlabel` — плавающая метка внутри поля.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { InputTextComponent } from '@cdek-it/angular-ui-kit'; +import { FloatLabel } from 'primeng/floatlabel'; + +@Component({ + standalone: true, + imports: [InputTextComponent, FloatLabel, ReactiveFormsModule], + template: \` + + + + + \`, +}) +export class FloatLabelExample { + control = new FormControl(''); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputtext/examples/inputtext-invalid.component.ts b/src/stories/components/inputtext/examples/inputtext-invalid.component.ts new file mode 100644 index 00000000..f3a8f25a --- /dev/null +++ b/src/stories/components/inputtext/examples/inputtext-invalid.component.ts @@ -0,0 +1,45 @@ +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputTextComponent } from '../../../../lib/components/inputtext/inputtext.component'; + +export const Invalid: StoryObj = { + name: 'Invalid', + render: (args) => { + const control = new FormControl('', Validators.required); + return { + props: { ...args, control }, + template: ``, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraInputTextComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Невалидное состояние — управляется через FormControl + Validators.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, Validators, ReactiveFormsModule } from '@angular/forms'; +import { ExtraInputTextComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraInputTextComponent, ReactiveFormsModule], + template: \`\`, +}) +export class InvalidExample { + control = new FormControl('', Validators.required); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputtext/examples/inputtext-readonly.component.ts b/src/stories/components/inputtext/examples/inputtext-readonly.component.ts new file mode 100644 index 00000000..37a12849 --- /dev/null +++ b/src/stories/components/inputtext/examples/inputtext-readonly.component.ts @@ -0,0 +1,54 @@ +import { Component } from '@angular/core'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraInputTextComponent } from '../../../../lib/components/inputtext/inputtext.component'; + +@Component({ + selector: 'app-inputtext-readonly', + standalone: true, + imports: [ExtraInputTextComponent, ReactiveFormsModule, FormsModule], + template: `` +}) +export class InputTextReadonlyComponent { + control = new FormControl(''); +} + +export const Readonly: StoryObj = { + name: 'Readonly', + render: () => ({ + template: ``, + }), + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [InputTextReadonlyComponent], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Режим только для чтения — поле отображает значение, но недоступно для редактирования.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { InputTextComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [InputTextComponent, ReactiveFormsModule], + template: \`\`, +}) +export class ReadonlyExample { + control = new FormControl(''); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/inputtext/inputtext.stories.ts b/src/stories/components/inputtext/inputtext.stories.ts new file mode 100644 index 00000000..5b459de9 --- /dev/null +++ b/src/stories/components/inputtext/inputtext.stories.ts @@ -0,0 +1,167 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ExtraInputTextComponent } from '../../../lib/components/inputtext/inputtext.component'; +import { InputTextClearComponent, ClearButton } from './examples/inputtext-clear.component'; +import { InputTextFloatLabelComponent, FloatLabelStory } from './examples/inputtext-float-label.component'; +import { InputTextFloatLabelInvalidComponent, FloatLabelInvalid } from './examples/inputtext-float-label-invalid.component'; +import { Disabled } from './examples/inputtext-disabled.component'; +import { InputTextReadonlyComponent, Readonly } from './examples/inputtext-readonly.component'; +import { Invalid } from './examples/inputtext-invalid.component'; + +type InputTextArgs = ExtraInputTextComponent & { disabled: boolean; invalid: boolean }; + +const meta: Meta = { + title: 'Components/Form/InputText', + component: ExtraInputTextComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraInputTextComponent, + ReactiveFormsModule, + InputTextFloatLabelComponent, + InputTextFloatLabelInvalidComponent, + InputTextReadonlyComponent, + InputTextClearComponent, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-inputtext' }, + docs: { + description: { + component: `Текстовое поле для ввода данных. + +\`\`\`typescript +import { InputTextModule } from 'primeng/inputtext'; +\`\`\``, + }, + }, + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + placeholder: { + control: 'text', + description: 'Подсказка при пустом поле', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + size: { + control: 'select', + options: ['small', 'base', 'large', 'xlarge'], + description: 'Размер поля', + table: { + category: 'Props', + defaultValue: { summary: "'base'" }, + type: { summary: "'small' | 'base' | 'large' | 'xlarge'" }, + }, + }, + disabled: { + control: 'boolean', + description: 'Отключает взаимодействие — управляется через FormControl', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + invalid: { + control: 'boolean', + description: 'Невалидное состояние — управляется через FormControl', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + readonly: { + control: 'boolean', + description: 'Только для чтения', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + showClear: { + control: 'boolean', + description: 'Показывает иконку очистки при наличии значения', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + fluid: { + control: 'boolean', + description: 'Растягивает поле на всю ширину контейнера', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + // Hidden computed props + modelValue: { table: { disable: true } }, + primeSize: { table: { disable: true } }, + sizeClass: { table: { disable: true } }, + + // ── Events ─────────────────────────────────────────────── + onClear: { + control: false, + description: 'Событие очистки поля (при showClear)', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, + args: { + placeholder: 'Введите текст...', + size: 'base', + disabled: false, + invalid: false, + readonly: false, + showClear: false, + fluid: false, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args: Record) => { + const parts: string[] = []; + + if (args['placeholder']) parts.push(`placeholder="${args['placeholder']}"`); + if (args['size'] && args['size'] !== 'base') parts.push(`size="${args['size']}"`); + if (args['readonly']) parts.push(`[readonly]="true"`); + if (args['showClear']) parts.push(`[showClear]="true"`); + if (args['fluid']) parts.push(`[fluid]="true"`); + + const validators = []; + if (args['invalid']) validators.push(Validators.required); + + const control = new FormControl({ value: '', disabled: args['disabled'] as boolean }, validators); + + const template = ``; + + return { props: { ...args, control }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { ClearButton, FloatLabelStory as FloatLabel, FloatLabelInvalid, Disabled, Readonly, Invalid }; diff --git a/src/stories/components/listbox/examples/listbox-checkmark.component.ts b/src/stories/components/listbox/examples/listbox-checkmark.component.ts new file mode 100644 index 00000000..b5ccb60f --- /dev/null +++ b/src/stories/components/listbox/examples/listbox-checkmark.component.ts @@ -0,0 +1,66 @@ +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraListboxComponent } from '../../../../lib/components/listbox/listbox.component'; + +const options = [ + { label: 'New York', value: 'NY' }, + { label: 'Rome', value: 'RM' }, + { label: 'London', value: 'LDN' }, + { label: 'Istanbul', value: 'IST' }, + { label: 'Paris', value: 'PRS' }, +]; + +const template = ` + +`; +const styles = ''; + +@Component({ + selector: 'app-listbox-checkmark', + standalone: true, + imports: [ExtraListboxComponent, ReactiveFormsModule], + template, + styles, +}) +export class ListboxCheckmarkComponent { + ctrl = new FormControl(null); + options = options; +} + +export const Checkmark: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Галочка рядом с выбранным элементом.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; + import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-listbox-checkmark', + standalone: true, + imports: [ExtraListboxComponent, ReactiveFormsModule], + template: \` + + \`, +}) +export class ListboxCheckmarkComponent { + ctrl = new FormControl(null); + options = [ + { label: 'New York', value: 'NY' }, + { label: 'Rome', value: 'RM' }, + { label: 'London', value: 'LDN' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/listbox/examples/listbox-custom.component.ts b/src/stories/components/listbox/examples/listbox-custom.component.ts new file mode 100644 index 00000000..1643bed9 --- /dev/null +++ b/src/stories/components/listbox/examples/listbox-custom.component.ts @@ -0,0 +1,80 @@ +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraListboxComponent } from '../../../../lib/components/listbox/listbox.component'; + +const options = [ + { name: 'Profile', description: 'Manage your account', icon: 'ti ti-user' }, + { name: 'Settings', description: 'App preferences', icon: 'ti ti-settings' }, + { name: 'Messages', description: 'Your inbox', icon: 'ti ti-message' }, +]; + +const template = ` + + + + +
+ {{ item.name }} + {{ item.description }} +
+
+`; + +@Component({ + selector: 'app-listbox-custom', + standalone: true, + imports: [ExtraListboxComponent, ReactiveFormsModule], + template, +}) +export class ListboxCustomComponent { + ctrl = new FormControl(null); + options = options; +} + +export const Custom: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Кастомный шаблон элемента с иконкой и подписью через `itemTemplate`.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-listbox-custom', + standalone: true, + imports: [ExtraListboxComponent, ReactiveFormsModule], + template: \` + + + + +
+ {{ item.name }} + {{ item.description }} +
+
+ \`, +}) +export class ListboxCustomComponent { + ctrl = new FormControl(null); + options = [ + { name: 'Profile', description: 'Manage your account', icon: 'ti ti-user' }, + { name: 'Settings', description: 'App preferences', icon: 'ti ti-settings' }, + { name: 'Messages', description: 'Your inbox', icon: 'ti ti-message' }, + ]; +} + `, + }, + }, + }, +}; + + diff --git a/src/stories/components/listbox/examples/listbox-disabled.component.ts b/src/stories/components/listbox/examples/listbox-disabled.component.ts new file mode 100644 index 00000000..3d59b2a6 --- /dev/null +++ b/src/stories/components/listbox/examples/listbox-disabled.component.ts @@ -0,0 +1,66 @@ +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraListboxComponent } from '../../../../lib/components/listbox/listbox.component'; + +const options = [ + { label: 'New York', value: 'NY' }, + { label: 'Rome', value: 'RM' }, + { label: 'London', value: 'LDN' }, + { label: 'Istanbul', value: 'IST' }, + { label: 'Paris', value: 'PRS' }, +]; + +const template = ` + +`; +const styles = ''; + +@Component({ + selector: 'app-listbox-disabled', + standalone: true, + imports: [ExtraListboxComponent, ReactiveFormsModule], + template, + styles, +}) +export class ListboxDisabledComponent { + ctrl = new FormControl({ value: null, disabled: true }); + options = options; +} + +export const Disabled: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Список в отключённом состоянии — взаимодействие заблокировано.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; + import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-listbox-disabled', + standalone: true, + imports: [ExtraListboxComponent, ReactiveFormsModule], + template: \` + + \`, +}) +export class ListboxDisabledComponent { + ctrl = new FormControl({ value: null, disabled: true }); + options = [ + { label: 'New York', value: 'NY' }, + { label: 'Rome', value: 'RM' }, + { label: 'London', value: 'LDN' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/listbox/examples/listbox-filter.component.ts b/src/stories/components/listbox/examples/listbox-filter.component.ts new file mode 100644 index 00000000..931910fe --- /dev/null +++ b/src/stories/components/listbox/examples/listbox-filter.component.ts @@ -0,0 +1,66 @@ +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraListboxComponent } from '../../../../lib/components/listbox/listbox.component'; + +const options = [ + { label: 'New York', value: 'NY' }, + { label: 'Rome', value: 'RM' }, + { label: 'London', value: 'LDN' }, + { label: 'Istanbul', value: 'IST' }, + { label: 'Paris', value: 'PRS' }, +]; + +const template = ` + +`; +const styles = ''; + +@Component({ + selector: 'app-listbox-filter', + standalone: true, + imports: [ExtraListboxComponent, ReactiveFormsModule], + template, + styles, +}) +export class ListboxFilterComponent { + ctrl = new FormControl(null); + options = options; +} + +export const Filter: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Фильтрация списка по введённому тексту.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; + import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-listbox-filter', + standalone: true, + imports: [ExtraListboxComponent, ReactiveFormsModule], + template: \` + + \`, +}) +export class ListboxFilterComponent { + ctrl = new FormControl(null); + options = [ + { label: 'New York', value: 'NY' }, + { label: 'Rome', value: 'RM' }, + { label: 'London', value: 'LDN' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/listbox/examples/listbox-grouped.component.ts b/src/stories/components/listbox/examples/listbox-grouped.component.ts new file mode 100644 index 00000000..7b0938c9 --- /dev/null +++ b/src/stories/components/listbox/examples/listbox-grouped.component.ts @@ -0,0 +1,102 @@ +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraListboxComponent } from '../../../../lib/components/listbox/listbox.component'; + +const groupedOptions = [ + { + label: 'Германия', + items: [ + { label: 'Берлин', value: 'BE' }, + { label: 'Франкфурт', value: 'FR' }, + { label: 'Гамбург', value: 'HA' }, + ], + }, + { + label: 'США', + items: [ + { label: 'Чикаго', value: 'CH' }, + { label: 'Лос-Анджелес', value: 'LA' }, + { label: 'Нью-Йорк', value: 'NY' }, + ], + }, +]; + +const template = ` + +`; +const styles = ''; + +@Component({ + selector: 'app-listbox-grouped', + standalone: true, + imports: [ExtraListboxComponent, ReactiveFormsModule], + template, + styles, +}) +export class ListboxGroupedComponent { + ctrl = new FormControl(null); + options = groupedOptions; +} + +export const Grouped: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Группировка элементов с заголовками категорий.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; + import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-listbox-grouped', + standalone: true, + imports: [ExtraListboxComponent, ReactiveFormsModule], + template: \` + + \`, +}) +export class ListboxGroupedComponent { + ctrl = new FormControl(null); + options = [ + { + label: 'Германия', + items: [ + { label: 'Берлин', value: 'BE' }, + { label: 'Франкфурт', value: 'FR' }, + ], + }, + { + label: 'США', + items: [ + { label: 'Чикаго', value: 'CH' }, + { label: 'Нью-Йорк', value: 'NY' }, + ], + }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/listbox/examples/listbox-multiple.component.ts b/src/stories/components/listbox/examples/listbox-multiple.component.ts new file mode 100644 index 00000000..92be37d0 --- /dev/null +++ b/src/stories/components/listbox/examples/listbox-multiple.component.ts @@ -0,0 +1,66 @@ +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraListboxComponent } from '../../../../lib/components/listbox/listbox.component'; + +const options = [ + { label: 'New York', value: 'NY' }, + { label: 'Rome', value: 'RM' }, + { label: 'London', value: 'LDN' }, + { label: 'Istanbul', value: 'IST' }, + { label: 'Paris', value: 'PRS' }, +]; + +const template = ` + +`; +const styles = ''; + +@Component({ + selector: 'app-listbox-multiple', + standalone: true, + imports: [ExtraListboxComponent, ReactiveFormsModule], + template, + styles, +}) +export class ListboxMultipleComponent { + ctrl = new FormControl([]); + options = options; +} + +export const Multiple: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Множественный выбор элементов.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; + import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-listbox-multiple', + standalone: true, + imports: [ExtraListboxComponent, ReactiveFormsModule], + template: \` + + \`, +}) +export class ListboxMultipleComponent { + ctrl = new FormControl([]); + options = [ + { label: 'New York', value: 'NY' }, + { label: 'Rome', value: 'RM' }, + { label: 'London', value: 'LDN' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/listbox/listbox.stories.ts b/src/stories/components/listbox/listbox.stories.ts new file mode 100644 index 00000000..0e0933d5 --- /dev/null +++ b/src/stories/components/listbox/listbox.stories.ts @@ -0,0 +1,204 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { Listbox } from 'primeng/listbox'; +import { ExtraListboxComponent } from '../../../lib/components/listbox/listbox.component'; +import { ListboxCheckmarkComponent, Checkmark } from './examples/listbox-checkmark.component'; +import { ListboxFilterComponent, Filter } from './examples/listbox-filter.component'; +import { ListboxMultipleComponent, Multiple } from './examples/listbox-multiple.component'; +import { ListboxGroupedComponent, Grouped } from './examples/listbox-grouped.component'; +import { ListboxCustomComponent, Custom } from './examples/listbox-custom.component'; +import { ListboxDisabledComponent, Disabled } from './examples/listbox-disabled.component'; + +type ListboxArgs = ExtraListboxComponent; + +const meta: Meta = { + title: 'Components/Form/Listbox', + component: ExtraListboxComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraListboxComponent, + ReactiveFormsModule, + Listbox, + ListboxCheckmarkComponent, + ListboxFilterComponent, + ListboxMultipleComponent, + ListboxGroupedComponent, + ListboxCustomComponent, + ListboxDisabledComponent + ] + }) + ], + parameters: { + designTokens: { prefix: '--p-listbox' }, + docs: { + description: { + component: `Список опций с поддержкой одиночного и множественного выбора. Поддерживает группировку, фильтрацию, галочку выбора и кастомные шаблоны пунктов. + +\`\`\`typescript +import { ExtraListboxComponent } from '@cdek-it/angular-ui-kit'; +\`\`\`` + } + } + }, + argTypes: { + options: { table: { disable: true } }, + optionGroupLabel: { table: { disable: true } }, + optionGroupChildren: { table: { disable: true } }, + // ── Props ──────────────────────────────────────────────── + optionLabel: { + control: 'text', + description: 'Поле объекта для отображения текста элемента', + table: { + category: 'Props', + defaultValue: { summary: 'label' }, + type: { summary: 'string' } + } + }, + optionValue: { + control: 'text', + description: 'Поле объекта, используемое как значение', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'string' } + } + }, + multiple: { + control: 'boolean', + description: 'Множественный выбор', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + filter: { + control: 'boolean', + description: 'Показывать строку фильтрации', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + filterPlaceHolder: { + control: 'text', + description: 'Placeholder строки фильтрации', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'string' } + } + }, + checkmark: { + control: 'boolean', + description: 'Показывать галочку у выбранного элемента', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + group: { + control: 'boolean', + description: 'Группировка элементов', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + scrollHeight: { + control: 'text', + description: 'Высота прокручиваемой области', + table: { + category: 'Props', + defaultValue: { summary: '200px' }, + type: { summary: 'string' } + } + }, + emptyMessage: { + control: 'text', + description: 'Текст при пустом списке', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'string' } + } + }, + // ── Events ─────────────────────────────────────────────── + onFocus: { + control: false, + description: 'Событие фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' } + } + }, + onBlur: { + control: false, + description: 'Событие потери фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' } + } + } + }, + args: { + optionLabel: 'label', + multiple: false, + filter: false, + checkmark: false, + group: false, + scrollHeight: '200px' + } +}; + +export default meta; +type Story = StoryObj; + +const defaultOptions = [ + { label: 'New York', value: 'NY' }, + { label: 'Rome', value: 'RM' }, + { label: 'London', value: 'LDN' }, + { label: 'Istanbul', value: 'IST' }, + { label: 'Paris', value: 'PRS' } +]; + +// ── Default ────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => ({ + props: { + ...args, + ctrl: new FormControl(null), + options: defaultOptions + }, + template: ` +` + }), + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.' + } + } + } +}; + +// ── Варианты ───────────────────────────────────────────────────────────────── + +export { Checkmark, Filter, Multiple, Grouped, Custom, Disabled }; diff --git a/src/stories/components/megamenu/examples/megamenu-custom.component.ts b/src/stories/components/megamenu/examples/megamenu-custom.component.ts new file mode 100644 index 00000000..a2bc0ab8 --- /dev/null +++ b/src/stories/components/megamenu/examples/megamenu-custom.component.ts @@ -0,0 +1,81 @@ +import { Component } from '@angular/core'; +import { ExtraMegaMenuComponent, MegaMenuModel } from '../../../../lib/components/megamenu/megamenu.component'; + +const template = ``; + +@Component({ + selector: 'app-megamenu-custom', + standalone: true, + imports: [ExtraMegaMenuComponent], + template, +}) +export class MegaMenuCustomComponent { + items: MegaMenuModel[] = [ + { + label: 'Products', + icon: 'ti ti-box', + items: [ + [ + { + label: 'Components', + items: [ + { + label: 'Form', + description: 'Input, Select, Checkbox', + icon: 'ti ti-forms', + badge: 'New', + }, + { + label: 'Button', + description: 'Actions and triggers', + icon: 'ti ti-hand-click', + }, + ], + }, + ], + [ + { + label: 'Charts', + items: [ + { + label: 'Bar Chart', + description: 'Categorical comparison', + icon: 'ti ti-chart-bar', + }, + { + label: 'Line Chart', + description: 'Trends over time', + icon: 'ti ti-chart-line', + badge: 'Beta', + }, + ], + }, + ], + ], + }, + { + label: 'Solutions', + icon: 'ti ti-bulb', + items: [ + [ + { + label: 'Business', + items: [ + { + label: 'Analytics', + description: 'Reports and dashboards', + icon: 'ti ti-chart-dots', + }, + { + label: 'CRM', + description: 'Customer management', + icon: 'ti ti-users', + badge: 'Pro', + }, + ], + }, + ], + ], + }, + ]; +} diff --git a/src/stories/components/megamenu/examples/megamenu-horizontal.component.ts b/src/stories/components/megamenu/examples/megamenu-horizontal.component.ts new file mode 100644 index 00000000..e494169c --- /dev/null +++ b/src/stories/components/megamenu/examples/megamenu-horizontal.component.ts @@ -0,0 +1,60 @@ +import { Component } from '@angular/core'; +import { ExtraMegaMenuComponent, MegaMenuModel } from '../../../../lib/components/megamenu/megamenu.component'; + +const template = ``; + +@Component({ + selector: 'app-megamenu-horizontal', + standalone: true, + imports: [ExtraMegaMenuComponent], + template, +}) +export class MegaMenuHorizontalComponent { + items: MegaMenuModel[] = [ + { + label: 'Products', + icon: 'ti ti-box', + items: [ + [ + { + label: 'UI Components', + items: [ + { label: 'Form', icon: 'ti ti-forms' }, + { label: 'Button', icon: 'ti ti-hand-click' }, + { label: 'Table', icon: 'ti ti-table' }, + ], + }, + ], + [ + { + label: 'Charts', + items: [ + { label: 'Bar Chart', icon: 'ti ti-chart-bar' }, + { label: 'Line Chart', icon: 'ti ti-chart-line' }, + ], + }, + ], + ], + }, + { + label: 'Solutions', + icon: 'ti ti-bulb', + items: [ + [ + { + label: 'Business', + items: [ + { label: 'Analytics', icon: 'ti ti-chart-dots' }, + { label: 'CRM', icon: 'ti ti-users' }, + ], + }, + ], + ], + }, + { + label: 'Contact', + icon: 'ti ti-mail', + disabled: true, + }, + ]; +} diff --git a/src/stories/components/megamenu/examples/megamenu-vertical.component.ts b/src/stories/components/megamenu/examples/megamenu-vertical.component.ts new file mode 100644 index 00000000..ed1a6e8a --- /dev/null +++ b/src/stories/components/megamenu/examples/megamenu-vertical.component.ts @@ -0,0 +1,60 @@ +import { Component } from '@angular/core'; +import { ExtraMegaMenuComponent, MegaMenuModel } from '../../../../lib/components/megamenu/megamenu.component'; + +const template = ``; + +@Component({ + selector: 'app-megamenu-vertical', + standalone: true, + imports: [ExtraMegaMenuComponent], + template, +}) +export class MegaMenuVerticalComponent { + items: MegaMenuModel[] = [ + { + label: 'Products', + icon: 'ti ti-box', + items: [ + [ + { + label: 'UI Components', + items: [ + { label: 'Form', icon: 'ti ti-forms' }, + { label: 'Button', icon: 'ti ti-hand-click' }, + { label: 'Table', icon: 'ti ti-table' }, + ], + }, + ], + [ + { + label: 'Charts', + items: [ + { label: 'Bar Chart', icon: 'ti ti-chart-bar' }, + { label: 'Line Chart', icon: 'ti ti-chart-line' }, + ], + }, + ], + ], + }, + { + label: 'Solutions', + icon: 'ti ti-bulb', + items: [ + [ + { + label: 'Business', + items: [ + { label: 'Analytics', icon: 'ti ti-chart-dots' }, + { label: 'CRM', icon: 'ti ti-users' }, + ], + }, + ], + ], + }, + { + label: 'Contact', + icon: 'ti ti-mail', + disabled: true, + }, + ]; +} diff --git a/src/stories/components/megamenu/megamenu.stories.ts b/src/stories/components/megamenu/megamenu.stories.ts new file mode 100644 index 00000000..c6c1cc02 --- /dev/null +++ b/src/stories/components/megamenu/megamenu.stories.ts @@ -0,0 +1,460 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraMegaMenuComponent, MegaMenuModel } from '../../../lib/components/megamenu/megamenu.component'; + +const meta: Meta = { + title: 'Components/Menu/MegaMenu', + component: ExtraMegaMenuComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ExtraMegaMenuComponent], + }), + ], + parameters: { + docs: { + description: { + component: `Расширенное меню с поддержкой многоколоночных подменю. Поддерживает горизонтальную и вертикальную ориентацию. + +\`\`\`typescript +import { ExtraMegaMenuComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + story: { + height: '300px', + }, + }, + designTokens: { prefix: '--p-megamenu' }, + }, + argTypes: { + model: { + control: false, + description: 'Массив пунктов меню.', + table: { + category: 'Props', + type: { summary: 'MegaMenuModel[]' }, + }, + }, + orientation: { + control: 'select', + options: ['horizontal', 'vertical'], + description: 'Ориентация меню.', + table: { + category: 'Props', + defaultValue: { summary: 'horizontal' }, + type: { summary: "'horizontal' | 'vertical'" }, + }, + }, + disabled: { + control: 'boolean', + description: 'Отключает взаимодействие с меню.', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + }, + args: { + orientation: 'horizontal', + disabled: false, + }, +}; + +export default meta; +type Story = StoryObj; + +const baseItems: MegaMenuModel[] = [ + { + label: 'Products', + icon: 'ti ti-box', + items: [ + [ + { + label: 'UI Components', + items: [ + { label: 'Form', icon: 'ti ti-forms' }, + { label: 'Button', icon: 'ti ti-hand-click' }, + { label: 'Table', icon: 'ti ti-table' }, + ], + }, + ], + [ + { + label: 'Charts', + items: [ + { label: 'Bar Chart', icon: 'ti ti-chart-bar' }, + { label: 'Line Chart', icon: 'ti ti-chart-line' }, + ], + }, + ], + ], + }, + { + label: 'Solutions', + icon: 'ti ti-bulb', + items: [ + [ + { + label: 'Business', + items: [ + { label: 'Analytics', icon: 'ti ti-chart-dots' }, + { label: 'CRM', icon: 'ti ti-users' }, + ], + }, + ], + ], + }, + { + label: 'Contact', + icon: 'ti ti-mail', + disabled: true, + }, +]; + +const commonTemplate = ` + +`; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = ['[model]="model"']; + + if (args.orientation && args.orientation !== 'horizontal') parts.push(`orientation="${args.orientation}"`); + if (args.disabled) parts.push(`[disabled]="true"`); + + const template = ``; + return { props: { ...args, model: baseItems }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Horizontal ────────────────────────────────────────────────────────────── +export const Horizontal: Story = { + render: (args) => ({ + props: { ...args, model: baseItems }, + template: commonTemplate, + }), + args: { orientation: 'horizontal' }, + parameters: { + docs: { + description: { story: 'Горизонтальная ориентация (по умолчанию).' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMegaMenuComponent, MegaMenuModel } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraMegaMenuComponent], + template: \`\`, +}) +export class HorizontalExample { + items: MegaMenuModel[] = [ + { + label: 'Products', + icon: 'ti ti-box', + items: [ + [ + { + label: 'UI Components', + items: [ + { label: 'Form', icon: 'ti ti-forms' }, + { label: 'Button', icon: 'ti ti-hand-click' }, + { label: 'Table', icon: 'ti ti-table' }, + ], + }, + ], + [ + { + label: 'Charts', + items: [ + { label: 'Bar Chart', icon: 'ti ti-chart-bar' }, + { label: 'Line Chart', icon: 'ti ti-chart-line' }, + ], + }, + ], + ], + }, + { + label: 'Solutions', + icon: 'ti ti-bulb', + items: [ + [ + { + label: 'Business', + items: [ + { label: 'Analytics', icon: 'ti ti-chart-dots' }, + { label: 'CRM', icon: 'ti ti-users' }, + ], + }, + ], + ], + }, + { + label: 'Contact', + icon: 'ti ti-mail', + disabled: true, + }, + ]; +} + `, + }, + }, + }, +}; + +// ── Vertical ──────────────────────────────────────────────────────────────── +export const Vertical: Story = { + render: (args) => ({ + props: { ...args, model: baseItems }, + template: commonTemplate, + }), + args: { orientation: 'vertical' }, + parameters: { + docs: { + description: { story: 'Вертикальная ориентация.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMegaMenuComponent, MegaMenuModel } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraMegaMenuComponent], + template: \`\`, +}) +export class VerticalExample { + items: MegaMenuModel[] = [ + { + label: 'Products', + icon: 'ti ti-box', + items: [ + [ + { + label: 'UI Components', + items: [ + { label: 'Form', icon: 'ti ti-forms' }, + { label: 'Button', icon: 'ti ti-hand-click' }, + { label: 'Table', icon: 'ti ti-table' }, + ], + }, + ], + [ + { + label: 'Charts', + items: [ + { label: 'Bar Chart', icon: 'ti ti-chart-bar' }, + { label: 'Line Chart', icon: 'ti ti-chart-line' }, + ], + }, + ], + ], + }, + { + label: 'Solutions', + icon: 'ti ti-bulb', + items: [ + [ + { + label: 'Business', + items: [ + { label: 'Analytics', icon: 'ti ti-chart-dots' }, + { label: 'CRM', icon: 'ti ti-users' }, + ], + }, + ], + ], + }, + { + label: 'Contact', + icon: 'ti ti-mail', + disabled: true, + }, + ]; +} + `, + }, + }, + }, +}; + +// ── Custom ────────────────────────────────────────────────────────────────── +const customItems: MegaMenuModel[] = [ + { + label: 'Products', + icon: 'ti ti-box', + items: [ + [ + { + label: 'Components', + items: [ + { + label: 'Form', + description: 'Input, Select, Checkbox', + icon: 'ti ti-forms', + badge: 'New', + }, + { + label: 'Button', + description: 'Actions and triggers', + icon: 'ti ti-hand-click', + }, + ], + }, + ], + [ + { + label: 'Charts', + items: [ + { + label: 'Bar Chart', + description: 'Categorical comparison', + icon: 'ti ti-chart-bar', + }, + { + label: 'Line Chart', + description: 'Trends over time', + icon: 'ti ti-chart-line', + badge: 'Beta', + }, + ], + }, + ], + ], + }, + { + label: 'Solutions', + icon: 'ti ti-bulb', + items: [ + [ + { + label: 'Business', + items: [ + { + label: 'Analytics', + description: 'Reports and dashboards', + icon: 'ti ti-chart-dots', + }, + { + label: 'CRM', + description: 'Customer management', + icon: 'ti ti-users', + badge: 'Pro', + }, + ], + }, + ], + ], + }, +]; + +export const Custom: Story = { + render: (args) => ({ + props: { ...args, model: customItems }, + template: commonTemplate, + }), + parameters: { + docs: { + description: { + story: 'Пункты меню с описанием (description) и бейджами.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMegaMenuComponent, MegaMenuModel } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraMegaMenuComponent], + template: \`\`, +}) +export class CustomExample { + items: MegaMenuModel[] = [ + { + label: 'Products', + icon: 'ti ti-box', + items: [ + [ + { + label: 'Components', + items: [ + { + label: 'Form', + description: 'Input, Select, Checkbox', + icon: 'ti ti-forms', + badge: 'New', + }, + { + label: 'Button', + description: 'Actions and triggers', + icon: 'ti ti-hand-click', + }, + ], + }, + ], + [ + { + label: 'Charts', + items: [ + { + label: 'Bar Chart', + description: 'Categorical comparison', + icon: 'ti ti-chart-bar', + }, + { + label: 'Line Chart', + description: 'Trends over time', + icon: 'ti ti-chart-line', + badge: 'Beta', + }, + ], + }, + ], + ], + }, + { + label: 'Solutions', + icon: 'ti ti-bulb', + items: [ + [ + { + label: 'Business', + items: [ + { + label: 'Analytics', + description: 'Reports and dashboards', + icon: 'ti ti-chart-dots', + }, + { + label: 'CRM', + description: 'Customer management', + icon: 'ti ti-users', + badge: 'Pro', + }, + ], + }, + ], + ], + }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/menu/examples/menu-basic.component.ts b/src/stories/components/menu/examples/menu-basic.component.ts new file mode 100644 index 00000000..48d59eed --- /dev/null +++ b/src/stories/components/menu/examples/menu-basic.component.ts @@ -0,0 +1,23 @@ +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '../../../../lib/components/menu/menu.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-menu-basic', + standalone: true, + imports: [ExtraMenuComponent], + template, +}) +export class MenuBasicComponent { + items: ExtraMenuModel[] = [ + { label: 'Новый заказ' }, + { label: 'Поиск отправления' }, + { separator: true }, + { label: 'Экспорт' }, + ]; +} diff --git a/src/stories/components/menu/examples/menu-custom.component.ts b/src/stories/components/menu/examples/menu-custom.component.ts new file mode 100644 index 00000000..12237473 --- /dev/null +++ b/src/stories/components/menu/examples/menu-custom.component.ts @@ -0,0 +1,71 @@ +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '../../../../lib/components/menu/menu.component'; + +const template = ` +
+ +
+ + + + @if (item.icon) { + + } +
+ {{ item.label }} + @if (item.caption) { + {{ item.caption }} + } +
+ @if (item.badge) { + + {{ item.badge }} + + } +
+
+`; + +@Component({ + selector: 'app-menu-custom', + standalone: true, + imports: [ExtraMenuComponent], + template, +}) +export class MenuCustomComponent { + items: ExtraMenuModel[] = [ + { + label: 'Создать отправление', + caption: 'Оформление нового заказа', + icon: 'ti ti-file-plus', + badge: 'Новое', + }, + { + label: 'Найти посылку', + caption: 'Поиск по трек-номеру', + icon: 'ti ti-map-pin', + }, + { separator: true }, + { + label: 'Экспорт данных', + caption: 'Выгрузка в CSV или Excel', + icon: 'ti ti-download', + }, + { + label: 'Удалить', + caption: 'Действие недоступно', + icon: 'ti ti-trash', + disabled: true, + }, + ]; +} diff --git a/src/stories/components/menu/examples/menu-grouped.component.ts b/src/stories/components/menu/examples/menu-grouped.component.ts new file mode 100644 index 00000000..3d838ce1 --- /dev/null +++ b/src/stories/components/menu/examples/menu-grouped.component.ts @@ -0,0 +1,35 @@ +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '../../../../lib/components/menu/menu.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-menu-grouped', + standalone: true, + imports: [ExtraMenuComponent], + template, +}) +export class MenuGroupedComponent { + items: ExtraMenuModel[] = [ + { + label: 'Заказы', + items: [ + { label: 'Новый заказ', icon: 'ti ti-plus' }, + { label: 'Список заказов', icon: 'ti ti-list' }, + { label: 'Архив', icon: 'ti ti-archive' }, + ], + }, + { + label: 'Отправления', + items: [ + { label: 'Создать накладную', icon: 'ti ti-file-invoice' }, + { label: 'Отследить посылку', icon: 'ti ti-map-pin' }, + { label: 'Отменить отправление', icon: 'ti ti-ban' }, + ], + }, + ]; +} diff --git a/src/stories/components/menu/examples/menu-popup.component.ts b/src/stories/components/menu/examples/menu-popup.component.ts new file mode 100644 index 00000000..fd222ee4 --- /dev/null +++ b/src/stories/components/menu/examples/menu-popup.component.ts @@ -0,0 +1,31 @@ +import { Component, ViewChild } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '../../../../lib/components/menu/menu.component'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; + +const template = ` +
+ + +
+`; + +@Component({ + selector: 'app-menu-popup', + standalone: true, + imports: [ExtraMenuComponent, ExtraButtonComponent], + template, +}) +export class MenuPopupComponent { + @ViewChild('menuRef') menuRef!: ExtraMenuComponent; + + items: ExtraMenuModel[] = [ + { label: 'Создать отправление', icon: 'ti ti-file-plus' }, + { label: 'Найти по трек-номеру', icon: 'ti ti-search' }, + { separator: true }, + { label: 'Экспорт данных', icon: 'ti ti-download' }, + ]; + + toggle(event: Event): void { + this.menuRef.toggle(event); + } +} diff --git a/src/stories/components/menu/examples/menu-with-icons.component.ts b/src/stories/components/menu/examples/menu-with-icons.component.ts new file mode 100644 index 00000000..061da8d6 --- /dev/null +++ b/src/stories/components/menu/examples/menu-with-icons.component.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '../../../../lib/components/menu/menu.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-menu-with-icons', + standalone: true, + imports: [ExtraMenuComponent], + template, +}) +export class MenuWithIconsComponent { + items: ExtraMenuModel[] = [ + { label: 'Создать отправление', icon: 'ti ti-file-plus' }, + { label: 'Открыть список заказов', icon: 'ti ti-folder-open' }, + { label: 'Сохранить черновик', icon: 'ti ti-device-floppy' }, + { separator: true }, + { label: 'Распечатать накладную', icon: 'ti ti-printer' }, + { label: 'Экспорт данных', icon: 'ti ti-download' }, + ]; +} diff --git a/src/stories/components/menu/menu.stories.ts b/src/stories/components/menu/menu.stories.ts new file mode 100644 index 00000000..42349142 --- /dev/null +++ b/src/stories/components/menu/menu.stories.ts @@ -0,0 +1,311 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraMenuComponent } from '../../../lib/components/menu/menu.component'; +import { MenuPopupComponent } from './examples/menu-popup.component'; +import { MenuBasicComponent } from './examples/menu-basic.component'; +import { MenuWithIconsComponent } from './examples/menu-with-icons.component'; +import { MenuGroupedComponent } from './examples/menu-grouped.component'; +import { MenuCustomComponent } from './examples/menu-custom.component'; + +const meta: Meta = { + title: 'Components/Menu/Menu', + component: ExtraMenuComponent, + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: `Компонент навигационного меню. Поддерживает режим popup (по нажатию на триггер) и inline-отображение, группировку пунктов и пункты с описанием (caption). + +\`\`\`typescript +import { ExtraMenuComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-menu' }, + }, + argTypes: { + model: { + control: false, + description: 'Массив пунктов меню.', + table: { + category: 'Props', + type: { summary: 'ExtraMenuModel[]' }, + }, + }, + popup: { + control: 'boolean', + description: 'Режим popup — меню отображается при вызове метода toggle().', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Popup ───────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Popup', + decorators: [moduleMetadata({ imports: [MenuPopupComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Меню вызывается по нажатию на кнопку. Используйте метод toggle() для показа/скрытия.', + }, + source: { + language: 'ts', + code: ` +import { Component, ViewChild } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel, ButtonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-menu-popup', + standalone: true, + imports: [ExtraMenuComponent, ButtonComponent], + template: \` + + + \`, +}) +export class MenuPopupComponent { + @ViewChild('menuRef') menuRef!: ExtraMenuComponent; + + items: ExtraMenuModel[] = [ + { label: 'Создать отправление', icon: 'ti ti-file-plus' }, + { label: 'Найти по трек-номеру', icon: 'ti ti-search' }, + { separator: true }, + { label: 'Экспорт данных', icon: 'ti ti-download' }, + ]; + + toggle(event: Event): void { + this.menuRef.toggle(event); + } +} + `, + }, + }, + }, +}; + +// ── Basic ───────────────────────────────────────────────────────────────────── + +export const Basic: Story = { + name: 'Basic', + decorators: [moduleMetadata({ imports: [MenuBasicComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Базовый вариант inline-меню без иконок.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-menu-basic', + standalone: true, + imports: [ExtraMenuComponent], + template: \` + + \`, +}) +export class MenuBasicComponent { + items: ExtraMenuModel[] = [ + { label: 'Новый заказ' }, + { label: 'Поиск отправления' }, + { separator: true }, + { label: 'Экспорт' }, + ]; +} + `, + }, + }, + }, +}; + +// ── WithIcons ───────────────────────────────────────────────────────────────── + +export const WithIcons: Story = { + name: 'WithIcons', + decorators: [moduleMetadata({ imports: [MenuWithIconsComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Пункты меню с иконками.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-menu-with-icons', + standalone: true, + imports: [ExtraMenuComponent], + template: \` + + \`, +}) +export class MenuWithIconsComponent { + items: ExtraMenuModel[] = [ + { label: 'Создать отправление', icon: 'ti ti-file-plus' }, + { label: 'Открыть список заказов', icon: 'ti ti-folder-open' }, + { label: 'Сохранить черновик', icon: 'ti ti-device-floppy' }, + { separator: true }, + { label: 'Распечатать накладную', icon: 'ti ti-printer' }, + { label: 'Экспорт данных', icon: 'ti ti-download' }, + ]; +} + `, + }, + }, + }, +}; + +// ── Grouped ─────────────────────────────────────────────────────────────────── + +export const Grouped: Story = { + name: 'Grouped', + decorators: [moduleMetadata({ imports: [MenuGroupedComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Группировка пунктов меню через label у родительского элемента.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-menu-grouped', + standalone: true, + imports: [ExtraMenuComponent], + template: \` + + \`, +}) +export class MenuGroupedComponent { + items: ExtraMenuModel[] = [ + { + label: 'Заказы', + items: [ + { label: 'Новый заказ', icon: 'ti ti-plus' }, + { label: 'Список заказов', icon: 'ti ti-list' }, + { label: 'Архив', icon: 'ti ti-archive' }, + ], + }, + { + label: 'Отправления', + items: [ + { label: 'Создать накладную', icon: 'ti ti-file-invoice' }, + { label: 'Отследить посылку', icon: 'ti ti-map-pin' }, + { label: 'Отменить отправление', icon: 'ti ti-ban' }, + ], + }, + ]; +} + `, + }, + }, + }, +}; + +// ── Custom ──────────────────────────────────────────────────────────────────── + +export const Custom: Story = { + name: 'Custom', + decorators: [moduleMetadata({ imports: [MenuCustomComponent] })], + render: () => ({ template: `` }), + parameters: { + docs: { + description: { + story: 'Кастомизация отображения пунктов меню через входной параметр `itemTemplate`. Передайте `ng-template` с произвольной разметкой — он получит объект пункта меню через `let-item`.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMenuComponent, ExtraMenuModel } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-menu-custom', + standalone: true, + imports: [ExtraMenuComponent], + template: \` + + + + + @if (item.icon) { + + } +
+ {{ item.label }} + @if (item.caption) { + {{ item.caption }} + } +
+ @if (item.badge) { + + {{ item.badge }} + + } +
+
+ \`, +}) +export class MenuCustomComponent { + items: ExtraMenuModel[] = [ + { + label: 'Создать отправление', + caption: 'Оформление нового заказа', + icon: 'ti ti-file-plus', + badge: 'Новое', + }, + { + label: 'Найти посылку', + caption: 'Поиск по трек-номеру', + icon: 'ti ti-map-pin', + }, + { separator: true }, + { + label: 'Экспорт данных', + caption: 'Выгрузка в CSV или Excel', + icon: 'ti ti-download', + }, + { + label: 'Удалить', + caption: 'Действие недоступно', + icon: 'ti ti-trash', + disabled: true, + }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/message/examples/message-severities.component.ts b/src/stories/components/message/examples/message-severities.component.ts new file mode 100644 index 00000000..ffa64456 --- /dev/null +++ b/src/stories/components/message/examples/message-severities.component.ts @@ -0,0 +1,41 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraMessageComponent } from '../../../../lib/components/message/message.component'; + +const template = ` +
+ + + + +
+`; + +@Component({ + selector: 'app-message-severities', + standalone: true, + imports: [ExtraMessageComponent], + template, +}) +export class MessageSeveritiesComponent {} + +export const Severities: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Четыре типа сообщений: информация, успех, предупреждение, ошибка.' }, + source: { + language: 'html', + code: ` + + + + + `, + }, + }, + }, +}; diff --git a/src/stories/components/message/examples/message-with-close-button.component.ts b/src/stories/components/message/examples/message-with-close-button.component.ts new file mode 100644 index 00000000..0e59bdf1 --- /dev/null +++ b/src/stories/components/message/examples/message-with-close-button.component.ts @@ -0,0 +1,36 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraMessageComponent } from '../../../../lib/components/message/message.component'; + +const template = ` +
+ + + + +
+`; + +@Component({ + selector: 'app-message-with-close-button', + standalone: true, + imports: [ExtraMessageComponent], + template, +}) +export class MessageWithCloseButtonComponent {} + +export const WithCloseButton: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Сообщения с кнопкой закрытия.' }, + source: { + language: 'html', + code: ``, + }, + }, + }, +}; diff --git a/src/stories/components/message/examples/message-with-content.component.ts b/src/stories/components/message/examples/message-with-content.component.ts new file mode 100644 index 00000000..c5889796 --- /dev/null +++ b/src/stories/components/message/examples/message-with-content.component.ts @@ -0,0 +1,78 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraMessageComponent } from '../../../../lib/components/message/message.component'; + +const template = ` +
+ +
+
CONTENT
+
+
+
Cell 1
+
Cell 2
+
+
+ +
+
CONTENT
+
+
+
Cell 1
+
Cell 2
+
+
+ +
+
CONTENT
+
+
+
Cell 1
+
Cell 2
+
+
+ +
+
CONTENT
+
+
+
Cell 1
+
Cell 2
+
+
+
+`; + +@Component({ + selector: 'app-message-with-content', + standalone: true, + imports: [ExtraMessageComponent], + template, +}) +export class MessageWithContentComponent {} + +export const WithContent: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Сообщения с дополнительным контентом и кнопкой закрытия.' }, + source: { + language: 'html', + code: ` + +
+
CONTENT
+
+
+
Cell 1
+
Cell 2
+
+
+ `, + }, + }, + }, +}; diff --git a/src/stories/components/message/message.stories.ts b/src/stories/components/message/message.stories.ts new file mode 100644 index 00000000..a31111cd --- /dev/null +++ b/src/stories/components/message/message.stories.ts @@ -0,0 +1,125 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraMessageComponent } from '../../../lib/components/message/message.component'; +import { MessageSeveritiesComponent, Severities } from './examples/message-severities.component'; +import { MessageWithCloseButtonComponent, WithCloseButton } from './examples/message-with-close-button.component'; +import { MessageWithContentComponent, WithContent } from './examples/message-with-content.component'; + +type MessageArgs = ExtraMessageComponent; + +const meta: Meta = { + title: 'Components/Feedback/Message', + component: ExtraMessageComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraMessageComponent, + MessageSeveritiesComponent, + MessageWithCloseButtonComponent, + MessageWithContentComponent, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-message' }, + docs: { + description: { + component: `Компонент для отображения встроенных уведомлений с различными уровнями важности. + +\`\`\`typescript +import { ExtraMessageComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + severity: { + control: 'select', + options: ['info', 'success', 'warn', 'error', 'secondary', 'contrast'], + description: 'Тип сообщения.', + table: { + category: 'Props', + defaultValue: { summary: 'info' }, + type: { summary: "'info' | 'success' | 'warn' | 'error' | 'secondary' | 'contrast'" }, + }, + }, + summary: { + control: 'text', + description: 'Заголовок сообщения.', + table: { category: 'Props', type: { summary: 'string' } }, + }, + detail: { + control: 'text', + description: 'Подробный текст сообщения.', + table: { category: 'Props', type: { summary: 'string' } }, + }, + icon: { + control: 'text', + description: 'CSS-класс иконки. По умолчанию подбирается по severity.', + table: { category: 'Props', type: { summary: 'string' } }, + }, + closable: { + control: 'boolean', + description: 'Показывает кнопку закрытия.', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + life: { + control: 'number', + description: 'Время автоматического закрытия в миллисекундах. 0 — отключено.', + table: { + category: 'Props', + defaultValue: { summary: '0' }, + type: { summary: 'number' }, + }, + }, + // ── Events ─────────────────────────────────────────────── + onClose: { + control: false, + description: 'Событие закрытия сообщения.', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, + args: { + severity: 'info', + summary: 'Message', + detail: 'caption', + closable: false, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = [`severity="${args.severity}"`]; + if (args.summary) parts.push(`summary="${args.summary}"`); + if (args.detail) parts.push(`detail="${args.detail}"`); + if (args.icon) parts.push(`icon="${args.icon}"`); + if (args.closable) parts.push(`[closable]="true"`); + if (args.life) parts.push(`[life]="${args.life}"`); + + const template = ``; + return { props: args, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Вариации ───────────────────────────────────────────────────────────────── +export { Severities, WithCloseButton, WithContent }; diff --git a/src/stories/components/metergroup/examples/metergroup-basic.component.ts b/src/stories/components/metergroup/examples/metergroup-basic.component.ts new file mode 100644 index 00000000..2f0e2512 --- /dev/null +++ b/src/stories/components/metergroup/examples/metergroup-basic.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; +import { ExtraMeterGroupComponent } from '../../../../lib/components/metergroup/metergroup.component'; +import { MeterItem } from 'primeng/metergroup'; +import { defaultValue } from '../metergroup.data'; + +@Component({ + selector: 'app-metergroup-basic', + standalone: true, + imports: [ExtraMeterGroupComponent], + template: ` +
+ +
+ `, +}) +export class MeterGroupBasicComponent { + value: MeterItem[] = defaultValue; +} diff --git a/src/stories/components/metergroup/examples/metergroup-horizontal.component.ts b/src/stories/components/metergroup/examples/metergroup-horizontal.component.ts new file mode 100644 index 00000000..5cbc6cf8 --- /dev/null +++ b/src/stories/components/metergroup/examples/metergroup-horizontal.component.ts @@ -0,0 +1,54 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraMeterGroupComponent } from '../../../../lib/components/metergroup/metergroup.component'; +import { MeterItem } from 'primeng/metergroup'; +import { defaultValue } from '../metergroup.data'; + +@Component({ + selector: 'app-metergroup-horizontal', + standalone: true, + imports: [ExtraMeterGroupComponent], + template: ` +
+ +
+ `, +}) +export class MeterGroupHorizontalComponent { + value: MeterItem[] = defaultValue; +} + +export const Horizontal: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Горизонтальная ориентация (по умолчанию).' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMeterGroupComponent } from '@cdek-it/angular-ui-kit'; +import { MeterItem } from 'primeng/metergroup'; + +@Component({ + selector: 'app-metergroup-horizontal', + standalone: true, + imports: [ExtraMeterGroupComponent], + template: \` + + \`, +}) +export class MeterGroupHorizontalComponent { + value: MeterItem[] = [ + { label: 'Space used', color: '#34d399', value: 16 }, + { label: 'Unused', color: '#fbbf24', value: 8 }, + { label: 'System', color: '#60a5fa', value: 24 }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/metergroup/examples/metergroup-icon.component.ts b/src/stories/components/metergroup/examples/metergroup-icon.component.ts new file mode 100644 index 00000000..856d0a0c --- /dev/null +++ b/src/stories/components/metergroup/examples/metergroup-icon.component.ts @@ -0,0 +1,54 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraMeterGroupComponent } from '../../../../lib/components/metergroup/metergroup.component'; +import { MeterItem } from 'primeng/metergroup'; +import { iconValue } from '../metergroup.data'; + +@Component({ + selector: 'app-metergroup-icon', + standalone: true, + imports: [ExtraMeterGroupComponent], + template: ` +
+ +
+ `, +}) +export class MeterGroupIconComponent { + value: MeterItem[] = iconValue; +} + +export const Icon: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Элементы с иконками в метках.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMeterGroupComponent } from '@cdek-it/angular-ui-kit'; +import { MeterItem } from 'primeng/metergroup'; + +@Component({ + selector: 'app-metergroup-icon', + standalone: true, + imports: [ExtraMeterGroupComponent], + template: \` + + \`, +}) +export class MeterGroupIconComponent { + value: MeterItem[] = [ + { label: 'Apps', color: '#34d399', value: 16, icon: 'ti ti-apps' }, + { label: 'Messages', color: '#fbbf24', value: 8, icon: 'ti ti-message' }, + { label: 'System', color: '#60a5fa', value: 24, icon: 'ti ti-cpu' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/metergroup/examples/metergroup-label-start.component.ts b/src/stories/components/metergroup/examples/metergroup-label-start.component.ts new file mode 100644 index 00000000..bf03d557 --- /dev/null +++ b/src/stories/components/metergroup/examples/metergroup-label-start.component.ts @@ -0,0 +1,54 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraMeterGroupComponent } from '../../../../lib/components/metergroup/metergroup.component'; +import { MeterItem } from 'primeng/metergroup'; +import { defaultValue } from '../metergroup.data'; + +@Component({ + selector: 'app-metergroup-label-start', + standalone: true, + imports: [ExtraMeterGroupComponent], + template: ` +
+ +
+ `, +}) +export class MeterGroupLabelStartComponent { + value: MeterItem[] = defaultValue; +} + +export const LabelStart: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Метки расположены над полосой (labelPosition="start").' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMeterGroupComponent } from '@cdek-it/angular-ui-kit'; +import { MeterItem } from 'primeng/metergroup'; + +@Component({ + selector: 'app-metergroup-label-start', + standalone: true, + imports: [ExtraMeterGroupComponent], + template: \` + + \`, +}) +export class MeterGroupLabelStartComponent { + value: MeterItem[] = [ + { label: 'Space used', color: '#34d399', value: 16 }, + { label: 'Unused', color: '#fbbf24', value: 8 }, + { label: 'System', color: '#60a5fa', value: 24 }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/metergroup/examples/metergroup-label-vertical.component.ts b/src/stories/components/metergroup/examples/metergroup-label-vertical.component.ts new file mode 100644 index 00000000..26ca91b4 --- /dev/null +++ b/src/stories/components/metergroup/examples/metergroup-label-vertical.component.ts @@ -0,0 +1,54 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraMeterGroupComponent } from '../../../../lib/components/metergroup/metergroup.component'; +import { MeterItem } from 'primeng/metergroup'; +import { defaultValue } from '../metergroup.data'; + +@Component({ + selector: 'app-metergroup-label-vertical', + standalone: true, + imports: [ExtraMeterGroupComponent], + template: ` +
+ +
+ `, +}) +export class MeterGroupLabelVerticalComponent { + value: MeterItem[] = defaultValue; +} + +export const LabelVertical: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Метки расположены вертикально (labelOrientation="vertical").' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMeterGroupComponent } from '@cdek-it/angular-ui-kit'; +import { MeterItem } from 'primeng/metergroup'; + +@Component({ + selector: 'app-metergroup-label-vertical', + standalone: true, + imports: [ExtraMeterGroupComponent], + template: \` + + \`, +}) +export class MeterGroupLabelVerticalComponent { + value: MeterItem[] = [ + { label: 'Space used', color: '#34d399', value: 16 }, + { label: 'Unused', color: '#fbbf24', value: 8 }, + { label: 'System', color: '#60a5fa', value: 24 }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/metergroup/examples/metergroup-vertical.component.ts b/src/stories/components/metergroup/examples/metergroup-vertical.component.ts new file mode 100644 index 00000000..0abbd19f --- /dev/null +++ b/src/stories/components/metergroup/examples/metergroup-vertical.component.ts @@ -0,0 +1,58 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraMeterGroupComponent } from '../../../../lib/components/metergroup/metergroup.component'; +import { MeterItem } from 'primeng/metergroup'; +import { defaultValue } from '../metergroup.data'; + +@Component({ + selector: 'app-metergroup-vertical', + standalone: true, + imports: [ExtraMeterGroupComponent], + template: ` +
+
+ +
+
+ `, +}) +export class MeterGroupVerticalComponent { + value: MeterItem[] = defaultValue; +} + +export const Vertical: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Вертикальная ориентация полосы.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraMeterGroupComponent } from '@cdek-it/angular-ui-kit'; +import { MeterItem } from 'primeng/metergroup'; + +@Component({ + selector: 'app-metergroup-vertical', + standalone: true, + imports: [ExtraMeterGroupComponent], + template: \` +
+ +
+ \`, +}) +export class MeterGroupVerticalComponent { + value: MeterItem[] = [ + { label: 'Space used', color: '#34d399', value: 16 }, + { label: 'Unused', color: '#fbbf24', value: 8 }, + { label: 'System', color: '#60a5fa', value: 24 }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/metergroup/metergroup.data.ts b/src/stories/components/metergroup/metergroup.data.ts new file mode 100644 index 00000000..3c13fd5b --- /dev/null +++ b/src/stories/components/metergroup/metergroup.data.ts @@ -0,0 +1,13 @@ +import { MeterItem } from 'primeng/metergroup'; + +export const defaultValue: MeterItem[] = [ + { label: 'Space used', color: '#34d399', value: 16 }, + { label: 'Unused', color: '#fbbf24', value: 8 }, + { label: 'System', color: '#60a5fa', value: 24 }, +]; + +export const iconValue: MeterItem[] = [ + { label: 'Apps', color: '#34d399', value: 16, icon: 'ti ti-apps' }, + { label: 'Messages', color: '#fbbf24', value: 8, icon: 'ti ti-message' }, + { label: 'System', color: '#60a5fa', value: 24, icon: 'ti ti-cpu' }, +]; diff --git a/src/stories/components/metergroup/metergroup.stories.ts b/src/stories/components/metergroup/metergroup.stories.ts new file mode 100644 index 00000000..f4eb03d2 --- /dev/null +++ b/src/stories/components/metergroup/metergroup.stories.ts @@ -0,0 +1,119 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraMeterGroupComponent as MeterGroupComponent } from '../../../lib/components/metergroup/metergroup.component'; +import { MeterGroupHorizontalComponent, Horizontal } from './examples/metergroup-horizontal.component'; +import { MeterGroupVerticalComponent, Vertical } from './examples/metergroup-vertical.component'; +import { MeterGroupIconComponent, Icon } from './examples/metergroup-icon.component'; +import { MeterGroupLabelStartComponent, LabelStart } from './examples/metergroup-label-start.component'; +import { MeterGroupLabelVerticalComponent, LabelVertical } from './examples/metergroup-label-vertical.component'; +import { defaultValue } from './metergroup.data'; + +type MeterGroupArgs = MeterGroupComponent; + +const meta: Meta = { + title: 'Components/Misc/MeterGroup', + component: MeterGroupComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + MeterGroupComponent, + MeterGroupHorizontalComponent, + MeterGroupVerticalComponent, + MeterGroupIconComponent, + MeterGroupLabelStartComponent, + MeterGroupLabelVerticalComponent, + ], + }), + ], + parameters: { + docs: { + description: { + component: `Визуализирует несколько числовых значений в виде единой полосы прогресса с подписями. + +\`\`\`typescript +import { ExtraMeterGroupComponent as MeterGroupComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { + prefix: '--p-metergroup', + }, + }, + argTypes: { + value: { + control: false, + description: 'Массив элементов с полями label, color, value и опционально icon', + table: { + category: 'Props', + type: { summary: 'MeterItem[]' }, + }, + }, + orientation: { + control: 'select', + options: ['horizontal', 'vertical'], + description: 'Ориентация полосы', + table: { + category: 'Props', + defaultValue: { summary: 'horizontal' }, + type: { summary: "'horizontal' | 'vertical'" }, + }, + }, + labelPosition: { + control: 'select', + options: ['start', 'end'], + description: 'Позиция списка меток относительно полосы', + table: { + category: 'Props', + defaultValue: { summary: 'end' }, + type: { summary: "'start' | 'end'" }, + }, + }, + labelOrientation: { + control: 'select', + options: ['horizontal', 'vertical'], + description: 'Ориентация списка меток', + table: { + category: 'Props', + defaultValue: { summary: 'horizontal' }, + type: { summary: "'horizontal' | 'vertical'" }, + }, + }, + }, +}; + +const commonTemplate = ``; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => ({ + props: { ...args, value: defaultValue }, + template: args.orientation === 'vertical' + ? `
${commonTemplate}
` + : commonTemplate, + }), + args: { + orientation: 'horizontal', + labelPosition: 'end', + labelOrientation: 'horizontal', + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { Horizontal, Vertical, Icon, LabelStart, LabelVertical }; diff --git a/src/stories/components/paginator/examples/paginator-current-page-report.component.ts b/src/stories/components/paginator/examples/paginator-current-page-report.component.ts new file mode 100644 index 00000000..ad370181 --- /dev/null +++ b/src/stories/components/paginator/examples/paginator-current-page-report.component.ts @@ -0,0 +1,78 @@ +import { Component } from '@angular/core'; +import { ExtraPaginatorComponent } from '../../../../lib/components/paginator/paginator.component'; +import type { PaginatorState } from 'primeng/types/paginator'; + +const template = ` + +`; +const styles = ''; + +@Component({ + selector: 'app-paginator-current-page-report', + standalone: true, + imports: [ExtraPaginatorComponent], + template, + styles, +}) +export class PaginatorCurrentPageReportComponent { + first = 0; + rows = 10; + + onPageChange(event: PaginatorState): void { + this.first = event.first ?? 0; + this.rows = event.rows ?? 10; + } +} + +export const CurrentPageReport = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { + story: 'Пагинатор с отображением текущей страницы и общего числа страниц.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraPaginatorComponent } from '@cdek-it/angular-ui-kit'; +import type { PaginatorState } from 'primeng/types/paginator'; + +@Component({ + selector: 'app-paginator-current-page-report', + standalone: true, + imports: [ExtraPaginatorComponent], + template: \` + + \`, +}) +export class PaginatorCurrentPageReportComponent { + first = 0; + rows = 10; + + onPageChange(event: PaginatorState): void { + this.first = event.first ?? 0; + this.rows = event.rows ?? 10; + } +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/paginator/examples/paginator-rows-per-page.component.ts b/src/stories/components/paginator/examples/paginator-rows-per-page.component.ts new file mode 100644 index 00000000..1c2792fd --- /dev/null +++ b/src/stories/components/paginator/examples/paginator-rows-per-page.component.ts @@ -0,0 +1,78 @@ +import { Component } from '@angular/core'; +import { ExtraPaginatorComponent } from '../../../../lib/components/paginator/paginator.component'; +import type { PaginatorState } from 'primeng/types/paginator'; + +const template = ` + +`; +const styles = ''; + +@Component({ + selector: 'app-paginator-rows-per-page', + standalone: true, + imports: [ExtraPaginatorComponent], + template, + styles, +}) +export class PaginatorRowsPerPageComponent { + first = 0; + rows = 10; + + onPageChange(event: PaginatorState): void { + this.first = event.first ?? 0; + this.rows = event.rows ?? 10; + } +} + +export const RowsPerPage = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { + story: 'Пагинатор с выбором количества строк на странице и переходом на конкретную страницу.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraPaginatorComponent } from '@cdek-it/angular-ui-kit'; +import type { PaginatorState } from 'primeng/types/paginator'; + +@Component({ + selector: 'app-paginator-rows-per-page', + standalone: true, + imports: [ExtraPaginatorComponent], + template: \` + + \`, +}) +export class PaginatorRowsPerPageComponent { + first = 0; + rows = 10; + + onPageChange(event: PaginatorState): void { + this.first = event.first ?? 0; + this.rows = event.rows ?? 10; + } +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/paginator/paginator.stories.ts b/src/stories/components/paginator/paginator.stories.ts new file mode 100644 index 00000000..22159d45 --- /dev/null +++ b/src/stories/components/paginator/paginator.stories.ts @@ -0,0 +1,168 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraPaginatorComponent } from '../../../lib/components/paginator/paginator.component'; +import { PaginatorCurrentPageReportComponent, CurrentPageReport as CurrentPageReportStory } from './examples/paginator-current-page-report.component'; +import { PaginatorRowsPerPageComponent, RowsPerPage as RowsPerPageStory } from './examples/paginator-rows-per-page.component'; + +type PaginatorArgs = Pick< + ExtraPaginatorComponent, + 'totalRecords' | 'rows' | 'pageLinkSize' | 'showFirstLastIcon' | 'showPageLinks' | 'showCurrentPageReport' | 'showJumpToPageInput' | 'alwaysShow' +>; + +const meta: Meta = { + title: 'Components/Data/Paginator', + component: ExtraPaginatorComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraPaginatorComponent, + PaginatorCurrentPageReportComponent, + PaginatorRowsPerPageComponent, + ], + }), + ], + parameters: { + docs: { + description: { + component: `Отображает навигацию по страницам для больших наборов данных. + +\`\`\`typescript +import { ExtraPaginatorComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-paginator' }, + }, + argTypes: { + totalRecords: { + control: { type: 'number', min: 0 }, + description: 'Общее количество записей', + table: { + category: 'Props', + defaultValue: { summary: '0' }, + type: { summary: 'number' }, + }, + }, + rows: { + control: { type: 'number', min: 1 }, + description: 'Количество строк на странице', + table: { + category: 'Props', + defaultValue: { summary: '10' }, + type: { summary: 'number' }, + }, + }, + pageLinkSize: { + control: { type: 'number', min: 1 }, + description: 'Количество отображаемых ссылок на страницы', + table: { + category: 'Props', + defaultValue: { summary: '5' }, + type: { summary: 'number' }, + }, + }, + showFirstLastIcon: { + control: 'boolean', + description: 'Показывать кнопки перехода на первую и последнюю страницу', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + showPageLinks: { + control: 'boolean', + description: 'Показывать номера страниц', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + showCurrentPageReport: { + control: 'boolean', + description: 'Показывать текст с текущей страницей и общим количеством', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + showJumpToPageInput: { + control: 'boolean', + description: 'Показывать поле ввода для перехода на конкретную страницу', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + alwaysShow: { + control: 'boolean', + description: 'Показывать пагинатор даже при единственной странице', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + }, + args: { + totalRecords: 120, + rows: 10, + pageLinkSize: 5, + showFirstLastIcon: true, + showPageLinks: true, + showCurrentPageReport: false, + showJumpToPageInput: false, + alwaysShow: true, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ─────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => ({ + props: args, + template: ` + + `, + }), + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── CurrentPageReport ───────────────────────────────────────────────────────── + +export const CurrentPageReport: Story = CurrentPageReportStory; + +// ── RowsPerPage ─────────────────────────────────────────────────────────────── + +export const RowsPerPage: Story = { + ...RowsPerPageStory, + parameters: { + ...RowsPerPageStory.parameters, + docs: { + ...RowsPerPageStory.parameters?.docs, + story: { height: '200px' }, + }, + }, +}; diff --git a/src/stories/components/panelmenu/examples/panelmenu-basic.component.ts b/src/stories/components/panelmenu/examples/panelmenu-basic.component.ts new file mode 100644 index 00000000..086e7a15 --- /dev/null +++ b/src/stories/components/panelmenu/examples/panelmenu-basic.component.ts @@ -0,0 +1,93 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { MenuItem } from 'primeng/api'; +import { ExtraPanelMenuComponent } from '../../../../lib/components/panelmenu/panelmenu.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-panelmenu-basic', + standalone: true, + imports: [ExtraPanelMenuComponent], + template, + styles, +}) +export class PanelMenuBasicComponent { + items: MenuItem[] = [ + { + label: 'Отправления', + items: [ + { label: 'Новые' }, + { label: 'В пути' }, + { label: 'Доставленные' }, + { label: 'Возвраты', items: [{ label: 'Ожидают' }, { label: 'Завершённые' }] }, + ], + }, + { label: 'Маршруты' }, + { + label: 'Склады', + items: [ + { label: 'Москва' }, + { label: 'Новосибирск' }, + { label: 'Екатеринбург' }, + ], + }, + { label: 'Настройки', disabled: true }, + ]; +} + +export const Basic: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Базовое аккордеон-меню без иконок.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { MenuItem } from 'primeng/api'; +import { ExtraPanelMenuComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-panelmenu-basic', + standalone: true, + imports: [ExtraPanelMenuComponent], + template: \` + + \`, +}) +export class PanelMenuBasicComponent { + items: MenuItem[] = [ + { + label: 'Отправления', + items: [ + { label: 'Новые' }, + { label: 'В пути' }, + { label: 'Доставленные' }, + { label: 'Возвраты', items: [{ label: 'Ожидают' }, { label: 'Завершённые' }] }, + ], + }, + { label: 'Маршруты' }, + { + label: 'Склады', + items: [ + { label: 'Москва' }, + { label: 'Новосибирск' }, + { label: 'Екатеринбург' }, + ], + }, + { label: 'Настройки', disabled: true }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/panelmenu/examples/panelmenu-custom.component.ts b/src/stories/components/panelmenu/examples/panelmenu-custom.component.ts new file mode 100644 index 00000000..2bc5bb34 --- /dev/null +++ b/src/stories/components/panelmenu/examples/panelmenu-custom.component.ts @@ -0,0 +1,132 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { MenuItem } from 'primeng/api'; +import { PanelMenu } from 'primeng/panelmenu'; +import { Badge } from 'primeng/badge'; + +const template = ` + +`; +const styles = ''; + +@Component({ + selector: 'app-panelmenu-custom', + standalone: true, + imports: [PanelMenu, Badge], + template, + styles +}) +export class PanelMenuCustomComponent { + items: MenuItem[] = [ + { + label: 'Дашборд', + icon: 'ti ti-layout-dashboard', + description: 'Главная страница', + items: [ + { label: 'Аналитика', icon: 'ti ti-chart-line', description: 'Аналитика данных' }, + { label: 'Отчёты', icon: 'ti ti-file-analytics', description: 'Сводные отчёты' }, + { label: 'Статистика', icon: 'ti ti-chart-bar', description: 'Показатели доставки' } + ] + }, + { + label: 'Отправления', + icon: 'ti ti-package', + description: 'Управление заказами', + badge: 'New' + }, + { + label: 'Склады', + icon: 'ti ti-building-warehouse', + description: 'Складское хранение', + items: [ + { label: 'Документы', icon: 'ti ti-file-text', description: 'Накладные и акты' }, + { label: 'Фото', icon: 'ti ti-photo', description: 'Фотофиксация грузов' } + ] + }, + { + label: 'Настройки', + icon: 'ti ti-settings', + description: 'Параметры системы', + disabled: true + } + ]; +} + +export const Custom: StoryObj = { + render: () => ({ + template: `` + }), + parameters: { + docs: { + description: { story: 'Кастомный шаблон пункта меню с описанием и бейджем.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { MenuItem } from 'primeng/api'; +import { PanelMenu } from 'primeng/panelmenu'; +import { Badge } from 'primeng/badge'; + +@Component({ + selector: 'app-panelmenu-custom', + standalone: true, + imports: [PanelMenu, Badge], + template: \` + + + + @if (item.icon) {} +
+ {{ item.label }} + @if (item['description']) {{{ item['description'] }}} +
+ @if (item['badge']) {} + @if (hasSubmenu) {} +
+
+
+ \`, +}) +export class PanelMenuCustomComponent { + items: MenuItem[] = [ + { + label: 'Дашборд', + icon: 'ti ti-layout-dashboard', + description: 'Главная страница', + items: [ + { label: 'Аналитика', icon: 'ti ti-chart-line', description: 'Аналитика данных' }, + { label: 'Отчёты', icon: 'ti ti-file-analytics', description: 'Сводные отчёты' }, + ], + }, + { label: 'Отправления', icon: 'ti ti-package', description: 'Управление заказами', badge: 'New' }, + { + label: 'Склады', + icon: 'ti ti-building-warehouse', + description: 'Складское хранение', + items: [ + { label: 'Документы', icon: 'ti ti-file-text', description: 'Накладные и акты' }, + { label: 'Фото', icon: 'ti ti-photo', description: 'Фотофиксация грузов' }, + ], + }, + { label: 'Настройки', icon: 'ti ti-settings', description: 'Параметры системы', disabled: true }, + ]; +} + ` + } + } + } +}; diff --git a/src/stories/components/panelmenu/examples/panelmenu-multiple.component.ts b/src/stories/components/panelmenu/examples/panelmenu-multiple.component.ts new file mode 100644 index 00000000..110e2da6 --- /dev/null +++ b/src/stories/components/panelmenu/examples/panelmenu-multiple.component.ts @@ -0,0 +1,97 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { MenuItem } from 'primeng/api'; +import { ExtraPanelMenuComponent } from '../../../../lib/components/panelmenu/panelmenu.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-panelmenu-multiple', + standalone: true, + imports: [ExtraPanelMenuComponent], + template, + styles, +}) +export class PanelMenuMultipleComponent { + items: MenuItem[] = [ + { + label: 'Отправления', + icon: 'ti ti-package', + items: [ + { label: 'Новые', icon: 'ti ti-circle-plus' }, + { label: 'В пути', icon: 'ti ti-truck' }, + { label: 'Доставленные', icon: 'ti ti-circle-check' }, + { + label: 'Возвраты', + icon: 'ti ti-arrow-back', + items: [{ label: 'Ожидают' }, { label: 'Завершённые' }], + }, + ], + }, + { label: 'Маршруты', icon: 'ti ti-route' }, + { + label: 'Склады', + icon: 'ti ti-building-warehouse', + items: [ + { label: 'Москва' }, + { label: 'Новосибирск' }, + { label: 'Екатеринбург' }, + ], + }, + { label: 'Настройки', icon: 'ti ti-settings', disabled: true }, + ]; +} + +export const Multiple: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Несколько панелей могут быть раскрыты одновременно.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { MenuItem } from 'primeng/api'; +import { ExtraPanelMenuComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-panelmenu-multiple', + standalone: true, + imports: [ExtraPanelMenuComponent], + template: \` + + \`, +}) +export class PanelMenuMultipleComponent { + items: MenuItem[] = [ + { + label: 'Отправления', + icon: 'ti ti-package', + items: [ + { label: 'Новые', icon: 'ti ti-circle-plus' }, + { label: 'В пути', icon: 'ti ti-truck' }, + { label: 'Доставленные', icon: 'ti ti-circle-check' }, + { label: 'Возвраты', icon: 'ti ti-arrow-back', items: [{ label: 'Ожидают' }, { label: 'Завершённые' }] }, + ], + }, + { label: 'Маршруты', icon: 'ti ti-route' }, + { + label: 'Склады', + icon: 'ti ti-building-warehouse', + items: [{ label: 'Москва' }, { label: 'Новосибирск' }, { label: 'Екатеринбург' }], + }, + { label: 'Настройки', icon: 'ti ti-settings', disabled: true }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/panelmenu/panelmenu.stories.ts b/src/stories/components/panelmenu/panelmenu.stories.ts new file mode 100644 index 00000000..601617f9 --- /dev/null +++ b/src/stories/components/panelmenu/panelmenu.stories.ts @@ -0,0 +1,108 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraPanelMenuComponent } from '../../../lib/components/panelmenu/panelmenu.component'; +import { PanelMenuBasicComponent, Basic } from './examples/panelmenu-basic.component'; +import { PanelMenuMultipleComponent, Multiple } from './examples/panelmenu-multiple.component'; +import { PanelMenuCustomComponent, Custom } from './examples/panelmenu-custom.component'; + +const meta: Meta = { + title: 'Components/Menu/PanelMenu', + component: ExtraPanelMenuComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraPanelMenuComponent, + PanelMenuBasicComponent, + PanelMenuMultipleComponent, + PanelMenuCustomComponent, + ], + }), + ], + parameters: { + docs: { + description: { + component: `Аккордеон-меню с поддержкой вложенных подменю и раскрытием нескольких панелей. + +\`\`\`typescript +import { ExtraPanelMenuComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-panelmenu' }, + }, + argTypes: { + model: { + table: { disable: true }, + }, + multiple: { + control: 'boolean', + description: 'Разрешает одновременное раскрытие нескольких панелей', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + tabindex: { + control: 'number', + description: 'Порядок фокуса при навигации клавиатурой', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'number' }, + }, + }, + }, + args: { + multiple: false, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ─────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = [`[model]="model"`]; + if (args.multiple) parts.push(`[multiple]="true"`); + if (args.tabindex !== undefined) parts.push(`[tabindex]="${args.tabindex}"`); + + const template = ``; + + return { + props: { + ...args, + model: [ + { + label: 'Отправления', + items: [ + { label: 'Новые' }, + { label: 'В пути' }, + { label: 'Доставленные' }, + ], + }, + { label: 'Маршруты' }, + { + label: 'Склады', + items: [{ label: 'Москва' }, { label: 'Новосибирск' }], + }, + { label: 'Настройки', disabled: true }, + ], + }, + template, + }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { Basic, Multiple, Custom }; diff --git a/src/stories/components/password/examples/password-disabled.component.ts b/src/stories/components/password/examples/password-disabled.component.ts new file mode 100644 index 00000000..0f87111d --- /dev/null +++ b/src/stories/components/password/examples/password-disabled.component.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraPasswordComponent } from '../../../../lib/components/password/password.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-password-disabled', + standalone: true, + imports: [ExtraPasswordComponent, FormsModule], + template, +}) +export class PasswordDisabledComponent { + value: string | null = 'secret123'; +} + +export const Disabled: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Поле ввода пароля в отключённом состоянии.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraPasswordComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-password-disabled', + standalone: true, + imports: [ExtraPasswordComponent, FormsModule], + template: \` + + \`, +}) +export class PasswordDisabledComponent { + value: string | null = 'secret123'; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/password/examples/password-feedback.component.ts b/src/stories/components/password/examples/password-feedback.component.ts new file mode 100644 index 00000000..811ff78b --- /dev/null +++ b/src/stories/components/password/examples/password-feedback.component.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraPasswordComponent } from '../../../../lib/components/password/password.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-password-feedback', + standalone: true, + imports: [ExtraPasswordComponent, FormsModule], + template, +}) +export class PasswordFeedbackComponent { + value: string | null = null; +} + +export const Feedback: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Индикатор надёжности пароля с визуальной шкалой (слабый / средний / сильный).' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraPasswordComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-password-feedback', + standalone: true, + imports: [ExtraPasswordComponent, FormsModule], + template: \` + + \`, +}) +export class PasswordFeedbackComponent { + value: string | null = null; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/password/examples/password-float-label.component.ts b/src/stories/components/password/examples/password-float-label.component.ts new file mode 100644 index 00000000..46015ccf --- /dev/null +++ b/src/stories/components/password/examples/password-float-label.component.ts @@ -0,0 +1,78 @@ +import { Component, Input } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraPasswordComponent } from '../../../../lib/components/password/password.component'; + +@Component({ + selector: 'app-password-float-label', + standalone: true, + imports: [ExtraPasswordComponent, FormsModule], + template: ` +
+ +
+ `, +}) +export class PasswordFloatLabelComponent { + @Input() feedback = true; + @Input() toggleMask = false; + @Input() disabled = false; + @Input() invalid = false; + @Input() fluid = false; + @Input() placeholder: string | undefined = undefined; + @Input() label = 'Пароль'; + value = ''; +} + +export const FloatLabel: StoryObj = { + name: 'FloatLabel', + render: (args) => { + const parts: string[] = []; + + if (!args.feedback) parts.push(`[feedback]="false"`); + if (args.toggleMask) parts.push(`[toggleMask]="true"`); + if (args.placeholder) parts.push(`placeholder="${args.placeholder}"`); + if (args.disabled) parts.push(`[disabled]="true"`); + if (args.invalid) parts.push(`[invalid]="true"`); + if (args.fluid) parts.push(`[fluid]="true"`); + + const attrs = parts.length ? `\n ${parts.join('\n ')}` : ''; + + return { + props: args, + template: ``, + }; + }, + args: { + feedback: true, + toggleMask: false, + placeholder: undefined, + disabled: false, + invalid: false, + fluid: false, + label: 'Пароль', + }, + parameters: { + docs: { + description: { + story: + 'Интеграция с `floatLabel` — плавающая метка внутри поля. Кликните на поле чтобы увидеть анимацию.', + }, + source: { + language: 'html', + code: ``, + }, + }, + }, +}; diff --git a/src/stories/components/password/examples/password-invalid.component.ts b/src/stories/components/password/examples/password-invalid.component.ts new file mode 100644 index 00000000..4f8f63da --- /dev/null +++ b/src/stories/components/password/examples/password-invalid.component.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraPasswordComponent } from '../../../../lib/components/password/password.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-password-invalid', + standalone: true, + imports: [ExtraPasswordComponent, FormsModule], + template, +}) +export class PasswordInvalidComponent { + value: string | null = null; +} + +export const Invalid: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Поле ввода пароля в состоянии ошибки валидации.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraPasswordComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-password-invalid', + standalone: true, + imports: [ExtraPasswordComponent, FormsModule], + template: \` + + \`, +}) +export class PasswordInvalidComponent { + value: string | null = null; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/password/examples/password-template.component.ts b/src/stories/components/password/examples/password-template.component.ts new file mode 100644 index 00000000..28908205 --- /dev/null +++ b/src/stories/components/password/examples/password-template.component.ts @@ -0,0 +1,146 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraPasswordComponent } from '../../../../lib/components/password/password.component'; +import { Divider } from 'primeng/divider'; + +@Component({ + selector: 'app-password-template', + standalone: true, + imports: [ExtraPasswordComponent, Divider, FormsModule], + template: ` +
+ + + +
+
+ + Минимум одна строчная буква +
+
+ + Минимум одна заглавная буква +
+
+ + Минимум одна цифра +
+
+ + Не менее 8 символов +
+
+
+
+
+ `, +}) +export class PasswordTemplateComponent { + value: string | null = null; + + get hasLowercase(): boolean { + return /[a-z]/.test(this.value ?? ''); + } + + get hasUppercase(): boolean { + return /[A-Z]/.test(this.value ?? ''); + } + + get hasDigit(): boolean { + return /\d/.test(this.value ?? ''); + } + + get hasMinLength(): boolean { + return (this.value ?? '').length >= 8; + } +} + +export const Template: StoryObj = { + name: 'Template', + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Кастомный контент через `ng-template`: разделитель и список требований к паролю с tabler-иконками.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraPasswordComponent } from '@cdek-it/angular-ui-kit'; +import { Divider } from 'primeng/divider'; + +@Component({ + standalone: true, + imports: [ExtraPasswordComponent, Divider, FormsModule], + template: \` + + + +
+
+ + Минимум одна строчная буква +
+
+ + Минимум одна заглавная буква +
+
+ + Минимум одна цифра +
+
+ + Не менее 8 символов +
+
+
+
+ \`, +}) +export class PasswordTemplateExample { + value: string | null = null; + + get hasLowercase(): boolean { + return /[a-z]/.test(this.value ?? ''); + } + + get hasUppercase(): boolean { + return /[A-Z]/.test(this.value ?? ''); + } + + get hasDigit(): boolean { + return /\\d/.test(this.value ?? ''); + } + + get hasMinLength(): boolean { + return (this.value ?? '').length >= 8; + } +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/password/examples/password-toggle.component.ts b/src/stories/components/password/examples/password-toggle.component.ts new file mode 100644 index 00000000..28fda1de --- /dev/null +++ b/src/stories/components/password/examples/password-toggle.component.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraPasswordComponent } from '../../../../lib/components/password/password.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-password-toggle', + standalone: true, + imports: [ExtraPasswordComponent, FormsModule], + template, +}) +export class PasswordToggleComponent { + value: string | null = null; +} + +export const ToggleMask: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Возможность показать/скрыть введённый пароль по иконке.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraPasswordComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-password-toggle', + standalone: true, + imports: [ExtraPasswordComponent, FormsModule], + template: \` + + \`, +}) +export class PasswordToggleComponent { + value: string | null = null; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/password/password.stories.ts b/src/stories/components/password/password.stories.ts new file mode 100644 index 00000000..4e7d2596 --- /dev/null +++ b/src/stories/components/password/password.stories.ts @@ -0,0 +1,214 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormsModule } from '@angular/forms'; +import { ExtraPasswordComponent } from '../../../lib/components/password/password.component'; +import { PasswordToggleComponent, ToggleMask } from './examples/password-toggle.component'; +import { PasswordFeedbackComponent, Feedback } from './examples/password-feedback.component'; +import { PasswordDisabledComponent, Disabled } from './examples/password-disabled.component'; +import { PasswordInvalidComponent, Invalid } from './examples/password-invalid.component'; +import { PasswordFloatLabelComponent, FloatLabel } from './examples/password-float-label.component'; +import { PasswordTemplateComponent, Template } from './examples/password-template.component'; + +const meta: Meta = { + title: 'Components/Form/Password', + component: ExtraPasswordComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraPasswordComponent, + FormsModule, + PasswordToggleComponent, + PasswordFeedbackComponent, + PasswordDisabledComponent, + PasswordInvalidComponent, + PasswordFloatLabelComponent, + PasswordTemplateComponent, + ], + }), + (story) => ({ + ...story(), + template: `
${story().template}
`, + }), + ], + parameters: { + designTokens: { prefix: '--p-password' }, + docs: { + description: { + component: `Поле ввода пароля с поддержкой индикатора надёжности и переключения видимости. + +\`\`\`typescript +import { ExtraPasswordComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + story: { height: '280px' }, + }, + }, + argTypes: { + feedback: { + control: 'boolean', + description: 'Показывать индикатор надёжности пароля', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + toggleMask: { + control: 'boolean', + description: 'Возможность показать/скрыть пароль', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + placeholder: { + control: 'text', + description: 'Текст-подсказка', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'string' }, + }, + }, + size: { + control: 'select', + options: ['small', 'base', 'large', 'xlarge'], + description: 'Размер поля', + table: { + category: 'Props', + defaultValue: { summary: 'base' }, + type: { summary: "'small' | 'base' | 'large' | 'xlarge'" }, + }, + }, + disabled: { + control: 'boolean', + description: 'Отключает возможность взаимодействия', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + invalid: { + control: 'boolean', + description: 'Подсвечивает поле как невалидное', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + fluid: { + control: 'boolean', + description: 'Растягивает поле на всю ширину контейнера', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + floatLabel: { + control: 'boolean', + description: 'Плавающая метка внутри поля', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + label: { + control: 'text', + description: 'Текст плавающей метки (используется с floatLabel)', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + // Hidden props + variant: { table: { disable: true } }, + promptLabel: { table: { disable: true } }, + weakLabel: { table: { disable: true } }, + mediumLabel: { table: { disable: true } }, + strongLabel: { table: { disable: true } }, + inputId: { table: { disable: true } }, + inputStyleClass: { table: { disable: true } }, + ariaLabel: { table: { disable: true } }, + ariaLabelledBy: { table: { disable: true } }, + autofocus: { table: { disable: true } }, + sizeClass: { table: { disable: true } }, + computedInputStyleClass: { table: { disable: true } }, + + // Events + onFocus: { + control: false, + description: 'Событие фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + onBlur: { + control: false, + description: 'Событие потери фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, + args: { + feedback: true, + toggleMask: false, + placeholder: 'Введите пароль', + size: 'base', + disabled: false, + invalid: false, + fluid: false, + floatLabel: false, + label: 'Пароль', + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (!args.feedback) parts.push(`[feedback]="false"`); + if (args.toggleMask) parts.push(`[toggleMask]="true"`); + if (args.floatLabel) { + parts.push(`[floatLabel]="true"`); + if (args.label) parts.push(`label="${args.label}"`); + } else { + if (args.placeholder) parts.push(`placeholder="${args.placeholder}"`); + } + if (args.size && args.size !== 'base') parts.push(`size="${args.size}"`); + if (args.disabled) parts.push(`[disabled]="true"`); + if (args.invalid) parts.push(`[invalid]="true"`); + if (args.fluid) parts.push(`[fluid]="true"`); + + parts.push(`[(ngModel)]="value"`); + + const template = parts.length > 1 + ? `` + : ``; + + return { props: { ...args, value: null }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { ToggleMask, Feedback, Disabled, Invalid, FloatLabel, Template }; diff --git a/src/stories/components/progressbar/examples/progressbar-indeterminate.component.ts b/src/stories/components/progressbar/examples/progressbar-indeterminate.component.ts new file mode 100644 index 00000000..51ebe671 --- /dev/null +++ b/src/stories/components/progressbar/examples/progressbar-indeterminate.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { ExtraProgressBarComponent } from '../../../../lib/components/progressbar/progressbar.component'; + +@Component({ + selector: 'app-progressbar-indeterminate', + standalone: true, + imports: [ExtraProgressBarComponent], + template: ` +
+ +
+ `, +}) +export class ProgressBarIndeterminateComponent {} diff --git a/src/stories/components/progressbar/examples/progressbar-no-label.component.ts b/src/stories/components/progressbar/examples/progressbar-no-label.component.ts new file mode 100644 index 00000000..48718cf4 --- /dev/null +++ b/src/stories/components/progressbar/examples/progressbar-no-label.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { ExtraProgressBarComponent } from '../../../../lib/components/progressbar/progressbar.component'; + +@Component({ + selector: 'app-progressbar-no-label', + standalone: true, + imports: [ExtraProgressBarComponent], + template: ` +
+ +
+ `, +}) +export class ProgressBarNoLabelComponent {} diff --git a/src/stories/components/progressbar/progressbar.stories.ts b/src/stories/components/progressbar/progressbar.stories.ts new file mode 100644 index 00000000..9ff6b2fa --- /dev/null +++ b/src/stories/components/progressbar/progressbar.stories.ts @@ -0,0 +1,170 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraProgressBarComponent } from '../../../lib/components/progressbar/progressbar.component'; +import { ProgressBarIndeterminateComponent } from './examples/progressbar-indeterminate.component'; +import { ProgressBarNoLabelComponent } from './examples/progressbar-no-label.component'; + +type ProgressBarArgs = ExtraProgressBarComponent; + +const meta: Meta = { + title: 'Components/Misc/ProgressBar', + component: ExtraProgressBarComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraProgressBarComponent, + ProgressBarIndeterminateComponent, + ProgressBarNoLabelComponent, + ], + }), + ], + parameters: { + docs: { + description: { + component: `Информирует пользователя о статусе длительного процесса. + +\`\`\`typescript +import { ExtraProgressBarComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { + prefix: '--p-progressbar', + }, + }, + argTypes: { + value: { + control: { type: 'number', min: 0, max: 100 }, + description: 'Значение прогресса (0–100)', + table: { + category: 'Props', + defaultValue: { summary: '0' }, + type: { summary: 'number' }, + }, + }, + mode: { + control: 'select', + options: ['determinate', 'indeterminate'], + description: 'Режим отображения', + table: { + category: 'Props', + defaultValue: { summary: 'determinate' }, + type: { summary: "'determinate' | 'indeterminate'" }, + }, + }, + showValue: { + control: 'boolean', + description: 'Отображать числовое значение', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + }, +}; + +const commonTemplate = ``; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.value != null && args.value !== 0) parts.push(`[value]="${args.value}"`); + if (args.mode && args.mode !== 'determinate') parts.push(`mode="${args.mode}"`); + if (!args.showValue) parts.push(`[showValue]="false"`); + + const template = parts.length + ? `` + : ``; + + return { props: args, template }; + }, + args: { + value: 50, + mode: 'determinate', + showValue: true, + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Stories ────────────────────────────────────────────────────────────────── + +export const Indeterminate: Story = { + render: (args) => ({ props: args, template: commonTemplate }), + args: { + mode: 'indeterminate', + showValue: true, + }, + parameters: { + docs: { + description: { story: 'Анимированный режим без конкретного значения.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraProgressBarComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-progressbar-indeterminate', + standalone: true, + imports: [ExtraProgressBarComponent], + template: \` +
+ +
+ \`, +}) +export class ProgressBarIndeterminateComponent {}`, + }, + }, + }, +}; + +export const NoLabel: Story = { + render: (args) => ({ props: args, template: commonTemplate }), + args: { + value: 60, + mode: 'determinate', + showValue: false, + }, + parameters: { + docs: { + description: { story: 'Полоса прогресса без числовой метки.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraProgressBarComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-progressbar-no-label', + standalone: true, + imports: [ExtraProgressBarComponent], + template: \` +
+ +
+ \`, +}) +export class ProgressBarNoLabelComponent {}`, + }, + }, + }, +}; diff --git a/src/stories/components/progressspinner/examples/progressspinner-monochrome.component.ts b/src/stories/components/progressspinner/examples/progressspinner-monochrome.component.ts new file mode 100644 index 00000000..23d2934b --- /dev/null +++ b/src/stories/components/progressspinner/examples/progressspinner-monochrome.component.ts @@ -0,0 +1,51 @@ +import { Component, Input } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraProgressSpinnerComponent } from '../../../../lib/components/progressspinner/progressspinner.component'; + +const template = ` + +`; + +@Component({ + selector: 'progressspinner-monochrome', + standalone: true, + imports: [ExtraProgressSpinnerComponent], + template +}) +export class ProgressSpinnerMonochromeComponent { + @Input() size: any = 'medium'; + @Input() multicolor = false; +} + +export const Monochrome: StoryObj = { + render: (args) => ({ + props: args, + template: `` + }), + args: { + size: 'medium', + multicolor: false + }, + parameters: { + docs: { + description: { + story: 'Одноцветный вариант спиннера (monochrome).' + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraProgressSpinnerComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'progressspinner-monochrome', + standalone: true, + imports: [ExtraProgressSpinnerComponent], + template: \`\` +}) +export class ProgressSpinnerMonochromeComponent {} + ` + } + } + } +}; diff --git a/src/stories/components/progressspinner/examples/progressspinner-sizes.component.ts b/src/stories/components/progressspinner/examples/progressspinner-sizes.component.ts new file mode 100644 index 00000000..5fedd7a4 --- /dev/null +++ b/src/stories/components/progressspinner/examples/progressspinner-sizes.component.ts @@ -0,0 +1,51 @@ +import { Component, Input } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraProgressSpinnerComponent, ProgressSpinnerSize } from '../../../../lib/components/progressspinner/progressspinner.component'; + +const template = ` + +`; + +@Component({ + selector: 'progressspinner-sizes', + standalone: true, + imports: [ExtraProgressSpinnerComponent], + template +}) +export class ProgressSpinnerSizesComponent { + @Input() size: ProgressSpinnerSize = 'xlarge'; + @Input() multicolor = true; +} + +export const Sizes: StoryObj = { + render: (args) => ({ + props: args, + template: `` + }), + args: { + size: 'xlarge', + multicolor: true + }, + parameters: { + docs: { + description: { + story: 'Изменение размера спиннера. Используйте Controls для выбора других вариантов.' + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraProgressSpinnerComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'progressspinner-sizes', + standalone: true, + imports: [ExtraProgressSpinnerComponent], + template: \`\` +}) +export class ProgressSpinnerSizesComponent {} + ` + } + } + } +}; diff --git a/src/stories/components/progressspinner/progressspinner.stories.ts b/src/stories/components/progressspinner/progressspinner.stories.ts new file mode 100644 index 00000000..cc0ad2f3 --- /dev/null +++ b/src/stories/components/progressspinner/progressspinner.stories.ts @@ -0,0 +1,106 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraProgressSpinnerComponent } from '../../../lib/components/progressspinner/progressspinner.component'; +import { Sizes, ProgressSpinnerSizesComponent } from './examples/progressspinner-sizes.component'; +import { Monochrome, ProgressSpinnerMonochromeComponent } from './examples/progressspinner-monochrome.component'; + +const meta: Meta = { + title: 'Prime/Misc/ProgressSpinner', + component: ExtraProgressSpinnerComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ imports: [ExtraProgressSpinnerComponent, ProgressSpinnerSizesComponent, ProgressSpinnerMonochromeComponent] }) + ], + parameters: { + docs: { + description: { + component: `Используется для отображения индикатора процесса/состояния загрузки неопределенного времени. + +\`\`\`typescript +import { ExtraProgressSpinnerComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-progressspinner' }, + }, + argTypes: { + size: { + control: 'select', + options: ['small', 'medium', 'large', 'xlarge'], + description: 'Размер спиннера (задает вычисленные CSS-классы).', + table: { + category: 'Props', + defaultValue: { summary: 'medium' }, + type: { summary: "'small' | 'medium' | 'large' | 'xlarge'" }, + }, + }, + multicolor: { + control: 'boolean', + description: 'Включить многоцветную анимацию.', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + strokeWidth: { + table: { disable: true }, + }, + fill: { + table: { disable: true }, + }, + animationDuration: { + control: 'text', + description: 'Длительность одной итерации анимации вращения.', + table: { + category: 'Props', + defaultValue: { summary: '2s' }, + type: { summary: 'string' }, + }, + }, + ariaLabel: { + table: { disable: true }, + }, + }, + args: { + size: 'medium', + multicolor: true, + strokeWidth: '2', + fill: 'none', + animationDuration: '2s', + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.size && args.size !== 'medium') parts.push(`size="${args.size}"`); + if (!args.multicolor) parts.push(`[multicolor]="false"`); + if (args.strokeWidth && args.strokeWidth !== '2') parts.push(`strokeWidth="${args.strokeWidth}"`); + if (args.fill && args.fill !== 'none') parts.push(`fill="${args.fill}"`); + if (args.animationDuration && args.animationDuration !== '2s') parts.push(`animationDuration="${args.animationDuration}"`); + + const properties = parts.length > 0 ? ' ' + parts.join('\n ') : ''; + + const template = ` + +`; + return { props: args, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для изменения размера, цвета и толщины линии.', + }, + }, + }, +}; + +// ── Вариации ───────────────────────────────────────────────────────────────── + +export { Sizes, Monochrome }; diff --git a/src/stories/components/radiobutton/examples/radiobutton-disabled.component.ts b/src/stories/components/radiobutton/examples/radiobutton-disabled.component.ts new file mode 100644 index 00000000..750b7e29 --- /dev/null +++ b/src/stories/components/radiobutton/examples/radiobutton-disabled.component.ts @@ -0,0 +1,59 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraRadiobuttonComponent } from '../../../../lib/components/radiobutton/radiobutton.component'; + +const template = ` +
+
+ + +
+
+ + +
+
+`; + +@Component({ + selector: 'app-radiobutton-disabled', + standalone: true, + imports: [ExtraRadiobuttonComponent, FormsModule], + template, +}) +export class RadiobuttonDisabledComponent { + selected = '2'; +} + +export const Disabled: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Заблокированное состояние радиокнопки.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraRadiobuttonComponent } from '@cdek-it/angular-ui-kit'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-radiobutton-disabled', + standalone: true, + imports: [ExtraRadiobuttonComponent, FormsModule], + template: \` + + + \`, +}) +export class RadiobuttonDisabledComponent { + selected = '2'; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/radiobutton/examples/radiobutton-group.component.ts b/src/stories/components/radiobutton/examples/radiobutton-group.component.ts new file mode 100644 index 00000000..b3d63e02 --- /dev/null +++ b/src/stories/components/radiobutton/examples/radiobutton-group.component.ts @@ -0,0 +1,67 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraRadiobuttonComponent } from '../../../../lib/components/radiobutton/radiobutton.component'; + +const template = ` +
+
+ + +
+
+ + +
+
+ + +
+
+`; + +@Component({ + selector: 'app-radiobutton-group', + standalone: true, + imports: [ExtraRadiobuttonComponent, FormsModule], + template, +}) +export class RadiobuttonGroupComponent { + selected = '1'; +} + +export const Group: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Группа радиокнопок для выбора одного варианта из нескольких.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraRadiobuttonComponent } from '@cdek-it/angular-ui-kit'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-radiobutton-group', + standalone: true, + imports: [ExtraRadiobuttonComponent, FormsModule], + template: \` + + + + + + + \`, +}) +export class RadiobuttonGroupComponent { + selected = '1'; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/radiobutton/examples/radiobutton-invalid.component.ts b/src/stories/components/radiobutton/examples/radiobutton-invalid.component.ts new file mode 100644 index 00000000..ea028959 --- /dev/null +++ b/src/stories/components/radiobutton/examples/radiobutton-invalid.component.ts @@ -0,0 +1,59 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraRadiobuttonComponent } from '../../../../lib/components/radiobutton/radiobutton.component'; + +const template = ` +
+
+ + +
+
+ + +
+
+`; + +@Component({ + selector: 'app-radiobutton-invalid', + standalone: true, + imports: [ExtraRadiobuttonComponent, FormsModule], + template +}) +export class RadiobuttonInvalidComponent { + selected = '2'; +} + +export const Invalid: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Невалидное состояние радиокнопки.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraRadiobuttonComponent } from '@cdek-it/angular-ui-kit'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-radiobutton-invalid', + standalone: true, + imports: [ExtraRadiobuttonComponent, FormsModule], + template: \` + + + \`, +}) +export class RadiobuttonInvalidComponent { + selected = '2'; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/radiobutton/radiobutton.stories.ts b/src/stories/components/radiobutton/radiobutton.stories.ts new file mode 100644 index 00000000..c942f6b2 --- /dev/null +++ b/src/stories/components/radiobutton/radiobutton.stories.ts @@ -0,0 +1,122 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormsModule } from '@angular/forms'; +import { ExtraRadiobuttonComponent as RadiobuttonComponent } from '../../../lib/components/radiobutton/radiobutton.component'; +import { RadiobuttonGroupComponent, Group } from './examples/radiobutton-group.component'; +import { RadiobuttonInvalidComponent, Invalid } from './examples/radiobutton-invalid.component'; +import { RadiobuttonDisabledComponent, Disabled } from './examples/radiobutton-disabled.component'; + +type RadiobuttonArgs = RadiobuttonComponent; + +const meta: Meta = { + title: 'Components/Form/RadioButton', + component: RadiobuttonComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + RadiobuttonComponent, + FormsModule, + RadiobuttonGroupComponent, + RadiobuttonInvalidComponent, + RadiobuttonDisabledComponent, + ] + }) + ], + parameters: { + designTokens: { prefix: '--p-radiobutton' }, + docs: { + description: { + component: `Компонент для выбора одного варианта из группы.`, + }, + }, + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + disabled: { + control: 'boolean', + description: 'Отключает возможность взаимодействия', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + invalid: { + control: 'boolean', + description: 'Подсвечивает поле как невалидное', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + variant: { table: { disable: true } }, + // Hidden props + value: { table: { disable: true } }, + name: { table: { disable: true } }, + size: { table: { disable: true } }, + inputId: { table: { disable: true } }, + tabindex: { table: { disable: true } }, + ariaLabel: { table: { disable: true } }, + ariaLabelledBy: { table: { disable: true } }, + autofocus: { table: { disable: true } }, + + // ── Events ─────────────────────────────────────────────── + onClick: { + control: false, + description: 'Событие выбора радиокнопки', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + onFocus: { + control: false, + description: 'Событие фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + onBlur: { + control: false, + description: 'Событие потери фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, + args: { + disabled: false, + invalid: false, + variant: 'outlined', + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = [`value="option1"`, `name="demo"`, `[(ngModel)]="selected"`]; + if (args.disabled) parts.push(`[disabled]="true"`); + if (args.invalid) parts.push(`[invalid]="true"`); + if (args.variant && args.variant !== 'outlined') parts.push(`variant="${args.variant}"`); + + const template = ``; + return { props: { ...args, selected: 'option1' }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { Group, Invalid, Disabled }; diff --git a/src/stories/components/rating/examples/rating-disabled.component.ts b/src/stories/components/rating/examples/rating-disabled.component.ts new file mode 100644 index 00000000..3ed4db9b --- /dev/null +++ b/src/stories/components/rating/examples/rating-disabled.component.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraRatingComponent } from '../../../../lib/components/rating/rating.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-rating-disabled', + standalone: true, + imports: [ExtraRatingComponent, FormsModule], + template +}) +export class RatingDisabledComponent { + value = 2; +} + +export const Disabled: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Заблокированное состояние — компонент недоступен для взаимодействия.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { RatingComponent } from '@cdek-it/angular-ui-kit'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-rating-disabled', + standalone: true, + imports: [RatingComponent, FormsModule], + template: \` + + \`, +}) +export class RatingDisabledComponent { + value = 2; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/rating/examples/rating-readonly.component.ts b/src/stories/components/rating/examples/rating-readonly.component.ts new file mode 100644 index 00000000..47e22bc3 --- /dev/null +++ b/src/stories/components/rating/examples/rating-readonly.component.ts @@ -0,0 +1,51 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraRatingComponent } from '../../../../lib/components/rating/rating.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-rating-readonly', + standalone: true, + imports: [ExtraRatingComponent, FormsModule], + template +}) +export class RatingReadonlyComponent { + value = 4; +} + +export const ReadOnly: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Режим только для чтения — значение отображается, но не может быть изменено.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { RatingComponent } from '@cdek-it/angular-ui-kit'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-rating-readonly', + standalone: true, + imports: [RatingComponent, FormsModule], + template: \` + + \`, +}) +export class RatingReadonlyComponent { + value = 4; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/rating/rating.stories.ts b/src/stories/components/rating/rating.stories.ts new file mode 100644 index 00000000..6f1f147d --- /dev/null +++ b/src/stories/components/rating/rating.stories.ts @@ -0,0 +1,115 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormsModule } from '@angular/forms'; +import { ExtraRatingComponent } from '../../../lib/components/rating/rating.component'; +import { RatingReadonlyComponent, ReadOnly } from './examples/rating-readonly.component'; +import { RatingDisabledComponent, Disabled } from './examples/rating-disabled.component'; + +type RatingArgs = ExtraRatingComponent; + +const meta: Meta = { + title: 'Components/Form/Rating', + component: ExtraRatingComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ExtraRatingComponent, FormsModule, RatingReadonlyComponent, RatingDisabledComponent] + }) + ], + parameters: { + designTokens: { prefix: '--p-rating' }, + docs: { + description: { + component: `Компонент для отображения и выбора оценки в виде звёзд.` + } + } + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + stars: { + control: 'number', + description: 'Количество отображаемых звёзд', + table: { + category: 'Props', + defaultValue: { summary: '5' }, + type: { summary: 'number' } + } + }, + readonly: { + control: 'boolean', + description: 'Режим только для чтения — значение нельзя изменить', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + disabled: { + control: 'boolean', + description: 'Отключает возможность взаимодействия', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + autofocus: { table: { disable: true } }, + + // ── Events ─────────────────────────────────────────────── + onRate: { + control: false, + description: 'Событие изменения оценки', + table: { + category: 'Events', + type: { summary: 'EventEmitter' } + } + }, + onFocus: { + control: false, + description: 'Событие фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' } + } + }, + onBlur: { + control: false, + description: 'Событие потери фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' } + } + } + }, + args: { + stars: 5, + readonly: false, + disabled: false + } +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = [`[(ngModel)]="value"`]; + if (args.stars !== 5) parts.push(`[stars]="${args.stars}"`); + if (args.readonly) parts.push(`[readonly]="true"`); + if (args.disabled) parts.push(`[disabled]="true"`); + + const template = ``; + return { props: { ...args, value: 3 }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { ReadOnly, Disabled }; diff --git a/src/stories/components/scroll-panel/examples/scroll-panel-both.component.ts b/src/stories/components/scroll-panel/examples/scroll-panel-both.component.ts new file mode 100644 index 00000000..b0324e31 --- /dev/null +++ b/src/stories/components/scroll-panel/examples/scroll-panel-both.component.ts @@ -0,0 +1,29 @@ +import { Component } from '@angular/core'; +import { ExtraScrollPanelComponent } from '../../../../lib/components/scroll-panel/scroll-panel.component'; + +const template = ` +
+ +
+

№ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места · Принят 14 апр 09:15 · Доставлен 15 апр 14:20

+

№ЦД-00123457 · Санкт-Петербург → Казань · 0.8 кг · 1 место · Принят 13 апр 11:00 · Доставлен 15 апр 10:30

+

№ЦД-00123458 · Екатеринбург → Краснодар · 5.2 кг · 2 места · Принят 12 апр 08:45 · В пути

+

№ЦД-00123459 · Нижний Новгород → Омск · 1.1 кг · 1 место · Принят 14 апр 15:00 · Ожидает отправки

+

№ЦД-00123460 · Самара → Ростов-на-Дону · 3.7 кг · 4 места · Принят 11 апр 13:20 · Доставлен 14 апр 09:00

+

№ЦД-00123461 · Уфа → Владивосток · 7.3 кг · 5 мест · Принят 10 апр 10:00 · Задержан на сортировке

+

№ЦД-00123462 · Пермь → Иркутск · 2.0 кг · 2 места · Принят 13 апр 09:30 · В пути

+

№ЦД-00123463 · Воронеж → Красноярск · 4.5 кг · 3 места · Принят 12 апр 16:00 · В пути

+
+
+
+`; +const styles = ''; + +@Component({ + selector: 'app-scroll-panel-both', + standalone: true, + imports: [ExtraScrollPanelComponent], + template, + styles, +}) +export class ScrollPanelBothComponent {} diff --git a/src/stories/components/scroll-panel/examples/scroll-panel-horizontal.component.ts b/src/stories/components/scroll-panel/examples/scroll-panel-horizontal.component.ts new file mode 100644 index 00000000..3becdf99 --- /dev/null +++ b/src/stories/components/scroll-panel/examples/scroll-panel-horizontal.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; +import { ExtraScrollPanelComponent } from '../../../../lib/components/scroll-panel/scroll-panel.component'; + +const template = ` +
+ +

+ Заказ №ЦД-00123456 · Москва → Новосибирск · Принят 14 апр 09:15 · Передан перевозчику 14 апр 14:30 · Отправлен из Москвы 14 апр 23:50 · Прибыл в Новосибирск 15 апр 08:00 · Передан курьеру Петрову А.В. 15 апр 12:00 · Доставлен получателю 15 апр 14:20 +

+
+
+`; +const styles = ''; + +@Component({ + selector: 'app-scroll-panel-horizontal', + standalone: true, + imports: [ExtraScrollPanelComponent], + template, + styles, +}) +export class ScrollPanelHorizontalComponent {} diff --git a/src/stories/components/scroll-panel/scroll-panel.stories.ts b/src/stories/components/scroll-panel/scroll-panel.stories.ts new file mode 100644 index 00000000..7d765632 --- /dev/null +++ b/src/stories/components/scroll-panel/scroll-panel.stories.ts @@ -0,0 +1,200 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraScrollPanelComponent } from '../../../lib/components/scroll-panel/scroll-panel.component'; +import { ScrollPanelHorizontalComponent } from './examples/scroll-panel-horizontal.component'; +import { ScrollPanelBothComponent } from './examples/scroll-panel-both.component'; + +const TRACKING_CONTENT = ` +
+

Заказ №ЦД-00123456 · Москва → Новосибирск

+

14 апр, 09:15 — Принят в сортировочном центре «Москва-Север»

+

14 апр, 14:30 — Передан перевозчику CDEK

+

14 апр, 23:50 — Отправлен из Москвы

+

15 апр, 08:00 — Прибыл в сортировочный центр «Новосибирск»

+

15 апр, 12:00 — Передан курьеру Петрову А.В.

+

15 апр, 14:20 — Попытка доставки (получатель отсутствовал)

+

15 апр, 18:00 — Ожидает получения в ПВЗ на ул. Ленина, 42

+

16 апр, 10:30 — Доставлен получателю

+
+`; + +const HORIZONTAL_CONTENT = ` +

+ Заказ №ЦД-00123456 · Москва → Новосибирск · Принят 14 апр 09:15 · Передан перевозчику 14 апр 14:30 · Отправлен из Москвы 14 апр 23:50 · Прибыл в Новосибирск 15 апр 08:00 · Передан курьеру Петрову А.В. 15 апр 12:00 · Доставлен получателю 15 апр 14:20 +

+`; + +const BOTH_CONTENT = ` +
+

№ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места · Принят 14 апр 09:15 · Доставлен 15 апр 14:20

+

№ЦД-00123457 · Санкт-Петербург → Казань · 0.8 кг · 1 место · Принят 13 апр 11:00 · Доставлен 15 апр 10:30

+

№ЦД-00123458 · Екатеринбург → Краснодар · 5.2 кг · 2 места · Принят 12 апр 08:45 · В пути

+

№ЦД-00123459 · Нижний Новгород → Омск · 1.1 кг · 1 место · Принят 14 апр 15:00 · Ожидает отправки

+

№ЦД-00123460 · Самара → Ростов-на-Дону · 3.7 кг · 4 места · Принят 11 апр 13:20 · Доставлен 14 апр 09:00

+

№ЦД-00123461 · Уфа → Владивосток · 7.3 кг · 5 мест · Принят 10 апр 10:00 · Задержан на сортировке

+

№ЦД-00123462 · Пермь → Иркутск · 2.0 кг · 2 места · Принят 13 апр 09:30 · В пути

+

№ЦД-00123463 · Воронеж → Красноярск · 4.5 кг · 3 места · Принят 12 апр 16:00 · В пути

+
+`; + +const meta: Meta = { + title: 'Components/Panel/ScrollPanel', + component: ExtraScrollPanelComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraScrollPanelComponent, + ScrollPanelHorizontalComponent, + ScrollPanelBothComponent, + ], + }), + ], + parameters: { + docs: { + description: { + component: `Кроссбраузерная панель с кастомной полосой прокрутки. Заменяет стандартный скроллбар браузера на стилизованный в соответствии с темой. + +\`\`\`typescript +import { ScrollPanelModule } from 'primeng/scrollpanel'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-scrollpanel' }, + }, + argTypes: { + step: { + control: 'number', + description: 'Шаг прокрутки при нажатии клавиш со стрелками (в пикселях)', + table: { + category: 'Props', + defaultValue: { summary: '10' }, + type: { summary: 'number' }, + }, + }, + height: { + control: 'text', + description: 'Высота области прокрутки', + table: { + category: 'Props', + defaultValue: { summary: '100px' }, + type: { summary: 'string' }, + }, + }, + width: { + control: 'text', + description: 'Ширина области прокрутки', + table: { + category: 'Props', + defaultValue: { summary: '100%' }, + type: { summary: 'string' }, + }, + }, + }, + args: { + step: 10, + height: '100px', + width: '100%', + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.height && args.height !== '100px') parts.push(`height="${args.height}"`); + if (args.width && args.width !== '100%') parts.push(`width="${args.width}"`); + if (args.step !== 10) parts.push(`[step]="${args.step}"`); + + const attrStr = parts.length ? `\n ${parts.join('\n ')}\n` : ''; + const template = `${TRACKING_CONTENT}`; + + return { props: args, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Stories ────────────────────────────────────────────────────────────────── + +export const Horizontal: Story = { + render: (args) => ({ + props: args, + template: `${HORIZONTAL_CONTENT}`, + }), + args: { step: 10, height: '80px', width: '100%' }, + parameters: { + docs: { + description: { story: 'Горизонтальная прокрутка. Контент без переносов выходит за ширину контейнера.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraScrollPanelComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-scroll-panel-horizontal', + standalone: true, + imports: [ExtraScrollPanelComponent], + template: \` + +

+ Заказ №ЦД-00123456 · Москва → Новосибирск · Принят 14 апр 09:15 · Передан перевозчику 14 апр 14:30 · Отправлен из Москвы 14 апр 23:50 · Прибыл в Новосибирск 15 апр 08:00 · Доставлен получателю 15 апр 14:20 +

+
+ \`, +}) +export class ScrollPanelHorizontalComponent {} + `, + }, + }, + }, +}; + +export const Both: Story = { + render: (args) => ({ + props: args, + template: `${BOTH_CONTENT}`, + }), + args: { step: 10, height: '200px', width: '50%' }, + parameters: { + docs: { + description: { story: 'Прокрутка в обоих направлениях — таблица отправлений шире и длиннее контейнера.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraScrollPanelComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-scroll-panel-both', + standalone: true, + imports: [ExtraScrollPanelComponent], + template: \` + +
+

№ЦД-00123456 · Москва → Новосибирск · 2.5 кг · 3 места · Принят 14 апр · Доставлен 15 апр

+

№ЦД-00123457 · Санкт-Петербург → Казань · 0.8 кг · 1 место · Принят 13 апр · Доставлен 15 апр

+

№ЦД-00123458 · Екатеринбург → Краснодар · 5.2 кг · 2 места · Принят 12 апр · В пути

+

№ЦД-00123459 · Нижний Новгород → Омск · 1.1 кг · 1 место · Принят 14 апр · Ожидает отправки

+

№ЦД-00123460 · Самара → Ростов-на-Дону · 3.7 кг · 4 места · Принят 11 апр · Доставлен 14 апр

+
+
+ \`, +}) +export class ScrollPanelBothComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/select/examples/select-custom.component.ts b/src/stories/components/select/examples/select-custom.component.ts new file mode 100644 index 00000000..dee3aa0f --- /dev/null +++ b/src/stories/components/select/examples/select-custom.component.ts @@ -0,0 +1,108 @@ +import { Component, Input } from '@angular/core'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ExtraSelectComponent, SelectSize } from '../../../../lib/components/select/select.component'; + +const OPTIONS = [ + { name: 'Профиль', description: 'Настройки аккаунта', icon: 'ti ti-user' }, + { name: 'Настройки', description: 'Параметры приложения', icon: 'ti ti-settings' }, + { name: 'Сообщения', description: 'Входящие', icon: 'ti ti-message' }, +]; + +const template = ` + +
+ +
+
{{ option.name }}
+ {{ option.description }} +
+
+
+ +`; +const styles = ''; + +@Component({ + selector: 'app-select-custom', + standalone: true, + imports: [ExtraSelectComponent, ReactiveFormsModule], + template, + styles, +}) +export class SelectCustomComponent { + @Input() size: SelectSize = 'base'; + @Input() showClear = false; + @Input() readonly = false; + control = new FormControl(null); + options = OPTIONS; + + @Input() set disabled(val: boolean) { + val ? this.control.disable() : this.control.enable(); + } + + @Input() set invalid(val: boolean) { + this.control.setValidators(val ? [Validators.required] : []); + this.control.updateValueAndValidity(); + if (val) this.control.markAsTouched(); + } +} + +export const Custom = { + render: (args: any) => ({ + props: { size: args['size'], showClear: args['showClear'], readonly: args['readonly'], disabled: args['disabled'], invalid: args['invalid'] }, + template: ``, + }), + parameters: { + docs: { + description: { story: 'Кастомный шаблон опции с иконкой и описанием.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraSelectComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraSelectComponent, ReactiveFormsModule], + template: \` + +
+ +
+
{{ option.name }}
+ {{ option.description }} +
+
+
+ + \`, +}) +export class SelectCustomExample { + control = new FormControl(null); + options = [ + { name: 'Профиль', description: 'Настройки аккаунта', icon: 'ti ti-user' }, + { name: 'Настройки', description: 'Параметры приложения', icon: 'ti ti-settings' }, + { name: 'Сообщения', description: 'Входящие', icon: 'ti ti-message' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/select/examples/select-disabled.component.ts b/src/stories/components/select/examples/select-disabled.component.ts new file mode 100644 index 00000000..14a6143e --- /dev/null +++ b/src/stories/components/select/examples/select-disabled.component.ts @@ -0,0 +1,69 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraSelectComponent } from '../../../../lib/components/select/select.component'; + +const OPTIONS = [ + { name: 'Новосибирск', code: 'NSK' }, + { name: 'Москва', code: 'MSK' }, + { name: 'Санкт-Петербург', code: 'SPB' }, +]; + +export const Disabled: StoryObj = { + name: 'Disabled', + render: () => { + const control = new FormControl({ value: null, disabled: true }); + return { + props: { control, options: OPTIONS }, + template: ` + + `, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraSelectComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Отключённое состояние — управляется через FormControl.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraSelectComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraSelectComponent, ReactiveFormsModule], + template: \` + + \`, +}) +export class SelectDisabledExample { + control = new FormControl({ value: null, disabled: true }); + options = [ + { name: 'Новосибирск', code: 'NSK' }, + { name: 'Москва', code: 'MSK' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/select/examples/select-editable.component.ts b/src/stories/components/select/examples/select-editable.component.ts new file mode 100644 index 00000000..bab7fc85 --- /dev/null +++ b/src/stories/components/select/examples/select-editable.component.ts @@ -0,0 +1,90 @@ +import { Component, Input } from '@angular/core'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ExtraSelectComponent, SelectSize } from '../../../../lib/components/select/select.component'; + +const OPTIONS = [ + { name: 'Новосибирск', code: 'NSK' }, + { name: 'Москва', code: 'MSK' }, + { name: 'Санкт-Петербург', code: 'SPB' }, +]; + +const template = ` + +`; +const styles = ''; + +@Component({ + selector: 'app-select-editable', + standalone: true, + imports: [ExtraSelectComponent, ReactiveFormsModule], + template, + styles, +}) +export class SelectEditableComponent { + @Input() size: SelectSize = 'base'; + @Input() showClear = false; + @Input() readonly = false; + control = new FormControl(null); + options = OPTIONS; + + @Input() set disabled(val: boolean) { + val ? this.control.disable() : this.control.enable(); + } + + @Input() set invalid(val: boolean) { + this.control.setValidators(val ? [Validators.required] : []); + this.control.updateValueAndValidity(); + if (val) this.control.markAsTouched(); + } +} + +export const Editable = { + render: (args: any) => ({ + props: { size: args['size'], showClear: args['showClear'], readonly: args['readonly'], disabled: args['disabled'], invalid: args['invalid'] }, + template: ``, + }), + parameters: { + docs: { + description: { story: 'Редактируемый режим — позволяет вводить произвольное значение.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraSelectComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraSelectComponent, ReactiveFormsModule], + template: \` + + \`, +}) +export class SelectEditableExample { + control = new FormControl(null); + options = [ + { name: 'Новосибирск', code: 'NSK' }, + { name: 'Москва', code: 'MSK' }, + { name: 'Санкт-Петербург', code: 'SPB' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/select/examples/select-filter.component.ts b/src/stories/components/select/examples/select-filter.component.ts new file mode 100644 index 00000000..fa83be8b --- /dev/null +++ b/src/stories/components/select/examples/select-filter.component.ts @@ -0,0 +1,92 @@ +import { Component, Input } from '@angular/core'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ExtraSelectComponent, SelectSize } from '../../../../lib/components/select/select.component'; + +const OPTIONS = [ + { name: 'Новосибирск', code: 'NSK' }, + { name: 'Москва', code: 'MSK' }, + { name: 'Санкт-Петербург', code: 'SPB' }, + { name: 'Екатеринбург', code: 'EKB' }, + { name: 'Казань', code: 'KZN' }, +]; + +const template = ` + +`; +const styles = ''; + +@Component({ + selector: 'app-select-filter', + standalone: true, + imports: [ExtraSelectComponent, ReactiveFormsModule], + template, + styles, +}) +export class SelectFilterComponent { + @Input() size: SelectSize = 'base'; + @Input() readonly = false; + control = new FormControl(null); + options = OPTIONS; + + @Input() set disabled(val: boolean) { + val ? this.control.disable() : this.control.enable(); + } + + @Input() set invalid(val: boolean) { + this.control.setValidators(val ? [Validators.required] : []); + this.control.updateValueAndValidity(); + if (val) this.control.markAsTouched(); + } +} + +export const Filter = { + render: (args: any) => ({ + props: { size: args['size'], readonly: args['readonly'], disabled: args['disabled'], invalid: args['invalid'] }, + template: ``, + }), + parameters: { + docs: { + description: { story: 'Выпадающий список с поиском по опциям.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraSelectComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraSelectComponent, ReactiveFormsModule], + template: \` + + \`, +}) +export class SelectFilterExample { + control = new FormControl(null); + options = [ + { name: 'Новосибирск', code: 'NSK' }, + { name: 'Москва', code: 'MSK' }, + { name: 'Санкт-Петербург', code: 'SPB' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/select/examples/select-float-label.component.ts b/src/stories/components/select/examples/select-float-label.component.ts new file mode 100644 index 00000000..f1aa5c34 --- /dev/null +++ b/src/stories/components/select/examples/select-float-label.component.ts @@ -0,0 +1,103 @@ +import { Component, Input } from '@angular/core'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ExtraSelectComponent } from '../../../../lib/components/select/select.component'; + +const OPTIONS = [ + { name: 'Новосибирск', code: 'NSK' }, + { name: 'Москва', code: 'MSK' }, + { name: 'Санкт-Петербург', code: 'SPB' }, + { name: 'Екатеринбург', code: 'EKB' }, +]; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-select-float-label', + standalone: true, + imports: [ExtraSelectComponent, ReactiveFormsModule], + template, + styles, +}) +export class SelectFloatLabelComponent { + @Input() showClear = false; + @Input() readonly = false; + @Input() label = 'Город'; + control = new FormControl(null); + options = OPTIONS; + + @Input() set disabled(val: boolean) { + val ? this.control.disable() : this.control.enable(); + } + + @Input() set invalid(val: boolean) { + this.control.setValidators(val ? [Validators.required] : []); + this.control.updateValueAndValidity(); + if (val) this.control.markAsTouched(); + } +} + +export const FloatLabelStory = { + name: 'FloatLabel', + render: (args: any) => ({ + props: { showClear: args['showClear'], label: 'Город', readonly: args['readonly'], disabled: args['disabled'], invalid: args['invalid'] }, + template: ``, + }), + argTypes: { + size: { table: { disable: true } }, + }, + parameters: { + docs: { + description: { + story: 'Плавающая метка внутри поля. Используйте пропс `floatLabel` для встроенной интеграции с `p-floatlabel`.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraSelectComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraSelectComponent, ReactiveFormsModule], + template: \` +
+ +
+ \`, +}) +export class SelectFloatLabelExample { + control = new FormControl(null); + options = [ + { name: 'Новосибирск', code: 'NSK' }, + { name: 'Москва', code: 'MSK' }, + { name: 'Санкт-Петербург', code: 'SPB' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/select/examples/select-grouped.component.ts b/src/stories/components/select/examples/select-grouped.component.ts new file mode 100644 index 00000000..19b893dd --- /dev/null +++ b/src/stories/components/select/examples/select-grouped.component.ts @@ -0,0 +1,132 @@ +import { Component, Input } from '@angular/core'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ExtraSelectComponent, SelectSize } from '../../../../lib/components/select/select.component'; + +const GROUPED_OPTIONS = [ + { + label: 'Германия', + items: [ + { label: 'Берлин', value: 'BE' }, + { label: 'Франкфурт', value: 'FR' }, + { label: 'Гамбург', value: 'HA' }, + ], + }, + { + label: 'США', + items: [ + { label: 'Чикаго', value: 'CH' }, + { label: 'Лос-Анджелес', value: 'LA' }, + { label: 'Нью-Йорк', value: 'NY' }, + ], + }, +]; + +const template = ` + +
+ + {{ group.label }} +
+
+ +`; +const styles = ''; + +@Component({ + selector: 'app-select-grouped', + standalone: true, + imports: [ExtraSelectComponent, ReactiveFormsModule], + template, + styles, +}) +export class SelectGroupedComponent { + @Input() size: SelectSize = 'base'; + @Input() showClear = false; + @Input() readonly = false; + control = new FormControl(null); + options = GROUPED_OPTIONS; + + @Input() set disabled(val: boolean) { + val ? this.control.disable() : this.control.enable(); + } + + @Input() set invalid(val: boolean) { + this.control.setValidators(val ? [Validators.required] : []); + this.control.updateValueAndValidity(); + if (val) this.control.markAsTouched(); + } +} + +export const Grouped = { + render: (args: any) => ({ + props: { size: args['size'], showClear: args['showClear'], readonly: args['readonly'], disabled: args['disabled'], invalid: args['invalid'] }, + template: ``, + }), + parameters: { + docs: { + description: { story: 'Опции, сгруппированные по категориям с кастомным заголовком группы.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraSelectComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraSelectComponent, ReactiveFormsModule], + template: \` + +
+ + {{ group.label }} +
+
+ + \`, +}) +export class SelectGroupedExample { + control = new FormControl(null); + options = [ + { + label: 'Германия', + items: [ + { label: 'Берлин', value: 'BE' }, + { label: 'Франкфурт', value: 'FR' }, + ], + }, + { + label: 'США', + items: [ + { label: 'Нью-Йорк', value: 'NY' }, + { label: 'Чикаго', value: 'CH' }, + ], + }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/select/select.stories.ts b/src/stories/components/select/select.stories.ts new file mode 100644 index 00000000..1e66cd37 --- /dev/null +++ b/src/stories/components/select/select.stories.ts @@ -0,0 +1,203 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ExtraSelectComponent } from '../../../lib/components/select/select.component'; +import { SelectFilterComponent, Filter as FilterStory } from './examples/select-filter.component'; +import { SelectGroupedComponent, Grouped as GroupedStory } from './examples/select-grouped.component'; +import { SelectCustomComponent, Custom as CustomStory } from './examples/select-custom.component'; +import { SelectEditableComponent, Editable as EditableStory } from './examples/select-editable.component'; +import { Disabled as DisabledStory } from './examples/select-disabled.component'; +import { SelectFloatLabelComponent, FloatLabelStory } from './examples/select-float-label.component'; + + +const BASIC_OPTIONS = [ + { name: 'Новосибирск', code: 'NSK' }, + { name: 'Москва', code: 'MSK' }, + { name: 'Санкт-Петербург', code: 'SPB' }, + { name: 'Екатеринбург', code: 'EKB' }, + { name: 'Казань', code: 'KZN' }, +]; + +type SelectArgs = Pick & { + disabled: boolean; + invalid: boolean; +}; + +const meta: Meta = { + title: 'Components/Form/Select', + component: ExtraSelectComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraSelectComponent, + ReactiveFormsModule, + SelectFilterComponent, + SelectGroupedComponent, + SelectCustomComponent, + SelectEditableComponent, + SelectFloatLabelComponent, + ], + }), + ], + parameters: { + docs: { + description: { + component: `Выпадающий список для выбора одного значения из набора опций. Поддерживает фильтрацию, группировку, кастомные шаблоны и редактируемый ввод. + +\`\`\`typescript +import { ExtraSelectComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-select' }, + }, + argTypes: { + size: { + control: 'select', + options: ['small', 'base', 'large', 'xlarge'], + description: 'Размер поля', + table: { + category: 'Props', + defaultValue: { summary: "'base'" }, + type: { summary: "'small' | 'base' | 'large' | 'xlarge'" }, + }, + }, + placeholder: { + control: 'text', + description: 'Текст подсказки при пустом поле', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + showClear: { + control: 'boolean', + description: 'Отображает иконку очистки выбранного значения', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + filter: { + control: 'boolean', + description: 'Включает строку поиска в выпадающем списке', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + readonly: { + control: 'boolean', + description: 'Режим только для чтения', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + checkmark: { + control: 'boolean', + description: 'Отображает иконку выбранного элемента в списке', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + disabled: { + control: 'boolean', + description: 'Отключает взаимодействие — управляется через FormControl', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + invalid: { + control: 'boolean', + description: 'Невалидное состояние — управляется через FormControl', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + }, + args: { + size: 'base', + placeholder: 'Выберите город...', + showClear: true, + filter: false, + readonly: false, + checkmark: true, + disabled: false, + invalid: false, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ─────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => { + const control = new FormControl( + { value: null, disabled: !!args['disabled'] }, + args['invalid'] ? [Validators.required] : [] + ); + if (args['invalid']) control.markAsTouched(); + + return { + props: { ...args, control, options: BASIC_OPTIONS }, + template: ` + + `, + }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Filter ──────────────────────────────────────────────────────────────────── + +export const Filter: Story = FilterStory; + +// ── Grouped ─────────────────────────────────────────────────────────────────── + +export const Grouped: Story = GroupedStory; + +// ── Custom ──────────────────────────────────────────────────────────────────── + +export const Custom: Story = CustomStory; + +// ── Editable ────────────────────────────────────────────────────────────────── + +export const Editable: Story = EditableStory; + +// ── Disabled ────────────────────────────────────────────────────────────────── + +export const Disabled: Story = DisabledStory; + +// ── FloatLabel ──────────────────────────────────────────────────────────────── + +export const FloatLabel: Story = FloatLabelStory; diff --git a/src/stories/components/skeleton/examples/skeleton-card-placeholder.component.ts b/src/stories/components/skeleton/examples/skeleton-card-placeholder.component.ts new file mode 100644 index 00000000..a36f5ff2 --- /dev/null +++ b/src/stories/components/skeleton/examples/skeleton-card-placeholder.component.ts @@ -0,0 +1,61 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraSkeletonComponent } from '../../../../lib/components/skeleton/skeleton.component'; + +const template = ` +
+
+ +
+ + + +
+
+
+`; +const styles = ''; + +@Component({ + selector: 'app-skeleton-card-placeholder', + standalone: true, + imports: [ExtraSkeletonComponent], + template, + styles, +}) +export class SkeletonCardPlaceholderComponent {} + +export const CardPlaceholder: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Составная заглушка карточки отправления: аватар курьера и строки с информацией.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { SkeletonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-skeleton-card-placeholder', + standalone: true, + imports: [SkeletonComponent], + template: \` +
+ +
+ + + +
+
+ \`, +}) +export class SkeletonCardPlaceholderComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/skeleton/examples/skeleton-circle.component.ts b/src/stories/components/skeleton/examples/skeleton-circle.component.ts new file mode 100644 index 00000000..42d9e380 --- /dev/null +++ b/src/stories/components/skeleton/examples/skeleton-circle.component.ts @@ -0,0 +1,55 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraSkeletonComponent } from '../../../../lib/components/skeleton/skeleton.component'; + +const template = ` +
+
+ + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-skeleton-circle', + standalone: true, + imports: [ExtraSkeletonComponent], + template, + styles, +}) +export class SkeletonCircleComponent {} + +export const Circle: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Круглые заглушки для аватаров и иконок — например, фото курьера или транспортного средства.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { SkeletonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-skeleton-circle', + standalone: true, + imports: [SkeletonComponent], + template: \` +
+ + + +
+ \`, +}) +export class SkeletonCircleComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/skeleton/examples/skeleton-no-animation.component.ts b/src/stories/components/skeleton/examples/skeleton-no-animation.component.ts new file mode 100644 index 00000000..7798a6e2 --- /dev/null +++ b/src/stories/components/skeleton/examples/skeleton-no-animation.component.ts @@ -0,0 +1,55 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraSkeletonComponent } from '../../../../lib/components/skeleton/skeleton.component'; + +const template = ` +
+
+ + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-skeleton-no-animation', + standalone: true, + imports: [ExtraSkeletonComponent], + template, + styles, +}) +export class SkeletonNoAnimationComponent {} + +export const NoAnimation: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Скелетон без волновой анимации — статичная заглушка для состояния ожидания.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { SkeletonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-skeleton-no-animation', + standalone: true, + imports: [SkeletonComponent], + template: \` +
+ + + +
+ \`, +}) +export class SkeletonNoAnimationComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/skeleton/examples/skeleton-rectangles.component.ts b/src/stories/components/skeleton/examples/skeleton-rectangles.component.ts new file mode 100644 index 00000000..3800faeb --- /dev/null +++ b/src/stories/components/skeleton/examples/skeleton-rectangles.component.ts @@ -0,0 +1,55 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraSkeletonComponent } from '../../../../lib/components/skeleton/skeleton.component'; + +const template = ` +
+
+ + + +
+
+`; +const styles = ''; + +@Component({ + selector: 'app-skeleton-rectangles', + standalone: true, + imports: [ExtraSkeletonComponent], + template, + styles, +}) +export class SkeletonRectanglesComponent {} + +export const Rectangles: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Прямоугольные строки-заглушки разной ширины — паттерн для списка отправлений или текстового контента.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { SkeletonComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-skeleton-rectangles', + standalone: true, + imports: [SkeletonComponent], + template: \` +
+ + + +
+ \`, +}) +export class SkeletonRectanglesComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/skeleton/skeleton.stories.ts b/src/stories/components/skeleton/skeleton.stories.ts new file mode 100644 index 00000000..752121ed --- /dev/null +++ b/src/stories/components/skeleton/skeleton.stories.ts @@ -0,0 +1,139 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraSkeletonComponent as SkeletonComponent } from '../../../lib/components/skeleton/skeleton.component'; +import { SkeletonRectanglesComponent, Rectangles } from './examples/skeleton-rectangles.component'; +import { SkeletonCircleComponent, Circle } from './examples/skeleton-circle.component'; +import { SkeletonCardPlaceholderComponent, CardPlaceholder } from './examples/skeleton-card-placeholder.component'; +import { SkeletonNoAnimationComponent, NoAnimation } from './examples/skeleton-no-animation.component'; + +const meta: Meta = { + title: 'Components/Misc/Skeleton', + component: SkeletonComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + SkeletonComponent, + SkeletonRectanglesComponent, + SkeletonCircleComponent, + SkeletonCardPlaceholderComponent, + SkeletonNoAnimationComponent, + ], + }), + ], + parameters: { + docs: { + description: { + component: `Заглушка для контента, пока данные загружаются. Поддерживает прямоугольную и круглую форму, а также волновую анимацию. + +\`\`\`typescript +import { SkeletonComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-skeleton' }, + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + shape: { + control: 'select', + options: ['rectangle', 'circle'], + description: 'Форма скелетона', + table: { + category: 'Props', + defaultValue: { summary: 'rectangle' }, + type: { summary: "'rectangle' | 'circle'" }, + }, + }, + animation: { + control: 'select', + options: ['wave', 'none'], + description: 'Тип анимации', + table: { + category: 'Props', + defaultValue: { summary: 'wave' }, + type: { summary: "'wave' | 'none'" }, + }, + }, + width: { + control: 'text', + description: 'Ширина элемента', + table: { + category: 'Props', + defaultValue: { summary: '100%' }, + type: { summary: 'string' }, + }, + }, + height: { + control: 'text', + description: 'Высота элемента', + table: { + category: 'Props', + defaultValue: { summary: '1rem' }, + type: { summary: 'string' }, + }, + }, + size: { + control: 'text', + description: 'Размер элемента (устанавливает ширину и высоту одновременно; используется для circle)', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'string' }, + }, + }, + borderRadius: { + control: 'text', + description: 'Скругление углов (переопределяет значение темы)', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'string' }, + }, + }, + }, + args: { + shape: 'rectangle', + animation: 'wave', + width: '100%', + height: '1rem', + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => { + const effectiveSize = args.shape === 'circle' && !args.size ? '4rem' : args.size; + const parts: string[] = []; + + if (args.shape && args.shape !== 'rectangle') parts.push(`shape="${args.shape}"`); + if (args.animation && args.animation !== 'wave') parts.push(`animation="${args.animation}"`); + if (effectiveSize) { + parts.push(`size="${effectiveSize}"`); + } else { + if (args.width && args.width !== '100%') parts.push(`width="${args.width}"`); + if (args.height && args.height !== '1rem') parts.push(`height="${args.height}"`); + } + if (args.borderRadius) parts.push(`borderRadius="${args.borderRadius}"`); + + const template = parts.length + ? `` + : ``; + + return { props: { ...args, size: effectiveSize }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { Rectangles, Circle, CardPlaceholder, NoAnimation }; diff --git a/src/stories/components/slider/examples/slider-disabled.component.ts b/src/stories/components/slider/examples/slider-disabled.component.ts new file mode 100644 index 00000000..b1ff55fb --- /dev/null +++ b/src/stories/components/slider/examples/slider-disabled.component.ts @@ -0,0 +1,47 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraSliderComponent } from '../../../../lib/components/slider/slider.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-slider-disabled', + standalone: true, + imports: [ExtraSliderComponent], + template, + styles, +}) +export class SliderDisabledComponent {} + +export const Disabled: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Слайдер в отключённом состоянии.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraSliderComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-slider-disabled', + standalone: true, + imports: [ExtraSliderComponent], + template: \` + + \`, +}) +export class SliderDisabledComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/slider/examples/slider-range.component.ts b/src/stories/components/slider/examples/slider-range.component.ts new file mode 100644 index 00000000..a7f72547 --- /dev/null +++ b/src/stories/components/slider/examples/slider-range.component.ts @@ -0,0 +1,53 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraSliderComponent } from '../../../../lib/components/slider/slider.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-slider-range', + standalone: true, + imports: [ExtraSliderComponent, FormsModule], + template, + styles, +}) +export class SliderRangeComponent { + value: number[] = [20, 80]; +} + +export const Range: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Выбор диапазона значений с двумя ползунками.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraSliderComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-slider-range', + standalone: true, + imports: [ExtraSliderComponent, FormsModule], + template: \` + + \`, +}) +export class SliderRangeComponent { + value: number[] = [20, 80]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/slider/examples/slider-step.component.ts b/src/stories/components/slider/examples/slider-step.component.ts new file mode 100644 index 00000000..4ea85e0f --- /dev/null +++ b/src/stories/components/slider/examples/slider-step.component.ts @@ -0,0 +1,53 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraSliderComponent } from '../../../../lib/components/slider/slider.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-slider-step', + standalone: true, + imports: [ExtraSliderComponent, FormsModule], + template, + styles, +}) +export class SliderStepComponent { + value = 50; +} + +export const Step: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Слайдер с шагом изменения значения.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraSliderComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-slider-step', + standalone: true, + imports: [ExtraSliderComponent, FormsModule], + template: \` + + \`, +}) +export class SliderStepComponent { + value = 50; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/slider/examples/slider-vertical.component.ts b/src/stories/components/slider/examples/slider-vertical.component.ts new file mode 100644 index 00000000..652f402e --- /dev/null +++ b/src/stories/components/slider/examples/slider-vertical.component.ts @@ -0,0 +1,55 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraSliderComponent } from '../../../../lib/components/slider/slider.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-slider-vertical', + standalone: true, + imports: [ExtraSliderComponent, FormsModule], + template, + styles, +}) +export class SliderVerticalComponent { + value = 50; +} + +export const Vertical: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Слайдер с вертикальной ориентацией.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ExtraSliderComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-slider-vertical', + standalone: true, + imports: [ExtraSliderComponent, FormsModule], + template: \` +
+ +
+ \`, +}) +export class SliderVerticalComponent { + value = 50; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/slider/slider.stories.ts b/src/stories/components/slider/slider.stories.ts new file mode 100644 index 00000000..2366ad7d --- /dev/null +++ b/src/stories/components/slider/slider.stories.ts @@ -0,0 +1,143 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraSliderComponent as SliderComponent } from '../../../lib/components/slider/slider.component'; +import { SliderRangeComponent, Range } from './examples/slider-range.component'; +import { SliderStepComponent, Step } from './examples/slider-step.component'; +import { SliderVerticalComponent, Vertical } from './examples/slider-vertical.component'; +import { SliderDisabledComponent, Disabled } from './examples/slider-disabled.component'; + +const meta: Meta = { + title: 'Components/Form/Slider', + component: SliderComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + SliderComponent, + SliderRangeComponent, + SliderStepComponent, + SliderVerticalComponent, + SliderDisabledComponent, + ], + }), + ], + parameters: { + docs: { + description: { + component: `Слайдер позволяет выбрать числовое значение или диапазон путём перемещения ползунка. + +\`\`\`typescript +import { ExtraSliderComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + designTokens: { prefix: '--p-slider' }, + }, + argTypes: { + min: { + control: 'number', + description: 'Минимальное значение', + table: { + category: 'Props', + defaultValue: { summary: '0' }, + type: { summary: 'number' }, + }, + }, + max: { + control: 'number', + description: 'Максимальное значение', + table: { + category: 'Props', + defaultValue: { summary: '100' }, + type: { summary: 'number' }, + }, + }, + step: { + control: 'number', + description: 'Шаг изменения значения', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'number | undefined' }, + }, + }, + range: { + control: 'boolean', + description: 'Режим выбора диапазона с двумя ползунками', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + orientation: { + control: 'select', + options: ['horizontal', 'vertical'], + description: 'Ориентация слайдера', + table: { + category: 'Props', + defaultValue: { summary: 'horizontal' }, + type: { summary: "'horizontal' | 'vertical'" }, + }, + }, + disabled: { + control: 'boolean', + description: 'Отключает взаимодействие с компонентом', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + onSlideEnd: { + control: false, + description: 'Событие завершения перетаскивания ползунка', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ─────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.min !== 0) parts.push(`[min]="${args.min}"`); + if (args.max !== 100) parts.push(`[max]="${args.max}"`); + if (args.step !== undefined) parts.push(`[step]="${args.step}"`); + if (args.range) parts.push(`[range]="true"`); + if (args.orientation !== 'horizontal') parts.push(`orientation="${args.orientation}"`); + if (args.disabled) parts.push(`[disabled]="true"`); + + const template = parts.length + ? `` + : ``; + + return { props: args, template }; + }, + args: { + min: 0, + max: 100, + step: undefined, + range: false, + orientation: 'horizontal', + disabled: false, + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { Range, Step, Vertical, Disabled }; diff --git a/src/stories/components/tabs/examples/tabs-with-badge.component.ts b/src/stories/components/tabs/examples/tabs-with-badge.component.ts new file mode 100644 index 00000000..afc3e868 --- /dev/null +++ b/src/stories/components/tabs/examples/tabs-with-badge.component.ts @@ -0,0 +1,59 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraTabsComponent, TabItem } from '../../../../lib/components/tabs/tabs.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-tabs-with-badge', + standalone: true, + imports: [ExtraTabsComponent], + template, + styles +}) +export class TabsWithBadgeComponent { + tabs: TabItem[] = [ + { value: '0', label: 'Tab 1', icon: 'ti ti-user', badge: '99+', content: 'Tab 1 Content' }, + { value: '1', label: 'Tab 2', icon: 'ti ti-settings', badge: '5', content: 'Tab 2 Content' }, + { value: '2', label: 'Tab 3', icon: 'ti ti-bell', badge: '2', content: 'Tab 3 Content' } + ]; +} + +export const WithBadge: StoryObj = { + render: () => ({ + template: `` + }), + parameters: { + docs: { + description: { story: 'Табы с бейджами.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraTabsComponent, TabItem } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-tabs-with-badge', + standalone: true, + imports: [ExtraTabsComponent], + template: \` + + \`, +}) +export class TabsWithBadgeComponent { + tabs: TabItem[] = [ + { value: '0', label: 'Tab 1', icon: 'ti ti-user', badge: '99+', content: 'Tab 1 Content' }, + { value: '1', label: 'Tab 2', icon: 'ti ti-settings', badge: '5', content: 'Tab 2 Content' }, + { value: '2', label: 'Tab 3', icon: 'ti ti-bell', badge: '2', content: 'Tab 3 Content' }, + ]; +} + ` + } + } + } +}; diff --git a/src/stories/components/tabs/examples/tabs-with-disabled.component.ts b/src/stories/components/tabs/examples/tabs-with-disabled.component.ts new file mode 100644 index 00000000..7e0f49dc --- /dev/null +++ b/src/stories/components/tabs/examples/tabs-with-disabled.component.ts @@ -0,0 +1,59 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraTabsComponent, TabItem } from '../../../../lib/components/tabs/tabs.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-tabs-with-disabled', + standalone: true, + imports: [ExtraTabsComponent], + template, + styles +}) +export class TabsWithDisabledComponent { + tabs: TabItem[] = [ + { value: '0', label: 'Active Tab', icon: 'ti ti-user', content: 'Active Tab Content' }, + { value: '1', label: 'Default Tab', icon: 'ti ti-settings', content: 'Default Tab Content' }, + { value: '2', label: 'Disabled Tab', icon: 'ti ti-bell', disabled: true, content: 'Disabled Tab Content' } + ]; +} + +export const WithDisabled: StoryObj = { + render: () => ({ + template: `` + }), + parameters: { + docs: { + description: { story: 'Табы с заблокированной вкладкой.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraTabsComponent, TabItem } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-tabs-with-disabled', + standalone: true, + imports: [ExtraTabsComponent], + template: \` + + \`, +}) +export class TabsWithDisabledComponent { + tabs: TabItem[] = [ + { value: '0', label: 'Active Tab', icon: 'ti ti-user', content: 'Active Tab Content' }, + { value: '1', label: 'Default Tab', icon: 'ti ti-settings', content: 'Default Tab Content' }, + { value: '2', label: 'Disabled Tab', icon: 'ti ti-bell', disabled: true, content: 'Disabled Tab Content' }, + ]; +} + ` + } + } + } +}; diff --git a/src/stories/components/tabs/tabs.stories.ts b/src/stories/components/tabs/tabs.stories.ts new file mode 100644 index 00000000..14a14b4b --- /dev/null +++ b/src/stories/components/tabs/tabs.stories.ts @@ -0,0 +1,104 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraTabsComponent } from '../../../lib/components/tabs/tabs.component'; +import { TabsWithBadgeComponent, WithBadge } from './examples/tabs-with-badge.component'; +import { TabsWithDisabledComponent, WithDisabled } from './examples/tabs-with-disabled.component'; + +const meta: Meta = { + title: 'Components/Menu/Tabs', + component: ExtraTabsComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ExtraTabsComponent, TabsWithBadgeComponent, TabsWithDisabledComponent] + }) + ], + parameters: { + docs: { + description: { + component: `Организует контент по вкладкам с возможностью переключения между ними. + +\`\`\`typescript +import { ExtraTabsComponent, TabItem } from '@cdek-it/angular-ui-kit'; +\`\`\`` + } + }, + designTokens: { prefix: '--p-tabs' } + }, + argTypes: { + value: { + control: 'text', + description: 'Значение активной вкладки', + table: { + category: 'Props', + defaultValue: { summary: '0' }, + type: { summary: 'string | number' } + } + }, + scrollable: { + control: 'boolean', + description: 'Включает горизонтальную прокрутку списка вкладок', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + lazy: { + control: 'boolean', + description: 'Ленивая инициализация панелей — содержимое рендерится только при первом открытии', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' } + } + }, + tabs: { + control: 'object', + description: 'Массив вкладок', + table: { + category: 'Props', + type: { summary: 'TabItem[]' } + } + } + } +}; + +export default meta; +type Story = StoryObj; + +// ── Default ─────────────────────────────────────────────────────────────────── + +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + parts.push(`value="${args.value}"`); + parts.push(`[tabs]="tabs"`); + if (args.scrollable) parts.push(`[scrollable]="true"`); + if (args.lazy) parts.push(`[lazy]="true"`); + + const template = ``; + + return { props: args, template }; + }, + args: { + value: '0', + tabs: [ + { value: '0', label: 'Tab 1', icon: 'ti ti-user', content: 'Tab 1 Content' }, + { value: '1', label: 'Tab 2', icon: 'ti ti-settings', content: 'Tab 2 Content' }, + { value: '2', label: 'Tab 3', icon: 'ti ti-bell', content: 'Tab 3 Content' }, + ], + scrollable: false, + lazy: false, + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента с иконками. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +export { WithBadge, WithDisabled }; diff --git a/src/stories/components/tag/examples/tag-icon.component.ts b/src/stories/components/tag/examples/tag-icon.component.ts new file mode 100644 index 00000000..58c16892 --- /dev/null +++ b/src/stories/components/tag/examples/tag-icon.component.ts @@ -0,0 +1,45 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraTagComponent } from '../../../../lib/components/tag/tag.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-tag-icon', + standalone: true, + imports: [ExtraTagComponent], + template, +}) +export class TagIconComponent { } + +export const WithIcon: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Тег с иконкой из библиотеки Tabler Icons.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraTagComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-tag-icon', + standalone: true, + imports: [ExtraTagComponent], + template: \` + + \`, +}) +export class TagIconComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/tag/examples/tag-rounded.component.ts b/src/stories/components/tag/examples/tag-rounded.component.ts new file mode 100644 index 00000000..ae2549dd --- /dev/null +++ b/src/stories/components/tag/examples/tag-rounded.component.ts @@ -0,0 +1,45 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraTagComponent } from '../../../../lib/components/tag/tag.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-tag-rounded', + standalone: true, + imports: [ExtraTagComponent], + template, +}) +export class TagRoundedComponent { } + +export const Rounded: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Скруглённый вариант тега.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraTagComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-tag-rounded', + standalone: true, + imports: [ExtraTagComponent], + template: \` + + \`, +}) +export class TagRoundedComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/tag/examples/tag-severity.component.ts b/src/stories/components/tag/examples/tag-severity.component.ts new file mode 100644 index 00000000..0aa66574 --- /dev/null +++ b/src/stories/components/tag/examples/tag-severity.component.ts @@ -0,0 +1,45 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraTagComponent } from '../../../../lib/components/tag/tag.component'; + +const template = ` +
+ +
+`; + +@Component({ + selector: 'app-tag-severity', + standalone: true, + imports: [ExtraTagComponent], + template, +}) +export class TagSeverityComponent { } + +export const Severity: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Вариант цветового оформления. Доступные значения: primary, secondary, success, info, warn, danger.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraTagComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-tag-severity', + standalone: true, + imports: [ExtraTagComponent], + template: \` + + \`, +}) +export class TagSeverityComponent {} + `, + }, + }, + }, +}; diff --git a/src/stories/components/tag/tag.stories.ts b/src/stories/components/tag/tag.stories.ts new file mode 100644 index 00000000..ba1ac98f --- /dev/null +++ b/src/stories/components/tag/tag.stories.ts @@ -0,0 +1,114 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraTagComponent } from '../../../lib/components/tag/tag.component'; +import { TagSeverityComponent, Severity } from './examples/tag-severity.component'; +import { TagRoundedComponent, Rounded } from './examples/tag-rounded.component'; +import { TagIconComponent, WithIcon } from './examples/tag-icon.component'; + +type TagArgs = ExtraTagComponent; + +const meta: Meta = { + title: 'Components/Misc/Tag', + component: ExtraTagComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraTagComponent, + TagSeverityComponent, + TagRoundedComponent, + TagIconComponent, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-tag' }, + docs: { + description: { + component: `Компонент для цветового выделения и классификации элементов интерфейса.`, + }, + }, + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + value: { + control: 'text', + description: 'Текст тега', + table: { + category: 'Props', + type: { summary: 'string' }, + }, + }, + severity: { + control: 'select', + options: ['primary', 'secondary', 'success', 'info', 'warn', 'danger'], + description: 'Вариант цветового оформления', + table: { + category: 'Props', + defaultValue: { summary: "'primary'" }, + type: { summary: "'primary' | 'secondary' | 'success' | 'info' | 'warn' | 'danger'" }, + }, + }, + rounded: { + control: 'boolean', + description: 'Скруглённый вариант тега', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + icon: { + control: 'text', + description: 'CSS-класс иконки (например ti ti-check)', + table: { + category: 'Props', + type: { summary: 'string' }, + }, + }, + }, + args: { + value: 'Tag', + severity: 'primary', + rounded: false, + icon: '', + }, +}; + +export default meta; +type Story = StoryObj; + +const commonTemplate = ` + +`; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + if (args.value) parts.push(`value="${args.value}"`); + if (args.severity && args.severity !== 'primary') parts.push(`severity="${args.severity}"`); + if (args.rounded) parts.push(`[rounded]="true"`); + if (args.icon) parts.push(`icon="${args.icon}"`); + + const template = parts.length + ? `` + : ``; + + return { props: args, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +export { WithIcon, Rounded, Severity }; diff --git a/src/stories/components/textarea/examples/textarea-autoresize.component.ts b/src/stories/components/textarea/examples/textarea-autoresize.component.ts new file mode 100644 index 00000000..86403613 --- /dev/null +++ b/src/stories/components/textarea/examples/textarea-autoresize.component.ts @@ -0,0 +1,48 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraTextareaComponent } from '../../../../lib/components/textarea/textarea.component'; + +export const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-textarea-autoresize', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ExtraTextareaComponent, FormsModule], + template, + styles, +}) +export class TextareaAutoResizeComponent { + value = ''; +} + +export const AutoResize: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + controls: { disable: true }, + docs: { + description: { + story: 'Режим авторасширения — поле увеличивается по высоте по мере ввода текста.', + }, + source: { + language: 'ts', + code: `@Component({ + template: \`${template}\`, +})`, + }, + }, + }, +}; diff --git a/src/stories/components/textarea/examples/textarea-disabled.component.ts b/src/stories/components/textarea/examples/textarea-disabled.component.ts new file mode 100644 index 00000000..075381be --- /dev/null +++ b/src/stories/components/textarea/examples/textarea-disabled.component.ts @@ -0,0 +1,45 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraTextareaComponent } from '../../../../lib/components/textarea/textarea.component'; + +export const Disabled: StoryObj = { + name: 'Disabled', + render: (args) => { + const control = new FormControl({ value: 'Текст в заблокированном поле', disabled: true }); + return { + props: { ...args, control }, + template: ``, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraTextareaComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Отключённое состояние — управляется через FormControl.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraTextareaComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraTextareaComponent, ReactiveFormsModule], + template: \`\`, +}) +export class DisabledExample { + control = new FormControl({ value: '', disabled: true }); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/textarea/examples/textarea-float-label.component.ts b/src/stories/components/textarea/examples/textarea-float-label.component.ts new file mode 100644 index 00000000..4da826a8 --- /dev/null +++ b/src/stories/components/textarea/examples/textarea-float-label.component.ts @@ -0,0 +1,118 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { FloatLabel } from 'primeng/floatlabel'; +import { Textarea } from 'primeng/textarea'; +import { IconField } from 'primeng/iconfield'; +import { InputIcon } from 'primeng/inputicon'; +import { StoryObj } from '@storybook/angular'; + +@Component({ + selector: 'app-textarea-float-label', + standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [Textarea, FloatLabel, ReactiveFormsModule, IconField, InputIcon], + template: ` +
+ + @if (showClear) { + + + + + } @else { + + } + + +
+ `, +}) +export class TextareaFloatLabelComponent { + control = new FormControl(''); + @Input() label = 'Комментарий'; + @Input() required = false; + @Input() showClear = false; +} + +export const FloatLabelStory: StoryObj = { + name: 'FloatLabel', + render: (args) => ({ + props: { label: args['label'], required: args['required'], showClear: args['showClear'] }, + template: ``, + }), + args: { + label: 'Комментарий', + required: false, + }, + argTypes: { + label: { + control: 'text', + description: 'Текст плавающей метки', + table: { + category: 'Props', + defaultValue: { summary: "'Комментарий'" }, + type: { summary: 'string' }, + }, + }, + required: { + control: 'boolean', + description: 'Показывает маркер обязательного поля `*` рядом с меткой', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + }, + parameters: { + docs: { + description: { + story: + 'Интеграция с `p-floatlabel variant="in"` — плавающая метка внутри поля. `required` добавляет красный маркер `*`. Требует нативный ` + + + \`, +}) +export class FloatLabelExample { + control = new FormControl(''); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/textarea/examples/textarea-invalid.component.ts b/src/stories/components/textarea/examples/textarea-invalid.component.ts new file mode 100644 index 00000000..b5ba07ab --- /dev/null +++ b/src/stories/components/textarea/examples/textarea-invalid.component.ts @@ -0,0 +1,45 @@ +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraTextareaComponent } from '../../../../lib/components/textarea/textarea.component'; + +export const Invalid: StoryObj = { + name: 'Invalid', + render: (args) => { + const control = new FormControl('', Validators.required); + return { + props: { ...args, control }, + template: ``, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraTextareaComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Невалидное состояние — управляется через FormControl + Validators.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, Validators, ReactiveFormsModule } from '@angular/forms'; +import { ExtraTextareaComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraTextareaComponent, ReactiveFormsModule], + template: \`\`, +}) +export class InvalidExample { + control = new FormControl('', Validators.required); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/textarea/examples/textarea-readonly.component.ts b/src/stories/components/textarea/examples/textarea-readonly.component.ts new file mode 100644 index 00000000..996da6d3 --- /dev/null +++ b/src/stories/components/textarea/examples/textarea-readonly.component.ts @@ -0,0 +1,45 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraTextareaComponent } from '../../../../lib/components/textarea/textarea.component'; + +export const Readonly: StoryObj = { + name: 'Readonly', + render: (args) => { + const control = new FormControl('Только для чтения — этот текст нельзя изменить.'); + return { + props: { ...args, control }, + template: ``, + }; + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraTextareaComponent, ReactiveFormsModule], + }, + }), + ], + parameters: { + controls: { disable: true }, + docs: { + description: { story: 'Режим только для чтения — содержимое отображается, но не редактируется.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraTextareaComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ExtraTextareaComponent, ReactiveFormsModule], + template: \`\`, +}) +export class ReadonlyExample { + control = new FormControl('Только для чтения.'); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/textarea/examples/textarea-sizes.component.ts b/src/stories/components/textarea/examples/textarea-sizes.component.ts new file mode 100644 index 00000000..0341cd21 --- /dev/null +++ b/src/stories/components/textarea/examples/textarea-sizes.component.ts @@ -0,0 +1,48 @@ +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraTextareaComponent } from '../../../../lib/components/textarea/textarea.component'; + +export const Sizes: StoryObj = { + name: 'Sizes', + render: (args) => { + const control = new FormControl(''); + return { + props: { ...args, control }, + template: `` + }; + }, + args: { + size: 'base', + placeholder: 'Введите текст...' + }, + argTypes: { + size: { + control: 'select', + options: ['small', 'base', 'large', 'xlarge'] + } + }, + decorators: [ + (story: any) => ({ + ...story(), + moduleMetadata: { + imports: [ExtraTextareaComponent, ReactiveFormsModule] + } + }) + ], + parameters: { + docs: { + description: { + story: 'Все доступные размеры компонента: small, base, large, xlarge. Выберите размер через Controls.' + }, + source: { + language: 'ts', + code: ` + + + + + ` + } + } + } +}; diff --git a/src/stories/components/textarea/textarea.stories.ts b/src/stories/components/textarea/textarea.stories.ts new file mode 100644 index 00000000..098fe865 --- /dev/null +++ b/src/stories/components/textarea/textarea.stories.ts @@ -0,0 +1,203 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ExtraTextareaComponent } from '../../../lib/components/textarea/textarea.component'; +import { Disabled } from './examples/textarea-disabled.component'; +import { Readonly } from './examples/textarea-readonly.component'; +import { Invalid } from './examples/textarea-invalid.component'; +import { AutoResize, TextareaAutoResizeComponent } from './examples/textarea-autoresize.component'; +import { Sizes } from './examples/textarea-sizes.component'; +import { FloatLabelStory, TextareaFloatLabelComponent } from './examples/textarea-float-label.component'; + +type TextareaArgs = ExtraTextareaComponent & { disabled: boolean; invalid: boolean }; + +const meta: Meta = { + title: 'Components/Form/Textarea', + component: ExtraTextareaComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraTextareaComponent, + ReactiveFormsModule, + TextareaAutoResizeComponent, + TextareaFloatLabelComponent, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-textarea' }, + docs: { + description: { + component: `Многострочное текстовое поле для ввода данных. + +\`\`\`typescript +import { ExtraTextareaComponent } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + }, + argTypes: { + placeholder: { + control: 'text', + description: 'Подсказка при пустом поле', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + size: { + control: 'select', + options: ['small', 'base', 'large', 'xlarge'], + description: 'Размер поля', + table: { + category: 'Props', + defaultValue: { summary: "'base'" }, + type: { summary: "'small' | 'base' | 'large' | 'xlarge'" }, + }, + }, + disabled: { + control: 'boolean', + description: 'Отключает взаимодействие — управляется через FormControl', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + invalid: { + control: 'boolean', + description: 'Невалидное состояние — управляется через FormControl', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + readonly: { + control: 'boolean', + description: 'Только для чтения', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + showClear: { + control: 'boolean', + description: 'Показывает иконку очистки при наличии значения', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + fluid: { + control: 'boolean', + description: 'Растягивает поле на всю ширину контейнера', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + autoResize: { + control: 'boolean', + description: 'Автоматически увеличивает высоту по мере ввода', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + rows: { + control: 'number', + description: 'Количество видимых строк', + table: { + category: 'Props', + defaultValue: { summary: '3' }, + type: { summary: 'number' }, + }, + }, + cols: { + control: 'number', + description: 'Количество видимых столбцов', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'number' }, + }, + }, + // Hidden computed props + modelValue: { table: { disable: true } }, + primeSize: { table: { disable: true } }, + sizeClass: { table: { disable: true } }, + // Events + onResize: { + control: false, + description: 'Событие изменения высоты поля (при autoResize)', + table: { + category: 'Events', + type: { summary: 'EventEmitter<{ height: string }>' }, + }, + }, + onClear: { + control: false, + description: 'Событие очистки поля (при showClear)', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, + args: { + placeholder: 'Введите текст...', + size: 'base', + disabled: false, + invalid: false, + readonly: false, + showClear: false, + fluid: false, + autoResize: false, + rows: 3, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.placeholder) parts.push(`placeholder="${args.placeholder}"`); + if (args.size && args.size !== 'base') parts.push(`size="${args.size}"`); + if (args.readonly) parts.push(`[readonly]="true"`); + if (args.showClear) parts.push(`[showClear]="true"`); + if (args.fluid) parts.push(`[fluid]="true"`); + if (args.autoResize) parts.push(`[autoResize]="true"`); + if (args.rows && args.rows !== 3) parts.push(`[rows]="${args.rows}"`); + if (args.cols) parts.push(`[cols]="${args.cols}"`); + + const validators = []; + if (args.invalid) validators.push(Validators.required); + + const control = new FormControl({ value: '', disabled: args.disabled }, validators); + + const template = ``; + + return { props: { ...args, control }, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Re-exports from example components ──────────────────────────────────── +export { Disabled, Readonly, Invalid, AutoResize, Sizes, FloatLabelStory as FloatLabel }; diff --git a/src/stories/components/tieredmenu/examples/tieredmenu-basic.component.ts b/src/stories/components/tieredmenu/examples/tieredmenu-basic.component.ts new file mode 100644 index 00000000..b6c1f11d --- /dev/null +++ b/src/stories/components/tieredmenu/examples/tieredmenu-basic.component.ts @@ -0,0 +1,71 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { MenuItem } from 'primeng/api'; +import { ExtraTieredMenuComponent } from '../../../../lib/components/tieredmenu/tieredmenu.component'; +import { basicItems } from '../tieredmenu.data'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-tieredmenu-basic', + standalone: true, + imports: [ExtraTieredMenuComponent], + template, + styles, +}) +export class TieredMenuBasicComponent { + items: MenuItem[] = basicItems; +} + +export const Basic: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Базовое иерархическое меню с вложенными подменю.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { MenuItem } from 'primeng/api'; +import { ExtraTieredMenuComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-tieredmenu-basic', + standalone: true, + imports: [ExtraTieredMenuComponent], + template: \` + + \`, +}) +export class TieredMenuBasicComponent { + items: MenuItem[] = [ + { + label: 'Отправления', + icon: 'ti ti-package', + items: [ + { label: 'Новые' }, + { label: 'В обработке' }, + { label: 'Доставленные' }, + { label: 'Возвраты', items: [{ label: 'Ожидают' }, { label: 'Завершённые' }] }, + ], + }, + { label: 'Маршруты' }, + { + label: 'Склады', + items: [{ label: 'Москва' }, { label: 'Новосибирск' }, { label: 'Екатеринбург' }], + }, + { label: 'Настройки', icon: 'ti ti-settings', disabled: true }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/tieredmenu/examples/tieredmenu-custom.component.ts b/src/stories/components/tieredmenu/examples/tieredmenu-custom.component.ts new file mode 100644 index 00000000..051ceceb --- /dev/null +++ b/src/stories/components/tieredmenu/examples/tieredmenu-custom.component.ts @@ -0,0 +1,135 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { MenuItem } from 'primeng/api'; +import { Badge } from 'primeng/badge'; +import { ExtraTieredMenuComponent } from '../../../../lib/components/tieredmenu/tieredmenu.component'; + +const template = ` + +`; +const styles = ''; + +@Component({ + selector: 'app-tieredmenu-custom', + standalone: true, + imports: [ExtraTieredMenuComponent, Badge], + template, + styles, +}) +export class TieredMenuCustomComponent { + items: MenuItem[] = [ + { + label: 'Дашборд', + icon: 'ti ti-home', + description: 'Перейти на главную', + }, + { + label: 'Отправления', + icon: 'ti ti-package', + description: 'Управление заказами', + badge: 'New', + items: [ + { label: 'Активные', icon: 'ti ti-circle-check', description: 'Текущие заказы' }, + { label: 'Архив', icon: 'ti ti-archive', description: 'Завершённые заказы' }, + ], + }, + { + label: 'Склады', + icon: 'ti ti-building-warehouse', + description: 'Складское хранение', + items: [ + { label: 'Документы', icon: 'ti ti-file-text', description: 'Накладные и акты' }, + { label: 'Фото', icon: 'ti ti-photo', description: 'Фотофиксация грузов' }, + ], + }, + { + label: 'Настройки', + icon: 'ti ti-settings', + description: 'Параметры системы', + disabled: true, + }, + ]; +} + +export const Custom: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Кастомный шаблон пункта меню с описанием и бейджем.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { MenuItem } from 'primeng/api'; +import { TieredMenu } from 'primeng/tieredmenu'; +import { Badge } from 'primeng/badge'; + +@Component({ + selector: 'app-tieredmenu-custom', + standalone: true, + imports: [TieredMenu, Badge], + template: \` + + + + @if (item.icon) { + + } +
+ {{ item.label }} + @if (item['description']) { + {{ item['description'] }} + } +
+ @if (item['badge']) { + + } + @if (hasSubmenu) { + + } +
+
+
+ \`, +}) +export class TieredMenuCustomComponent { + items: MenuItem[] = [ + { label: 'Дашборд', icon: 'ti ti-home', description: 'Перейти на главную' }, + { label: 'Отправления', icon: 'ti ti-package', description: 'Управление заказами', badge: 'New', + items: [ + { label: 'Активные', icon: 'ti ti-circle-check', description: 'Текущие заказы' }, + { label: 'Архив', icon: 'ti ti-archive', description: 'Завершённые заказы' }, + ], + }, + { label: 'Настройки', icon: 'ti ti-settings', description: 'Параметры системы', disabled: true }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/tieredmenu/examples/tieredmenu-selected.component.ts b/src/stories/components/tieredmenu/examples/tieredmenu-selected.component.ts new file mode 100644 index 00000000..b584309a --- /dev/null +++ b/src/stories/components/tieredmenu/examples/tieredmenu-selected.component.ts @@ -0,0 +1,61 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { MenuItem } from 'primeng/api'; +import { ExtraTieredMenuComponent } from '../../../../lib/components/tieredmenu/tieredmenu.component'; + +const template = ` +
+ +
+`; +const styles = ''; + +@Component({ + selector: 'app-tieredmenu-selected', + standalone: true, + imports: [ExtraTieredMenuComponent], + template, + styles, +}) +export class TieredMenuSelectedComponent { + items: MenuItem[] = [ + { label: 'Отправления', icon: 'ti ti-package' }, + { label: 'Маршруты', icon: 'ti ti-route' }, + { label: 'Склады', icon: 'ti ti-building-warehouse' }, + ]; +} + +export const WithSelected: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'При клике на пункт меню он визуально выделяется как активный.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { MenuItem } from 'primeng/api'; +import { ExtraTieredMenuComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-tieredmenu-selected', + standalone: true, + imports: [ExtraTieredMenuComponent], + template: \` + + \`, +}) +export class TieredMenuSelectedComponent { + items: MenuItem[] = [ + { label: 'Отправления', icon: 'ti ti-package' }, + { label: 'Маршруты', icon: 'ti ti-route' }, + { label: 'Склады', icon: 'ti ti-building-warehouse' }, + ]; +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/tieredmenu/tieredmenu.data.ts b/src/stories/components/tieredmenu/tieredmenu.data.ts new file mode 100644 index 00000000..448a4d7b --- /dev/null +++ b/src/stories/components/tieredmenu/tieredmenu.data.ts @@ -0,0 +1,39 @@ +import { MenuItem } from 'primeng/api'; + +export const basicItems: MenuItem[] = [ + { + label: 'Отправления', + icon: 'ti ti-package', + items: [ + { label: 'Новые' }, + { label: 'В обработке' }, + { label: 'Доставленные' }, + { + label: 'Возвраты', + items: [{ label: 'Ожидают' }, { label: 'Завершённые' }], + }, + ], + }, + { label: 'Маршруты' }, + { + label: 'Склады', + items: [ + { label: 'Москва' }, + { label: 'Новосибирск' }, + { label: 'Екатеринбург' }, + { + label: 'Регионы', + items: [{ label: 'Урал' }, { label: 'Сибирь' }], + }, + ], + }, + { + icon: 'ti ti-settings', + label: 'Настройки', + disabled: true, + items: [ + { label: 'Профиль' }, + { label: 'Уведомления' }, + ], + }, +]; diff --git a/src/stories/components/tieredmenu/tieredmenu.stories.ts b/src/stories/components/tieredmenu/tieredmenu.stories.ts new file mode 100644 index 00000000..f460f4dd --- /dev/null +++ b/src/stories/components/tieredmenu/tieredmenu.stories.ts @@ -0,0 +1,62 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraTieredMenuComponent } from '../../../lib/components/tieredmenu/tieredmenu.component'; +import { TieredMenuBasicComponent, Basic } from './examples/tieredmenu-basic.component'; +import { TieredMenuSelectedComponent, WithSelected } from './examples/tieredmenu-selected.component'; +import { TieredMenuCustomComponent, Custom } from './examples/tieredmenu-custom.component'; + +const meta: Meta = { + title: 'Components/Menu/TieredMenu', + component: ExtraTieredMenuComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraTieredMenuComponent, + TieredMenuBasicComponent, + TieredMenuSelectedComponent, + TieredMenuCustomComponent + ] + }) + ], + parameters: { + docs: { + description: { + component: `Компонент для отображения иерархического меню с вложенными подменю, которые открываются в виде вложенных оверлеев при наведении на пункт. + +\`\`\`typescript +import { ExtraTieredMenuComponent as TieredMenuComponent } from '@cdek-it/angular-ui-kit'; +\`\`\`` + } + }, + designTokens: { prefix: '--p-tieredmenu' } + }, + argTypes: { + model: { + table: { disable: true } + }, + autoDisplay: { + control: 'boolean', + description: 'Показывать подменю при наведении', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' } + } + }, + tabindex: { + control: 'number', + description: 'Порядок фокуса при навигации клавиатурой', + table: { + category: 'Props', + defaultValue: { summary: 'undefined' }, + type: { summary: 'number' } + } + } + } +}; + +export default meta; +type Story = StoryObj; + +// ── Re-exports from example components ──────────────────────────────────── +export { Basic, WithSelected, Custom }; diff --git a/src/stories/components/timeline/timeline.stories.ts b/src/stories/components/timeline/timeline.stories.ts new file mode 100644 index 00000000..eb440f10 --- /dev/null +++ b/src/stories/components/timeline/timeline.stories.ts @@ -0,0 +1,282 @@ +import { Meta, StoryObj } from '@storybook/angular'; +import { ExtraTimelineComponent as TimelineComponent } from '../../../lib/components/timeline/timeline.component'; + +type TimelineArgs = TimelineComponent & { verticalAlign: string; horizontalAlign: string }; +type Story = StoryObj; + +const defaultEvents = [ + { value: 'Заказ создан', caption: '15 апр 2026, 10:00' }, + { value: 'Принят на склад', caption: '16 апр 2026, 14:30' }, + { value: 'В пути', caption: '17 апр 2026, 09:15' }, + { value: 'Доставлен', caption: '18 апр 2026, 11:45' }, +]; + +const meta: Meta = { + title: 'Components/Data/Timeline', + component: TimelineComponent, + tags: ['autodocs'], + parameters: { + designTokens: { prefix: '--p-timeline' }, + docs: { + description: { + component: + 'Компонент для визуализации последовательности событий в хронологическом порядке. Поддерживает горизонтальную и вертикальную ориентацию, кастомные маркеры.\n\n```typescript\nimport { ExtraTimelineComponent as TimelineComponent } from \'@cdek-it/angular-ui-kit\';\n```', + }, + }, + }, + argTypes: { + value: { + control: 'object', + description: 'Массив событий для отображения', + table: { + category: 'Props', + type: { summary: '{ value: string, caption?: string }[]' }, + }, + }, + layout: { + control: 'select', + options: ['vertical', 'horizontal'], + description: 'Ориентация таймлайна', + table: { + category: 'Props', + defaultValue: { summary: "'vertical'" }, + type: { summary: "'vertical' | 'horizontal'" }, + }, + }, + align: { table: { disable: true } }, + verticalAlign: { + name: 'align', + control: 'select', + options: ['left', 'right', 'alternate'], + description: 'Положение контента относительно маркера', + table: { + category: 'Props', + defaultValue: { summary: "'left'" }, + type: { summary: "'left' | 'right' | 'alternate'" }, + }, + if: { arg: 'layout', eq: 'vertical' }, + }, + horizontalAlign: { + name: 'align', + control: 'select', + options: ['top', 'bottom', 'alternate'], + description: 'Положение контента относительно маркера', + table: { + category: 'Props', + defaultValue: { summary: "'top'" }, + type: { summary: "'top' | 'bottom' | 'alternate'" }, + }, + if: { arg: 'layout', eq: 'horizontal' }, + }, + showCaption: { + control: 'boolean', + description: 'Показывать описание (caption) под основным контентом', + table: { + category: 'Props', + defaultValue: { summary: 'true' }, + type: { summary: 'boolean' }, + }, + }, + line: { + control: 'select', + options: ['solid', 'dashed', 'dotted', 'none'], + description: 'Стиль линии коннектора', + table: { + category: 'Props', + defaultValue: { summary: "'solid'" }, + type: { summary: "'solid' | 'dashed' | 'dotted' | 'none'" }, + }, + }, + icon: { + control: 'text', + description: 'CSS-класс иконки маркера (например `ti ti-check`)', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + markerColor: { + control: 'color', + description: 'Кастомный цвет маркера', + table: { + category: 'Props', + defaultValue: { summary: "''" }, + type: { summary: 'string' }, + }, + }, + contentTemplate: { table: { disable: true } }, + oppositeTemplate: { table: { disable: true } }, + markerTemplate: { table: { disable: true } }, + }, + args: { + value: defaultEvents, + layout: 'vertical', + verticalAlign: 'left', + horizontalAlign: 'top', + showCaption: true, + line: 'solid', + icon: '', + markerColor: '', + }, +}; + +export default meta; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function renderStory(args: any) { + const layout = args.layout ?? 'vertical'; + const align = layout === 'horizontal' + ? (args.horizontalAlign ?? 'top') + : (args.verticalAlign ?? 'left'); + + const defaultAlign = layout === 'horizontal' ? 'top' : 'left'; + + const parts: string[] = []; + + parts.push(`[value]="value"`); + if (align !== defaultAlign) parts.push(`align="${align}"`); + if (layout !== 'vertical') parts.push(`layout="${layout}"`); + parts.push(`[showCaption]="${args.showCaption}"`); + if (args.line && args.line !== 'solid') parts.push(`line="${args.line}"`); + if (args.icon) parts.push(`icon="${args.icon}"`); + if (args.markerColor) parts.push(`markerColor="${args.markerColor}"`); + + const attrs = parts.join('\n '); + + const template = ` + +
{{ event.value }}
+ @if (${args.showCaption}) {{{ event.caption }}} +
+
`; + + return { props: { ...args, value: args.value }, template }; +} + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => renderStory(args), + parameters: { + docs: { + description: { + story: 'Базовый пример. Используйте Controls для изменения ориентации и выравнивания.', + }, + }, + }, +}; + +// ── Horizontal ─────────────────────────────────────────────────────────────── +export const Horizontal: Story = { + name: 'Horizontal', + render: (args) => renderStory(args), + args: { + layout: 'horizontal', + horizontalAlign: 'top', + }, + parameters: { + docs: { + description: { + story: 'Горизонтальная ориентация через `layout="horizontal"` и `align="top"`.', + }, + }, + }, +}; + +// ── Opposite ───────────────────────────────────────────────────────────────── +export const Opposite: Story = { + name: 'Opposite', + render: (args) => { + const layout = args.layout ?? 'vertical'; + const align = layout === 'horizontal' + ? (args.horizontalAlign ?? 'top') + : (args.verticalAlign ?? 'left'); + + const defaultAlign = layout === 'horizontal' ? 'top' : 'left'; + + const parts: string[] = []; + + parts.push(`[value]="value"`); + if (align !== defaultAlign) parts.push(`align="${align}"`); + if (layout !== 'vertical') parts.push(`layout="${layout}"`); + parts.push(`[showCaption]="${args.showCaption}"`); + if (args.line && args.line !== 'solid') parts.push(`line="${args.line}"`); + if (args.icon) parts.push(`icon="${args.icon}"`); + if (args.markerColor) parts.push(`markerColor="${args.markerColor}"`); + + const attrs = parts.join('\n '); + + const template = ` + {{ event.value }} + + {{ event.caption }} + +`; + + return { props: { ...args, value: args.value }, template }; + }, + args: { + verticalAlign: 'alternate', + }, + parameters: { + docs: { + description: { + story: 'Контент по другую сторону линии через шаблон `#opposite`. Отображается при `align="alternate"`, `"right"`, `"bottom"`.', + }, + }, + }, +}; + +// ── Dashed Line ────────────────────────────────────────────────────────────── +export const DashedLine: Story = { + name: 'Dashed Line', + render: (args) => renderStory(args), + args: { + line: 'dashed', + }, + parameters: { + docs: { + description: { + story: 'Пунктирная линия коннектора через проп `line="dashed"`. Другие варианты: `solid`, `dotted`, `none`.', + }, + }, + }, +}; + +// ── Custom Icon ────────────────────────────────────────────────────────────── +export const CustomIcon: Story = { + name: 'Custom Icon', + render: (args) => renderStory(args), + args: { + icon: 'ti ti-check', + markerColor: '#3182ce', + }, + parameters: { + docs: { + description: { + story: 'Кастомная иконка вместо маркера через проп `icon` (CSS-класс иконки, например Tabler Icons).', + }, + }, + }, +}; + +// ── Marker Color ───────────────────────────────────────────────────────────── +export const MarkerColor: Story = { + name: 'Marker Color', + render: (args) => renderStory(args), + args: { + markerColor: '#e53e3e', + }, + parameters: { + docs: { + description: { + story: 'Кастомный цвет маркера через проп `markerColor`.', + }, + }, + }, +}; diff --git a/src/stories/components/toast/examples/toast-position.component.ts b/src/stories/components/toast/examples/toast-position.component.ts new file mode 100644 index 00000000..2aedab9c --- /dev/null +++ b/src/stories/components/toast/examples/toast-position.component.ts @@ -0,0 +1,119 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraToastComponent } from '../../../../lib/components/toast/toast.component'; +import { ExtraToastService } from '../../../../lib/components/toast/toast.service'; + +const POSITIONS = [ + { position: 'top-left', label: 'Вверх слева', key: 'pos-top-left' }, + { position: 'top-center', label: 'Вверх по центру', key: 'pos-top-center' }, + { position: 'top-right', label: 'Вверх справа', key: 'pos-top-right' }, + { position: 'bottom-left', label: 'Вниз слева', key: 'pos-bottom-left' }, + { position: 'bottom-center', label: 'Вниз по центру', key: 'pos-bottom-center' }, + { position: 'bottom-right', label: 'Вниз справа', key: 'pos-bottom-right' }, +] as const; + +const template = ` +@for (p of positions; track p.key) { + +} + +
+ @for (p of positions; track p.key) { + + } +
+`; +const styles = ''; + +@Component({ + selector: 'app-toast-position', + standalone: true, + imports: [ExtraToastComponent, ExtraButtonComponent], + template, + styles, +}) +export class ToastPositionComponent { + readonly positions = POSITIONS; + + constructor(private readonly toastService: ExtraToastService) {} + + show(key: string, position: string): void { + this.toastService.add({ + key, + severity: 'info', + summary: 'Сообщение', + detail: 'Позиция: ' + position, + life: 3000, + icon: 'ti ti-info-circle', + }); + } +} + +export const Position: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Расположение тоста задаётся через `position` и `key`.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraButtonComponent, ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; + +const POSITIONS = [ + { position: 'top-left', label: 'Вверх слева', key: 'pos-top-left' }, + { position: 'top-center', label: 'Вверх по центру', key: 'pos-top-center' }, + { position: 'top-right', label: 'Вверх справа', key: 'pos-top-right' }, + { position: 'bottom-left', label: 'Вниз слева', key: 'pos-bottom-left' }, + { position: 'bottom-center', label: 'Вниз по центру', key: 'pos-bottom-center' }, + { position: 'bottom-right', label: 'Вниз справа', key: 'pos-bottom-right' }, +] as const; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [ExtraToastComponent, ExtraButtonComponent], + template: \` + @for (p of positions; track p.key) { + + } + +
+ @for (p of positions; track p.key) { + + } +
+ \`, +}) +export class ExampleComponent { + readonly positions = POSITIONS; + + constructor(private toastService: ExtraToastService) {} + + show(key: string, position: string): void { + this.toastService.add({ + key, + severity: 'info', + summary: 'Сообщение', + detail: 'Позиция: ' + position, + life: 3000, + icon: 'ti ti-info-circle', + }); + } +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/toast/examples/toast-severities.component.ts b/src/stories/components/toast/examples/toast-severities.component.ts new file mode 100644 index 00000000..f521fce8 --- /dev/null +++ b/src/stories/components/toast/examples/toast-severities.component.ts @@ -0,0 +1,128 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraToastComponent } from '../../../../lib/components/toast/toast.component'; +import { ExtraToastService } from '../../../../lib/components/toast/toast.service'; + +const SEVERITIES = [ + { type: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, + { type: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, + { type: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, + { type: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' }, +] as const; + +const template = ` + + +
+ @for (s of severities; track s.type) { +
+
+
+ +
+ Сообщение +
Подпись
+
+
+
+ } +
+ +
+ @for (s of severities; track s.type) { + + } +
+`; +const styles = ''; + +@Component({ + selector: 'app-toast-severities', + standalone: true, + imports: [ExtraToastComponent, ExtraButtonComponent], + template, + styles, +}) +export class ToastSeveritiesComponent { + readonly severities = SEVERITIES; + + constructor(private readonly toastService: ExtraToastService) {} + + show(severity: string, icon: string): void { + this.toastService.add({ + key: 'severities', + severity: severity as any, + summary: 'Сообщение', + detail: 'Подпись', + life: 5000, + icon, + }); + } +} + +export const Severities: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Четыре типа уведомлений: информация, успех, предупреждение, ошибка.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraButtonComponent, ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; + +const SEVERITIES = [ + { type: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, + { type: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, + { type: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, + { type: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' }, +] as const; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [ExtraToastComponent, ExtraButtonComponent], + template: \` + + +
+ @for (s of severities; track s.type) { + + } +
+ \`, +}) +export class ExampleComponent { + readonly severities = SEVERITIES; + + constructor(private toastService: ExtraToastService) {} + + show(severity: string, icon: string): void { + this.toastService.add({ + key: 'severities', + severity: severity as any, + summary: 'Сообщение', + detail: 'Подпись', + life: 5000, + icon, + }); + } +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/toast/examples/toast-width.component.ts b/src/stories/components/toast/examples/toast-width.component.ts new file mode 100644 index 00000000..a8349746 --- /dev/null +++ b/src/stories/components/toast/examples/toast-width.component.ts @@ -0,0 +1,141 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraToastComponent } from '../../../../lib/components/toast/toast.component'; +import { ExtraToastService } from '../../../../lib/components/toast/toast.service'; + +const SIZES = [ + { key: 'sm', label: 'Small (20rem)', width: '20rem', cssVar: '20rem' }, + { key: 'base', label: 'Base (25rem)', width: '25rem', cssVar: '25rem' }, + { key: 'lg', label: 'Large (30rem)', width: '30rem', cssVar: '30rem' }, + { key: 'xlg', label: 'X-Large (45rem)', width: '45rem', cssVar: '45rem' }, +] as const; + +const template = ` + + +
+ @for (s of sizes; track s.key) { +
+
+
+ +
+ Сообщение +
Ширина {{ s.key }}: {{ s.width }}
+
+
+
+ } +
+ +
+ @for (s of sizes; track s.key) { + + } +
+`; +const styles = ''; + +@Component({ + selector: 'app-toast-width', + standalone: true, + imports: [ExtraToastComponent, ExtraButtonComponent], + template, + styles, +}) +export class ToastWidthComponent { + readonly sizes = SIZES; + currentWidth = '25rem'; + + constructor(private readonly toastService: ExtraToastService) {} + + show(cssVar: string): void { + this.currentWidth = cssVar; + this.toastService.clear('width-preview'); + this.toastService.add({ + key: 'width-preview', + severity: 'info', + summary: 'Сообщение', + detail: 'Ширина: ' + cssVar, + life: 3000, + icon: 'ti ti-info-circle', + }); + } +} + +export const Width: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Ширина задаётся через CSS-переменную `--p-toast-width` с помощью пропа `pt`.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraButtonComponent, ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; + +const SIZES = [ + { key: 'sm', label: 'Small (20rem)', cssVar: '20rem' }, + { key: 'base', label: 'Base (25rem)', cssVar: '25rem' }, + { key: 'lg', label: 'Large (30rem)', cssVar: '30rem' }, + { key: 'xlg', label: 'X-Large (45rem)', cssVar: '45rem' }, +] as const; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [ExtraToastComponent, ExtraButtonComponent], + template: \` + + +
+ @for (s of sizes; track s.key) { + + } +
+ \`, +}) +export class ExampleComponent { + readonly sizes = SIZES; + currentWidth = '25rem'; + + constructor(private toastService: ExtraToastService) {} + + show(cssVar: string): void { + this.currentWidth = cssVar; + this.toastService.clear('width-preview'); + this.toastService.add({ + key: 'width-preview', + severity: 'info', + summary: 'Сообщение', + detail: 'Ширина: ' + cssVar, + life: 3000, + icon: 'ti ti-info-circle', + }); + } +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/toast/examples/toast-with-close-button.component.ts b/src/stories/components/toast/examples/toast-with-close-button.component.ts new file mode 100644 index 00000000..08116a6b --- /dev/null +++ b/src/stories/components/toast/examples/toast-with-close-button.component.ts @@ -0,0 +1,136 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraToastComponent } from '../../../../lib/components/toast/toast.component'; +import { ExtraToastService } from '../../../../lib/components/toast/toast.service'; + +const SEVERITIES = [ + { type: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, + { type: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, + { type: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, + { type: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' }, +] as const; + +const template = ` + + +
+ @for (s of severities; track s.type) { +
+
+
+ +
+ Сообщение +
Подпись
+
+ +
+
+ } +
+ +
+ @for (s of severities; track s.type) { + + } +
+`; +const styles = ''; + +@Component({ + selector: 'app-toast-with-close-button', + standalone: true, + imports: [ExtraToastComponent, ExtraButtonComponent], + template, + styles, +}) +export class ToastWithCloseButtonComponent { + readonly severities = SEVERITIES; + + constructor(private readonly toastService: ExtraToastService) {} + + show(severity: string, icon: string): void { + this.toastService.add({ + key: 'with-close', + severity: severity as any, + summary: 'Сообщение', + detail: 'Подпись', + life: 5000, + icon, + closable: true, + }); + } +} + +export const WithCloseButton: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Уведомления с кнопкой закрытия (closable: true).' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraButtonComponent, ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; + +const SEVERITIES = [ + { type: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, + { type: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, + { type: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, + { type: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' }, +] as const; + +@Component({ + selector: 'app-example', + standalone: true, + imports: [ExtraToastComponent, ExtraButtonComponent], + template: \` + + +
+ @for (s of severities; track s.type) { + + } +
+ \`, +}) +export class ExampleComponent { + readonly severities = SEVERITIES; + + constructor(private toastService: ExtraToastService) {} + + show(severity: string, icon: string): void { + this.toastService.add({ + key: 'with-close', + severity: severity as any, + summary: 'Сообщение', + detail: 'Подпись', + life: 5000, + icon, + closable: true, + }); + } +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/toast/examples/toast-with-content.component.ts b/src/stories/components/toast/examples/toast-with-content.component.ts new file mode 100644 index 00000000..db2a2de5 --- /dev/null +++ b/src/stories/components/toast/examples/toast-with-content.component.ts @@ -0,0 +1,158 @@ +import { Component } from '@angular/core'; +import { StoryObj } from '@storybook/angular'; +import { ExtraButtonComponent } from '../../../../lib/components/button/button.component'; +import { ExtraToastComponent } from '../../../../lib/components/toast/toast.component'; +import { ExtraToastService } from '../../../../lib/components/toast/toast.service'; + +const SEVERITIES = [ + { type: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, + { type: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, + { type: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, + { type: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' }, +] as const; + +const template = ` + + +
+ @for (s of severities; track s.type) { +
+
+
+ +
+ Сообщение +
Подпись
+
+
Дополнительный контент
+
+
+
Ячейка 1
+
Ячейка 2
+
+
+ +
+
+ } +
+ +
+ @for (s of severities; track s.type) { + + } +
+`; +const styles = ''; + +@Component({ + selector: 'app-toast-with-content', + standalone: true, + imports: [ExtraToastComponent, ExtraButtonComponent], + template, + styles, +}) +export class ToastWithContentComponent { + readonly severities = SEVERITIES; + + constructor(private readonly toastService: ExtraToastService) {} + + show(severity: string, icon: string): void { + this.toastService.add({ + key: 'with-content', + severity: severity as any, + summary: 'Сообщение', + detail: 'Подпись', + life: 5000, + icon, + closable: true, + }); + } +} + +export const WithContent: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Уведомления с дополнительным контентом под заголовком.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { ExtraButtonComponent, ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; + +const SEVERITIES = [ + { type: 'info', icon: 'ti ti-info-circle', label: 'Информация' }, + { type: 'success', icon: 'ti ti-circle-check', label: 'Успех' }, + { type: 'warn', icon: 'ti ti-alert-triangle', label: 'Предупреждение' }, + { type: 'error', icon: 'ti ti-alert-circle', label: 'Ошибка' }, +] as const; + +// Кастомный шаблон сообщения передаётся через ng-template +@Component({ + selector: 'app-example', + standalone: true, + imports: [ExtraToastComponent, ExtraButtonComponent], + template: \` + + +
+ {{ message.summary }} +
{{ message.detail }}
+
+
Дополнительный контент
+
+
+
Ячейка 1
+
Ячейка 2
+
+
+
+
+ +
+ @for (s of severities; track s.type) { + + } +
+ \`, +}) +export class ExampleComponent { + readonly severities = SEVERITIES; + + constructor(private toastService: ExtraToastService) {} + + show(severity: string, icon: string): void { + this.toastService.add({ + key: 'with-content', + severity: severity as any, + summary: 'Сообщение', + detail: 'Подпись', + life: 5000, + icon, + closable: true, + }); + } +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/toast/toast.stories.ts b/src/stories/components/toast/toast.stories.ts new file mode 100644 index 00000000..e349a19b --- /dev/null +++ b/src/stories/components/toast/toast.stories.ts @@ -0,0 +1,100 @@ +import { Meta, applicationConfig, moduleMetadata } from '@storybook/angular'; +import { ExtraToastComponent } from '../../../lib/components/toast/toast.component'; +import { provideExtraToast } from '../../../lib/components/toast/provide-toast'; +import { ToastSeveritiesComponent, Severities } from './examples/toast-severities.component'; +import { ToastWithCloseButtonComponent, WithCloseButton } from './examples/toast-with-close-button.component'; +import { ToastWithContentComponent, WithContent } from './examples/toast-with-content.component'; +import { ToastWidthComponent, Width } from './examples/toast-width.component'; +import { ToastPositionComponent, Position } from './examples/toast-position.component'; + +const meta: Meta = { + title: 'Components/Feedback/Toast', + component: ExtraToastComponent, + tags: ['autodocs'], + decorators: [ + applicationConfig({ providers: [provideExtraToast()] }), + moduleMetadata({ + imports: [ + ExtraToastComponent, + ToastSeveritiesComponent, + ToastWithCloseButtonComponent, + ToastWithContentComponent, + ToastWidthComponent, + ToastPositionComponent, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-toast' }, + docs: { + description: { + component: `Компонент для отображения всплывающих уведомлений поверх интерфейса. + +## Подключение + +Добавьте \`provideExtraToast()\` в провайдеры приложения: + +\`\`\`typescript +// app.config.ts +import { provideExtraToast } from '@cdek-it/angular-ui-kit'; + +export const appConfig: ApplicationConfig = { + providers: [provideExtraToast()], +}; +\`\`\` + +## Использование + +\`\`\`typescript +import { ExtraToastComponent, ExtraToastService } from '@cdek-it/angular-ui-kit'; + +@Component({ + imports: [ExtraToastComponent], + template: \`\`, +}) +export class AppComponent { + private toast = inject(ExtraToastService); + + show() { + this.toast.add({ severity: 'success', summary: 'Готово', detail: 'Операция выполнена успешно' }); + } +} +\`\`\``, + }, + }, + }, + argTypes: { + position: { + control: 'select', + options: ['top-right', 'top-left', 'top-center', 'bottom-right', 'bottom-left', 'bottom-center', 'center'], + description: 'Позиция тоста на экране.', + table: { + category: 'Props', + defaultValue: { summary: 'top-right' }, + type: { summary: "'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left' | 'bottom-center' | 'center'" }, + }, + }, + key: { + table: { disable: true }, + }, + life: { + control: 'number', + description: 'Время (мс) до автоматического закрытия тоста.', + table: { + category: 'Props', + defaultValue: { summary: '5000' }, + type: { summary: 'number' }, + }, + }, + }, + args: { + position: 'top-right', + key: undefined, + life: 5000, + }, +}; + +export default meta; + +// ── Re-exports from example components ──────────────────────────────────── +export { Severities as Default, WithCloseButton, WithContent, Width, Position }; diff --git a/src/stories/components/toggleswitch/examples/toggleswitch-checked.component.ts b/src/stories/components/toggleswitch/examples/toggleswitch-checked.component.ts new file mode 100644 index 00000000..a64d5301 --- /dev/null +++ b/src/stories/components/toggleswitch/examples/toggleswitch-checked.component.ts @@ -0,0 +1,47 @@ +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraToggleSwitchComponent } from '../../../../lib/components/toggleswitch/toggleswitch.component'; + +@Component({ + selector: 'app-toggleswitch-checked', + standalone: true, + imports: [ExtraToggleSwitchComponent, ReactiveFormsModule], + template: ` + + `, +}) +export class ToggleSwitchCheckedComponent { + control = new FormControl(true); +} + +export const Checked: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Переключатель во включённом состоянии.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ToggleSwitchComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-toggleswitch-checked', + standalone: true, + imports: [ToggleSwitchComponent, ReactiveFormsModule], + template: \` + + \`, +}) +export class ToggleSwitchCheckedComponent { + control = new FormControl(true); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/toggleswitch/examples/toggleswitch-disabled.component.ts b/src/stories/components/toggleswitch/examples/toggleswitch-disabled.component.ts new file mode 100644 index 00000000..993e7061 --- /dev/null +++ b/src/stories/components/toggleswitch/examples/toggleswitch-disabled.component.ts @@ -0,0 +1,47 @@ +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraToggleSwitchComponent } from '../../../../lib/components/toggleswitch/toggleswitch.component'; + +@Component({ + selector: 'app-toggleswitch-disabled', + standalone: true, + imports: [ExtraToggleSwitchComponent, ReactiveFormsModule], + template: ` + + `, +}) +export class ToggleSwitchDisabledComponent { + control = new FormControl({ value: false, disabled: true }); +} + +export const Disabled: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Заблокированное состояние переключателя через FormControl.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ToggleSwitchComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-toggleswitch-disabled', + standalone: true, + imports: [ToggleSwitchComponent, ReactiveFormsModule], + template: \` + + \`, +}) +export class ToggleSwitchDisabledComponent { + control = new FormControl({ value: false, disabled: true }); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/toggleswitch/examples/toggleswitch-invalid.component.ts b/src/stories/components/toggleswitch/examples/toggleswitch-invalid.component.ts new file mode 100644 index 00000000..b9eb6e74 --- /dev/null +++ b/src/stories/components/toggleswitch/examples/toggleswitch-invalid.component.ts @@ -0,0 +1,48 @@ +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { StoryObj } from '@storybook/angular'; +import { ExtraToggleSwitchComponent } from '../../../../lib/components/toggleswitch/toggleswitch.component'; + +@Component({ + selector: 'app-toggleswitch-invalid', + standalone: true, + imports: [ExtraToggleSwitchComponent, ReactiveFormsModule], + template: ` + + `, +}) +export class ToggleSwitchInvalidComponent { + // Validators.requiredTrue требует значение true, поэтому false делает контрол невалидным + control = new FormControl(false, [Validators.requiredTrue]); +} + +export const Invalid: StoryObj = { + render: () => ({ + template: ``, + }), + parameters: { + docs: { + description: { story: 'Невалидное состояние переключателя через FormControl и Validators.' }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ToggleSwitchComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'app-toggleswitch-invalid', + standalone: true, + imports: [ToggleSwitchComponent, ReactiveFormsModule], + template: \` + + \`, +}) +export class ToggleSwitchInvalidComponent { + control = new FormControl(false, [Validators.requiredTrue]); +} + `, + }, + }, + }, +}; diff --git a/src/stories/components/toggleswitch/toggleswitch.stories.ts b/src/stories/components/toggleswitch/toggleswitch.stories.ts new file mode 100644 index 00000000..d2919200 --- /dev/null +++ b/src/stories/components/toggleswitch/toggleswitch.stories.ts @@ -0,0 +1,96 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ExtraToggleSwitchComponent } from '../../../lib/components/toggleswitch/toggleswitch.component'; +import { ToggleSwitchCheckedComponent, Checked } from './examples/toggleswitch-checked.component'; +import { ToggleSwitchInvalidComponent, Invalid } from './examples/toggleswitch-invalid.component'; +import { ToggleSwitchDisabledComponent, Disabled } from './examples/toggleswitch-disabled.component'; + +const meta: Meta = { + title: 'Components/Form/ToggleSwitch', + component: ExtraToggleSwitchComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [ + ExtraToggleSwitchComponent, + ReactiveFormsModule, + ToggleSwitchCheckedComponent, + ToggleSwitchInvalidComponent, + ToggleSwitchDisabledComponent, + ], + }), + ], + parameters: { + designTokens: { prefix: '--p-toggleswitch' }, + docs: { + description: { + component: `Компонент для переключения между двумя состояниями. Состояния \`disabled\` и \`invalid\` управляются через \`FormControl\`, не через пропсы.`, + }, + }, + }, + argTypes: { + // ── Events ─────────────────────────────────────────────── + onChange: { + control: false, + description: 'Событие изменения состояния', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + onFocus: { + control: false, + description: 'Событие фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + onBlur: { + control: false, + description: 'Событие потери фокуса', + table: { + category: 'Events', + type: { summary: 'EventEmitter' }, + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: () => ({ + props: { control: new FormControl(false) }, + template: ``, + }), + parameters: { + docs: { + description: { + story: 'Базовый пример. Управление значением и состоянием через `FormControl`.', + }, + source: { + language: 'ts', + code: ` +import { Component } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { ToggleSwitchComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + standalone: true, + imports: [ToggleSwitchComponent, ReactiveFormsModule], + template: \`\`, +}) +export class Example { + control = new FormControl(false); +} + `, + }, + }, + }, +}; + +export { Checked, Disabled, Invalid }; diff --git a/src/stories/components/tooltip/tooltip.stories.ts b/src/stories/components/tooltip/tooltip.stories.ts new file mode 100644 index 00000000..eae13e8f --- /dev/null +++ b/src/stories/components/tooltip/tooltip.stories.ts @@ -0,0 +1,196 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ExtraTooltipDirective as TooltipDirective } from '../../../lib/components/tooltip/tooltip.directive'; +import { ExtraButtonComponent as ButtonComponent } from '../../../lib/components/button/button.component'; + +const meta: Meta = { + title: 'Prime/Form/Tooltip', + // @ts-ignore + component: TooltipDirective, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ imports: [TooltipDirective, ButtonComponent] }) + ], + parameters: { + designTokens: { prefix: '--p-tooltip' }, + docs: { + description: { + component: `Компонент для отображения информационного текста при наведении на элемент. + +\`\`\`typescript +import { ExtraTooltipDirective as TooltipDirective } from '@cdek-it/angular-ui-kit'; +\`\`\``, + }, + }, + }, + argTypes: { + // ── Props ──────────────────────────────────────────────── + tooltip: { + control: 'text', + description: 'Текст внутри подсказки.', + table: { category: 'Props', type: { summary: 'string' } }, + }, + label: { + control: 'text', + description: 'Текст кнопки-примера (не является свойством директивы).', + table: { category: 'Props', type: { summary: 'string' } }, + }, + position: { + control: 'select', + options: ['top', 'bottom', 'left', 'right'], + description: 'Позиция подсказки относительно элемента.', + table: { + category: 'Props', + defaultValue: { summary: 'right' }, + type: { summary: "'top' | 'bottom' | 'left' | 'right'" }, + }, + }, + event: { + control: 'select', + options: ['hover', 'focus', 'both'], + description: 'Событие, по которому показывается подсказка.', + table: { + category: 'Props', + defaultValue: { summary: 'hover' }, + type: { summary: "'hover' | 'focus' | 'both'" }, + }, + }, + showDelay: { + control: 'number', + description: 'Задержка перед появлением в миллисекундах.', + table: { category: 'Props', type: { summary: 'number' } }, + }, + hideDelay: { + control: 'number', + description: 'Задержка перед скрытием в миллисекундах.', + table: { category: 'Props', type: { summary: 'number' } }, + }, + disabled: { + control: 'boolean', + description: 'Отключает подсказку.', + table: { + category: 'Props', + defaultValue: { summary: 'false' }, + type: { summary: 'boolean' }, + }, + }, + }, + args: { + tooltip: 'Это текст подсказки', + label: 'Наведи на меня', + position: 'right', + event: 'hover', + disabled: false, + }, +}; + +export default meta; +type Story = StoryObj; + +const commonTemplate = ` + + +`; + +// ── Default ────────────────────────────────────────────────────────────────── +export const Default: Story = { + name: 'Default', + render: (args) => { + const parts: string[] = []; + + if (args.tooltip) parts.push(`extra-tooltip="${args.tooltip}"`); + if (args.position && args.position !== 'right') parts.push(`position="${args.position}"`); + if (args.event && args.event !== 'hover') parts.push(`event="${args.event}"`); + if (args.showDelay) parts.push(`[showDelay]="${args.showDelay}"`); + if (args.hideDelay) parts.push(`[hideDelay]="${args.hideDelay}"`); + if (args.disabled) parts.push(`[disabled]="true"`); + if (args.label) parts.push(`label="${args.label}"`); + + const template = ` + +`; + return { props: args, template }; + }, + parameters: { + docs: { + description: { + story: 'Базовый пример компонента. Используйте Controls для интерактивного изменения пропсов.', + }, + }, + }, +}; + +// ── Вариации ───────────────────────────────────────────────────────────────── + +export const Positions: Story = { + render: (args) => ({ props: args, template: commonTemplate }), + args: { + tooltip: 'Подсказка сверху', + position: 'top', + label: 'Сверху' + }, + parameters: { + docs: { + description: { story: 'Различные варианты позиционирования (измените через Controls).' }, + source: { + code: `` + } + } + } +}; + +export const Delay: Story = { + render: (args) => ({ props: args, template: commonTemplate }), + args: { + tooltip: 'Подсказка с задержкой 1с', + showDelay: 1000, + label: 'Задержка появления (1с)' + }, + parameters: { + docs: { + description: { story: 'Подсказка может появляться или скрываться с задержкой в миллисекундах.' }, + source: { + code: `` + } + } + } +}; + +export const EventFocus: Story = { + name: 'Event', + render: (args) => ({ + props: args, + template: ` + +` + }), + args: { + tooltip: 'Введите ваше имя', + event: 'focus', + label: 'Кликни для фокуса', + isFocused: false + }, + parameters: { + docs: { + description: { story: 'Подсказка может реагировать на фокус элемента вместо наведения.' }, + source: { + code: `` + } + } + } +}; diff --git a/src/styles.scss b/src/styles.scss index e6cb491c..9449e0df 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -3,3 +3,33 @@ @tailwind utilities; @import '@tabler/icons-webfont/dist/tabler-icons.min.css'; + +@font-face { + font-family: 'TT Fellows'; + src: url('./assets/fonts/tt-fellows/TT_Fellows_Regular.woff2') format('woff2'); + font-weight: 400; + font-style: normal; +} + +@font-face { + font-family: 'TT Fellows'; + src: url('./assets/fonts/tt-fellows/TT_Fellows_DemiBold.woff2') format('woff2'); + font-weight: 600; + font-style: normal; +} + +html { + font-size: 14px; +} + +body .css-3rewwu { + background: rgb(245 246 248); +} + +body.dark-mode { + background: var(--p-content-background, #18181b); + + .css-3rewwu { + background: var(--p-content-background, #18181b); + } +}