@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.
Files changed (245) hide show
  1. package/README.md +188 -0
  2. package/fesm2022/tony-ui-library-core.mjs +8756 -0
  3. package/fesm2022/tony-ui-library-core.mjs.map +1 -0
  4. package/package.json +55 -0
  5. package/schematics/collection.json +16 -0
  6. package/schematics/ng-add/index.d.ts +5 -0
  7. package/schematics/ng-add/index.js +53 -0
  8. package/schematics/ng-add/schema.json +19 -0
  9. package/schematics/ng-generate/component/index.d.ts +9 -0
  10. package/schematics/ng-generate/component/index.js +439 -0
  11. package/schematics/ng-generate/component/schema.json +32 -0
  12. package/src/lib/accordion/accordion.directives.spec.ts +173 -0
  13. package/src/lib/accordion/accordion.directives.ts +143 -0
  14. package/src/lib/accordion/index.ts +8 -0
  15. package/src/lib/alert/alert.directives.spec.ts +154 -0
  16. package/src/lib/alert/alert.directives.ts +67 -0
  17. package/src/lib/alert/alert.variants.ts +25 -0
  18. package/src/lib/alert/index.ts +6 -0
  19. package/src/lib/avatar/avatar.component.spec.ts +75 -0
  20. package/src/lib/avatar/avatar.component.ts +43 -0
  21. package/src/lib/avatar/avatar.variants.ts +26 -0
  22. package/src/lib/avatar/index.ts +2 -0
  23. package/src/lib/avatar-group/avatar-group.component.spec.ts +74 -0
  24. package/src/lib/avatar-group/avatar-group.component.ts +88 -0
  25. package/src/lib/avatar-group/index.ts +1 -0
  26. package/src/lib/badge/badge.directive.spec.ts +74 -0
  27. package/src/lib/badge/badge.directive.ts +17 -0
  28. package/src/lib/badge/badge.variants.ts +29 -0
  29. package/src/lib/badge/index.ts +2 -0
  30. package/src/lib/breadcrumb/breadcrumb.directives.spec.ts +80 -0
  31. package/src/lib/breadcrumb/breadcrumb.directives.ts +78 -0
  32. package/src/lib/breadcrumb/index.ts +8 -0
  33. package/src/lib/button/button.directive.spec.ts +92 -0
  34. package/src/lib/button/button.directive.ts +28 -0
  35. package/src/lib/button/button.variants.ts +30 -0
  36. package/src/lib/button/index.ts +2 -0
  37. package/src/lib/button-group/button-group.directive.spec.ts +46 -0
  38. package/src/lib/button-group/button-group.directive.ts +19 -0
  39. package/src/lib/button-group/button-group.variants.ts +18 -0
  40. package/src/lib/button-group/index.ts +2 -0
  41. package/src/lib/calendar/calendar.component.spec.ts +192 -0
  42. package/src/lib/calendar/calendar.component.ts +342 -0
  43. package/src/lib/calendar/calendar.types.ts +24 -0
  44. package/src/lib/calendar/index.ts +7 -0
  45. package/src/lib/card/card.directives.spec.ts +104 -0
  46. package/src/lib/card/card.directives.ts +72 -0
  47. package/src/lib/card/card.variants.ts +28 -0
  48. package/src/lib/card/index.ts +9 -0
  49. package/src/lib/carousel/carousel.directives.spec.ts +85 -0
  50. package/src/lib/carousel/carousel.directives.ts +159 -0
  51. package/src/lib/carousel/index.ts +8 -0
  52. package/src/lib/chat-bubble/chat-bubble.directives.spec.ts +52 -0
  53. package/src/lib/chat-bubble/chat-bubble.directives.ts +96 -0
  54. package/src/lib/chat-bubble/index.ts +11 -0
  55. package/src/lib/checkbox/checkbox.directive.spec.ts +57 -0
  56. package/src/lib/checkbox/checkbox.directive.ts +16 -0
  57. package/src/lib/checkbox/checkbox.variants.ts +19 -0
  58. package/src/lib/checkbox/index.ts +2 -0
  59. package/src/lib/color-picker/color-picker.component.spec.ts +328 -0
  60. package/src/lib/color-picker/color-picker.component.ts +537 -0
  61. package/src/lib/color-picker/color-picker.types.ts +24 -0
  62. package/src/lib/color-picker/color-picker.utils.ts +183 -0
  63. package/src/lib/color-picker/color-picker.variants.ts +17 -0
  64. package/src/lib/color-picker/index.ts +20 -0
  65. package/src/lib/combobox/combobox.component.spec.ts +151 -0
  66. package/src/lib/combobox/combobox.component.ts +264 -0
  67. package/src/lib/combobox/combobox.variants.ts +19 -0
  68. package/src/lib/combobox/index.ts +2 -0
  69. package/src/lib/command-palette/command-palette.component.spec.ts +178 -0
  70. package/src/lib/command-palette/command-palette.component.ts +194 -0
  71. package/src/lib/command-palette/command-palette.service.ts +36 -0
  72. package/src/lib/command-palette/command-palette.types.ts +23 -0
  73. package/src/lib/command-palette/index.ts +7 -0
  74. package/src/lib/data-table/data-table.component.spec.ts +443 -0
  75. package/src/lib/data-table/data-table.component.ts +622 -0
  76. package/src/lib/data-table/data-table.directives.ts +31 -0
  77. package/src/lib/data-table/data-table.types.ts +26 -0
  78. package/src/lib/data-table/index.ts +14 -0
  79. package/src/lib/date-picker/date-picker.component.spec.ts +131 -0
  80. package/src/lib/date-picker/date-picker.component.ts +220 -0
  81. package/src/lib/date-picker/date-picker.variants.ts +17 -0
  82. package/src/lib/date-picker/index.ts +2 -0
  83. package/src/lib/date-range-picker/date-range-picker.component.spec.ts +151 -0
  84. package/src/lib/date-range-picker/date-range-picker.component.ts +340 -0
  85. package/src/lib/date-range-picker/index.ts +1 -0
  86. package/src/lib/diff/diff.component.spec.ts +47 -0
  87. package/src/lib/diff/diff.component.ts +82 -0
  88. package/src/lib/diff/index.ts +1 -0
  89. package/src/lib/divider/divider.component.spec.ts +48 -0
  90. package/src/lib/divider/divider.component.ts +51 -0
  91. package/src/lib/divider/divider.variants.ts +22 -0
  92. package/src/lib/divider/index.ts +2 -0
  93. package/src/lib/dock/dock.directives.spec.ts +85 -0
  94. package/src/lib/dock/dock.directives.ts +81 -0
  95. package/src/lib/dock/index.ts +1 -0
  96. package/src/lib/drawer/drawer.directives.spec.ts +62 -0
  97. package/src/lib/drawer/drawer.directives.ts +80 -0
  98. package/src/lib/drawer/index.ts +8 -0
  99. package/src/lib/dropdown/dropdown.directives.spec.ts +106 -0
  100. package/src/lib/dropdown/dropdown.directives.ts +136 -0
  101. package/src/lib/dropdown/dropdown.variants.ts +27 -0
  102. package/src/lib/dropdown/index.ts +15 -0
  103. package/src/lib/fab/fab.directives.spec.ts +60 -0
  104. package/src/lib/fab/fab.directives.ts +77 -0
  105. package/src/lib/fab/index.ts +8 -0
  106. package/src/lib/fieldset/fieldset.directives.spec.ts +74 -0
  107. package/src/lib/fieldset/fieldset.directives.ts +49 -0
  108. package/src/lib/fieldset/fieldset.variants.ts +15 -0
  109. package/src/lib/fieldset/index.ts +6 -0
  110. package/src/lib/file-input/file-input.component.spec.ts +114 -0
  111. package/src/lib/file-input/file-input.component.ts +155 -0
  112. package/src/lib/file-input/file-input.variants.ts +25 -0
  113. package/src/lib/file-input/index.ts +6 -0
  114. package/src/lib/indicator/index.ts +6 -0
  115. package/src/lib/indicator/indicator.directives.spec.ts +64 -0
  116. package/src/lib/indicator/indicator.directives.ts +59 -0
  117. package/src/lib/input/index.ts +3 -0
  118. package/src/lib/input/input.directive.spec.ts +103 -0
  119. package/src/lib/input/input.directive.ts +25 -0
  120. package/src/lib/input/input.variants.ts +42 -0
  121. package/src/lib/input/label.directive.ts +16 -0
  122. package/src/lib/kbd/index.ts +2 -0
  123. package/src/lib/kbd/kbd.directive.spec.ts +42 -0
  124. package/src/lib/kbd/kbd.directive.ts +18 -0
  125. package/src/lib/kbd/kbd.variants.ts +19 -0
  126. package/src/lib/link/index.ts +2 -0
  127. package/src/lib/link/link.directive.spec.ts +41 -0
  128. package/src/lib/link/link.directive.ts +18 -0
  129. package/src/lib/link/link.variants.ts +20 -0
  130. package/src/lib/list/index.ts +8 -0
  131. package/src/lib/list/list.directives.spec.ts +65 -0
  132. package/src/lib/list/list.directives.ts +81 -0
  133. package/src/lib/loader/index.ts +2 -0
  134. package/src/lib/loader/loader.component.spec.ts +58 -0
  135. package/src/lib/loader/loader.component.ts +47 -0
  136. package/src/lib/loader/loader.variants.ts +21 -0
  137. package/src/lib/modal/dialog-ref.ts +19 -0
  138. package/src/lib/modal/dialog.directives.ts +84 -0
  139. package/src/lib/modal/dialog.service.spec.ts +52 -0
  140. package/src/lib/modal/dialog.service.ts +61 -0
  141. package/src/lib/modal/dialog.types.ts +16 -0
  142. package/src/lib/modal/index.ts +11 -0
  143. package/src/lib/navbar/index.ts +7 -0
  144. package/src/lib/navbar/navbar.directives.spec.ts +59 -0
  145. package/src/lib/navbar/navbar.directives.ts +57 -0
  146. package/src/lib/number-input/index.ts +2 -0
  147. package/src/lib/number-input/number-input.component.spec.ts +151 -0
  148. package/src/lib/number-input/number-input.component.ts +152 -0
  149. package/src/lib/number-input/number-input.variants.ts +17 -0
  150. package/src/lib/otp-input/index.ts +2 -0
  151. package/src/lib/otp-input/otp-input.component.spec.ts +252 -0
  152. package/src/lib/otp-input/otp-input.component.ts +274 -0
  153. package/src/lib/otp-input/otp-input.variants.ts +18 -0
  154. package/src/lib/pagination/index.ts +6 -0
  155. package/src/lib/pagination/pagination.component.spec.ts +59 -0
  156. package/src/lib/pagination/pagination.component.ts +143 -0
  157. package/src/lib/pagination/pagination.variants.ts +31 -0
  158. package/src/lib/popover/index.ts +6 -0
  159. package/src/lib/popover/popover.directives.spec.ts +147 -0
  160. package/src/lib/popover/popover.directives.ts +151 -0
  161. package/src/lib/progress/index.ts +7 -0
  162. package/src/lib/progress/progress.component.spec.ts +117 -0
  163. package/src/lib/progress/progress.component.ts +64 -0
  164. package/src/lib/progress/progress.variants.ts +43 -0
  165. package/src/lib/radial-progress/index.ts +5 -0
  166. package/src/lib/radial-progress/radial-progress.component.spec.ts +41 -0
  167. package/src/lib/radial-progress/radial-progress.component.ts +70 -0
  168. package/src/lib/radio/index.ts +2 -0
  169. package/src/lib/radio/radio.directive.spec.ts +46 -0
  170. package/src/lib/radio/radio.directive.ts +16 -0
  171. package/src/lib/radio/radio.variants.ts +19 -0
  172. package/src/lib/rating/index.ts +2 -0
  173. package/src/lib/rating/rating.component.spec.ts +157 -0
  174. package/src/lib/rating/rating.component.ts +163 -0
  175. package/src/lib/rating/rating.variants.ts +20 -0
  176. package/src/lib/select/index.ts +2 -0
  177. package/src/lib/select/select.component.spec.ts +112 -0
  178. package/src/lib/select/select.component.ts +235 -0
  179. package/src/lib/select/select.variants.ts +19 -0
  180. package/src/lib/sheet/index.ts +10 -0
  181. package/src/lib/sheet/sheet-ref.ts +18 -0
  182. package/src/lib/sheet/sheet.component.spec.ts +67 -0
  183. package/src/lib/sheet/sheet.directives.ts +70 -0
  184. package/src/lib/sheet/sheet.service.ts +100 -0
  185. package/src/lib/sheet/sheet.types.ts +23 -0
  186. package/src/lib/skeleton/index.ts +2 -0
  187. package/src/lib/skeleton/skeleton.directive.spec.ts +63 -0
  188. package/src/lib/skeleton/skeleton.directive.ts +21 -0
  189. package/src/lib/skeleton/skeleton.variants.ts +27 -0
  190. package/src/lib/slider/index.ts +2 -0
  191. package/src/lib/slider/slider.component.spec.ts +104 -0
  192. package/src/lib/slider/slider.component.ts +181 -0
  193. package/src/lib/slider/slider.variants.ts +25 -0
  194. package/src/lib/stat/index.ts +8 -0
  195. package/src/lib/stat/stat.directives.spec.ts +60 -0
  196. package/src/lib/stat/stat.directives.ts +79 -0
  197. package/src/lib/status/index.ts +2 -0
  198. package/src/lib/status/status.directive.spec.ts +43 -0
  199. package/src/lib/status/status.directive.ts +37 -0
  200. package/src/lib/status/status.variants.ts +26 -0
  201. package/src/lib/steps/index.ts +8 -0
  202. package/src/lib/steps/steps.directives.spec.ts +52 -0
  203. package/src/lib/steps/steps.directives.ts +78 -0
  204. package/src/lib/switch/index.ts +2 -0
  205. package/src/lib/switch/switch.component.spec.ts +98 -0
  206. package/src/lib/switch/switch.component.ts +76 -0
  207. package/src/lib/switch/switch.variants.ts +31 -0
  208. package/src/lib/table/index.ts +12 -0
  209. package/src/lib/table/table.directives.spec.ts +111 -0
  210. package/src/lib/table/table.directives.ts +126 -0
  211. package/src/lib/table/table.variants.ts +36 -0
  212. package/src/lib/tabs/index.ts +8 -0
  213. package/src/lib/tabs/tabs.directives.spec.ts +136 -0
  214. package/src/lib/tabs/tabs.directives.ts +126 -0
  215. package/src/lib/tabs/tabs.variants.ts +17 -0
  216. package/src/lib/tag-input/index.ts +2 -0
  217. package/src/lib/tag-input/tag-input.component.spec.ts +190 -0
  218. package/src/lib/tag-input/tag-input.component.ts +172 -0
  219. package/src/lib/tag-input/tag-input.variants.ts +31 -0
  220. package/src/lib/textarea/index.ts +7 -0
  221. package/src/lib/textarea/textarea.directive.spec.ts +84 -0
  222. package/src/lib/textarea/textarea.directive.ts +71 -0
  223. package/src/lib/textarea/textarea.variants.ts +34 -0
  224. package/src/lib/timeline/index.ts +11 -0
  225. package/src/lib/timeline/timeline.directives.spec.ts +55 -0
  226. package/src/lib/timeline/timeline.directives.ts +85 -0
  227. package/src/lib/toast/index.ts +3 -0
  228. package/src/lib/toast/toast.service.spec.ts +71 -0
  229. package/src/lib/toast/toast.service.ts +60 -0
  230. package/src/lib/toast/toast.variants.ts +38 -0
  231. package/src/lib/toast/toaster.component.spec.ts +38 -0
  232. package/src/lib/toast/toaster.component.ts +81 -0
  233. package/src/lib/toggle/index.ts +2 -0
  234. package/src/lib/toggle/toggle.directive.spec.ts +100 -0
  235. package/src/lib/toggle/toggle.directive.ts +61 -0
  236. package/src/lib/toggle/toggle.variants.ts +25 -0
  237. package/src/lib/tooltip/index.ts +2 -0
  238. package/src/lib/tooltip/tooltip.directive.spec.ts +113 -0
  239. package/src/lib/tooltip/tooltip.directive.ts +130 -0
  240. package/src/lib/tooltip/tooltip.variants.ts +20 -0
  241. package/src/lib/validator/index.ts +5 -0
  242. package/src/lib/validator/validator.directives.spec.ts +47 -0
  243. package/src/lib/validator/validator.directives.ts +50 -0
  244. package/src/styles/sonny-theme.css +171 -0
  245. package/types/tony-ui-library-core.d.ts +2179 -0
@@ -0,0 +1,173 @@
1
+ import { Component, signal, viewChild } from '@angular/core';
2
+ import { TestBed, ComponentFixture } from '@angular/core/testing';
3
+ import {
4
+ TonAccordionDirective,
5
+ TonAccordionItemDirective,
6
+ TonAccordionTriggerDirective,
7
+ TonAccordionContentDirective,
8
+ } from './accordion.directives';
9
+
10
+ @Component({
11
+ standalone: true,
12
+ imports: [
13
+ TonAccordionDirective,
14
+ TonAccordionItemDirective,
15
+ TonAccordionTriggerDirective,
16
+ TonAccordionContentDirective,
17
+ ],
18
+ template: `
19
+ <div tonAccordion [multi]="multi()">
20
+ <div tonAccordionItem value="item-1">
21
+ <button tonAccordionTrigger>Item 1</button>
22
+ <div tonAccordionContent><div>Content 1</div></div>
23
+ </div>
24
+ <div tonAccordionItem value="item-2">
25
+ <button tonAccordionTrigger>Item 2</button>
26
+ <div tonAccordionContent><div>Content 2</div></div>
27
+ </div>
28
+ </div>
29
+ `,
30
+ })
31
+ class TestHostComponent {
32
+ multi = signal(false);
33
+ }
34
+
35
+ describe('Accordion Directives', () => {
36
+ let fixture: ComponentFixture<TestHostComponent>;
37
+
38
+ beforeEach(async () => {
39
+ await TestBed.configureTestingModule({
40
+ imports: [TestHostComponent],
41
+ }).compileComponents();
42
+
43
+ fixture = TestBed.createComponent(TestHostComponent);
44
+ fixture.detectChanges();
45
+ });
46
+
47
+ it('should render accordion items', () => {
48
+ const items = fixture.nativeElement.querySelectorAll('[tonAccordionItem]');
49
+ expect(items.length).toBe(2);
50
+ });
51
+
52
+ it('should toggle item on trigger click', () => {
53
+ const trigger = fixture.nativeElement.querySelector('[tonAccordionTrigger]');
54
+ trigger.click();
55
+ fixture.detectChanges();
56
+ expect(trigger.getAttribute('aria-expanded')).toBe('true');
57
+ });
58
+
59
+ it('should close previous item in single mode', () => {
60
+ const triggers = fixture.nativeElement.querySelectorAll('[tonAccordionTrigger]');
61
+ triggers[0].click();
62
+ fixture.detectChanges();
63
+ expect(triggers[0].getAttribute('aria-expanded')).toBe('true');
64
+
65
+ triggers[1].click();
66
+ fixture.detectChanges();
67
+ expect(triggers[0].getAttribute('aria-expanded')).toBe('false');
68
+ expect(triggers[1].getAttribute('aria-expanded')).toBe('true');
69
+ });
70
+
71
+ it('should allow multiple open in multi mode', () => {
72
+ fixture.componentInstance.multi.set(true);
73
+ fixture.detectChanges();
74
+
75
+ const triggers = fixture.nativeElement.querySelectorAll('[tonAccordionTrigger]');
76
+ triggers[0].click();
77
+ fixture.detectChanges();
78
+ triggers[1].click();
79
+ fixture.detectChanges();
80
+
81
+ expect(triggers[0].getAttribute('aria-expanded')).toBe('true');
82
+ expect(triggers[1].getAttribute('aria-expanded')).toBe('true');
83
+ });
84
+
85
+ it('should apply content visibility classes', () => {
86
+ const content = fixture.nativeElement.querySelector('[tonAccordionContent]');
87
+ expect(content.className).toContain('grid-rows-[0fr]');
88
+
89
+ const trigger = fixture.nativeElement.querySelector('[tonAccordionTrigger]');
90
+ trigger.click();
91
+ fixture.detectChanges();
92
+
93
+ expect(content.className).toContain('grid-rows-[1fr]');
94
+ });
95
+
96
+ it('should move focus with ArrowDown', () => {
97
+ const triggers = fixture.nativeElement.querySelectorAll('[tonAccordionTrigger]');
98
+ (triggers[0] as HTMLElement).focus();
99
+ triggers[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
100
+ fixture.detectChanges();
101
+ const updated = fixture.nativeElement.querySelectorAll('[tonAccordionTrigger]');
102
+ expect(document.activeElement).toBe(updated[1]);
103
+ });
104
+
105
+ it('should move focus with ArrowUp', () => {
106
+ const triggers = fixture.nativeElement.querySelectorAll('[tonAccordionTrigger]');
107
+ (triggers[1] as HTMLElement).focus();
108
+ triggers[1].dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }));
109
+ fixture.detectChanges();
110
+ const updated = fixture.nativeElement.querySelectorAll('[tonAccordionTrigger]');
111
+ expect(document.activeElement).toBe(updated[0]);
112
+ });
113
+
114
+ it('should move focus to first with Home', () => {
115
+ const triggers = fixture.nativeElement.querySelectorAll('[tonAccordionTrigger]');
116
+ (triggers[1] as HTMLElement).focus();
117
+ triggers[1].dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', bubbles: true }));
118
+ fixture.detectChanges();
119
+ const updated = fixture.nativeElement.querySelectorAll('[tonAccordionTrigger]');
120
+ expect(document.activeElement).toBe(updated[0]);
121
+ });
122
+
123
+ it('should move focus to last with End', () => {
124
+ const triggers = fixture.nativeElement.querySelectorAll('[tonAccordionTrigger]');
125
+ (triggers[0] as HTMLElement).focus();
126
+ triggers[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true }));
127
+ fixture.detectChanges();
128
+ const updated = fixture.nativeElement.querySelectorAll('[tonAccordionTrigger]');
129
+ expect(document.activeElement).toBe(updated[1]);
130
+ });
131
+ });
132
+
133
+ @Component({
134
+ standalone: true,
135
+ imports: [
136
+ TonAccordionDirective,
137
+ TonAccordionItemDirective,
138
+ TonAccordionTriggerDirective,
139
+ TonAccordionContentDirective,
140
+ ],
141
+ template: `
142
+ <div tonAccordion #a="tonAccordion">
143
+ <div tonAccordionItem #i="tonAccordionItem" value="item-1">
144
+ <button tonAccordionTrigger>Item 1</button>
145
+ <div tonAccordionContent><div>Content 1</div></div>
146
+ </div>
147
+ </div>
148
+ `,
149
+ })
150
+ class ExportAsHost {
151
+ accordion = viewChild<TonAccordionDirective>('a');
152
+ item = viewChild<TonAccordionItemDirective>('i');
153
+ }
154
+
155
+ describe('Accordion exportAs', () => {
156
+ it('should expose tonAccordion via template ref', async () => {
157
+ await TestBed.configureTestingModule({ imports: [ExportAsHost] }).compileComponents();
158
+ const fixture = TestBed.createComponent(ExportAsHost);
159
+ fixture.detectChanges();
160
+ const ref = fixture.componentInstance.accordion();
161
+ expect(ref).toBeTruthy();
162
+ expect(ref!.multi()).toBe(false);
163
+ });
164
+
165
+ it('should expose tonAccordionItem via template ref', async () => {
166
+ await TestBed.configureTestingModule({ imports: [ExportAsHost] }).compileComponents();
167
+ const fixture = TestBed.createComponent(ExportAsHost);
168
+ fixture.detectChanges();
169
+ const ref = fixture.componentInstance.item();
170
+ expect(ref).toBeTruthy();
171
+ expect(ref!.isOpen()).toBe(false);
172
+ });
173
+ });
@@ -0,0 +1,143 @@
1
+ import { Directive, ElementRef, computed, inject, input, signal, InjectionToken } from '@angular/core';
2
+ import { cn } from '../core/utils/cn';
3
+
4
+ export const TON_ACCORDION = new InjectionToken<TonAccordionDirective>('TonAccordion');
5
+ export const TON_ACCORDION_ITEM = new InjectionToken<TonAccordionItemDirective>('TonAccordionItem');
6
+
7
+ @Directive({
8
+ selector: '[tonAccordion]',
9
+ exportAs: 'tonAccordion',
10
+ providers: [{ provide: TON_ACCORDION, useExisting: TonAccordionDirective }],
11
+ host: {
12
+ '[class]': 'computedClass()',
13
+ '(keydown)': 'onKeydown($event)',
14
+ },
15
+ })
16
+ export class TonAccordionDirective {
17
+ readonly multi = input(false);
18
+ readonly class = input<string>('');
19
+
20
+ private readonly elRef = inject(ElementRef);
21
+ private readonly _openItems = signal(new Set<string>());
22
+
23
+ protected readonly computedClass = computed(() =>
24
+ cn('divide-y divide-border', this.class())
25
+ );
26
+
27
+ isOpen(id: string): boolean {
28
+ return this._openItems().has(id);
29
+ }
30
+
31
+ toggle(id: string): void {
32
+ this._openItems.update(set => {
33
+ const next = new Set(set);
34
+ if (next.has(id)) {
35
+ next.delete(id);
36
+ } else {
37
+ if (!this.multi()) next.clear();
38
+ next.add(id);
39
+ }
40
+ return next;
41
+ });
42
+ }
43
+
44
+ onKeydown(event: KeyboardEvent): void {
45
+ const target = event.target as HTMLElement;
46
+ if (!target.hasAttribute('tonaccordiontrigger') && !target.closest('[tonAccordionTrigger]')) return;
47
+
48
+ const triggers = Array.from(
49
+ (this.elRef.nativeElement as HTMLElement).querySelectorAll<HTMLElement>('[tonAccordionTrigger]')
50
+ );
51
+ if (triggers.length === 0) return;
52
+
53
+ const currentIndex = triggers.indexOf(target.closest('[tonAccordionTrigger]') as HTMLElement || target);
54
+ if (currentIndex === -1) return;
55
+
56
+ let nextIndex: number | null = null;
57
+ switch (event.key) {
58
+ case 'ArrowDown':
59
+ event.preventDefault();
60
+ nextIndex = (currentIndex + 1) % triggers.length;
61
+ break;
62
+ case 'ArrowUp':
63
+ event.preventDefault();
64
+ nextIndex = (currentIndex - 1 + triggers.length) % triggers.length;
65
+ break;
66
+ case 'Home':
67
+ event.preventDefault();
68
+ nextIndex = 0;
69
+ break;
70
+ case 'End':
71
+ event.preventDefault();
72
+ nextIndex = triggers.length - 1;
73
+ break;
74
+ }
75
+ if (nextIndex !== null) {
76
+ triggers[nextIndex].focus();
77
+ }
78
+ }
79
+ }
80
+
81
+ @Directive({
82
+ selector: '[tonAccordionItem]',
83
+ exportAs: 'tonAccordionItem',
84
+ providers: [{ provide: TON_ACCORDION_ITEM, useExisting: TonAccordionItemDirective }],
85
+ host: { '[class]': 'computedClass()' },
86
+ })
87
+ export class TonAccordionItemDirective {
88
+ readonly value = input.required<string>();
89
+ readonly class = input<string>('');
90
+ private readonly accordion = inject(TON_ACCORDION);
91
+
92
+ readonly isOpen = computed(() => this.accordion.isOpen(this.value()));
93
+
94
+ protected readonly computedClass = computed(() =>
95
+ cn('', this.class())
96
+ );
97
+
98
+ toggle(): void {
99
+ this.accordion.toggle(this.value());
100
+ }
101
+ }
102
+
103
+ @Directive({
104
+ selector: '[tonAccordionTrigger]',
105
+ host: {
106
+ '[class]': 'computedClass()',
107
+ '[attr.aria-expanded]': 'item.isOpen()',
108
+ 'tabindex': '0',
109
+ '(click)': 'item.toggle()',
110
+ },
111
+ })
112
+ export class TonAccordionTriggerDirective {
113
+ readonly class = input<string>('');
114
+ readonly item = inject(TON_ACCORDION_ITEM);
115
+
116
+ protected readonly computedClass = computed(() =>
117
+ cn(
118
+ 'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline cursor-pointer [&>svg]:transition-transform',
119
+ this.item.isOpen() && '[&>svg]:rotate-180',
120
+ this.class()
121
+ )
122
+ );
123
+ }
124
+
125
+ @Directive({
126
+ selector: '[tonAccordionContent]',
127
+ host: {
128
+ '[class]': 'computedClass()',
129
+ role: 'region',
130
+ },
131
+ })
132
+ export class TonAccordionContentDirective {
133
+ readonly class = input<string>('');
134
+ readonly item = inject(TON_ACCORDION_ITEM);
135
+
136
+ protected readonly computedClass = computed(() =>
137
+ cn(
138
+ 'grid transition-all duration-200',
139
+ this.item.isOpen() ? 'grid-rows-[1fr] opacity-100 pb-4' : 'grid-rows-[0fr] opacity-0 overflow-hidden',
140
+ this.class()
141
+ )
142
+ );
143
+ }
@@ -0,0 +1,8 @@
1
+ export {
2
+ TonAccordionDirective,
3
+ TonAccordionItemDirective,
4
+ TonAccordionTriggerDirective,
5
+ TonAccordionContentDirective,
6
+ TON_ACCORDION,
7
+ TON_ACCORDION_ITEM,
8
+ } from './accordion.directives';
@@ -0,0 +1,154 @@
1
+ import { Component, signal, viewChild } from '@angular/core';
2
+ import { TestBed, type ComponentFixture } from '@angular/core/testing';
3
+ import { TonAlertDirective, TonAlertTitleDirective, TonAlertDescriptionDirective } from './alert.directives';
4
+ import type { AlertVariant } from './alert.variants';
5
+
6
+ @Component({
7
+ standalone: true,
8
+ imports: [TonAlertDirective, TonAlertTitleDirective, TonAlertDescriptionDirective],
9
+ template: `
10
+ <div tonAlert [variant]="variant()" [dismissible]="dismissible()">
11
+ <h5 tonAlertTitle>{{ title() }}</h5>
12
+ <p tonAlertDescription>{{ description() }}</p>
13
+ </div>
14
+ `,
15
+ })
16
+ class TestHostComponent {
17
+ variant = signal<AlertVariant>('default');
18
+ dismissible = signal(false);
19
+ title = signal('Test Title');
20
+ description = signal('Test Description');
21
+ alert = viewChild(TonAlertDirective);
22
+ }
23
+
24
+ describe('TonAlertDirective', () => {
25
+ let fixture: ComponentFixture<TestHostComponent>;
26
+ let el: HTMLElement;
27
+
28
+ beforeEach(async () => {
29
+ await TestBed.configureTestingModule({
30
+ imports: [TestHostComponent],
31
+ }).compileComponents();
32
+ fixture = TestBed.createComponent(TestHostComponent);
33
+ fixture.detectChanges();
34
+ el = fixture.nativeElement.querySelector('[tonAlert]');
35
+ });
36
+
37
+ it('should apply default variant classes', () => {
38
+ expect(el.className).toContain('bg-background');
39
+ expect(el.className).toContain('rounded-lg');
40
+ });
41
+
42
+ it('should apply destructive variant classes', () => {
43
+ fixture.componentInstance.variant.set('destructive');
44
+ fixture.detectChanges();
45
+ expect(el.className).toContain('text-destructive');
46
+ });
47
+
48
+ it('should apply success variant classes', () => {
49
+ fixture.componentInstance.variant.set('success');
50
+ fixture.detectChanges();
51
+ expect(el.className).toContain('text-green-700');
52
+ });
53
+
54
+ it('should apply warning variant classes', () => {
55
+ fixture.componentInstance.variant.set('warning');
56
+ fixture.detectChanges();
57
+ expect(el.className).toContain('text-yellow-700');
58
+ });
59
+
60
+ it('should apply info variant classes', () => {
61
+ fixture.componentInstance.variant.set('info');
62
+ fixture.detectChanges();
63
+ expect(el.className).toContain('text-blue-700');
64
+ });
65
+
66
+ it('should set role="status" for default and success', () => {
67
+ expect(el.getAttribute('role')).toBe('status');
68
+ fixture.componentInstance.variant.set('success');
69
+ fixture.detectChanges();
70
+ expect(el.getAttribute('role')).toBe('status');
71
+ });
72
+
73
+ it('should set role="alert" for destructive, warning, info', () => {
74
+ fixture.componentInstance.variant.set('destructive');
75
+ fixture.detectChanges();
76
+ expect(el.getAttribute('role')).toBe('alert');
77
+
78
+ fixture.componentInstance.variant.set('warning');
79
+ fixture.detectChanges();
80
+ expect(el.getAttribute('role')).toBe('alert');
81
+
82
+ fixture.componentInstance.variant.set('info');
83
+ fixture.detectChanges();
84
+ expect(el.getAttribute('role')).toBe('alert');
85
+ });
86
+
87
+ it('should set aria-live="assertive" for destructive/warning', () => {
88
+ fixture.componentInstance.variant.set('destructive');
89
+ fixture.detectChanges();
90
+ expect(el.getAttribute('aria-live')).toBe('assertive');
91
+
92
+ fixture.componentInstance.variant.set('warning');
93
+ fixture.detectChanges();
94
+ expect(el.getAttribute('aria-live')).toBe('assertive');
95
+ });
96
+
97
+ it('should set aria-live="polite" for default/success/info', () => {
98
+ expect(el.getAttribute('aria-live')).toBe('polite');
99
+
100
+ fixture.componentInstance.variant.set('success');
101
+ fixture.detectChanges();
102
+ expect(el.getAttribute('aria-live')).toBe('polite');
103
+
104
+ fixture.componentInstance.variant.set('info');
105
+ fixture.detectChanges();
106
+ expect(el.getAttribute('aria-live')).toBe('polite');
107
+ });
108
+
109
+ it('should be visible by default', () => {
110
+ expect(el.style.display).not.toBe('none');
111
+ });
112
+
113
+ it('should hide when dismiss() is called', () => {
114
+ const alert = fixture.componentInstance.alert();
115
+ alert!.dismiss();
116
+ fixture.detectChanges();
117
+ expect(el.style.display).toBe('none');
118
+ });
119
+ });
120
+
121
+ describe('TonAlertTitleDirective', () => {
122
+ let fixture: ComponentFixture<TestHostComponent>;
123
+
124
+ beforeEach(async () => {
125
+ await TestBed.configureTestingModule({
126
+ imports: [TestHostComponent],
127
+ }).compileComponents();
128
+ fixture = TestBed.createComponent(TestHostComponent);
129
+ fixture.detectChanges();
130
+ });
131
+
132
+ it('should apply title classes', () => {
133
+ const title = fixture.nativeElement.querySelector('[tonAlertTitle]');
134
+ expect(title.className).toContain('font-medium');
135
+ expect(title.className).toContain('tracking-tight');
136
+ });
137
+ });
138
+
139
+ describe('TonAlertDescriptionDirective', () => {
140
+ let fixture: ComponentFixture<TestHostComponent>;
141
+
142
+ beforeEach(async () => {
143
+ await TestBed.configureTestingModule({
144
+ imports: [TestHostComponent],
145
+ }).compileComponents();
146
+ fixture = TestBed.createComponent(TestHostComponent);
147
+ fixture.detectChanges();
148
+ });
149
+
150
+ it('should apply description classes', () => {
151
+ const desc = fixture.nativeElement.querySelector('[tonAlertDescription]');
152
+ expect(desc.className).toContain('text-sm');
153
+ });
154
+ });
@@ -0,0 +1,67 @@
1
+ import { Directive, computed, input, signal } from '@angular/core';
2
+ import { cn } from '../core/utils/cn';
3
+ import { alertVariants, type AlertVariant } from './alert.variants';
4
+
5
+ @Directive({
6
+ selector: '[tonAlert]',
7
+ exportAs: 'tonAlert',
8
+ host: {
9
+ '[class]': 'computedClass()',
10
+ '[attr.role]': 'role()',
11
+ '[attr.aria-live]': 'ariaLive()',
12
+ '[style.display]': 'visible() ? null : "none"',
13
+ },
14
+ })
15
+ export class TonAlertDirective {
16
+ readonly variant = input<AlertVariant>('default');
17
+ readonly dismissible = input(false);
18
+ readonly class = input<string>('');
19
+
20
+ readonly visible = signal(true);
21
+
22
+ protected readonly role = computed(() => {
23
+ const v = this.variant();
24
+ return v === 'destructive' || v === 'warning' || v === 'info' ? 'alert' : 'status';
25
+ });
26
+
27
+ protected readonly ariaLive = computed(() => {
28
+ const v = this.variant();
29
+ return v === 'destructive' || v === 'warning' ? 'assertive' : 'polite';
30
+ });
31
+
32
+ protected readonly computedClass = computed(() =>
33
+ cn(alertVariants({ variant: this.variant() }), this.class())
34
+ );
35
+
36
+ dismiss(): void {
37
+ this.visible.set(false);
38
+ }
39
+ }
40
+
41
+ @Directive({
42
+ selector: '[tonAlertTitle]',
43
+ host: {
44
+ '[class]': 'computedClass()',
45
+ },
46
+ })
47
+ export class TonAlertTitleDirective {
48
+ readonly class = input<string>('');
49
+
50
+ protected readonly computedClass = computed(() =>
51
+ cn('mb-1 font-medium leading-none tracking-tight', this.class())
52
+ );
53
+ }
54
+
55
+ @Directive({
56
+ selector: '[tonAlertDescription]',
57
+ host: {
58
+ '[class]': 'computedClass()',
59
+ },
60
+ })
61
+ export class TonAlertDescriptionDirective {
62
+ readonly class = input<string>('');
63
+
64
+ protected readonly computedClass = computed(() =>
65
+ cn('text-sm [&_p]:leading-relaxed', this.class())
66
+ );
67
+ }
@@ -0,0 +1,25 @@
1
+ import { cva } from 'class-variance-authority';
2
+
3
+ export const alertVariants = cva(
4
+ 'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
5
+ {
6
+ variants: {
7
+ variant: {
8
+ default:
9
+ 'bg-background text-foreground border-border',
10
+ destructive:
11
+ 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive bg-destructive/10',
12
+ success:
13
+ 'border-green-500/50 text-green-700 dark:text-green-400 dark:border-green-500 [&>svg]:text-green-600 bg-green-50 dark:bg-green-950/30',
14
+ warning:
15
+ 'border-yellow-500/50 text-yellow-700 dark:text-yellow-400 dark:border-yellow-500 [&>svg]:text-yellow-600 bg-yellow-50 dark:bg-yellow-950/30',
16
+ info: 'border-blue-500/50 text-blue-700 dark:text-blue-400 dark:border-blue-500 [&>svg]:text-blue-600 bg-blue-50 dark:bg-blue-950/30',
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ variant: 'default',
21
+ },
22
+ }
23
+ );
24
+
25
+ export type AlertVariant = 'default' | 'destructive' | 'success' | 'warning' | 'info';
@@ -0,0 +1,6 @@
1
+ export {
2
+ TonAlertDirective,
3
+ TonAlertTitleDirective,
4
+ TonAlertDescriptionDirective,
5
+ } from './alert.directives';
6
+ export { alertVariants, type AlertVariant } from './alert.variants';
@@ -0,0 +1,75 @@
1
+ import { Component, signal } from '@angular/core';
2
+ import { TestBed, ComponentFixture } from '@angular/core/testing';
3
+ import { TonAvatarComponent } from './avatar.component';
4
+ import type { AvatarSize, AvatarVariant } from './avatar.variants';
5
+
6
+ @Component({
7
+ standalone: true,
8
+ imports: [TonAvatarComponent],
9
+ template: `<ton-avatar [src]="src()" [alt]="alt()" [fallback]="fallback()" [size]="size()" [variant]="variant()" />`,
10
+ })
11
+ class TestHostComponent {
12
+ src = signal('');
13
+ alt = signal('John Doe');
14
+ fallback = signal('');
15
+ size = signal<AvatarSize>('md');
16
+ variant = signal<AvatarVariant>('circle');
17
+ }
18
+
19
+ describe('TonAvatarComponent', () => {
20
+ let fixture: ComponentFixture<TestHostComponent>;
21
+ let el: HTMLElement;
22
+
23
+ beforeEach(async () => {
24
+ await TestBed.configureTestingModule({
25
+ imports: [TestHostComponent],
26
+ }).compileComponents();
27
+
28
+ fixture = TestBed.createComponent(TestHostComponent);
29
+ fixture.detectChanges();
30
+ el = fixture.nativeElement.querySelector('ton-avatar');
31
+ });
32
+
33
+ it('should show fallback text when no src', () => {
34
+ const span = el.querySelector('span');
35
+ expect(span?.textContent?.trim()).toBe('JD');
36
+ });
37
+
38
+ it('should show custom fallback', () => {
39
+ fixture.componentInstance.fallback.set('AB');
40
+ fixture.detectChanges();
41
+ const span = el.querySelector('span');
42
+ expect(span?.textContent?.trim()).toBe('AB');
43
+ });
44
+
45
+ it('should show image when src is provided', () => {
46
+ fixture.componentInstance.src.set('https://example.com/avatar.png');
47
+ fixture.detectChanges();
48
+ const img = el.querySelector('img');
49
+ expect(img).toBeTruthy();
50
+ expect(img?.getAttribute('src')).toBe('https://example.com/avatar.png');
51
+ });
52
+
53
+ it('should apply circle variant by default', () => {
54
+ expect(el.className).toContain('rounded-full');
55
+ });
56
+
57
+ it('should apply rounded variant', () => {
58
+ fixture.componentInstance.variant.set('rounded');
59
+ fixture.detectChanges();
60
+ expect(el.className).toContain('rounded-md');
61
+ });
62
+
63
+ it('should apply size classes', () => {
64
+ fixture.componentInstance.size.set('lg');
65
+ fixture.detectChanges();
66
+ expect(el.className).toContain('h-12');
67
+ expect(el.className).toContain('w-12');
68
+ });
69
+
70
+ it('should apply xl size', () => {
71
+ fixture.componentInstance.size.set('xl');
72
+ fixture.detectChanges();
73
+ expect(el.className).toContain('h-16');
74
+ });
75
+ });