Enable notifications
@@ -614,66 +612,66 @@ import { UiCheckbox, UiLabel, UiButton, UiFormField } from '@workspace/ui/direct
-
+
+
-
+
+
-
+
+
-
+
+
-
+
+
-
+
+
diff --git a/projects/docs/src/app/pages/docs/components/components.routes.ts b/projects/docs/src/app/pages/docs/components/components.routes.ts
index 224ba08..4700696 100644
--- a/projects/docs/src/app/pages/docs/components/components.routes.ts
+++ b/projects/docs/src/app/pages/docs/components/components.routes.ts
@@ -100,6 +100,10 @@ export const routes: Routes = [
path: 'skeleton',
loadComponent: () => import('./skeleton/skeleton').then(m => m.Skeleton)
},
+ {
+ path: 'switch',
+ loadComponent: () => import('./switch/switch').then(m => m.Switch)
+ },
{
path: '',
redirectTo: 'alert',
diff --git a/projects/docs/src/app/pages/docs/components/switch/switch.ts b/projects/docs/src/app/pages/docs/components/switch/switch.ts
new file mode 100644
index 0000000..5f137d6
--- /dev/null
+++ b/projects/docs/src/app/pages/docs/components/switch/switch.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+import { ComponentPreview } from '@components/component-preview/component-preview';
+import { switchVariants, switchMeta } from './switch.variants';
+
+@Component({
+ selector: 'docs-switch',
+ imports: [ComponentPreview],
+ template: `
+
+
+ `
+})
+export class Switch {
+ switchMeta = switchMeta;
+ switchVariants = switchVariants;
+}
diff --git a/projects/docs/src/app/pages/docs/components/switch/switch.variants.ts b/projects/docs/src/app/pages/docs/components/switch/switch.variants.ts
new file mode 100644
index 0000000..6890e4b
--- /dev/null
+++ b/projects/docs/src/app/pages/docs/components/switch/switch.variants.ts
@@ -0,0 +1,269 @@
+import { Component, model } from '@angular/core';
+import { UiFormField, UiLabel, UiSwitch, UiSwitchThumb } from 'ui';
+import { IVariant, IComponentMeta } from '@components/component-preview/component-preview';
+
+// Switch example components for dynamic rendering
+@Component({
+ selector: 'switch-default-example',
+ template: `
+
+
+
+
+ `,
+ imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
+})
+export class SwitchDefaultExample {
+ checked = model(false);
+}
+
+@Component({
+ selector: 'switch-disabled-example',
+ template: `
+
+
+
+
+ `,
+ imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
+})
+export class SwitchDisabledExample {
+ checked = model(false);
+}
+
+@Component({
+ selector: 'switch-checked-example',
+ template: `
+
+
+
+
+ `,
+ imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
+})
+export class SwitchCheckedExample {
+ checked = model(true);
+}
+
+@Component({
+ selector: 'switch-form-example',
+ template: `
+
+
+
Email Notifications
+
+
+
+
+
+ Receive emails about new products, features, and more.
+
+
+
+
+
+
+
+
+ Receive emails about your account security.
+
+
+
+
+
+
+
+ `,
+ imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
+})
+export class SwitchFormExample {
+ marketingEmails = model(false);
+ securityEmails = model(true);
+}
+
+// Component metadata for documentation
+export const switchMeta: IComponentMeta = {
+ title: 'Switch',
+ description: 'A control that allows the user to toggle between checked and not checked.',
+ installation: {
+ package: `switch';`,
+ import: `import { UiSwitch, UiSwitchThumb } from '@workspace/ui/directives/switch';
+import { UiFormField } from '@workspace/ui/directives/form-field';
+import { UiLabel } from '@workspace/ui/directives/label';`,
+ usage: `
+
+
+
`
+ },
+ api: {
+ props: [
+ {
+ name: 'checked',
+ type: 'boolean',
+ default: 'false',
+ description: 'Whether the switch is checked.'
+ },
+ {
+ name: 'disabled',
+ type: 'boolean',
+ default: 'false',
+ description: 'Whether the switch is disabled.'
+ },
+ {
+ name: 'class',
+ type: 'string',
+ description: 'Additional CSS classes to apply to the switch.'
+ }
+ ],
+ outputs: [
+ {
+ name: 'checkedChange',
+ type: 'boolean',
+ description: 'Emitted when the checked state changes.'
+ }
+ ]
+ }
+};
+
+export const switchVariants: IVariant[] = [
+ {
+ title: 'Default',
+ description: 'A basic switch component.',
+ code: `import { Component, model } from '@angular/core';
+import { UiSwitch, UiSwitchThumb } from '@workspace/ui/directives/switch';
+import { UiFormField } from '@workspace/ui/directives/form-field';
+import { UiLabel } from '@workspace/ui/directives/label';
+
+@Component({
+ selector: 'switch-default-example',
+ template: \`
+
+
+
+
+ \`,
+ imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
+})
+export class SwitchDefaultExample {
+ checked = model(false);
+}`,
+ component: SwitchDefaultExample
+ },
+ {
+ title: 'Disabled',
+ description: 'A switch in the disabled state.',
+ code: `import { Component, model } from '@angular/core';
+import { UiSwitch, UiSwitchThumb } from '@workspace/ui/directives/switch';
+import { UiFormField } from '@workspace/ui/directives/form-field';
+import { UiLabel } from '@workspace/ui/directives/label';
+
+@Component({
+ selector: 'switch-disabled-example',
+ template: \`
+
+
+
+
+ \`,
+ imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
+})
+export class SwitchDisabledExample {
+ checked = model(false);
+}`,
+ component: SwitchDisabledExample
+ },
+ {
+ title: 'Checked',
+ description: 'A switch in the checked state.',
+ code: `import { Component, model } from '@angular/core';
+import { UiSwitch, UiSwitchThumb } from '@workspace/ui/directives/switch';
+import { UiFormField } from '@workspace/ui/directives/form-field';
+import { UiLabel } from '@workspace/ui/directives/label';
+
+@Component({
+ selector: 'switch-checked-example',
+ template: \`
+
+
+
+
+ \`,
+ imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
+})
+export class SwitchCheckedExample {
+ checked = model(true);
+}`,
+ component: SwitchCheckedExample
+ },
+ {
+ title: 'Form',
+ description: 'A switch used in a form context with labels and descriptions.',
+ code: `import { Component, model } from '@angular/core';
+import { UiSwitch, UiSwitchThumb } from '@workspace/ui/directives/switch';
+import { UiFormField } from '@workspace/ui/directives/form-field';
+import { UiLabel } from '@workspace/ui/directives/label';
+
+@Component({
+ selector: 'switch-form-example',
+ template: \`
+
+
+
Email Notifications
+
+
+
+
+
+ Receive emails about new products, features, and more.
+
+
+
+
+
+
+
+
+ Receive emails about your account security.
+
+
+
+
+
+
+
+ \`,
+ imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
+})
+export class SwitchFormExample {
+ marketingEmails = model(false);
+ securityEmails = model(true);
+}`,
+ component: SwitchFormExample
+ }
+];
diff --git a/projects/docs/src/app/shared/components/sidebar/sidebar.ts b/projects/docs/src/app/shared/components/sidebar/sidebar.ts
index 90b674e..6357783 100644
--- a/projects/docs/src/app/shared/components/sidebar/sidebar.ts
+++ b/projects/docs/src/app/shared/components/sidebar/sidebar.ts
@@ -68,7 +68,7 @@ export class Sidebar {
{ name: 'Progress', path: 'progress' },
// { name: 'Select', path: 'select' },
{ name: 'Separator', path: 'separator' },
- // { name: 'Switch', path: 'switch' },
+ { name: 'Switch', path: 'switch' },
{ name: 'Tabs', path: 'tabs' },
// { name: 'Toast', path: 'toast' },
{ name: 'Tooltip', path: 'tooltip' },
diff --git a/projects/ui/src/directives/checkbox.ts b/projects/ui/src/directives/checkbox.ts
index 02018f3..170bd99 100644
--- a/projects/ui/src/directives/checkbox.ts
+++ b/projects/ui/src/directives/checkbox.ts
@@ -15,14 +15,14 @@ const checkboxVariants = tv({
hostDirectives: [{
directive: NgpCheckbox,
inputs: [
- 'ngpCheckboxChecked: uiCheckboxChecked',
- 'ngpCheckboxIndeterminate: uiCheckboxIndeterminate',
+ 'ngpCheckboxChecked: checked',
+ 'ngpCheckboxIndeterminate: indeterminate',
'ngpCheckboxRequired: required',
'ngpCheckboxDisabled: disabled',
],
outputs: [
- 'ngpCheckboxCheckedChange: uiCheckboxCheckedChange',
- 'ngpCheckboxIndeterminateChange: uiCheckboxIndeterminateChange',
+ 'ngpCheckboxCheckedChange: checkedChange',
+ 'ngpCheckboxIndeterminateChange: indeterminateChange',
],
}],
})
diff --git a/projects/ui/src/directives/switch.ts b/projects/ui/src/directives/switch.ts
new file mode 100644
index 0000000..2c63ad3
--- /dev/null
+++ b/projects/ui/src/directives/switch.ts
@@ -0,0 +1,49 @@
+import { computed, Directive, input } from '@angular/core';
+import { tv } from 'tailwind-variants';
+import { NgpSwitch, NgpSwitchThumb } from "ng-primitives/switch";
+
+const switchVariants = tv({
+ slots: {
+ switchRoot: 'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors data-[focus-visible]:outline-none data-[focus-visible]:ring-2 data-[focus-visible]:ring-ring data-[focus-visible]:ring-offset-2 data-[focus-visible]:ring-offset-background data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[checked]:bg-primary bg-input',
+ switchThumb: 'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[checked]:translate-x-5 translate-x-0'
+ }
+});
+
+const { switchThumb, switchRoot } = switchVariants();
+
+@Directive({
+ selector: '[uiSwitch]',
+ exportAs: 'uiSwitch',
+ host: {
+ '[class]': 'computedClass()'
+ },
+ hostDirectives: [
+ {
+ directive: NgpSwitch,
+ inputs: [
+ 'ngpSwitchChecked:checked',
+ 'ngpSwitchDisabled:disabled'
+ ],
+ outputs: [
+ 'ngpSwitchCheckedChange:checkedChange'
+ ],
+ },
+ ],
+})
+export class UiSwitch {
+ inputClass = input
('', { alias: 'class' });
+ computedClass = computed(() => switchRoot({ class: this.inputClass() }));
+}
+
+@Directive({
+ selector: '[uiSwitchThumb]',
+ exportAs: 'uiSwitchThumb',
+ host: {
+ '[class]': 'computedClass()'
+ },
+ hostDirectives: [NgpSwitchThumb],
+})
+export class UiSwitchThumb {
+ inputClass = input('', { alias: 'class' });
+ computedClass = computed(() => switchThumb({ class: this.inputClass() }));
+}
\ No newline at end of file
diff --git a/projects/ui/src/public-api.ts b/projects/ui/src/public-api.ts
index 7170885..eb1c919 100644
--- a/projects/ui/src/public-api.ts
+++ b/projects/ui/src/public-api.ts
@@ -22,4 +22,5 @@ export * from './directives/form-field';
export * from './directives/checkbox';
export * from './directives/toggle';
export * from './directives/toggle-group';
-export * from './directives/skeleton';
\ No newline at end of file
+export * from './directives/skeleton';
+export * from './directives/switch';
\ No newline at end of file
diff --git a/registry.json b/registry.json
index a3f36d9..0ff18e6 100644
--- a/registry.json
+++ b/registry.json
@@ -161,6 +161,19 @@
],
"dependencies": []
},
+ {
+ "name": "switch",
+ "type": "registry:directive",
+ "title": "Switch Directive",
+ "description": "A switch directive for creating toggle switches with on/off states and proper accessibility",
+ "files": [
+ {
+ "path": "directives/switch.ts",
+ "type": "registry:directive"
+ }
+ ],
+ "dependencies": []
+ },
{
"name": "avatar",
"type": "registry:directive",