Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
18 changes: 18 additions & 0 deletions projects/docs/src/app/pages/docs/components/switch/switch.ts
Original file line number Diff line number Diff line change
@@ -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: `
<docs-component-preview
[meta]="switchMeta"
[variants]="switchVariants">
</docs-component-preview>
`
})
export class Switch {
switchMeta = switchMeta;
switchVariants = switchVariants;
}
269 changes: 269 additions & 0 deletions projects/docs/src/app/pages/docs/components/switch/switch.variants.ts
Original file line number Diff line number Diff line change
@@ -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: `
<div class="flex items-center space-x-2" uiFormField>
<button uiSwitch [(checked)]="checked">
<span uiSwitchThumb></span>
</button>
<label uiLabel>Airplane Mode</label>
</div>
`,
imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
})
export class SwitchDefaultExample {
checked = model(false);
}

@Component({
selector: 'switch-disabled-example',
template: `
<div class="flex items-center space-x-2" uiFormField>
<button uiSwitch disabled [(checked)]="checked">
<span uiSwitchThumb></span>
</button>
<label uiLabel>Disabled Switch</label>
</div>
`,
imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
})
export class SwitchDisabledExample {
checked = model(false);
}

@Component({
selector: 'switch-checked-example',
template: `
<div class="flex items-center space-x-2" uiFormField>
<button uiSwitch checked [(checked)]="checked">
<span uiSwitchThumb></span>
</button>
<label uiLabel>
WiFi
</label>
</div>
`,
imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
})
export class SwitchCheckedExample {
checked = model(true);
}

@Component({
selector: 'switch-form-example',
template: `
<div class="w-full space-y-6">
<div uiFormField class="space-y-4">
<h3 class="mb-4 text-lg font-medium">Email Notifications</h3>
<div class="space-y-4">
<div class="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div class="space-y-0.5">
<label uiLabel>Marketing emails</label>
<p class="text-sm text-muted-foreground">
Receive emails about new products, features, and more.
</p>
</div>
<button uiSwitch [(checked)]="marketingEmails">
<span uiSwitchThumb></span>
</button>
</div>
<div class="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div class="space-y-0.5">
<label uiLabel>Security emails</label>
<p class="text-sm text-muted-foreground">
Receive emails about your account security.
</p>
</div>
<button uiSwitch [(checked)]="securityEmails" disabled>
<span uiSwitchThumb></span>
</button>
</div>
</div>
</div>
</div>
`,
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: `<div uiFormField>
<button uiSwitch [(checked)]="isChecked">
<span uiSwitchThumb></span>
</button>
<label uiLabel>Switch Label</label>
</div>`
},
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: \`
<div class="flex items-center space-x-2" uiFormField>
<button uiSwitch [(checked)]="checked">
<span uiSwitchThumb></span>
</button>
<label uiLabel>Airplane Mode</label>
</div>
\`,
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: \`
<div class="flex items-center space-x-2" uiFormField>
<button uiSwitch disabled [(checked)]="checked">
<span uiSwitchThumb></span>
</button>
<label uiLabel>Disabled Switch</label>
</div>
\`,
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: \`
<div class="flex items-center space-x-2" uiFormField>
<button uiSwitch checked [(checked)]="checked">
<span uiSwitchThumb></span>
</button>
<label uiLabel>
WiFi
</label>
</div>
\`,
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: \`
<div class="w-full space-y-6">
<div uiFormField class="space-y-4">
<h3 class="mb-4 text-lg font-medium">Email Notifications</h3>
<div class="space-y-4">
<div class="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div class="space-y-0.5">
<label uiLabel>Marketing emails</label>
<p class="text-sm text-muted-foreground">
Receive emails about new products, features, and more.
</p>
</div>
<button uiSwitch [(checked)]="marketingEmails">
<span uiSwitchThumb></span>
</button>
</div>
<div class="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div class="space-y-0.5">
<label uiLabel>Security emails</label>
<p class="text-sm text-muted-foreground">
Receive emails about your account security.
</p>
</div>
<button uiSwitch [(checked)]="securityEmails" disabled>
<span uiSwitchThumb></span>
</button>
</div>
</div>
</div>
</div>
\`,
imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
})
export class SwitchFormExample {
marketingEmails = model(false);
securityEmails = model(true);
}`,
component: SwitchFormExample
}
];
2 changes: 1 addition & 1 deletion projects/docs/src/app/shared/components/sidebar/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
8 changes: 4 additions & 4 deletions projects/ui/src/directives/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
}],
})
Expand Down
49 changes: 49 additions & 0 deletions projects/ui/src/directives/switch.ts
Original file line number Diff line number Diff line change
@@ -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<string>('', { alias: 'class' });
computedClass = computed(() => switchRoot({ class: this.inputClass() }));
}

@Directive({
selector: '[uiSwitchThumb]',
exportAs: 'uiSwitchThumb',
host: {
'[class]': 'computedClass()'
},
hostDirectives: [NgpSwitchThumb],
})
export class UiSwitchThumb {
inputClass = input<string>('', { alias: 'class' });
computedClass = computed(() => switchThumb({ class: this.inputClass() }));
}
3 changes: 2 additions & 1 deletion projects/ui/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
export * from './directives/skeleton';
export * from './directives/switch';
Loading