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 };