@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,81 @@
|
|
|
1
|
+
import { Directive, ElementRef, computed, inject, input } from '@angular/core';
|
|
2
|
+
import { cn } from '../core/utils/cn';
|
|
3
|
+
|
|
4
|
+
export type DockPosition = 'bottom' | 'top';
|
|
5
|
+
|
|
6
|
+
@Directive({
|
|
7
|
+
selector: '[tonDock]',
|
|
8
|
+
host: {
|
|
9
|
+
'role': 'toolbar',
|
|
10
|
+
'aria-label': 'Dock',
|
|
11
|
+
'[class]': 'computedClass()',
|
|
12
|
+
'(keydown)': 'onKeydown($event)',
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
export class TonDockDirective {
|
|
16
|
+
readonly position = input<DockPosition>('bottom');
|
|
17
|
+
readonly class = input<string>('');
|
|
18
|
+
|
|
19
|
+
private readonly elRef = inject(ElementRef);
|
|
20
|
+
|
|
21
|
+
protected readonly computedClass = computed(() =>
|
|
22
|
+
cn(
|
|
23
|
+
'fixed left-1/2 -translate-x-1/2 z-50 flex items-center gap-1 rounded-full border bg-background/80 backdrop-blur-sm px-3 py-2 shadow-lg',
|
|
24
|
+
this.position() === 'bottom' ? 'bottom-4' : 'top-4',
|
|
25
|
+
this.class()
|
|
26
|
+
)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
onKeydown(event: KeyboardEvent): void {
|
|
30
|
+
const items = Array.from(
|
|
31
|
+
(this.elRef.nativeElement as HTMLElement).querySelectorAll<HTMLElement>('[tonDockItem]')
|
|
32
|
+
);
|
|
33
|
+
if (items.length === 0) return;
|
|
34
|
+
|
|
35
|
+
const currentIndex = items.indexOf(document.activeElement as HTMLElement);
|
|
36
|
+
if (currentIndex === -1) return;
|
|
37
|
+
|
|
38
|
+
let nextIndex: number | null = null;
|
|
39
|
+
switch (event.key) {
|
|
40
|
+
case 'ArrowRight':
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
nextIndex = (currentIndex + 1) % items.length;
|
|
43
|
+
break;
|
|
44
|
+
case 'ArrowLeft':
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
nextIndex = (currentIndex - 1 + items.length) % items.length;
|
|
47
|
+
break;
|
|
48
|
+
case 'Home':
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
nextIndex = 0;
|
|
51
|
+
break;
|
|
52
|
+
case 'End':
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
nextIndex = items.length - 1;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
if (nextIndex !== null) {
|
|
58
|
+
items[nextIndex].focus();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Directive({
|
|
64
|
+
selector: '[tonDockItem]',
|
|
65
|
+
host: {
|
|
66
|
+
'[class]': 'computedClass()',
|
|
67
|
+
'[attr.tabindex]': 'active() ? 0 : -1',
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
export class TonDockItemDirective {
|
|
71
|
+
readonly active = input(false);
|
|
72
|
+
readonly class = input<string>('');
|
|
73
|
+
|
|
74
|
+
protected readonly computedClass = computed(() =>
|
|
75
|
+
cn(
|
|
76
|
+
'inline-flex items-center justify-center rounded-full p-2 transition-all hover:scale-110',
|
|
77
|
+
this.active() && 'bg-primary text-primary-foreground',
|
|
78
|
+
this.class()
|
|
79
|
+
)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TonDockDirective, TonDockItemDirective, type DockPosition } from './dock.directives';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Component, viewChild } from '@angular/core';
|
|
2
|
+
import { TestBed, type ComponentFixture } from '@angular/core/testing';
|
|
3
|
+
import { TonDrawerLayoutDirective, TonDrawerContentDirective, TonDrawerSideDirective } from './drawer.directives';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [TonDrawerLayoutDirective, TonDrawerContentDirective, TonDrawerSideDirective],
|
|
8
|
+
template: `
|
|
9
|
+
<div tonDrawerLayout #drawer="tonDrawerLayout">
|
|
10
|
+
<div tonDrawerSide>Sidebar</div>
|
|
11
|
+
<div tonDrawerContent>Main</div>
|
|
12
|
+
</div>
|
|
13
|
+
`,
|
|
14
|
+
})
|
|
15
|
+
class TestHostComponent {
|
|
16
|
+
drawer = viewChild(TonDrawerLayoutDirective);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('TonDrawerLayoutDirective', () => {
|
|
20
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
21
|
+
|
|
22
|
+
beforeEach(async () => {
|
|
23
|
+
await TestBed.configureTestingModule({ imports: [TestHostComponent] }).compileComponents();
|
|
24
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
25
|
+
fixture.detectChanges();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should be closed by default', () => {
|
|
29
|
+
const side = fixture.nativeElement.querySelector('[tonDrawerSide]');
|
|
30
|
+
expect(side.className).toContain('-translate-x-full');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should open on toggle', () => {
|
|
34
|
+
fixture.componentInstance.drawer()!.toggle();
|
|
35
|
+
fixture.detectChanges();
|
|
36
|
+
const side = fixture.nativeElement.querySelector('[tonDrawerSide]');
|
|
37
|
+
expect(side.className).toContain('translate-x-0');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should close after open', () => {
|
|
41
|
+
const d = fixture.componentInstance.drawer()!;
|
|
42
|
+
d.open();
|
|
43
|
+
fixture.detectChanges();
|
|
44
|
+
d.close();
|
|
45
|
+
fixture.detectChanges();
|
|
46
|
+
const side = fixture.nativeElement.querySelector('[tonDrawerSide]');
|
|
47
|
+
expect(side.className).toContain('-translate-x-full');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should have navigation role on drawer side', () => {
|
|
51
|
+
const side = fixture.nativeElement.querySelector('[tonDrawerSide]');
|
|
52
|
+
expect(side.getAttribute('role')).toBe('navigation');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should set aria-modal on side when overlay and open', () => {
|
|
56
|
+
const side = fixture.nativeElement.querySelector('[tonDrawerSide]');
|
|
57
|
+
expect(side.getAttribute('aria-modal')).toBeNull();
|
|
58
|
+
fixture.componentInstance.drawer()!.open();
|
|
59
|
+
fixture.detectChanges();
|
|
60
|
+
expect(side.getAttribute('aria-modal')).toBe('true');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, Directive, InjectionToken, computed, inject, input, signal } from '@angular/core';
|
|
2
|
+
import { cn } from '../core/utils/cn';
|
|
3
|
+
|
|
4
|
+
export const TON_DRAWER = new InjectionToken<TonDrawerLayoutComponent>('TonDrawer');
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: '[tonDrawerLayout]',
|
|
8
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
9
|
+
exportAs: 'tonDrawerLayout',
|
|
10
|
+
providers: [{ provide: TON_DRAWER, useExisting: TonDrawerLayoutComponent }],
|
|
11
|
+
host: {
|
|
12
|
+
'[class]': 'computedClass()',
|
|
13
|
+
},
|
|
14
|
+
template: `
|
|
15
|
+
<ng-content />
|
|
16
|
+
@if (isOpen() && overlay()) {
|
|
17
|
+
<div
|
|
18
|
+
class="fixed inset-0 z-30 bg-black/50 transition-opacity"
|
|
19
|
+
(click)="close()"
|
|
20
|
+
></div>
|
|
21
|
+
}
|
|
22
|
+
`,
|
|
23
|
+
})
|
|
24
|
+
export class TonDrawerLayoutComponent {
|
|
25
|
+
readonly class = input<string>('');
|
|
26
|
+
readonly overlay = input(true);
|
|
27
|
+
readonly isOpen = signal(false);
|
|
28
|
+
|
|
29
|
+
protected readonly computedClass = computed(() =>
|
|
30
|
+
cn('relative flex h-full w-full overflow-hidden', this.class())
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
toggle(): void { this.isOpen.update((v) => !v); }
|
|
34
|
+
open(): void { this.isOpen.set(true); }
|
|
35
|
+
close(): void { this.isOpen.set(false); }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** @deprecated Use TonDrawerLayoutComponent instead */
|
|
39
|
+
export const TonDrawerLayoutDirective = TonDrawerLayoutComponent;
|
|
40
|
+
|
|
41
|
+
@Directive({
|
|
42
|
+
selector: '[tonDrawerContent]',
|
|
43
|
+
host: {
|
|
44
|
+
'[class]': 'computedClass()',
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
export class TonDrawerContentDirective {
|
|
48
|
+
readonly class = input<string>('');
|
|
49
|
+
protected readonly computedClass = computed(() =>
|
|
50
|
+
cn('flex-1 overflow-auto', this.class())
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type DrawerSide = 'left' | 'right';
|
|
55
|
+
|
|
56
|
+
@Directive({
|
|
57
|
+
selector: '[tonDrawerSide]',
|
|
58
|
+
host: {
|
|
59
|
+
'role': 'navigation',
|
|
60
|
+
'aria-label': 'Sidebar navigation',
|
|
61
|
+
'[attr.aria-modal]': 'drawer.overlay() && drawer.isOpen() || null',
|
|
62
|
+
'[class]': 'computedClass()',
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
export class TonDrawerSideDirective {
|
|
66
|
+
protected readonly drawer = inject(TON_DRAWER);
|
|
67
|
+
readonly side = input<DrawerSide>('left');
|
|
68
|
+
readonly class = input<string>('');
|
|
69
|
+
|
|
70
|
+
protected readonly computedClass = computed(() => {
|
|
71
|
+
const isOpen = this.drawer.isOpen();
|
|
72
|
+
const s = this.side();
|
|
73
|
+
const base = 'fixed inset-y-0 z-50 w-64 bg-background border-r border-border transition-transform duration-300 ease-in-out overflow-y-auto';
|
|
74
|
+
const sideClass = s === 'left' ? 'left-0' : 'right-0 border-l border-r-0';
|
|
75
|
+
const transformClass = isOpen
|
|
76
|
+
? 'translate-x-0'
|
|
77
|
+
: s === 'left' ? '-translate-x-full' : 'translate-x-full';
|
|
78
|
+
return cn(base, sideClass, transformClass, this.class());
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import { TestBed, type ComponentFixture } from '@angular/core/testing';
|
|
3
|
+
import {
|
|
4
|
+
TonMenuContentDirective,
|
|
5
|
+
TonMenuItemDirective,
|
|
6
|
+
TonMenuSeparatorDirective,
|
|
7
|
+
TonMenuLabelDirective,
|
|
8
|
+
} from './dropdown.directives';
|
|
9
|
+
import type { DropdownItemVariant } from './dropdown.variants';
|
|
10
|
+
|
|
11
|
+
@Component({
|
|
12
|
+
standalone: true,
|
|
13
|
+
imports: [
|
|
14
|
+
TonMenuContentDirective,
|
|
15
|
+
TonMenuItemDirective,
|
|
16
|
+
TonMenuSeparatorDirective,
|
|
17
|
+
TonMenuLabelDirective,
|
|
18
|
+
],
|
|
19
|
+
template: `
|
|
20
|
+
<div tonMenuContent>
|
|
21
|
+
<div tonMenuLabel>Actions</div>
|
|
22
|
+
<div tonMenuItem [variant]="variant()">Edit</div>
|
|
23
|
+
<div tonMenuSeparator></div>
|
|
24
|
+
<div tonMenuItem variant="destructive">Delete</div>
|
|
25
|
+
</div>
|
|
26
|
+
`,
|
|
27
|
+
})
|
|
28
|
+
class TestHostComponent {
|
|
29
|
+
variant = signal<DropdownItemVariant>('default');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('TonMenuContentDirective', () => {
|
|
33
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
34
|
+
|
|
35
|
+
beforeEach(async () => {
|
|
36
|
+
await TestBed.configureTestingModule({
|
|
37
|
+
imports: [TestHostComponent],
|
|
38
|
+
}).compileComponents();
|
|
39
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
40
|
+
fixture.detectChanges();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should apply content classes', () => {
|
|
44
|
+
const content = fixture.nativeElement.querySelector('[tonMenuContent]');
|
|
45
|
+
expect(content.className).toContain('rounded-md');
|
|
46
|
+
expect(content.className).toContain('bg-popover');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('TonMenuItemDirective', () => {
|
|
51
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
52
|
+
|
|
53
|
+
beforeEach(async () => {
|
|
54
|
+
await TestBed.configureTestingModule({
|
|
55
|
+
imports: [TestHostComponent],
|
|
56
|
+
}).compileComponents();
|
|
57
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
58
|
+
fixture.detectChanges();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should apply default item classes', () => {
|
|
62
|
+
const items = fixture.nativeElement.querySelectorAll('[tonMenuItem]');
|
|
63
|
+
expect(items[0].className).toContain('text-sm');
|
|
64
|
+
expect(items[0].className).toContain('rounded-sm');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should apply destructive variant', () => {
|
|
68
|
+
const items = fixture.nativeElement.querySelectorAll('[tonMenuItem]');
|
|
69
|
+
expect(items[1].className).toContain('text-destructive');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('TonMenuSeparatorDirective', () => {
|
|
74
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
75
|
+
|
|
76
|
+
beforeEach(async () => {
|
|
77
|
+
await TestBed.configureTestingModule({
|
|
78
|
+
imports: [TestHostComponent],
|
|
79
|
+
}).compileComponents();
|
|
80
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
81
|
+
fixture.detectChanges();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should apply separator classes and role', () => {
|
|
85
|
+
const separator = fixture.nativeElement.querySelector('[tonMenuSeparator]');
|
|
86
|
+
expect(separator.getAttribute('role')).toBe('separator');
|
|
87
|
+
expect(separator.className).toContain('bg-muted');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('TonMenuLabelDirective', () => {
|
|
92
|
+
let fixture: ComponentFixture<TestHostComponent>;
|
|
93
|
+
|
|
94
|
+
beforeEach(async () => {
|
|
95
|
+
await TestBed.configureTestingModule({
|
|
96
|
+
imports: [TestHostComponent],
|
|
97
|
+
}).compileComponents();
|
|
98
|
+
fixture = TestBed.createComponent(TestHostComponent);
|
|
99
|
+
fixture.detectChanges();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should apply label classes', () => {
|
|
103
|
+
const label = fixture.nativeElement.querySelector('[tonMenuLabel]');
|
|
104
|
+
expect(label.className).toContain('font-semibold');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Directive, ElementRef, InjectionToken, computed, inject, input, signal } from '@angular/core';
|
|
2
|
+
import { cn } from '../core/utils/cn';
|
|
3
|
+
import {
|
|
4
|
+
dropdownContentVariants,
|
|
5
|
+
dropdownItemVariants,
|
|
6
|
+
type DropdownItemVariant,
|
|
7
|
+
} from './dropdown.variants';
|
|
8
|
+
|
|
9
|
+
export const TON_DROPDOWN = new InjectionToken<TonDropdownDirective>('TonDropdown');
|
|
10
|
+
|
|
11
|
+
@Directive({
|
|
12
|
+
selector: '[tonDropdown]',
|
|
13
|
+
exportAs: 'tonDropdown',
|
|
14
|
+
providers: [{ provide: TON_DROPDOWN, useExisting: TonDropdownDirective }],
|
|
15
|
+
host: {
|
|
16
|
+
'[class]': '"relative inline-block"',
|
|
17
|
+
'(document:click)': 'onDocumentClick($event)',
|
|
18
|
+
'(keydown.escape)': 'onEscape()',
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
export class TonDropdownDirective {
|
|
22
|
+
private readonly elementRef = inject(ElementRef);
|
|
23
|
+
readonly isOpen = signal(false);
|
|
24
|
+
|
|
25
|
+
toggle(): void { this.isOpen.update((v) => !v); }
|
|
26
|
+
open(): void { this.isOpen.set(true); }
|
|
27
|
+
close(): void { this.isOpen.set(false); }
|
|
28
|
+
|
|
29
|
+
onDocumentClick(event: MouseEvent): void {
|
|
30
|
+
if (!this.elementRef.nativeElement.contains(event.target)) {
|
|
31
|
+
this.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onEscape(): void {
|
|
36
|
+
this.close();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@Directive({
|
|
41
|
+
selector: '[tonDropdownTrigger]',
|
|
42
|
+
host: {
|
|
43
|
+
'(click)': 'dropdown.toggle()',
|
|
44
|
+
'[attr.aria-expanded]': 'dropdown.isOpen()',
|
|
45
|
+
'[attr.aria-haspopup]': '"menu"',
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
export class TonDropdownTriggerDirective {
|
|
49
|
+
readonly dropdown = inject(TON_DROPDOWN);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@Directive({
|
|
53
|
+
selector: '[tonDropdownContent]',
|
|
54
|
+
host: {
|
|
55
|
+
'role': 'menu',
|
|
56
|
+
'[class]': 'computedClass()',
|
|
57
|
+
'[style.display]': 'dropdown.isOpen() ? "" : "none"',
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
export class TonDropdownContentDirective {
|
|
61
|
+
readonly dropdown = inject(TON_DROPDOWN);
|
|
62
|
+
readonly class = input<string>('');
|
|
63
|
+
|
|
64
|
+
protected readonly computedClass = computed(() =>
|
|
65
|
+
cn(
|
|
66
|
+
dropdownContentVariants(),
|
|
67
|
+
'absolute mt-1 left-0 animate-in fade-in-0 zoom-in-95',
|
|
68
|
+
this.class()
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Directive({
|
|
74
|
+
selector: '[tonMenuContent]',
|
|
75
|
+
host: {
|
|
76
|
+
'[class]': 'computedClass()',
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
export class TonMenuContentDirective {
|
|
80
|
+
readonly class = input<string>('');
|
|
81
|
+
|
|
82
|
+
protected readonly computedClass = computed(() =>
|
|
83
|
+
cn(dropdownContentVariants(), this.class())
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@Directive({
|
|
88
|
+
selector: '[tonMenuItem]',
|
|
89
|
+
host: {
|
|
90
|
+
'role': 'menuitem',
|
|
91
|
+
'[class]': 'computedClass()',
|
|
92
|
+
'(click)': 'onClick()',
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
export class TonMenuItemDirective {
|
|
96
|
+
private readonly dropdown = inject(TON_DROPDOWN, { optional: true });
|
|
97
|
+
readonly variant = input<DropdownItemVariant>('default');
|
|
98
|
+
readonly class = input<string>('');
|
|
99
|
+
|
|
100
|
+
protected readonly computedClass = computed(() =>
|
|
101
|
+
cn(dropdownItemVariants({ variant: this.variant() }), 'cursor-pointer hover:bg-accent hover:text-accent-foreground', this.class())
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
onClick(): void {
|
|
105
|
+
this.dropdown?.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@Directive({
|
|
110
|
+
selector: '[tonMenuSeparator]',
|
|
111
|
+
host: {
|
|
112
|
+
'role': 'separator',
|
|
113
|
+
'[class]': 'computedClass()',
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
export class TonMenuSeparatorDirective {
|
|
117
|
+
readonly class = input<string>('');
|
|
118
|
+
|
|
119
|
+
protected readonly computedClass = computed(() =>
|
|
120
|
+
cn('-mx-1 my-1 h-px bg-muted', this.class())
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@Directive({
|
|
125
|
+
selector: '[tonMenuLabel]',
|
|
126
|
+
host: {
|
|
127
|
+
'[class]': 'computedClass()',
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
export class TonMenuLabelDirective {
|
|
131
|
+
readonly class = input<string>('');
|
|
132
|
+
|
|
133
|
+
protected readonly computedClass = computed(() =>
|
|
134
|
+
cn('px-2 py-1.5 text-sm font-semibold', this.class())
|
|
135
|
+
);
|
|
136
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { cva } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
export const dropdownContentVariants = cva(
|
|
4
|
+
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
|
|
5
|
+
{
|
|
6
|
+
variants: {},
|
|
7
|
+
defaultVariants: {},
|
|
8
|
+
}
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
export const dropdownItemVariants = cva(
|
|
12
|
+
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[active]:bg-accent data-[active]:text-accent-foreground',
|
|
13
|
+
{
|
|
14
|
+
variants: {
|
|
15
|
+
variant: {
|
|
16
|
+
default: '',
|
|
17
|
+
destructive:
|
|
18
|
+
'text-destructive data-[active]:bg-destructive/10 data-[active]:text-destructive',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
variant: 'default',
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
export type DropdownItemVariant = 'default' | 'destructive';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export {
|
|
2
|
+
TonDropdownDirective,
|
|
3
|
+
TonDropdownTriggerDirective,
|
|
4
|
+
TonDropdownContentDirective,
|
|
5
|
+
TonMenuContentDirective,
|
|
6
|
+
TonMenuItemDirective,
|
|
7
|
+
TonMenuSeparatorDirective,
|
|
8
|
+
TonMenuLabelDirective,
|
|
9
|
+
TON_DROPDOWN,
|
|
10
|
+
} from './dropdown.directives';
|
|
11
|
+
export {
|
|
12
|
+
dropdownContentVariants,
|
|
13
|
+
dropdownItemVariants,
|
|
14
|
+
type DropdownItemVariant,
|
|
15
|
+
} from './dropdown.variants';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Component, viewChild } from '@angular/core';
|
|
2
|
+
import { TestBed, type ComponentFixture } from '@angular/core/testing';
|
|
3
|
+
import { TonFabDirective, TonFabTriggerDirective, TonFabActionDirective } from './fab.directives';
|
|
4
|
+
|
|
5
|
+
@Component({
|
|
6
|
+
standalone: true,
|
|
7
|
+
imports: [TonFabDirective, TonFabTriggerDirective, TonFabActionDirective],
|
|
8
|
+
template: `
|
|
9
|
+
<div tonFab>
|
|
10
|
+
<button tonFabAction ariaLabel="Edit item">Action 1</button>
|
|
11
|
+
<button tonFabAction>Action 2</button>
|
|
12
|
+
<button tonFabTrigger>+</button>
|
|
13
|
+
</div>
|
|
14
|
+
`,
|
|
15
|
+
})
|
|
16
|
+
class TestHostComponent {
|
|
17
|
+
fab = viewChild(TonFabDirective);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('TonFabDirective', () => {
|
|
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 be closed by default', () => {
|
|
30
|
+
const actions = fixture.nativeElement.querySelectorAll('[tonFabAction]');
|
|
31
|
+
actions.forEach((a: HTMLElement) => expect(a.className).toContain('scale-0'));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should open on trigger click', () => {
|
|
35
|
+
const trigger = fixture.nativeElement.querySelector('[tonFabTrigger]');
|
|
36
|
+
trigger.click();
|
|
37
|
+
fixture.detectChanges();
|
|
38
|
+
const actions = fixture.nativeElement.querySelectorAll('[tonFabAction]');
|
|
39
|
+
actions.forEach((a: HTMLElement) => expect(a.className).toContain('scale-100'));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should have aria-expanded on trigger', () => {
|
|
43
|
+
const trigger = fixture.nativeElement.querySelector('[tonFabTrigger]');
|
|
44
|
+
expect(trigger.getAttribute('aria-expanded')).toBe('false');
|
|
45
|
+
trigger.click();
|
|
46
|
+
fixture.detectChanges();
|
|
47
|
+
expect(trigger.getAttribute('aria-expanded')).toBe('true');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should have menuitem role on actions', () => {
|
|
51
|
+
const actions = fixture.nativeElement.querySelectorAll('[tonFabAction]');
|
|
52
|
+
actions.forEach((a: HTMLElement) => expect(a.getAttribute('role')).toBe('menuitem'));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should set aria-label on action when ariaLabel input is provided', () => {
|
|
56
|
+
const actions = fixture.nativeElement.querySelectorAll('[tonFabAction]');
|
|
57
|
+
expect(actions[0].getAttribute('aria-label')).toBe('Edit item');
|
|
58
|
+
expect(actions[1].getAttribute('aria-label')).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Directive, InjectionToken, computed, inject, input, signal } from '@angular/core';
|
|
2
|
+
import { cn } from '../core/utils/cn';
|
|
3
|
+
|
|
4
|
+
export const TON_FAB = new InjectionToken<TonFabDirective>('TonFab');
|
|
5
|
+
export type FabPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
6
|
+
export type FabDirection = 'up' | 'down' | 'left' | 'right';
|
|
7
|
+
|
|
8
|
+
const positionMap: Record<FabPosition, string> = {
|
|
9
|
+
'bottom-right': 'fixed bottom-6 right-6',
|
|
10
|
+
'bottom-left': 'fixed bottom-6 left-6',
|
|
11
|
+
'top-right': 'fixed top-6 right-6',
|
|
12
|
+
'top-left': 'fixed top-6 left-6',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
@Directive({
|
|
16
|
+
selector: '[tonFab]',
|
|
17
|
+
exportAs: 'tonFab',
|
|
18
|
+
providers: [{ provide: TON_FAB, useExisting: TonFabDirective }],
|
|
19
|
+
host: { '[class]': 'computedClass()' },
|
|
20
|
+
})
|
|
21
|
+
export class TonFabDirective {
|
|
22
|
+
readonly position = input<FabPosition>('bottom-right');
|
|
23
|
+
readonly direction = input<FabDirection>('up');
|
|
24
|
+
readonly class = input<string>('');
|
|
25
|
+
readonly isOpen = signal(false);
|
|
26
|
+
|
|
27
|
+
toggle(): void { this.isOpen.update((v) => !v); }
|
|
28
|
+
open(): void { this.isOpen.set(true); }
|
|
29
|
+
close(): void { this.isOpen.set(false); }
|
|
30
|
+
|
|
31
|
+
protected readonly computedClass = computed(() =>
|
|
32
|
+
cn('z-50 flex flex-col items-center gap-2', positionMap[this.position()], this.class())
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Directive({
|
|
37
|
+
selector: '[tonFabTrigger]',
|
|
38
|
+
host: {
|
|
39
|
+
'(click)': 'fab.toggle()',
|
|
40
|
+
'[attr.aria-expanded]': 'fab.isOpen()',
|
|
41
|
+
'[attr.aria-haspopup]': '"menu"',
|
|
42
|
+
'aria-label': 'Quick actions',
|
|
43
|
+
'[class]': 'computedClass()',
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
export class TonFabTriggerDirective {
|
|
47
|
+
readonly fab = inject(TON_FAB);
|
|
48
|
+
readonly class = input<string>('');
|
|
49
|
+
protected readonly computedClass = computed(() =>
|
|
50
|
+
cn(
|
|
51
|
+
'inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg h-14 w-14 hover:bg-primary/90 transition-transform',
|
|
52
|
+
this.fab.isOpen() && 'rotate-45',
|
|
53
|
+
this.class()
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Directive({
|
|
59
|
+
selector: '[tonFabAction]',
|
|
60
|
+
host: {
|
|
61
|
+
'role': 'menuitem',
|
|
62
|
+
'[attr.aria-label]': 'ariaLabel() || null',
|
|
63
|
+
'[class]': 'computedClass()',
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
export class TonFabActionDirective {
|
|
67
|
+
readonly fab = inject(TON_FAB);
|
|
68
|
+
readonly ariaLabel = input<string>('');
|
|
69
|
+
readonly class = input<string>('');
|
|
70
|
+
protected readonly computedClass = computed(() =>
|
|
71
|
+
cn(
|
|
72
|
+
'inline-flex items-center justify-center rounded-full bg-secondary text-secondary-foreground shadow-md h-10 w-10 transition-all',
|
|
73
|
+
this.fab.isOpen() ? 'scale-100 opacity-100' : 'scale-0 opacity-0',
|
|
74
|
+
this.class()
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
}
|