Skip to content

Commit 112df5a

Browse files
Merge pull request #5 from angularcafe/dev
Add Switch component
2 parents fff6f34 + f1fddf3 commit 112df5a

File tree

9 files changed

+444
-92
lines changed

9 files changed

+444
-92
lines changed

projects/docs/src/app/pages/docs/components/checkbox/checkbox.variants.ts

Lines changed: 84 additions & 86 deletions
Large diffs are not rendered by default.

projects/docs/src/app/pages/docs/components/components.routes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ export const routes: Routes = [
100100
path: 'skeleton',
101101
loadComponent: () => import('./skeleton/skeleton').then(m => m.Skeleton)
102102
},
103+
{
104+
path: 'switch',
105+
loadComponent: () => import('./switch/switch').then(m => m.Switch)
106+
},
103107
{
104108
path: '',
105109
redirectTo: 'alert',
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Component } from '@angular/core';
2+
import { ComponentPreview } from '@components/component-preview/component-preview';
3+
import { switchVariants, switchMeta } from './switch.variants';
4+
5+
@Component({
6+
selector: 'docs-switch',
7+
imports: [ComponentPreview],
8+
template: `
9+
<docs-component-preview
10+
[meta]="switchMeta"
11+
[variants]="switchVariants">
12+
</docs-component-preview>
13+
`
14+
})
15+
export class Switch {
16+
switchMeta = switchMeta;
17+
switchVariants = switchVariants;
18+
}
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
import { Component, model } from '@angular/core';
2+
import { UiFormField, UiLabel, UiSwitch, UiSwitchThumb } from 'ui';
3+
import { IVariant, IComponentMeta } from '@components/component-preview/component-preview';
4+
5+
// Switch example components for dynamic rendering
6+
@Component({
7+
selector: 'switch-default-example',
8+
template: `
9+
<div class="flex items-center space-x-2" uiFormField>
10+
<button uiSwitch [(checked)]="checked">
11+
<span uiSwitchThumb></span>
12+
</button>
13+
<label uiLabel>Airplane Mode</label>
14+
</div>
15+
`,
16+
imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
17+
})
18+
export class SwitchDefaultExample {
19+
checked = model(false);
20+
}
21+
22+
@Component({
23+
selector: 'switch-disabled-example',
24+
template: `
25+
<div class="flex items-center space-x-2" uiFormField>
26+
<button uiSwitch disabled [(checked)]="checked">
27+
<span uiSwitchThumb></span>
28+
</button>
29+
<label uiLabel>Disabled Switch</label>
30+
</div>
31+
`,
32+
imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
33+
})
34+
export class SwitchDisabledExample {
35+
checked = model(false);
36+
}
37+
38+
@Component({
39+
selector: 'switch-checked-example',
40+
template: `
41+
<div class="flex items-center space-x-2" uiFormField>
42+
<button uiSwitch checked [(checked)]="checked">
43+
<span uiSwitchThumb></span>
44+
</button>
45+
<label uiLabel>
46+
WiFi
47+
</label>
48+
</div>
49+
`,
50+
imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
51+
})
52+
export class SwitchCheckedExample {
53+
checked = model(true);
54+
}
55+
56+
@Component({
57+
selector: 'switch-form-example',
58+
template: `
59+
<div class="w-full space-y-6">
60+
<div uiFormField class="space-y-4">
61+
<h3 class="mb-4 text-lg font-medium">Email Notifications</h3>
62+
<div class="space-y-4">
63+
<div class="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
64+
<div class="space-y-0.5">
65+
<label uiLabel>Marketing emails</label>
66+
<p class="text-sm text-muted-foreground">
67+
Receive emails about new products, features, and more.
68+
</p>
69+
</div>
70+
<button uiSwitch [(checked)]="marketingEmails">
71+
<span uiSwitchThumb></span>
72+
</button>
73+
</div>
74+
<div class="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
75+
<div class="space-y-0.5">
76+
<label uiLabel>Security emails</label>
77+
<p class="text-sm text-muted-foreground">
78+
Receive emails about your account security.
79+
</p>
80+
</div>
81+
<button uiSwitch [(checked)]="securityEmails" disabled>
82+
<span uiSwitchThumb></span>
83+
</button>
84+
</div>
85+
</div>
86+
</div>
87+
</div>
88+
`,
89+
imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
90+
})
91+
export class SwitchFormExample {
92+
marketingEmails = model(false);
93+
securityEmails = model(true);
94+
}
95+
96+
// Component metadata for documentation
97+
export const switchMeta: IComponentMeta = {
98+
title: 'Switch',
99+
description: 'A control that allows the user to toggle between checked and not checked.',
100+
installation: {
101+
package: `switch';`,
102+
import: `import { UiSwitch, UiSwitchThumb } from '@workspace/ui/directives/switch';
103+
import { UiFormField } from '@workspace/ui/directives/form-field';
104+
import { UiLabel } from '@workspace/ui/directives/label';`,
105+
usage: `<div uiFormField>
106+
<button uiSwitch [(checked)]="isChecked">
107+
<span uiSwitchThumb></span>
108+
</button>
109+
<label uiLabel>Switch Label</label>
110+
</div>`
111+
},
112+
api: {
113+
props: [
114+
{
115+
name: 'checked',
116+
type: 'boolean',
117+
default: 'false',
118+
description: 'Whether the switch is checked.'
119+
},
120+
{
121+
name: 'disabled',
122+
type: 'boolean',
123+
default: 'false',
124+
description: 'Whether the switch is disabled.'
125+
},
126+
{
127+
name: 'class',
128+
type: 'string',
129+
description: 'Additional CSS classes to apply to the switch.'
130+
}
131+
],
132+
outputs: [
133+
{
134+
name: 'checkedChange',
135+
type: 'boolean',
136+
description: 'Emitted when the checked state changes.'
137+
}
138+
]
139+
}
140+
};
141+
142+
export const switchVariants: IVariant[] = [
143+
{
144+
title: 'Default',
145+
description: 'A basic switch component.',
146+
code: `import { Component, model } from '@angular/core';
147+
import { UiSwitch, UiSwitchThumb } from '@workspace/ui/directives/switch';
148+
import { UiFormField } from '@workspace/ui/directives/form-field';
149+
import { UiLabel } from '@workspace/ui/directives/label';
150+
151+
@Component({
152+
selector: 'switch-default-example',
153+
template: \`
154+
<div class="flex items-center space-x-2" uiFormField>
155+
<button uiSwitch [(checked)]="checked">
156+
<span uiSwitchThumb></span>
157+
</button>
158+
<label uiLabel>Airplane Mode</label>
159+
</div>
160+
\`,
161+
imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
162+
})
163+
export class SwitchDefaultExample {
164+
checked = model(false);
165+
}`,
166+
component: SwitchDefaultExample
167+
},
168+
{
169+
title: 'Disabled',
170+
description: 'A switch in the disabled state.',
171+
code: `import { Component, model } from '@angular/core';
172+
import { UiSwitch, UiSwitchThumb } from '@workspace/ui/directives/switch';
173+
import { UiFormField } from '@workspace/ui/directives/form-field';
174+
import { UiLabel } from '@workspace/ui/directives/label';
175+
176+
@Component({
177+
selector: 'switch-disabled-example',
178+
template: \`
179+
<div class="flex items-center space-x-2" uiFormField>
180+
<button uiSwitch disabled [(checked)]="checked">
181+
<span uiSwitchThumb></span>
182+
</button>
183+
<label uiLabel>Disabled Switch</label>
184+
</div>
185+
\`,
186+
imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
187+
})
188+
export class SwitchDisabledExample {
189+
checked = model(false);
190+
}`,
191+
component: SwitchDisabledExample
192+
},
193+
{
194+
title: 'Checked',
195+
description: 'A switch in the checked state.',
196+
code: `import { Component, model } from '@angular/core';
197+
import { UiSwitch, UiSwitchThumb } from '@workspace/ui/directives/switch';
198+
import { UiFormField } from '@workspace/ui/directives/form-field';
199+
import { UiLabel } from '@workspace/ui/directives/label';
200+
201+
@Component({
202+
selector: 'switch-checked-example',
203+
template: \`
204+
<div class="flex items-center space-x-2" uiFormField>
205+
<button uiSwitch checked [(checked)]="checked">
206+
<span uiSwitchThumb></span>
207+
</button>
208+
<label uiLabel>
209+
WiFi
210+
</label>
211+
</div>
212+
\`,
213+
imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
214+
})
215+
export class SwitchCheckedExample {
216+
checked = model(true);
217+
}`,
218+
component: SwitchCheckedExample
219+
},
220+
{
221+
title: 'Form',
222+
description: 'A switch used in a form context with labels and descriptions.',
223+
code: `import { Component, model } from '@angular/core';
224+
import { UiSwitch, UiSwitchThumb } from '@workspace/ui/directives/switch';
225+
import { UiFormField } from '@workspace/ui/directives/form-field';
226+
import { UiLabel } from '@workspace/ui/directives/label';
227+
228+
@Component({
229+
selector: 'switch-form-example',
230+
template: \`
231+
<div class="w-full space-y-6">
232+
<div uiFormField class="space-y-4">
233+
<h3 class="mb-4 text-lg font-medium">Email Notifications</h3>
234+
<div class="space-y-4">
235+
<div class="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
236+
<div class="space-y-0.5">
237+
<label uiLabel>Marketing emails</label>
238+
<p class="text-sm text-muted-foreground">
239+
Receive emails about new products, features, and more.
240+
</p>
241+
</div>
242+
<button uiSwitch [(checked)]="marketingEmails">
243+
<span uiSwitchThumb></span>
244+
</button>
245+
</div>
246+
<div class="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
247+
<div class="space-y-0.5">
248+
<label uiLabel>Security emails</label>
249+
<p class="text-sm text-muted-foreground">
250+
Receive emails about your account security.
251+
</p>
252+
</div>
253+
<button uiSwitch [(checked)]="securityEmails" disabled>
254+
<span uiSwitchThumb></span>
255+
</button>
256+
</div>
257+
</div>
258+
</div>
259+
</div>
260+
\`,
261+
imports: [UiSwitch, UiSwitchThumb, UiFormField, UiLabel]
262+
})
263+
export class SwitchFormExample {
264+
marketingEmails = model(false);
265+
securityEmails = model(true);
266+
}`,
267+
component: SwitchFormExample
268+
}
269+
];

projects/docs/src/app/shared/components/sidebar/sidebar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export class Sidebar {
6868
{ name: 'Progress', path: 'progress' },
6969
// { name: 'Select', path: 'select' },
7070
{ name: 'Separator', path: 'separator' },
71-
// { name: 'Switch', path: 'switch' },
71+
{ name: 'Switch', path: 'switch' },
7272
{ name: 'Tabs', path: 'tabs' },
7373
// { name: 'Toast', path: 'toast' },
7474
{ name: 'Tooltip', path: 'tooltip' },

projects/ui/src/directives/checkbox.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ const checkboxVariants = tv({
1515
hostDirectives: [{
1616
directive: NgpCheckbox,
1717
inputs: [
18-
'ngpCheckboxChecked: uiCheckboxChecked',
19-
'ngpCheckboxIndeterminate: uiCheckboxIndeterminate',
18+
'ngpCheckboxChecked: checked',
19+
'ngpCheckboxIndeterminate: indeterminate',
2020
'ngpCheckboxRequired: required',
2121
'ngpCheckboxDisabled: disabled',
2222
],
2323
outputs: [
24-
'ngpCheckboxCheckedChange: uiCheckboxCheckedChange',
25-
'ngpCheckboxIndeterminateChange: uiCheckboxIndeterminateChange',
24+
'ngpCheckboxCheckedChange: checkedChange',
25+
'ngpCheckboxIndeterminateChange: indeterminateChange',
2626
],
2727
}],
2828
})
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { computed, Directive, input } from '@angular/core';
2+
import { tv } from 'tailwind-variants';
3+
import { NgpSwitch, NgpSwitchThumb } from "ng-primitives/switch";
4+
5+
const switchVariants = tv({
6+
slots: {
7+
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',
8+
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'
9+
}
10+
});
11+
12+
const { switchThumb, switchRoot } = switchVariants();
13+
14+
@Directive({
15+
selector: '[uiSwitch]',
16+
exportAs: 'uiSwitch',
17+
host: {
18+
'[class]': 'computedClass()'
19+
},
20+
hostDirectives: [
21+
{
22+
directive: NgpSwitch,
23+
inputs: [
24+
'ngpSwitchChecked:checked',
25+
'ngpSwitchDisabled:disabled'
26+
],
27+
outputs: [
28+
'ngpSwitchCheckedChange:checkedChange'
29+
],
30+
},
31+
],
32+
})
33+
export class UiSwitch {
34+
inputClass = input<string>('', { alias: 'class' });
35+
computedClass = computed(() => switchRoot({ class: this.inputClass() }));
36+
}
37+
38+
@Directive({
39+
selector: '[uiSwitchThumb]',
40+
exportAs: 'uiSwitchThumb',
41+
host: {
42+
'[class]': 'computedClass()'
43+
},
44+
hostDirectives: [NgpSwitchThumb],
45+
})
46+
export class UiSwitchThumb {
47+
inputClass = input<string>('', { alias: 'class' });
48+
computedClass = computed(() => switchThumb({ class: this.inputClass() }));
49+
}

projects/ui/src/public-api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ export * from './directives/form-field';
2222
export * from './directives/checkbox';
2323
export * from './directives/toggle';
2424
export * from './directives/toggle-group';
25-
export * from './directives/skeleton';
25+
export * from './directives/skeleton';
26+
export * from './directives/switch';

0 commit comments

Comments
 (0)