diff --git a/src/lib/components/progressspinner/progressspinner.component.ts b/src/lib/components/progressspinner/progressspinner.component.ts new file mode 100644 index 00000000..d4647781 --- /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: 'progressspinner', + standalone: true, + imports: [ProgressSpinner], + template: ` + + ` +}) +export class ProgressSpinnerComponent { + @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/prime-preset/map-tokens.ts b/src/prime-preset/map-tokens.ts index 437e87f4..0194af83 100644 --- a/src/prime-preset/map-tokens.ts +++ b/src/prime-preset/map-tokens.ts @@ -6,6 +6,8 @@ import tokens from './tokens/tokens.json'; import { avatarCss } from './tokens/components/avatar'; import { buttonCss } from './tokens/components/button'; import { checkboxCss } from './tokens/components/checkbox'; +import { progressspinnerCss } from './tokens/components/progressspinner'; +import { tagCss } from './tokens/components/tag'; import { tooltipCss } from './tokens/components/tooltip'; const presetTokens: Preset = { @@ -25,10 +27,18 @@ const presetTokens: Preset = { ...(tokens.components.button as unknown as ComponentsDesignTokens['button']), css: buttonCss, }, + progressspinner: { + ...(tokens.components.progressspinner as unknown as ComponentsDesignTokens['progressspinner']), + css: progressspinnerCss, + }, tag: { ...(tokens.components.tag as unknown as ComponentsDesignTokens['tag']), css: tagCss, }, + tooltip: { + ...(tokens.components.tooltip as unknown as ComponentsDesignTokens['tooltip']), + css: tooltipCss, + }, } as ComponentsDesignTokens, }; diff --git a/src/prime-preset/tokens/components/progressspinner.ts b/src/prime-preset/tokens/components/progressspinner.ts new file mode 100644 index 00000000..d901e3e1 --- /dev/null +++ b/src/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/stories/components/progressspinner/examples/progressspinner-monochrome.component.ts b/src/stories/components/progressspinner/examples/progressspinner-monochrome.component.ts new file mode 100644 index 00000000..8e2f58f5 --- /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 { ProgressSpinnerComponent } from '../../../../lib/components/progressspinner/progressspinner.component'; + +const template = ` + +`; + +@Component({ + selector: 'progressspinner-monochrome', + standalone: true, + imports: [ProgressSpinnerComponent], + 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 { ProgressSpinnerComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'progressspinner-monochrome', + standalone: true, + imports: [ProgressSpinnerComponent], + 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..397cc189 --- /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 { ProgressSpinnerComponent, ProgressSpinnerSize } from '../../../../lib/components/progressspinner/progressspinner.component'; + +const template = ` + +`; + +@Component({ + selector: 'progressspinner-sizes', + standalone: true, + imports: [ProgressSpinnerComponent], + 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 { ProgressSpinnerComponent } from '@cdek-it/angular-ui-kit'; + +@Component({ + selector: 'progressspinner-sizes', + standalone: true, + imports: [ProgressSpinnerComponent], + 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..0676ec2b --- /dev/null +++ b/src/stories/components/progressspinner/progressspinner.stories.ts @@ -0,0 +1,118 @@ +import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; +import { ProgressSpinnerComponent } from '../../../lib/components/progressspinner/progressspinner.component'; +import { Sizes, ProgressSpinnerSizesComponent } from './examples/progressspinner-sizes.component'; +import { Monochrome, ProgressSpinnerMonochromeComponent } from './examples/progressspinner-monochrome.component'; + +type ProgressSpinnerArgs = ProgressSpinnerComponent; + +const meta: Meta = { + title: 'Prime/Misc/ProgressSpinner', + component: ProgressSpinnerComponent, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ imports: [ProgressSpinnerComponent, ProgressSpinnerSizesComponent, ProgressSpinnerMonochromeComponent] }) + ], + parameters: { + docs: { + description: { + component: `Используется для отображения индикатора процесса/состояния загрузки неопределенного времени. + +\`\`\`typescript +import { ProgressSpinnerComponent } 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; + +const commonTemplate = ` + +`; + +// ── 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 };