diff --git a/src/lib/components/toggleswitch/toggleswitch.component.ts b/src/lib/components/toggleswitch/toggleswitch.component.ts
new file mode 100644
index 0000000..7135892
--- /dev/null
+++ b/src/lib/components/toggleswitch/toggleswitch.component.ts
@@ -0,0 +1,67 @@
+import { Component, EventEmitter, Optional, Output, Self } from '@angular/core';
+import { ControlValueAccessor, FormsModule, NgControl } from '@angular/forms';
+import { ToggleSwitch } from 'primeng/toggleswitch';
+
+@Component({
+ selector: 'toggleswitch',
+ standalone: true,
+ imports: [ToggleSwitch, FormsModule],
+ template: `
+
+ `,
+})
+export class ToggleSwitchComponent 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/prime-preset/tokens/components/toggleswitch.ts b/src/prime-preset/tokens/components/toggleswitch.ts
new file mode 100644
index 0000000..8793c20
--- /dev/null
+++ b/src/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/stories/components/toggleswitch/examples/toggleswitch-checked.component.ts b/src/stories/components/toggleswitch/examples/toggleswitch-checked.component.ts
new file mode 100644
index 0000000..b0c6a88
--- /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 { ToggleSwitchComponent } from '../../../../lib/components/toggleswitch/toggleswitch.component';
+
+@Component({
+ selector: 'app-toggleswitch-checked',
+ standalone: true,
+ imports: [ToggleSwitchComponent, 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 0000000..7d041f4
--- /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 { ToggleSwitchComponent } from '../../../../lib/components/toggleswitch/toggleswitch.component';
+
+@Component({
+ selector: 'app-toggleswitch-disabled',
+ standalone: true,
+ imports: [ToggleSwitchComponent, 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 0000000..aced21f
--- /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 { ToggleSwitchComponent } from '../../../../lib/components/toggleswitch/toggleswitch.component';
+
+@Component({
+ selector: 'app-toggleswitch-invalid',
+ standalone: true,
+ imports: [ToggleSwitchComponent, 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 0000000..640568d
--- /dev/null
+++ b/src/stories/components/toggleswitch/toggleswitch.stories.ts
@@ -0,0 +1,181 @@
+import { Meta, StoryObj, moduleMetadata } from '@storybook/angular';
+import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
+import { ToggleSwitchComponent } from '../../../lib/components/toggleswitch/toggleswitch.component';
+import { ToggleSwitchCheckedComponent } from './examples/toggleswitch-checked.component';
+import { ToggleSwitchInvalidComponent } from './examples/toggleswitch-invalid.component';
+import { ToggleSwitchDisabledComponent } from './examples/toggleswitch-disabled.component';
+
+const meta: Meta = {
+ title: 'Components/Form/ToggleSwitch',
+ component: ToggleSwitchComponent,
+ tags: ['autodocs'],
+ decorators: [
+ moduleMetadata({
+ imports: [
+ ToggleSwitchComponent,
+ 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);
+}
+ `,
+ },
+ },
+ },
+};
+
+// ── Checked ───────────────────────────────────────────────────────────────────
+export const Checked: Story = {
+ 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({
+ standalone: true,
+ imports: [ToggleSwitchComponent, ReactiveFormsModule],
+ template: \`\`,
+})
+export class ToggleSwitchCheckedComponent {
+ control = new FormControl(true);
+}
+ `,
+ },
+ },
+ },
+};
+
+// ── Invalid ───────────────────────────────────────────────────────────────────
+export const Invalid: Story = {
+ 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({
+ standalone: true,
+ imports: [ToggleSwitchComponent, ReactiveFormsModule],
+ template: \`\`,
+})
+export class ToggleSwitchInvalidComponent {
+ control = new FormControl(false, [Validators.requiredTrue]);
+}
+ `,
+ },
+ },
+ },
+};
+
+// ── Disabled ──────────────────────────────────────────────────────────────────
+export const Disabled: Story = {
+ 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({
+ standalone: true,
+ imports: [ToggleSwitchComponent, ReactiveFormsModule],
+ template: \`\`,
+})
+export class ToggleSwitchDisabledComponent {
+ control = new FormControl({ value: false, disabled: true });
+}
+ `,
+ },
+ },
+ },
+};