@tony-ui-library/core 0.0.1
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 +188 -0
- package/fesm2022/tony-ui-library-core.mjs +8756 -0
- package/fesm2022/tony-ui-library-core.mjs.map +1 -0
- package/package.json +55 -0
- package/schematics/collection.json +16 -0
- package/schematics/ng-add/index.d.ts +5 -0
- package/schematics/ng-add/index.js +53 -0
- package/schematics/ng-add/schema.json +19 -0
- package/schematics/ng-generate/component/index.d.ts +9 -0
- package/schematics/ng-generate/component/index.js +439 -0
- package/schematics/ng-generate/component/schema.json +32 -0
- 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 +622 -0
- package/src/lib/data-table/data-table.directives.ts +31 -0
- package/src/lib/data-table/data-table.types.ts +26 -0
- package/src/lib/data-table/index.ts +14 -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 +171 -0
- package/types/tony-ui-library-core.d.ts +2179 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Directive, computed, input } from '@angular/core';
|
|
2
|
+
import { cn } from '../core/utils/cn';
|
|
3
|
+
import { inputVariants, type InputVariant, type InputSize } from './input.variants';
|
|
4
|
+
|
|
5
|
+
@Directive({
|
|
6
|
+
selector: 'input[tonInput], textarea[tonInput]',
|
|
7
|
+
host: {
|
|
8
|
+
'[class]': 'computedClass()',
|
|
9
|
+
'[attr.aria-invalid]': 'variant() === "error" || null',
|
|
10
|
+
'[attr.aria-describedby]': 'ariaDescribedBy() || null',
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
export class TonInputDirective {
|
|
14
|
+
readonly variant = input<InputVariant>('default');
|
|
15
|
+
readonly inputSize = input<InputSize>('md');
|
|
16
|
+
readonly class = input<string>('');
|
|
17
|
+
readonly ariaDescribedBy = input<string>('');
|
|
18
|
+
|
|
19
|
+
protected readonly computedClass = computed(() =>
|
|
20
|
+
cn(
|
|
21
|
+
inputVariants({ variant: this.variant(), inputSize: this.inputSize() }),
|
|
22
|
+
this.class()
|
|
23
|
+
)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { cva } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
export const inputVariants = cva(
|
|
4
|
+
'flex w-full rounded-sm border bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground 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
|
+
variant: {
|
|
8
|
+
default: 'border-input',
|
|
9
|
+
error: 'border-destructive focus-visible:ring-destructive',
|
|
10
|
+
success: 'border-green-500 focus-visible:ring-green-500',
|
|
11
|
+
},
|
|
12
|
+
inputSize: {
|
|
13
|
+
sm: 'h-9 text-xs',
|
|
14
|
+
md: 'h-10',
|
|
15
|
+
lg: 'h-11 text-base',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
variant: 'default',
|
|
20
|
+
inputSize: 'md',
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const labelVariants = cva(
|
|
26
|
+
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
|
27
|
+
{
|
|
28
|
+
variants: {
|
|
29
|
+
variant: {
|
|
30
|
+
default: '',
|
|
31
|
+
error: 'text-destructive',
|
|
32
|
+
success: 'text-green-600',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
variant: 'default',
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export type InputVariant = 'default' | 'error' | 'success';
|
|
42
|
+
export type InputSize = 'sm' | 'md' | 'lg';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Directive, computed, input } from '@angular/core';
|
|
2
|
+
import { cn } from '../core/utils/cn';
|
|
3
|
+
import { labelVariants, type InputVariant } from './input.variants';
|
|
4
|
+
|
|
5
|
+
@Directive({
|
|
6
|
+
selector: 'label[tonLabel]',
|
|
7
|
+
host: { '[class]': 'computedClass()' },
|
|
8
|
+
})
|
|
9
|
+
export class TonLabelDirective {
|
|
10
|
+
readonly variant = input<InputVariant>('default');
|
|
11
|
+
readonly class = input<string>('');
|
|
12
|
+
|
|
13
|
+
protected readonly computedClass = computed(() =>
|
|
14
|
+
cn(labelVariants({ variant: this.variant() }), this.class())
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { TestBed, type ComponentFixture } from '@angular/core/testing';
|
|
3
|
+
import { TonKbdDirective } from './kbd.directive';
|
|
4
|
+
import type { KbdSize } from './kbd.variants';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
standalone: true,
|
|
8
|
+
imports: [TonKbdDirective],
|
|
9
|
+
template: `<kbd tonKbd [size]="size()">Ctrl+K</kbd>`,
|
|
10
|
+
})
|
|
11
|
+
class TestHostComponent {
|
|
12
|
+
size = signal<KbdSize>('md');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('TonKbdDirective', () => {
|
|
16
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
17
|
+
let el: HTMLElement;
|
|
18
|
+
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
await TestBed.configureTestingModule({ imports: [TestHostComponent] }).compileComponents();
|
|
21
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
22
|
+
fixture.detectChanges();
|
|
23
|
+
el = fixture.nativeElement.querySelector('kbd');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should apply default size classes', () => {
|
|
27
|
+
expect(el.className).toContain('font-mono');
|
|
28
|
+
expect(el.className).toContain('h-6');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should apply sm size', () => {
|
|
32
|
+
fixture.componentInstance.size.set('sm');
|
|
33
|
+
fixture.detectChanges();
|
|
34
|
+
expect(el.className).toContain('h-5');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should apply lg size', () => {
|
|
38
|
+
fixture.componentInstance.size.set('lg');
|
|
39
|
+
fixture.detectChanges();
|
|
40
|
+
expect(el.className).toContain('h-7');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Directive, computed, input } from '@angular/core';
|
|
2
|
+
import { cn } from '../core/utils/cn';
|
|
3
|
+
import { kbdVariants, type KbdSize } from './kbd.variants';
|
|
4
|
+
|
|
5
|
+
@Directive({
|
|
6
|
+
selector: 'kbd[tonKbd]',
|
|
7
|
+
host: {
|
|
8
|
+
'[class]': 'computedClass()',
|
|
9
|
+
},
|
|
10
|
+
})
|
|
11
|
+
export class TonKbdDirective {
|
|
12
|
+
readonly size = input<KbdSize>('md');
|
|
13
|
+
readonly class = input<string>('');
|
|
14
|
+
|
|
15
|
+
protected readonly computedClass = computed(() =>
|
|
16
|
+
cn(kbdVariants({ size: this.size() }), this.class())
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { cva } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
export const kbdVariants = cva(
|
|
4
|
+
'pointer-events-none inline-flex select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono font-medium text-muted-foreground',
|
|
5
|
+
{
|
|
6
|
+
variants: {
|
|
7
|
+
size: {
|
|
8
|
+
sm: 'h-5 text-[10px]',
|
|
9
|
+
md: 'h-6 text-xs',
|
|
10
|
+
lg: 'h-7 text-sm',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
defaultVariants: {
|
|
14
|
+
size: 'md',
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export type KbdSize = 'sm' | 'md' | 'lg';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { TestBed, type ComponentFixture } from '@angular/core/testing';
|
|
3
|
+
import { TonLinkDirective } from './link.directive';
|
|
4
|
+
import type { LinkVariant } from './link.variants';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
standalone: true,
|
|
8
|
+
imports: [TonLinkDirective],
|
|
9
|
+
template: `<a tonLink [variant]="variant()" href="#">Click me</a>`,
|
|
10
|
+
})
|
|
11
|
+
class TestHostComponent {
|
|
12
|
+
variant = signal<LinkVariant>('default');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('TonLinkDirective', () => {
|
|
16
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
17
|
+
let el: HTMLElement;
|
|
18
|
+
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
await TestBed.configureTestingModule({ imports: [TestHostComponent] }).compileComponents();
|
|
21
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
22
|
+
fixture.detectChanges();
|
|
23
|
+
el = fixture.nativeElement.querySelector('a');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should apply default classes', () => {
|
|
27
|
+
expect(el.className).toContain('underline');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should apply primary variant', () => {
|
|
31
|
+
fixture.componentInstance.variant.set('primary');
|
|
32
|
+
fixture.detectChanges();
|
|
33
|
+
expect(el.className).toContain('text-primary');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should apply hover variant', () => {
|
|
37
|
+
fixture.componentInstance.variant.set('hover');
|
|
38
|
+
fixture.detectChanges();
|
|
39
|
+
expect(el.className).toContain('no-underline');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Directive, computed, input } from '@angular/core';
|
|
2
|
+
import { cn } from '../core/utils/cn';
|
|
3
|
+
import { linkVariants, type LinkVariant } from './link.variants';
|
|
4
|
+
|
|
5
|
+
@Directive({
|
|
6
|
+
selector: 'a[tonLink]',
|
|
7
|
+
host: {
|
|
8
|
+
'[class]': 'computedClass()',
|
|
9
|
+
},
|
|
10
|
+
})
|
|
11
|
+
export class TonLinkDirective {
|
|
12
|
+
readonly variant = input<LinkVariant>('default');
|
|
13
|
+
readonly class = input<string>('');
|
|
14
|
+
|
|
15
|
+
protected readonly computedClass = computed(() =>
|
|
16
|
+
cn(linkVariants({ variant: this.variant() }), this.class())
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cva } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
export const linkVariants = cva(
|
|
4
|
+
'inline-flex items-center gap-1 underline-offset-4 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded',
|
|
5
|
+
{
|
|
6
|
+
variants: {
|
|
7
|
+
variant: {
|
|
8
|
+
default: 'text-foreground underline hover:text-foreground/80',
|
|
9
|
+
primary: 'text-primary underline hover:text-primary/80',
|
|
10
|
+
secondary: 'text-muted-foreground underline hover:text-foreground',
|
|
11
|
+
hover: 'text-foreground no-underline hover:underline',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: {
|
|
15
|
+
variant: 'default',
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export type LinkVariant = 'default' | 'primary' | 'secondary' | 'hover';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { TestBed, type ComponentFixture } from '@angular/core/testing';
|
|
3
|
+
import { TonListDirective, TonListItemDirective, TonListItemContentDirective, type ListVariant } from './list.directives';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [TonListDirective, TonListItemDirective, TonListItemContentDirective],
|
|
8
|
+
template: `
|
|
9
|
+
<div tonList [variant]="variant()">
|
|
10
|
+
<div tonListItem [active]="active()" [disabled]="disabled()">
|
|
11
|
+
<div tonListItemContent>Item 1</div>
|
|
12
|
+
</div>
|
|
13
|
+
<div tonListItem>
|
|
14
|
+
<div tonListItemContent>Item 2</div>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
`,
|
|
18
|
+
})
|
|
19
|
+
class TestHostComponent {
|
|
20
|
+
variant = signal<ListVariant>('default');
|
|
21
|
+
active = signal(false);
|
|
22
|
+
disabled = signal(false);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('TonListDirective', () => {
|
|
26
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
27
|
+
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
await TestBed.configureTestingModule({ imports: [TestHostComponent] }).compileComponents();
|
|
30
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
31
|
+
fixture.detectChanges();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should render with list role', () => {
|
|
35
|
+
const list = fixture.nativeElement.querySelector('[tonList]');
|
|
36
|
+
expect(list.getAttribute('role')).toBe('list');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should render items with listitem role', () => {
|
|
40
|
+
const items = fixture.nativeElement.querySelectorAll('[tonListItem]');
|
|
41
|
+
expect(items.length).toBe(2);
|
|
42
|
+
expect(items[0].getAttribute('role')).toBe('listitem');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should apply bordered variant', () => {
|
|
46
|
+
fixture.componentInstance.variant.set('bordered');
|
|
47
|
+
fixture.detectChanges();
|
|
48
|
+
const list = fixture.nativeElement.querySelector('[tonList]');
|
|
49
|
+
expect(list.className).toContain('border');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should apply active state', () => {
|
|
53
|
+
fixture.componentInstance.active.set(true);
|
|
54
|
+
fixture.detectChanges();
|
|
55
|
+
const item = fixture.nativeElement.querySelector('[tonListItem]');
|
|
56
|
+
expect(item.className).toContain('bg-accent');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should set aria-disabled', () => {
|
|
60
|
+
fixture.componentInstance.disabled.set(true);
|
|
61
|
+
fixture.detectChanges();
|
|
62
|
+
const item = fixture.nativeElement.querySelector('[tonListItem]');
|
|
63
|
+
expect(item.getAttribute('aria-disabled')).toBe('true');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Directive, computed, input } from '@angular/core';
|
|
2
|
+
import { cn } from '../core/utils/cn';
|
|
3
|
+
|
|
4
|
+
export type ListVariant = 'default' | 'bordered' | 'hover';
|
|
5
|
+
|
|
6
|
+
@Directive({
|
|
7
|
+
selector: '[tonList]',
|
|
8
|
+
host: {
|
|
9
|
+
'role': 'list',
|
|
10
|
+
'[class]': 'computedClass()',
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
export class TonListDirective {
|
|
14
|
+
readonly variant = input<ListVariant>('default');
|
|
15
|
+
readonly class = input<string>('');
|
|
16
|
+
|
|
17
|
+
protected readonly computedClass = computed(() => {
|
|
18
|
+
const v = this.variant();
|
|
19
|
+
const variantClass =
|
|
20
|
+
v === 'bordered' ? 'divide-y divide-border border rounded-md' :
|
|
21
|
+
v === 'hover' ? '[&>[tonListItem]]:hover:bg-accent' :
|
|
22
|
+
'';
|
|
23
|
+
return cn('flex flex-col', variantClass, this.class());
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Directive({
|
|
28
|
+
selector: '[tonListItem]',
|
|
29
|
+
host: {
|
|
30
|
+
'role': 'listitem',
|
|
31
|
+
'[class]': 'computedClass()',
|
|
32
|
+
'[attr.aria-disabled]': 'disabled() || null',
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
export class TonListItemDirective {
|
|
36
|
+
readonly active = input(false);
|
|
37
|
+
readonly disabled = input(false);
|
|
38
|
+
readonly class = input<string>('');
|
|
39
|
+
|
|
40
|
+
protected readonly computedClass = computed(() =>
|
|
41
|
+
cn(
|
|
42
|
+
'flex items-center gap-3 px-3 py-2 transition-colors',
|
|
43
|
+
this.active() && 'bg-accent text-accent-foreground',
|
|
44
|
+
this.disabled() && 'opacity-50 pointer-events-none',
|
|
45
|
+
this.class()
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Directive({
|
|
51
|
+
selector: '[tonListItemIcon]',
|
|
52
|
+
host: { '[class]': 'computedClass()' },
|
|
53
|
+
})
|
|
54
|
+
export class TonListItemIconDirective {
|
|
55
|
+
readonly class = input<string>('');
|
|
56
|
+
protected readonly computedClass = computed(() =>
|
|
57
|
+
cn('flex-shrink-0 text-muted-foreground', this.class())
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Directive({
|
|
62
|
+
selector: '[tonListItemContent]',
|
|
63
|
+
host: { '[class]': 'computedClass()' },
|
|
64
|
+
})
|
|
65
|
+
export class TonListItemContentDirective {
|
|
66
|
+
readonly class = input<string>('');
|
|
67
|
+
protected readonly computedClass = computed(() =>
|
|
68
|
+
cn('flex-1 min-w-0', this.class())
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Directive({
|
|
73
|
+
selector: '[tonListItemAction]',
|
|
74
|
+
host: { '[class]': 'computedClass()' },
|
|
75
|
+
})
|
|
76
|
+
export class TonListItemActionDirective {
|
|
77
|
+
readonly class = input<string>('');
|
|
78
|
+
protected readonly computedClass = computed(() =>
|
|
79
|
+
cn('flex-shrink-0', this.class())
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
|
3
|
+
import { TonLoaderComponent } from './loader.component';
|
|
4
|
+
import type { LoaderVariant, LoaderSize } from './loader.variants';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
standalone: true,
|
|
8
|
+
imports: [TonLoaderComponent],
|
|
9
|
+
template: `<ton-loader [variant]="variant()" [size]="size()" />`,
|
|
10
|
+
})
|
|
11
|
+
class TestHostComponent {
|
|
12
|
+
variant = signal<LoaderVariant>('spinner');
|
|
13
|
+
size = signal<LoaderSize>('md');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('TonLoaderComponent', () => {
|
|
17
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
18
|
+
let el: HTMLElement;
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
await TestBed.configureTestingModule({
|
|
22
|
+
imports: [TestHostComponent],
|
|
23
|
+
}).compileComponents();
|
|
24
|
+
|
|
25
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
26
|
+
fixture.detectChanges();
|
|
27
|
+
el = fixture.nativeElement.querySelector('ton-loader');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should have status role', () => {
|
|
31
|
+
expect(el.getAttribute('role')).toBe('status');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should render spinner by default', () => {
|
|
35
|
+
expect(el.querySelector('svg')).toBeTruthy();
|
|
36
|
+
expect(el.className).toContain('h-6');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should render dots variant', () => {
|
|
40
|
+
fixture.componentInstance.variant.set('dots');
|
|
41
|
+
fixture.detectChanges();
|
|
42
|
+
const dots = el.querySelectorAll('.rounded-full');
|
|
43
|
+
expect(dots.length).toBe(3);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should render bars variant', () => {
|
|
47
|
+
fixture.componentInstance.variant.set('bars');
|
|
48
|
+
fixture.detectChanges();
|
|
49
|
+
const bars = el.querySelectorAll('.rounded-sm');
|
|
50
|
+
expect(bars.length).toBe(4);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should apply lg size', () => {
|
|
54
|
+
fixture.componentInstance.size.set('lg');
|
|
55
|
+
fixture.detectChanges();
|
|
56
|
+
expect(el.className).toContain('h-8');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
|
2
|
+
import { cn } from '../core/utils/cn';
|
|
3
|
+
import { loaderVariants, type LoaderSize, type LoaderVariant } from './loader.variants';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
selector: 'ton-loader',
|
|
7
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
8
|
+
host: {
|
|
9
|
+
'[class]': 'computedClass()',
|
|
10
|
+
role: 'status',
|
|
11
|
+
'[attr.aria-label]': '"Loading"',
|
|
12
|
+
},
|
|
13
|
+
template: `
|
|
14
|
+
@switch (variant()) {
|
|
15
|
+
@case ('spinner') {
|
|
16
|
+
<svg class="animate-spin h-full w-full" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
17
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
18
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
19
|
+
</svg>
|
|
20
|
+
}
|
|
21
|
+
@case ('dots') {
|
|
22
|
+
<span class="flex items-center gap-1">
|
|
23
|
+
<span class="h-1.5 w-1.5 rounded-full bg-current animate-bounce [animation-delay:-0.3s]"></span>
|
|
24
|
+
<span class="h-1.5 w-1.5 rounded-full bg-current animate-bounce [animation-delay:-0.15s]"></span>
|
|
25
|
+
<span class="h-1.5 w-1.5 rounded-full bg-current animate-bounce"></span>
|
|
26
|
+
</span>
|
|
27
|
+
}
|
|
28
|
+
@case ('bars') {
|
|
29
|
+
<span class="flex items-end gap-0.5 h-full">
|
|
30
|
+
<span class="w-1 bg-current animate-pulse rounded-sm [animation-delay:-0.3s]" style="height:60%"></span>
|
|
31
|
+
<span class="w-1 bg-current animate-pulse rounded-sm [animation-delay:-0.15s]" style="height:100%"></span>
|
|
32
|
+
<span class="w-1 bg-current animate-pulse rounded-sm" style="height:40%"></span>
|
|
33
|
+
<span class="w-1 bg-current animate-pulse rounded-sm [animation-delay:-0.2s]" style="height:80%"></span>
|
|
34
|
+
</span>
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
`,
|
|
38
|
+
})
|
|
39
|
+
export class TonLoaderComponent {
|
|
40
|
+
readonly variant = input<LoaderVariant>('spinner');
|
|
41
|
+
readonly size = input<LoaderSize>('md');
|
|
42
|
+
readonly class = input<string>('');
|
|
43
|
+
|
|
44
|
+
protected readonly computedClass = computed(() =>
|
|
45
|
+
cn(loaderVariants({ size: this.size() }), this.class())
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { cva } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
export const loaderVariants = cva(
|
|
4
|
+
'inline-flex items-center justify-center text-current',
|
|
5
|
+
{
|
|
6
|
+
variants: {
|
|
7
|
+
size: {
|
|
8
|
+
sm: 'h-4 w-4',
|
|
9
|
+
md: 'h-6 w-6',
|
|
10
|
+
lg: 'h-8 w-8',
|
|
11
|
+
xl: 'h-12 w-12',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: {
|
|
15
|
+
size: 'md',
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export type LoaderSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
21
|
+
export type LoaderVariant = 'spinner' | 'dots' | 'bars';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Observable } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
/** Structural interface for the subset of CDK DialogRef we consume. */
|
|
4
|
+
interface CdkDialogRefLike<R> {
|
|
5
|
+
close(result?: R): void;
|
|
6
|
+
readonly closed: Observable<R | undefined>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class TonDialogRef<R = unknown> {
|
|
10
|
+
constructor(private readonly cdkRef: CdkDialogRefLike<R>) {}
|
|
11
|
+
|
|
12
|
+
close(result?: R): void {
|
|
13
|
+
this.cdkRef.close(result);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get closed(): Observable<R | undefined> {
|
|
17
|
+
return this.cdkRef.closed;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Directive, computed, input, inject } from '@angular/core';
|
|
2
|
+
import { DialogRef } from '@angular/cdk/dialog';
|
|
3
|
+
import { cn } from '../core/utils/cn';
|
|
4
|
+
|
|
5
|
+
@Directive({
|
|
6
|
+
selector: '[tonDialogHeader]',
|
|
7
|
+
host: { '[class]': 'computedClass()' },
|
|
8
|
+
})
|
|
9
|
+
export class TonDialogHeaderDirective {
|
|
10
|
+
readonly class = input<string>('');
|
|
11
|
+
protected readonly computedClass = computed(() =>
|
|
12
|
+
cn('flex flex-col space-y-1.5 text-center sm:text-left px-6 pt-6 shrink-0', this.class())
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Directive({
|
|
17
|
+
selector: '[tonDialogTitle]',
|
|
18
|
+
host: { '[class]': 'computedClass()' },
|
|
19
|
+
})
|
|
20
|
+
export class TonDialogTitleDirective {
|
|
21
|
+
readonly class = input<string>('');
|
|
22
|
+
protected readonly computedClass = computed(() =>
|
|
23
|
+
cn('text-lg font-semibold leading-none tracking-tight', this.class())
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Directive({
|
|
28
|
+
selector: '[tonDialogDescription]',
|
|
29
|
+
host: { '[class]': 'computedClass()' },
|
|
30
|
+
})
|
|
31
|
+
export class TonDialogDescriptionDirective {
|
|
32
|
+
readonly class = input<string>('');
|
|
33
|
+
protected readonly computedClass = computed(() =>
|
|
34
|
+
cn('text-sm text-muted-foreground', this.class())
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Directive({
|
|
39
|
+
selector: '[tonDialogContent]',
|
|
40
|
+
host: { '[class]': 'computedClass()' },
|
|
41
|
+
})
|
|
42
|
+
export class TonDialogContentDirective {
|
|
43
|
+
readonly class = input<string>('');
|
|
44
|
+
protected readonly computedClass = computed(() =>
|
|
45
|
+
cn(
|
|
46
|
+
'flex-1 overflow-y-auto px-6 py-4',
|
|
47
|
+
this.class()
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@Directive({
|
|
53
|
+
selector: '[tonDialogFooter]',
|
|
54
|
+
host: { '[class]': 'computedClass()' },
|
|
55
|
+
})
|
|
56
|
+
export class TonDialogFooterDirective {
|
|
57
|
+
readonly class = input<string>('');
|
|
58
|
+
protected readonly computedClass = computed(() =>
|
|
59
|
+
cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 px-6 pb-6 shrink-0', this.class())
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Directive({
|
|
64
|
+
selector: '[tonDialogClose]',
|
|
65
|
+
host: {
|
|
66
|
+
'[class]': 'computedClass()',
|
|
67
|
+
'(click)': 'onClick()',
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
export class TonDialogCloseDirective {
|
|
71
|
+
readonly class = input<string>('');
|
|
72
|
+
protected readonly computedClass = computed(() =>
|
|
73
|
+
cn(
|
|
74
|
+
'absolute right-4 top-4 z-10 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none cursor-pointer',
|
|
75
|
+
this.class()
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
private readonly dialogRef = inject(DialogRef, { optional: true });
|
|
80
|
+
|
|
81
|
+
onClick(): void {
|
|
82
|
+
this.dialogRef?.close();
|
|
83
|
+
}
|
|
84
|
+
}
|