@sonny-ui/core 0.1.0-alpha.2 → 0.1.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +187 -40
- package/fesm2022/sonny-ui-core.mjs +6642 -268
- package/fesm2022/sonny-ui-core.mjs.map +1 -1
- package/package.json +8 -5
- package/schematics/ng-add/index.js +27 -0
- package/schematics/ng-add/schema.json +1 -1
- package/schematics/ng-generate/component/index.js +182 -1
- package/schematics/ng-generate/component/schema.json +2 -2
- package/src/lib/accordion/accordion.directives.spec.ts +173 -0
- package/src/lib/accordion/accordion.directives.ts +143 -0
- package/src/lib/accordion/index.ts +8 -0
- package/src/lib/alert/alert.directives.spec.ts +154 -0
- package/src/lib/alert/alert.directives.ts +67 -0
- package/src/lib/alert/alert.variants.ts +25 -0
- package/src/lib/alert/index.ts +6 -0
- package/src/lib/avatar/avatar.component.spec.ts +75 -0
- package/src/lib/avatar/avatar.component.ts +43 -0
- package/src/lib/avatar/avatar.variants.ts +26 -0
- package/src/lib/avatar/index.ts +2 -0
- package/src/lib/avatar-group/avatar-group.component.spec.ts +74 -0
- package/src/lib/avatar-group/avatar-group.component.ts +88 -0
- package/src/lib/avatar-group/index.ts +1 -0
- package/src/lib/badge/badge.directive.spec.ts +74 -0
- package/src/lib/badge/badge.directive.ts +17 -0
- package/src/lib/badge/badge.variants.ts +29 -0
- package/src/lib/badge/index.ts +2 -0
- package/src/lib/breadcrumb/breadcrumb.directives.spec.ts +80 -0
- package/src/lib/breadcrumb/breadcrumb.directives.ts +78 -0
- package/src/lib/breadcrumb/index.ts +8 -0
- package/src/lib/button/button.directive.spec.ts +92 -0
- package/src/lib/button/button.directive.ts +28 -0
- package/src/lib/button/button.variants.ts +30 -0
- package/src/lib/button/index.ts +2 -0
- package/src/lib/button-group/button-group.directive.spec.ts +46 -0
- package/src/lib/button-group/button-group.directive.ts +19 -0
- package/src/lib/button-group/button-group.variants.ts +18 -0
- package/src/lib/button-group/index.ts +2 -0
- package/src/lib/calendar/calendar.component.spec.ts +192 -0
- package/src/lib/calendar/calendar.component.ts +342 -0
- package/src/lib/calendar/calendar.types.ts +24 -0
- package/src/lib/calendar/index.ts +7 -0
- package/src/lib/card/card.directives.spec.ts +104 -0
- package/src/lib/card/card.directives.ts +72 -0
- package/src/lib/card/card.variants.ts +28 -0
- package/src/lib/card/index.ts +9 -0
- package/src/lib/carousel/carousel.directives.spec.ts +85 -0
- package/src/lib/carousel/carousel.directives.ts +159 -0
- package/src/lib/carousel/index.ts +8 -0
- package/src/lib/chat-bubble/chat-bubble.directives.spec.ts +52 -0
- package/src/lib/chat-bubble/chat-bubble.directives.ts +96 -0
- package/src/lib/chat-bubble/index.ts +11 -0
- package/src/lib/checkbox/checkbox.directive.spec.ts +57 -0
- package/src/lib/checkbox/checkbox.directive.ts +16 -0
- package/src/lib/checkbox/checkbox.variants.ts +19 -0
- package/src/lib/checkbox/index.ts +2 -0
- package/src/lib/color-picker/color-picker.component.spec.ts +328 -0
- package/src/lib/color-picker/color-picker.component.ts +537 -0
- package/src/lib/color-picker/color-picker.types.ts +24 -0
- package/src/lib/color-picker/color-picker.utils.ts +183 -0
- package/src/lib/color-picker/color-picker.variants.ts +17 -0
- package/src/lib/color-picker/index.ts +20 -0
- package/src/lib/combobox/combobox.component.spec.ts +151 -0
- package/src/lib/combobox/combobox.component.ts +264 -0
- package/src/lib/combobox/combobox.variants.ts +19 -0
- package/src/lib/combobox/index.ts +2 -0
- package/src/lib/command-palette/command-palette.component.spec.ts +178 -0
- package/src/lib/command-palette/command-palette.component.ts +194 -0
- package/src/lib/command-palette/command-palette.service.ts +36 -0
- package/src/lib/command-palette/command-palette.types.ts +23 -0
- package/src/lib/command-palette/index.ts +7 -0
- package/src/lib/data-table/data-table.component.spec.ts +443 -0
- package/src/lib/data-table/data-table.component.ts +602 -0
- package/src/lib/data-table/data-table.directives.ts +31 -0
- package/src/lib/data-table/data-table.types.ts +20 -0
- package/src/lib/data-table/index.ts +13 -0
- package/src/lib/date-picker/date-picker.component.spec.ts +131 -0
- package/src/lib/date-picker/date-picker.component.ts +220 -0
- package/src/lib/date-picker/date-picker.variants.ts +17 -0
- package/src/lib/date-picker/index.ts +2 -0
- package/src/lib/date-range-picker/date-range-picker.component.spec.ts +151 -0
- package/src/lib/date-range-picker/date-range-picker.component.ts +340 -0
- package/src/lib/date-range-picker/index.ts +1 -0
- package/src/lib/diff/diff.component.spec.ts +47 -0
- package/src/lib/diff/diff.component.ts +82 -0
- package/src/lib/diff/index.ts +1 -0
- package/src/lib/divider/divider.component.spec.ts +48 -0
- package/src/lib/divider/divider.component.ts +51 -0
- package/src/lib/divider/divider.variants.ts +22 -0
- package/src/lib/divider/index.ts +2 -0
- package/src/lib/dock/dock.directives.spec.ts +85 -0
- package/src/lib/dock/dock.directives.ts +81 -0
- package/src/lib/dock/index.ts +1 -0
- package/src/lib/drawer/drawer.directives.spec.ts +62 -0
- package/src/lib/drawer/drawer.directives.ts +80 -0
- package/src/lib/drawer/index.ts +8 -0
- package/src/lib/dropdown/dropdown.directives.spec.ts +106 -0
- package/src/lib/dropdown/dropdown.directives.ts +136 -0
- package/src/lib/dropdown/dropdown.variants.ts +27 -0
- package/src/lib/dropdown/index.ts +15 -0
- package/src/lib/fab/fab.directives.spec.ts +60 -0
- package/src/lib/fab/fab.directives.ts +77 -0
- package/src/lib/fab/index.ts +8 -0
- package/src/lib/fieldset/fieldset.directives.spec.ts +74 -0
- package/src/lib/fieldset/fieldset.directives.ts +49 -0
- package/src/lib/fieldset/fieldset.variants.ts +15 -0
- package/src/lib/fieldset/index.ts +6 -0
- package/src/lib/file-input/file-input.component.spec.ts +114 -0
- package/src/lib/file-input/file-input.component.ts +155 -0
- package/src/lib/file-input/file-input.variants.ts +25 -0
- package/src/lib/file-input/index.ts +6 -0
- package/src/lib/indicator/index.ts +6 -0
- package/src/lib/indicator/indicator.directives.spec.ts +64 -0
- package/src/lib/indicator/indicator.directives.ts +59 -0
- package/src/lib/input/index.ts +3 -0
- package/src/lib/input/input.directive.spec.ts +103 -0
- package/src/lib/input/input.directive.ts +25 -0
- package/src/lib/input/input.variants.ts +42 -0
- package/src/lib/input/label.directive.ts +16 -0
- package/src/lib/kbd/index.ts +2 -0
- package/src/lib/kbd/kbd.directive.spec.ts +42 -0
- package/src/lib/kbd/kbd.directive.ts +18 -0
- package/src/lib/kbd/kbd.variants.ts +19 -0
- package/src/lib/link/index.ts +2 -0
- package/src/lib/link/link.directive.spec.ts +41 -0
- package/src/lib/link/link.directive.ts +18 -0
- package/src/lib/link/link.variants.ts +20 -0
- package/src/lib/list/index.ts +8 -0
- package/src/lib/list/list.directives.spec.ts +65 -0
- package/src/lib/list/list.directives.ts +81 -0
- package/src/lib/loader/index.ts +2 -0
- package/src/lib/loader/loader.component.spec.ts +58 -0
- package/src/lib/loader/loader.component.ts +47 -0
- package/src/lib/loader/loader.variants.ts +21 -0
- package/src/lib/modal/dialog-ref.ts +19 -0
- package/src/lib/modal/dialog.directives.ts +84 -0
- package/src/lib/modal/dialog.service.spec.ts +52 -0
- package/src/lib/modal/dialog.service.ts +61 -0
- package/src/lib/modal/dialog.types.ts +16 -0
- package/src/lib/modal/index.ts +11 -0
- package/src/lib/navbar/index.ts +7 -0
- package/src/lib/navbar/navbar.directives.spec.ts +59 -0
- package/src/lib/navbar/navbar.directives.ts +57 -0
- package/src/lib/number-input/index.ts +2 -0
- package/src/lib/number-input/number-input.component.spec.ts +151 -0
- package/src/lib/number-input/number-input.component.ts +152 -0
- package/src/lib/number-input/number-input.variants.ts +17 -0
- package/src/lib/otp-input/index.ts +2 -0
- package/src/lib/otp-input/otp-input.component.spec.ts +252 -0
- package/src/lib/otp-input/otp-input.component.ts +274 -0
- package/src/lib/otp-input/otp-input.variants.ts +18 -0
- package/src/lib/pagination/index.ts +6 -0
- package/src/lib/pagination/pagination.component.spec.ts +59 -0
- package/src/lib/pagination/pagination.component.ts +143 -0
- package/src/lib/pagination/pagination.variants.ts +31 -0
- package/src/lib/popover/index.ts +6 -0
- package/src/lib/popover/popover.directives.spec.ts +147 -0
- package/src/lib/popover/popover.directives.ts +151 -0
- package/src/lib/progress/index.ts +7 -0
- package/src/lib/progress/progress.component.spec.ts +117 -0
- package/src/lib/progress/progress.component.ts +64 -0
- package/src/lib/progress/progress.variants.ts +43 -0
- package/src/lib/radial-progress/index.ts +5 -0
- package/src/lib/radial-progress/radial-progress.component.spec.ts +41 -0
- package/src/lib/radial-progress/radial-progress.component.ts +70 -0
- package/src/lib/radio/index.ts +2 -0
- package/src/lib/radio/radio.directive.spec.ts +46 -0
- package/src/lib/radio/radio.directive.ts +16 -0
- package/src/lib/radio/radio.variants.ts +19 -0
- package/src/lib/rating/index.ts +2 -0
- package/src/lib/rating/rating.component.spec.ts +157 -0
- package/src/lib/rating/rating.component.ts +163 -0
- package/src/lib/rating/rating.variants.ts +20 -0
- package/src/lib/select/index.ts +2 -0
- package/src/lib/select/select.component.spec.ts +112 -0
- package/src/lib/select/select.component.ts +235 -0
- package/src/lib/select/select.variants.ts +19 -0
- package/src/lib/sheet/index.ts +10 -0
- package/src/lib/sheet/sheet-ref.ts +18 -0
- package/src/lib/sheet/sheet.component.spec.ts +67 -0
- package/src/lib/sheet/sheet.directives.ts +70 -0
- package/src/lib/sheet/sheet.service.ts +100 -0
- package/src/lib/sheet/sheet.types.ts +23 -0
- package/src/lib/skeleton/index.ts +2 -0
- package/src/lib/skeleton/skeleton.directive.spec.ts +63 -0
- package/src/lib/skeleton/skeleton.directive.ts +21 -0
- package/src/lib/skeleton/skeleton.variants.ts +27 -0
- package/src/lib/slider/index.ts +2 -0
- package/src/lib/slider/slider.component.spec.ts +104 -0
- package/src/lib/slider/slider.component.ts +181 -0
- package/src/lib/slider/slider.variants.ts +25 -0
- package/src/lib/stat/index.ts +8 -0
- package/src/lib/stat/stat.directives.spec.ts +60 -0
- package/src/lib/stat/stat.directives.ts +79 -0
- package/src/lib/status/index.ts +2 -0
- package/src/lib/status/status.directive.spec.ts +43 -0
- package/src/lib/status/status.directive.ts +37 -0
- package/src/lib/status/status.variants.ts +26 -0
- package/src/lib/steps/index.ts +8 -0
- package/src/lib/steps/steps.directives.spec.ts +52 -0
- package/src/lib/steps/steps.directives.ts +78 -0
- package/src/lib/switch/index.ts +2 -0
- package/src/lib/switch/switch.component.spec.ts +98 -0
- package/src/lib/switch/switch.component.ts +76 -0
- package/src/lib/switch/switch.variants.ts +31 -0
- package/src/lib/table/index.ts +12 -0
- package/src/lib/table/table.directives.spec.ts +111 -0
- package/src/lib/table/table.directives.ts +126 -0
- package/src/lib/table/table.variants.ts +36 -0
- package/src/lib/tabs/index.ts +8 -0
- package/src/lib/tabs/tabs.directives.spec.ts +136 -0
- package/src/lib/tabs/tabs.directives.ts +126 -0
- package/src/lib/tabs/tabs.variants.ts +17 -0
- package/src/lib/tag-input/index.ts +2 -0
- package/src/lib/tag-input/tag-input.component.spec.ts +190 -0
- package/src/lib/tag-input/tag-input.component.ts +172 -0
- package/src/lib/tag-input/tag-input.variants.ts +31 -0
- package/src/lib/textarea/index.ts +7 -0
- package/src/lib/textarea/textarea.directive.spec.ts +84 -0
- package/src/lib/textarea/textarea.directive.ts +71 -0
- package/src/lib/textarea/textarea.variants.ts +34 -0
- package/src/lib/timeline/index.ts +11 -0
- package/src/lib/timeline/timeline.directives.spec.ts +55 -0
- package/src/lib/timeline/timeline.directives.ts +85 -0
- package/src/lib/toast/index.ts +3 -0
- package/src/lib/toast/toast.service.spec.ts +71 -0
- package/src/lib/toast/toast.service.ts +60 -0
- package/src/lib/toast/toast.variants.ts +38 -0
- package/src/lib/toast/toaster.component.spec.ts +38 -0
- package/src/lib/toast/toaster.component.ts +81 -0
- package/src/lib/toggle/index.ts +2 -0
- package/src/lib/toggle/toggle.directive.spec.ts +100 -0
- package/src/lib/toggle/toggle.directive.ts +61 -0
- package/src/lib/toggle/toggle.variants.ts +25 -0
- package/src/lib/tooltip/index.ts +2 -0
- package/src/lib/tooltip/tooltip.directive.spec.ts +113 -0
- package/src/lib/tooltip/tooltip.directive.ts +130 -0
- package/src/lib/tooltip/tooltip.variants.ts +20 -0
- package/src/lib/validator/index.ts +5 -0
- package/src/lib/validator/validator.directives.spec.ts +47 -0
- package/src/lib/validator/validator.directives.ts +50 -0
- package/src/styles/sonny-theme.css +33 -0
- package/types/sonny-ui-core.d.ts +1443 -13
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { cva } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
export const statusVariants = cva('inline-block rounded-full', {
|
|
4
|
+
variants: {
|
|
5
|
+
variant: {
|
|
6
|
+
default: 'bg-primary',
|
|
7
|
+
success: 'bg-green-500',
|
|
8
|
+
warning: 'bg-yellow-500',
|
|
9
|
+
error: 'bg-destructive',
|
|
10
|
+
info: 'bg-blue-500',
|
|
11
|
+
neutral: 'bg-muted-foreground',
|
|
12
|
+
},
|
|
13
|
+
size: {
|
|
14
|
+
xs: 'h-1.5 w-1.5',
|
|
15
|
+
sm: 'h-2 w-2',
|
|
16
|
+
md: 'h-2.5 w-2.5',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
variant: 'default',
|
|
21
|
+
size: 'md',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type StatusVariant = 'default' | 'success' | 'warning' | 'error' | 'info' | 'neutral';
|
|
26
|
+
export type StatusSize = 'xs' | 'sm' | 'md';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { TestBed, type ComponentFixture } from '@angular/core/testing';
|
|
3
|
+
import { SnyStepsDirective, SnyStepDirective, type StepStatus } from './steps.directives';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [SnyStepsDirective, SnyStepDirective],
|
|
8
|
+
template: `
|
|
9
|
+
<div snySteps>
|
|
10
|
+
<div snyStep status="completed">Register</div>
|
|
11
|
+
<div snyStep [status]="activeStatus()">Payment</div>
|
|
12
|
+
<div snyStep status="default">Confirm</div>
|
|
13
|
+
</div>
|
|
14
|
+
`,
|
|
15
|
+
})
|
|
16
|
+
class TestHostComponent {
|
|
17
|
+
activeStatus = signal<StepStatus>('active');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('SnyStepsDirective', () => {
|
|
21
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
22
|
+
|
|
23
|
+
beforeEach(async () => {
|
|
24
|
+
await TestBed.configureTestingModule({ imports: [TestHostComponent] }).compileComponents();
|
|
25
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
26
|
+
fixture.detectChanges();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should render with list role', () => {
|
|
30
|
+
const steps = fixture.nativeElement.querySelector('[snySteps]');
|
|
31
|
+
expect(steps.getAttribute('role')).toBe('list');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should render steps with listitem role', () => {
|
|
35
|
+
const items = fixture.nativeElement.querySelectorAll('[snyStep]');
|
|
36
|
+
expect(items.length).toBe(3);
|
|
37
|
+
items.forEach((item: HTMLElement) => {
|
|
38
|
+
expect(item.getAttribute('role')).toBe('listitem');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should mark active step with aria-current', () => {
|
|
43
|
+
const items = fixture.nativeElement.querySelectorAll('[snyStep]');
|
|
44
|
+
expect(items[1].getAttribute('aria-current')).toBe('step');
|
|
45
|
+
expect(items[0].getAttribute('aria-current')).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should apply completed styling', () => {
|
|
49
|
+
const items = fixture.nativeElement.querySelectorAll('[snyStep]');
|
|
50
|
+
expect(items[0].className).toContain('text-primary');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Directive, InjectionToken, computed, contentChildren, inject, input } from '@angular/core';
|
|
2
|
+
import { cn } from '../core/utils/cn';
|
|
3
|
+
|
|
4
|
+
export const SNY_STEPS = new InjectionToken<SnyStepsDirective>('SnySteps');
|
|
5
|
+
|
|
6
|
+
export type StepStatus = 'default' | 'active' | 'completed' | 'error';
|
|
7
|
+
export type StepsOrientation = 'horizontal' | 'vertical';
|
|
8
|
+
export type StepsSize = 'sm' | 'md' | 'lg';
|
|
9
|
+
|
|
10
|
+
@Directive({
|
|
11
|
+
selector: '[snyStep]',
|
|
12
|
+
host: {
|
|
13
|
+
'role': 'listitem',
|
|
14
|
+
'[class]': 'computedClass()',
|
|
15
|
+
'[attr.aria-current]': 'status() === "active" ? "step" : null',
|
|
16
|
+
},
|
|
17
|
+
})
|
|
18
|
+
export class SnyStepDirective {
|
|
19
|
+
readonly status = input<StepStatus>('default');
|
|
20
|
+
readonly icon = input<string>('');
|
|
21
|
+
readonly class = input<string>('');
|
|
22
|
+
|
|
23
|
+
protected readonly computedClass = computed(() => {
|
|
24
|
+
const s = this.status();
|
|
25
|
+
const base = 'flex items-center gap-2';
|
|
26
|
+
|
|
27
|
+
// Step indicator via before: pseudo-element
|
|
28
|
+
const indicatorBase = 'before:flex before:items-center before:justify-center before:w-8 before:h-8 before:rounded-full before:border-2 before:text-xs before:shrink-0';
|
|
29
|
+
|
|
30
|
+
const statusIndicator =
|
|
31
|
+
s === 'completed' ? 'before:bg-primary before:border-primary before:text-primary-foreground before:content-["✓"]' :
|
|
32
|
+
s === 'active' ? 'before:border-primary before:text-primary before:content-["●"]' :
|
|
33
|
+
s === 'error' ? 'before:border-destructive before:text-destructive before:content-["!"]' :
|
|
34
|
+
'before:border-muted-foreground/30 before:text-muted-foreground before:content-["○"]';
|
|
35
|
+
|
|
36
|
+
const statusClass =
|
|
37
|
+
s === 'completed' ? 'text-primary' :
|
|
38
|
+
s === 'active' ? 'text-primary font-medium' :
|
|
39
|
+
s === 'error' ? 'text-destructive' :
|
|
40
|
+
'text-muted-foreground';
|
|
41
|
+
|
|
42
|
+
return cn(base, indicatorBase, statusIndicator, statusClass, this.class());
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@Directive({
|
|
47
|
+
selector: '[snySteps]',
|
|
48
|
+
exportAs: 'snySteps',
|
|
49
|
+
providers: [{ provide: SNY_STEPS, useExisting: SnyStepsDirective }],
|
|
50
|
+
host: {
|
|
51
|
+
'role': 'list',
|
|
52
|
+
'aria-label': 'Progress steps',
|
|
53
|
+
'[class]': 'computedClass()',
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
export class SnyStepsDirective {
|
|
57
|
+
readonly orientation = input<StepsOrientation>('horizontal');
|
|
58
|
+
readonly size = input<StepsSize>('md');
|
|
59
|
+
readonly class = input<string>('');
|
|
60
|
+
|
|
61
|
+
readonly steps = contentChildren(SnyStepDirective);
|
|
62
|
+
|
|
63
|
+
readonly activeIndex = computed(() => {
|
|
64
|
+
const s = this.steps();
|
|
65
|
+
return s.findIndex((step) => step.status() === 'active');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
protected readonly computedClass = computed(() => {
|
|
69
|
+
const o = this.orientation();
|
|
70
|
+
const s = this.size();
|
|
71
|
+
const base = 'flex';
|
|
72
|
+
const orientationClass = o === 'horizontal'
|
|
73
|
+
? 'flex-row items-center gap-2 [&>*+*]:before:content-[""] [&>*+*]:before:flex-1 [&>*+*]:before:h-px [&>*+*]:before:bg-border [&>*+*]:before:mx-2 [&>*+*]:before:min-w-[2rem]'
|
|
74
|
+
: 'flex-col gap-2 [&>*+*]:before:content-[""] [&>*+*]:before:w-px [&>*+*]:before:h-4 [&>*+*]:before:bg-border [&>*+*]:before:ml-4';
|
|
75
|
+
const sizeClass = s === 'sm' ? 'text-xs' : s === 'lg' ? 'text-base' : 'text-sm';
|
|
76
|
+
return cn(base, orientationClass, sizeClass, this.class());
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
|
3
|
+
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
|
4
|
+
import { SnySwitchComponent } from './switch.component';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
standalone: true,
|
|
8
|
+
imports: [SnySwitchComponent],
|
|
9
|
+
template: `<sny-switch [(checked)]="checked" [disabled]="disabled()" />`,
|
|
10
|
+
})
|
|
11
|
+
class TestHostComponent {
|
|
12
|
+
checked = signal(false);
|
|
13
|
+
disabled = signal(false);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('SnySwitchComponent', () => {
|
|
17
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
18
|
+
let button: HTMLButtonElement;
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
await TestBed.configureTestingModule({
|
|
22
|
+
imports: [TestHostComponent],
|
|
23
|
+
}).compileComponents();
|
|
24
|
+
|
|
25
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
26
|
+
fixture.detectChanges();
|
|
27
|
+
button = fixture.nativeElement.querySelector('button');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should have switch role', () => {
|
|
31
|
+
expect(button.getAttribute('role')).toBe('switch');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should be unchecked by default', () => {
|
|
35
|
+
expect(button.getAttribute('aria-checked')).toBe('false');
|
|
36
|
+
expect(button.className).toContain('bg-input');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should toggle on click', () => {
|
|
40
|
+
button.click();
|
|
41
|
+
fixture.detectChanges();
|
|
42
|
+
expect(button.getAttribute('aria-checked')).toBe('true');
|
|
43
|
+
expect(button.className).toContain('bg-primary');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should not toggle when disabled', () => {
|
|
47
|
+
fixture.componentInstance.disabled.set(true);
|
|
48
|
+
fixture.detectChanges();
|
|
49
|
+
expect(button.disabled).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
@Component({
|
|
54
|
+
standalone: true,
|
|
55
|
+
imports: [ReactiveFormsModule, SnySwitchComponent],
|
|
56
|
+
template: `<sny-switch [formControl]="ctrl" />`,
|
|
57
|
+
})
|
|
58
|
+
class ReactiveFormHost {
|
|
59
|
+
ctrl = new FormControl(false);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe('SnySwitchComponent — Reactive Forms', () => {
|
|
63
|
+
let fixture: ComponentFixture<ReactiveFormHost>;
|
|
64
|
+
let button: HTMLButtonElement;
|
|
65
|
+
|
|
66
|
+
beforeEach(async () => {
|
|
67
|
+
await TestBed.configureTestingModule({
|
|
68
|
+
imports: [ReactiveFormHost],
|
|
69
|
+
}).compileComponents();
|
|
70
|
+
fixture = TestBed.createComponent(ReactiveFormHost);
|
|
71
|
+
fixture.detectChanges();
|
|
72
|
+
button = fixture.nativeElement.querySelector('button');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should update view when FormControl value changes (writeValue)', () => {
|
|
76
|
+
fixture.componentInstance.ctrl.setValue(true);
|
|
77
|
+
fixture.detectChanges();
|
|
78
|
+
expect(button.getAttribute('aria-checked')).toBe('true');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should update FormControl when user interacts (onChange)', () => {
|
|
82
|
+
button.click();
|
|
83
|
+
fixture.detectChanges();
|
|
84
|
+
expect(fixture.componentInstance.ctrl.value).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should disable via FormControl.disable() (setDisabledState)', () => {
|
|
88
|
+
fixture.componentInstance.ctrl.disable();
|
|
89
|
+
fixture.detectChanges();
|
|
90
|
+
expect(button.disabled).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should mark as touched on blur (onTouched)', () => {
|
|
94
|
+
expect(fixture.componentInstance.ctrl.touched).toBe(false);
|
|
95
|
+
button.dispatchEvent(new Event('blur'));
|
|
96
|
+
expect(fixture.componentInstance.ctrl.touched).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, forwardRef, input, model, signal } from '@angular/core';
|
|
2
|
+
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
3
|
+
import { cn } from '../core/utils/cn';
|
|
4
|
+
import { switchTrackVariants, switchThumbSize, switchThumbTranslate, type SwitchSize } from './switch.variants';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'sny-switch',
|
|
8
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
9
|
+
host: { class: 'inline-flex' },
|
|
10
|
+
providers: [
|
|
11
|
+
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnySwitchComponent), multi: true },
|
|
12
|
+
],
|
|
13
|
+
template: `
|
|
14
|
+
<button
|
|
15
|
+
type="button"
|
|
16
|
+
role="switch"
|
|
17
|
+
[attr.aria-checked]="checked()"
|
|
18
|
+
[disabled]="isDisabled()"
|
|
19
|
+
[class]="trackClass()"
|
|
20
|
+
(click)="toggle()"
|
|
21
|
+
(blur)="onTouched()"
|
|
22
|
+
>
|
|
23
|
+
<span [class]="thumbClass()"></span>
|
|
24
|
+
</button>
|
|
25
|
+
`,
|
|
26
|
+
})
|
|
27
|
+
export class SnySwitchComponent implements ControlValueAccessor {
|
|
28
|
+
readonly checked = model(false);
|
|
29
|
+
readonly disabled = input(false);
|
|
30
|
+
readonly size = input<SwitchSize>('md');
|
|
31
|
+
readonly class = input<string>('');
|
|
32
|
+
|
|
33
|
+
private readonly _disabledByCva = signal(false);
|
|
34
|
+
protected readonly isDisabled = computed(() => this.disabled() || this._disabledByCva());
|
|
35
|
+
|
|
36
|
+
private _onChange: (value: boolean) => void = () => {};
|
|
37
|
+
protected onTouched: () => void = () => {};
|
|
38
|
+
|
|
39
|
+
writeValue(val: boolean): void {
|
|
40
|
+
this.checked.set(val ?? false);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
registerOnChange(fn: (value: boolean) => void): void {
|
|
44
|
+
this._onChange = fn;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
registerOnTouched(fn: () => void): void {
|
|
48
|
+
this.onTouched = fn;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setDisabledState(isDisabled: boolean): void {
|
|
52
|
+
this._disabledByCva.set(isDisabled);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
toggle(): void {
|
|
56
|
+
const newVal = !this.checked();
|
|
57
|
+
this.checked.set(newVal);
|
|
58
|
+
this._onChange(newVal);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected readonly trackClass = computed(() =>
|
|
62
|
+
cn(
|
|
63
|
+
switchTrackVariants({ size: this.size() }),
|
|
64
|
+
this.checked() ? 'bg-primary' : 'bg-input',
|
|
65
|
+
this.class()
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
protected readonly thumbClass = computed(() =>
|
|
70
|
+
cn(
|
|
71
|
+
'pointer-events-none block rounded-full bg-background shadow-lg ring-0 transition-transform',
|
|
72
|
+
switchThumbSize[this.size()],
|
|
73
|
+
this.checked() ? switchThumbTranslate[this.size()] : 'translate-x-0'
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cva } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
export const switchTrackVariants = cva(
|
|
4
|
+
'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
5
|
+
{
|
|
6
|
+
variants: {
|
|
7
|
+
size: {
|
|
8
|
+
sm: 'h-5 w-9',
|
|
9
|
+
md: 'h-6 w-11',
|
|
10
|
+
lg: 'h-7 w-14',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
defaultVariants: {
|
|
14
|
+
size: 'md',
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const switchThumbSize: Record<string, string> = {
|
|
20
|
+
sm: 'h-4 w-4',
|
|
21
|
+
md: 'h-5 w-5',
|
|
22
|
+
lg: 'h-6 w-6',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const switchThumbTranslate: Record<string, string> = {
|
|
26
|
+
sm: 'translate-x-4',
|
|
27
|
+
md: 'translate-x-5',
|
|
28
|
+
lg: 'translate-x-7',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type SwitchSize = 'sm' | 'md' | 'lg';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export {
|
|
2
|
+
SnyTableDirective,
|
|
3
|
+
SnyTableHeaderDirective,
|
|
4
|
+
SnyTableBodyDirective,
|
|
5
|
+
SnyTableRowDirective,
|
|
6
|
+
SnyTableHeadDirective,
|
|
7
|
+
SnyTableCellDirective,
|
|
8
|
+
SnyTableFooterDirective,
|
|
9
|
+
SnyTableCaptionDirective,
|
|
10
|
+
SNY_TABLE,
|
|
11
|
+
} from './table.directives';
|
|
12
|
+
export { tableVariants, tableCellVariants, type TableVariant, type TableDensity } from './table.variants';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
|
3
|
+
import {
|
|
4
|
+
SnyTableDirective,
|
|
5
|
+
SnyTableHeaderDirective,
|
|
6
|
+
SnyTableBodyDirective,
|
|
7
|
+
SnyTableRowDirective,
|
|
8
|
+
SnyTableHeadDirective,
|
|
9
|
+
SnyTableCellDirective,
|
|
10
|
+
SnyTableFooterDirective,
|
|
11
|
+
SnyTableCaptionDirective,
|
|
12
|
+
} from './table.directives';
|
|
13
|
+
import type { TableVariant, TableDensity } from './table.variants';
|
|
14
|
+
|
|
15
|
+
@Component({
|
|
16
|
+
standalone: true,
|
|
17
|
+
imports: [
|
|
18
|
+
SnyTableDirective, SnyTableHeaderDirective, SnyTableBodyDirective,
|
|
19
|
+
SnyTableRowDirective, SnyTableHeadDirective, SnyTableCellDirective,
|
|
20
|
+
SnyTableFooterDirective, SnyTableCaptionDirective,
|
|
21
|
+
],
|
|
22
|
+
template: `
|
|
23
|
+
<table snyTable [variant]="variant()" [density]="density()" [hoverable]="hoverable()" [stickyHeader]="stickyHeader()">
|
|
24
|
+
<caption snyTableCaption>Test table</caption>
|
|
25
|
+
<thead snyTableHeader>
|
|
26
|
+
<tr snyTableRow>
|
|
27
|
+
<th snyTableHead>Name</th>
|
|
28
|
+
<th snyTableHead>Value</th>
|
|
29
|
+
</tr>
|
|
30
|
+
</thead>
|
|
31
|
+
<tbody snyTableBody>
|
|
32
|
+
<tr snyTableRow>
|
|
33
|
+
<td snyTableCell>A</td>
|
|
34
|
+
<td snyTableCell>1</td>
|
|
35
|
+
</tr>
|
|
36
|
+
</tbody>
|
|
37
|
+
<tfoot snyTableFooter>
|
|
38
|
+
<tr snyTableRow>
|
|
39
|
+
<td snyTableCell>Total</td>
|
|
40
|
+
<td snyTableCell>1</td>
|
|
41
|
+
</tr>
|
|
42
|
+
</tfoot>
|
|
43
|
+
</table>
|
|
44
|
+
`,
|
|
45
|
+
})
|
|
46
|
+
class TestHostComponent {
|
|
47
|
+
variant = signal<TableVariant>('default');
|
|
48
|
+
density = signal<TableDensity>('normal');
|
|
49
|
+
hoverable = signal(false);
|
|
50
|
+
stickyHeader = signal(false);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('Table Directives', () => {
|
|
54
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
55
|
+
let table: HTMLTableElement;
|
|
56
|
+
|
|
57
|
+
beforeEach(async () => {
|
|
58
|
+
await TestBed.configureTestingModule({
|
|
59
|
+
imports: [TestHostComponent],
|
|
60
|
+
}).compileComponents();
|
|
61
|
+
|
|
62
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
63
|
+
fixture.detectChanges();
|
|
64
|
+
table = fixture.nativeElement.querySelector('table');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should apply base table classes', () => {
|
|
68
|
+
expect(table.className).toContain('w-full');
|
|
69
|
+
expect(table.className).toContain('caption-bottom');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should apply striped variant', () => {
|
|
73
|
+
fixture.componentInstance.variant.set('striped');
|
|
74
|
+
fixture.detectChanges();
|
|
75
|
+
expect(table.className).toContain('nth-child');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should apply bordered variant', () => {
|
|
79
|
+
fixture.componentInstance.variant.set('bordered');
|
|
80
|
+
fixture.detectChanges();
|
|
81
|
+
expect(table.className).toContain('border');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should apply compact density to cells', () => {
|
|
85
|
+
fixture.componentInstance.density.set('compact');
|
|
86
|
+
fixture.detectChanges();
|
|
87
|
+
const th = fixture.nativeElement.querySelector('th');
|
|
88
|
+
expect(th.className).toContain('px-2');
|
|
89
|
+
expect(th.className).toContain('text-xs');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should apply hoverable to rows', () => {
|
|
93
|
+
fixture.componentInstance.hoverable.set(true);
|
|
94
|
+
fixture.detectChanges();
|
|
95
|
+
const row = fixture.nativeElement.querySelector('tbody tr');
|
|
96
|
+
expect(row.className).toContain('hover:bg-muted/50');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should apply sticky header', () => {
|
|
100
|
+
fixture.componentInstance.stickyHeader.set(true);
|
|
101
|
+
fixture.detectChanges();
|
|
102
|
+
const thead = fixture.nativeElement.querySelector('thead');
|
|
103
|
+
expect(thead.className).toContain('sticky');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should render caption', () => {
|
|
107
|
+
const caption = fixture.nativeElement.querySelector('caption');
|
|
108
|
+
expect(caption.className).toContain('text-muted-foreground');
|
|
109
|
+
expect(caption.textContent).toContain('Test table');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Directive, computed, inject, input, InjectionToken } from '@angular/core';
|
|
2
|
+
import { cn } from '../core/utils/cn';
|
|
3
|
+
import { tableVariants, tableCellVariants, type TableVariant, type TableDensity } from './table.variants';
|
|
4
|
+
|
|
5
|
+
export const SNY_TABLE = new InjectionToken<SnyTableDirective>('SnyTable');
|
|
6
|
+
|
|
7
|
+
@Directive({
|
|
8
|
+
selector: 'table[snyTable]',
|
|
9
|
+
providers: [{ provide: SNY_TABLE, useExisting: SnyTableDirective }],
|
|
10
|
+
host: { '[class]': 'computedClass()' },
|
|
11
|
+
})
|
|
12
|
+
export class SnyTableDirective {
|
|
13
|
+
readonly variant = input<TableVariant>('default');
|
|
14
|
+
readonly density = input<TableDensity>('normal');
|
|
15
|
+
readonly hoverable = input(false);
|
|
16
|
+
readonly stickyHeader = input(false);
|
|
17
|
+
readonly class = input<string>('');
|
|
18
|
+
|
|
19
|
+
protected readonly computedClass = computed(() =>
|
|
20
|
+
cn(tableVariants({ variant: this.variant() }), this.class())
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@Directive({
|
|
25
|
+
selector: 'thead[snyTableHeader]',
|
|
26
|
+
host: { '[class]': 'computedClass()' },
|
|
27
|
+
})
|
|
28
|
+
export class SnyTableHeaderDirective {
|
|
29
|
+
readonly class = input<string>('');
|
|
30
|
+
private readonly table = inject(SNY_TABLE, { optional: true });
|
|
31
|
+
|
|
32
|
+
protected readonly computedClass = computed(() =>
|
|
33
|
+
cn(
|
|
34
|
+
'[&_tr]:border-b',
|
|
35
|
+
this.table?.stickyHeader() ? 'sticky top-0 z-10 bg-background' : '',
|
|
36
|
+
this.class()
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Directive({
|
|
42
|
+
selector: 'tbody[snyTableBody]',
|
|
43
|
+
host: { '[class]': 'computedClass()' },
|
|
44
|
+
})
|
|
45
|
+
export class SnyTableBodyDirective {
|
|
46
|
+
readonly class = input<string>('');
|
|
47
|
+
|
|
48
|
+
protected readonly computedClass = computed(() =>
|
|
49
|
+
cn('[&_tr:last-child]:border-0', this.class())
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Directive({
|
|
54
|
+
selector: 'tr[snyTableRow]',
|
|
55
|
+
host: { '[class]': 'computedClass()' },
|
|
56
|
+
})
|
|
57
|
+
export class SnyTableRowDirective {
|
|
58
|
+
readonly class = input<string>('');
|
|
59
|
+
private readonly table = inject(SNY_TABLE, { optional: true });
|
|
60
|
+
|
|
61
|
+
protected readonly computedClass = computed(() =>
|
|
62
|
+
cn(
|
|
63
|
+
'border-b border-border transition-colors data-[state=selected]:bg-muted',
|
|
64
|
+
this.table?.hoverable() ? 'hover:bg-muted/50' : '',
|
|
65
|
+
this.class()
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Directive({
|
|
71
|
+
selector: 'th[snyTableHead]',
|
|
72
|
+
host: { '[class]': 'computedClass()' },
|
|
73
|
+
})
|
|
74
|
+
export class SnyTableHeadDirective {
|
|
75
|
+
readonly class = input<string>('');
|
|
76
|
+
private readonly table = inject(SNY_TABLE, { optional: true });
|
|
77
|
+
|
|
78
|
+
protected readonly computedClass = computed(() =>
|
|
79
|
+
cn(
|
|
80
|
+
'text-left font-medium text-muted-foreground [&[align=center]]:text-center [&[align=right]]:text-right',
|
|
81
|
+
tableCellVariants({ density: this.table?.density() ?? 'normal' }),
|
|
82
|
+
this.class()
|
|
83
|
+
)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@Directive({
|
|
88
|
+
selector: 'td[snyTableCell]',
|
|
89
|
+
host: { '[class]': 'computedClass()' },
|
|
90
|
+
})
|
|
91
|
+
export class SnyTableCellDirective {
|
|
92
|
+
readonly class = input<string>('');
|
|
93
|
+
private readonly table = inject(SNY_TABLE, { optional: true });
|
|
94
|
+
|
|
95
|
+
protected readonly computedClass = computed(() =>
|
|
96
|
+
cn(
|
|
97
|
+
'[&[align=center]]:text-center [&[align=right]]:text-right',
|
|
98
|
+
tableCellVariants({ density: this.table?.density() ?? 'normal' }),
|
|
99
|
+
this.class()
|
|
100
|
+
)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@Directive({
|
|
105
|
+
selector: 'tfoot[snyTableFooter]',
|
|
106
|
+
host: { '[class]': 'computedClass()' },
|
|
107
|
+
})
|
|
108
|
+
export class SnyTableFooterDirective {
|
|
109
|
+
readonly class = input<string>('');
|
|
110
|
+
|
|
111
|
+
protected readonly computedClass = computed(() =>
|
|
112
|
+
cn('border-t border-border font-medium [&>tr]:last:border-b-0', this.class())
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@Directive({
|
|
117
|
+
selector: 'caption[snyTableCaption]',
|
|
118
|
+
host: { '[class]': 'computedClass()' },
|
|
119
|
+
})
|
|
120
|
+
export class SnyTableCaptionDirective {
|
|
121
|
+
readonly class = input<string>('');
|
|
122
|
+
|
|
123
|
+
protected readonly computedClass = computed(() =>
|
|
124
|
+
cn('mt-4 text-sm text-muted-foreground', this.class())
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { cva } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
export const tableVariants = cva(
|
|
4
|
+
'w-full caption-bottom text-sm border-collapse',
|
|
5
|
+
{
|
|
6
|
+
variants: {
|
|
7
|
+
variant: {
|
|
8
|
+
default: '',
|
|
9
|
+
striped: '[&_tbody_tr:nth-child(even)]:bg-muted/50',
|
|
10
|
+
bordered: 'border border-border [&_th]:border [&_th]:border-border [&_td]:border [&_td]:border-border',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
defaultVariants: {
|
|
14
|
+
variant: 'default',
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const tableCellVariants = cva(
|
|
20
|
+
'',
|
|
21
|
+
{
|
|
22
|
+
variants: {
|
|
23
|
+
density: {
|
|
24
|
+
compact: 'px-2 py-1 text-xs',
|
|
25
|
+
normal: 'px-4 py-3 text-sm',
|
|
26
|
+
comfortable: 'px-6 py-4 text-base',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
density: 'normal',
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export type TableVariant = 'default' | 'striped' | 'bordered';
|
|
36
|
+
export type TableDensity = 'compact' | 'normal' | 'comfortable';
|