@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,104 @@
1
+ import { Component, signal } from '@angular/core';
2
+ import { TestBed, ComponentFixture } from '@angular/core/testing';
3
+ import {
4
+ TonCardDirective,
5
+ TonCardHeaderDirective,
6
+ TonCardTitleDirective,
7
+ TonCardDescriptionDirective,
8
+ TonCardContentDirective,
9
+ TonCardFooterDirective,
10
+ } from './card.directives';
11
+ import type { CardVariant, CardPadding } from './card.variants';
12
+
13
+ @Component({
14
+ standalone: true,
15
+ imports: [
16
+ TonCardDirective,
17
+ TonCardHeaderDirective,
18
+ TonCardTitleDirective,
19
+ TonCardDescriptionDirective,
20
+ TonCardContentDirective,
21
+ TonCardFooterDirective,
22
+ ],
23
+ template: `
24
+ <div tonCard [variant]="variant()" [padding]="padding()">
25
+ <div tonCardHeader>
26
+ <h3 tonCardTitle>Title</h3>
27
+ <p tonCardDescription>Description</p>
28
+ </div>
29
+ <div tonCardContent>Content</div>
30
+ <div tonCardFooter>Footer</div>
31
+ </div>
32
+ `,
33
+ })
34
+ class TestHostComponent {
35
+ variant = signal<CardVariant>('default');
36
+ padding = signal<CardPadding>('none');
37
+ }
38
+
39
+ describe('Card Directives', () => {
40
+ let fixture: ComponentFixture<TestHostComponent>;
41
+
42
+ beforeEach(async () => {
43
+ await TestBed.configureTestingModule({
44
+ imports: [TestHostComponent],
45
+ }).compileComponents();
46
+ fixture = TestBed.createComponent(TestHostComponent);
47
+ fixture.detectChanges();
48
+ });
49
+
50
+ it('should apply default card classes', () => {
51
+ const card = fixture.nativeElement.querySelector('[tonCard]');
52
+ expect(card.className).toContain('bg-card');
53
+ expect(card.className).toContain('border');
54
+ });
55
+
56
+ it('should apply elevated variant', () => {
57
+ fixture.componentInstance.variant.set('elevated');
58
+ fixture.detectChanges();
59
+ const card = fixture.nativeElement.querySelector('[tonCard]');
60
+ expect(card.className).toContain('shadow-lg');
61
+ });
62
+
63
+ it('should apply ghost variant', () => {
64
+ fixture.componentInstance.variant.set('ghost');
65
+ fixture.detectChanges();
66
+ const card = fixture.nativeElement.querySelector('[tonCard]');
67
+ expect(card.className).toContain('bg-transparent');
68
+ });
69
+
70
+ it('should apply padding', () => {
71
+ fixture.componentInstance.padding.set('md');
72
+ fixture.detectChanges();
73
+ const card = fixture.nativeElement.querySelector('[tonCard]');
74
+ expect(card.className).toContain('p-6');
75
+ });
76
+
77
+ it('should render card header', () => {
78
+ const header = fixture.nativeElement.querySelector('[tonCardHeader]');
79
+ expect(header.className).toContain('flex');
80
+ expect(header.className).toContain('p-6');
81
+ });
82
+
83
+ it('should render card title', () => {
84
+ const title = fixture.nativeElement.querySelector('[tonCardTitle]');
85
+ expect(title.className).toContain('text-2xl');
86
+ expect(title.className).toContain('font-semibold');
87
+ });
88
+
89
+ it('should render card description', () => {
90
+ const desc = fixture.nativeElement.querySelector('[tonCardDescription]');
91
+ expect(desc.className).toContain('text-muted-foreground');
92
+ });
93
+
94
+ it('should render content projection', () => {
95
+ const content = fixture.nativeElement.querySelector('[tonCardContent]');
96
+ expect(content.textContent).toContain('Content');
97
+ });
98
+
99
+ it('should render card footer', () => {
100
+ const footer = fixture.nativeElement.querySelector('[tonCardFooter]');
101
+ expect(footer.className).toContain('flex');
102
+ expect(footer.className).toContain('items-center');
103
+ });
104
+ });
@@ -0,0 +1,72 @@
1
+ import { Directive, computed, input } from '@angular/core';
2
+ import { cn } from '../core/utils/cn';
3
+ import { cardVariants, type CardVariant, type CardPadding } from './card.variants';
4
+
5
+ @Directive({
6
+ selector: '[tonCard]',
7
+ host: { '[class]': 'computedClass()' },
8
+ })
9
+ export class TonCardDirective {
10
+ readonly variant = input<CardVariant>('default');
11
+ readonly padding = input<CardPadding>('none');
12
+ readonly class = input<string>('');
13
+
14
+ protected readonly computedClass = computed(() =>
15
+ cn(cardVariants({ variant: this.variant(), padding: this.padding() }), this.class())
16
+ );
17
+ }
18
+
19
+ @Directive({
20
+ selector: '[tonCardHeader]',
21
+ host: { '[class]': 'computedClass()' },
22
+ })
23
+ export class TonCardHeaderDirective {
24
+ readonly class = input<string>('');
25
+ protected readonly computedClass = computed(() =>
26
+ cn('flex flex-col space-y-1.5 p-6', this.class())
27
+ );
28
+ }
29
+
30
+ @Directive({
31
+ selector: '[tonCardTitle]',
32
+ host: { '[class]': 'computedClass()' },
33
+ })
34
+ export class TonCardTitleDirective {
35
+ readonly class = input<string>('');
36
+ protected readonly computedClass = computed(() =>
37
+ cn('text-2xl font-semibold leading-none tracking-tight', this.class())
38
+ );
39
+ }
40
+
41
+ @Directive({
42
+ selector: '[tonCardDescription]',
43
+ host: { '[class]': 'computedClass()' },
44
+ })
45
+ export class TonCardDescriptionDirective {
46
+ readonly class = input<string>('');
47
+ protected readonly computedClass = computed(() =>
48
+ cn('text-sm text-muted-foreground', this.class())
49
+ );
50
+ }
51
+
52
+ @Directive({
53
+ selector: '[tonCardContent]',
54
+ host: { '[class]': 'computedClass()' },
55
+ })
56
+ export class TonCardContentDirective {
57
+ readonly class = input<string>('');
58
+ protected readonly computedClass = computed(() =>
59
+ cn('p-6 pt-0', this.class())
60
+ );
61
+ }
62
+
63
+ @Directive({
64
+ selector: '[tonCardFooter]',
65
+ host: { '[class]': 'computedClass()' },
66
+ })
67
+ export class TonCardFooterDirective {
68
+ readonly class = input<string>('');
69
+ protected readonly computedClass = computed(() =>
70
+ cn('flex items-center p-6 pt-0', this.class())
71
+ );
72
+ }
@@ -0,0 +1,28 @@
1
+ import { cva } from 'class-variance-authority';
2
+
3
+ export const cardVariants = cva(
4
+ 'rounded-sm text-card-foreground',
5
+ {
6
+ variants: {
7
+ variant: {
8
+ default: 'bg-card border border-border',
9
+ outline: 'border-2 border-border bg-transparent',
10
+ elevated: 'bg-card shadow-lg',
11
+ ghost: 'bg-transparent',
12
+ },
13
+ padding: {
14
+ none: '',
15
+ sm: 'p-4',
16
+ md: 'p-6',
17
+ lg: 'p-8',
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: 'default',
22
+ padding: 'none',
23
+ },
24
+ }
25
+ );
26
+
27
+ export type CardVariant = 'default' | 'outline' | 'elevated' | 'ghost';
28
+ export type CardPadding = 'none' | 'sm' | 'md' | 'lg';
@@ -0,0 +1,9 @@
1
+ export {
2
+ TonCardDirective,
3
+ TonCardHeaderDirective,
4
+ TonCardTitleDirective,
5
+ TonCardDescriptionDirective,
6
+ TonCardContentDirective,
7
+ TonCardFooterDirective,
8
+ } from './card.directives';
9
+ export { cardVariants, type CardVariant, type CardPadding } from './card.variants';
@@ -0,0 +1,85 @@
1
+ import { Component, viewChild } from '@angular/core';
2
+ import { TestBed, type ComponentFixture } from '@angular/core/testing';
3
+ import { TonCarouselDirective, TonCarouselContentDirective, TonCarouselItemDirective } from './carousel.directives';
4
+
5
+ @Component({
6
+ standalone: true,
7
+ imports: [TonCarouselDirective, TonCarouselContentDirective, TonCarouselItemDirective],
8
+ template: `
9
+ <div tonCarousel [loop]="true">
10
+ <div tonCarouselContent>
11
+ <div tonCarouselItem>Slide 1</div>
12
+ <div tonCarouselItem>Slide 2</div>
13
+ <div tonCarouselItem>Slide 3</div>
14
+ </div>
15
+ </div>
16
+ `,
17
+ })
18
+ class TestHostComponent {
19
+ carousel = viewChild(TonCarouselDirective);
20
+ }
21
+
22
+ describe('TonCarouselDirective', () => {
23
+ let fixture: ComponentFixture<TestHostComponent>;
24
+
25
+ beforeEach(async () => {
26
+ await TestBed.configureTestingModule({ imports: [TestHostComponent] }).compileComponents();
27
+ fixture = TestBed.createComponent(TestHostComponent);
28
+ fixture.detectChanges();
29
+ await fixture.whenStable();
30
+ fixture.detectChanges();
31
+ });
32
+
33
+ it('should render with region role', () => {
34
+ const el = fixture.nativeElement.querySelector('[tonCarousel]');
35
+ expect(el.getAttribute('role')).toBe('region');
36
+ expect(el.getAttribute('aria-roledescription')).toBe('carousel');
37
+ });
38
+
39
+ it('should render slides with group role', () => {
40
+ const items = fixture.nativeElement.querySelectorAll('[tonCarouselItem]');
41
+ expect(items.length).toBe(3);
42
+ expect(items[0].getAttribute('role')).toBe('group');
43
+ });
44
+
45
+ it('should detect items via contentChildren', () => {
46
+ const c = fixture.componentInstance.carousel()!;
47
+ expect(c.totalItems()).toBe(3);
48
+ });
49
+
50
+ it('should navigate to next', () => {
51
+ const c = fixture.componentInstance.carousel()!;
52
+ expect(c.currentIndex()).toBe(0);
53
+ c.next();
54
+ expect(c.currentIndex()).toBe(1);
55
+ });
56
+
57
+ it('should loop around', () => {
58
+ const c = fixture.componentInstance.carousel()!;
59
+ c.goTo(2);
60
+ c.next();
61
+ expect(c.currentIndex()).toBe(0);
62
+ });
63
+
64
+ it('should navigate prev with loop', () => {
65
+ const c = fixture.componentInstance.carousel()!;
66
+ c.prev();
67
+ expect(c.currentIndex()).toBe(2);
68
+ });
69
+
70
+ it('should navigate next on ArrowRight keydown', () => {
71
+ const c = fixture.componentInstance.carousel()!;
72
+ const host = fixture.nativeElement.querySelector('[tonCarousel]');
73
+ expect(c.currentIndex()).toBe(0);
74
+ host.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }));
75
+ expect(c.currentIndex()).toBe(1);
76
+ });
77
+
78
+ it('should navigate prev on ArrowLeft keydown', () => {
79
+ const c = fixture.componentInstance.carousel()!;
80
+ const host = fixture.nativeElement.querySelector('[tonCarousel]');
81
+ c.goTo(1);
82
+ host.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft' }));
83
+ expect(c.currentIndex()).toBe(0);
84
+ });
85
+ });
@@ -0,0 +1,159 @@
1
+ import {
2
+ Directive, InjectionToken, computed, contentChildren, inject, input, signal, effect, OnDestroy,
3
+ } from '@angular/core';
4
+ import { cn } from '../core/utils/cn';
5
+
6
+ export const TON_CAROUSEL = new InjectionToken<TonCarouselDirective>('TonCarousel');
7
+
8
+ @Directive({
9
+ selector: '[tonCarouselItem]',
10
+ host: {
11
+ 'role': 'group',
12
+ '[attr.aria-roledescription]': '"slide"',
13
+ '[class]': 'computedClass()',
14
+ },
15
+ })
16
+ export class TonCarouselItemDirective {
17
+ readonly class = input<string>('');
18
+ protected readonly computedClass = computed(() =>
19
+ cn('min-w-0 shrink-0 grow-0 basis-full pl-4', this.class())
20
+ );
21
+ }
22
+
23
+ @Directive({
24
+ selector: '[tonCarouselContent]',
25
+ host: {
26
+ '[class]': 'computedClass()',
27
+ '[style.transform]': 'transformStyle()',
28
+ },
29
+ })
30
+ export class TonCarouselContentDirective {
31
+ private readonly carousel = inject(TON_CAROUSEL);
32
+ readonly class = input<string>('');
33
+
34
+ protected readonly computedClass = computed(() =>
35
+ cn('flex -ml-4 transition-transform duration-300 ease-in-out', this.class())
36
+ );
37
+
38
+ protected readonly transformStyle = computed(() =>
39
+ `translateX(-${this.carousel.currentIndex() * 100}%)`
40
+ );
41
+ }
42
+
43
+ @Directive({
44
+ selector: '[tonCarousel]',
45
+ exportAs: 'tonCarousel',
46
+ providers: [{ provide: TON_CAROUSEL, useExisting: TonCarouselDirective }],
47
+ host: {
48
+ 'role': 'region',
49
+ '[attr.aria-roledescription]': '"carousel"',
50
+ 'aria-label': 'Carousel',
51
+ 'tabindex': '0',
52
+ '[class]': 'computedClass()',
53
+ '(keydown)': 'onKeydown($event)',
54
+ },
55
+ })
56
+ export class TonCarouselDirective implements OnDestroy {
57
+ readonly orientation = input<'horizontal' | 'vertical'>('horizontal');
58
+ readonly loop = input(false);
59
+ readonly autoplay = input(0);
60
+ readonly class = input<string>('');
61
+
62
+ readonly items = contentChildren(TonCarouselItemDirective, { descendants: true });
63
+ readonly currentIndex = signal(0);
64
+ readonly totalItems = computed(() => this.items().length);
65
+
66
+ private autoplayInterval: ReturnType<typeof setInterval> | null = null;
67
+
68
+ constructor() {
69
+ effect(() => {
70
+ const ms = this.autoplay();
71
+ this.clearAutoplay();
72
+ if (ms > 0) {
73
+ this.autoplayInterval = setInterval(() => this.next(), ms);
74
+ }
75
+ });
76
+ }
77
+
78
+ next(): void {
79
+ const total = this.totalItems();
80
+ if (total === 0) return;
81
+ this.currentIndex.update((i) => {
82
+ if (i >= total - 1) return this.loop() ? 0 : i;
83
+ return i + 1;
84
+ });
85
+ }
86
+
87
+ prev(): void {
88
+ const total = this.totalItems();
89
+ if (total === 0) return;
90
+ this.currentIndex.update((i) => {
91
+ if (i <= 0) return this.loop() ? total - 1 : i;
92
+ return i - 1;
93
+ });
94
+ }
95
+
96
+ goTo(index: number): void {
97
+ this.currentIndex.set(Math.max(0, Math.min(index, this.totalItems() - 1)));
98
+ }
99
+
100
+ onKeydown(event: KeyboardEvent): void {
101
+ switch (event.key) {
102
+ case 'ArrowLeft':
103
+ event.preventDefault();
104
+ this.prev();
105
+ break;
106
+ case 'ArrowRight':
107
+ event.preventDefault();
108
+ this.next();
109
+ break;
110
+ }
111
+ }
112
+
113
+ ngOnDestroy(): void {
114
+ this.clearAutoplay();
115
+ }
116
+
117
+ private clearAutoplay(): void {
118
+ if (this.autoplayInterval) {
119
+ clearInterval(this.autoplayInterval);
120
+ this.autoplayInterval = null;
121
+ }
122
+ }
123
+
124
+ protected readonly computedClass = computed(() =>
125
+ cn('relative overflow-hidden', this.class())
126
+ );
127
+ }
128
+
129
+ @Directive({
130
+ selector: '[tonCarouselPrev]',
131
+ host: {
132
+ '(click)': 'carousel.prev()',
133
+ '[attr.aria-label]': '"Previous slide"',
134
+ '[class]': 'computedClass()',
135
+ },
136
+ })
137
+ export class TonCarouselPrevDirective {
138
+ readonly carousel = inject(TON_CAROUSEL);
139
+ readonly class = input<string>('');
140
+ protected readonly computedClass = computed(() =>
141
+ cn('absolute left-2 top-1/2 -translate-y-1/2 z-10', this.class())
142
+ );
143
+ }
144
+
145
+ @Directive({
146
+ selector: '[tonCarouselNext]',
147
+ host: {
148
+ '(click)': 'carousel.next()',
149
+ '[attr.aria-label]': '"Next slide"',
150
+ '[class]': 'computedClass()',
151
+ },
152
+ })
153
+ export class TonCarouselNextDirective {
154
+ readonly carousel = inject(TON_CAROUSEL);
155
+ readonly class = input<string>('');
156
+ protected readonly computedClass = computed(() =>
157
+ cn('absolute right-2 top-1/2 -translate-y-1/2 z-10', this.class())
158
+ );
159
+ }
@@ -0,0 +1,8 @@
1
+ export {
2
+ TonCarouselDirective,
3
+ TonCarouselContentDirective,
4
+ TonCarouselItemDirective,
5
+ TonCarouselPrevDirective,
6
+ TonCarouselNextDirective,
7
+ TON_CAROUSEL,
8
+ } from './carousel.directives';
@@ -0,0 +1,52 @@
1
+ import { Component, signal } from '@angular/core';
2
+ import { TestBed, type ComponentFixture } from '@angular/core/testing';
3
+ import { TonChatBubbleDirective, TonChatBubbleContentDirective, type ChatBubbleAlign, type ChatBubbleContentVariant } from './chat-bubble.directives';
4
+
5
+ @Component({
6
+ standalone: true,
7
+ imports: [TonChatBubbleDirective, TonChatBubbleContentDirective],
8
+ template: `
9
+ <div tonChatBubble [align]="align()">
10
+ <div tonChatBubbleContent [variant]="variant()">Hello!</div>
11
+ </div>
12
+ `,
13
+ })
14
+ class TestHostComponent {
15
+ align = signal<ChatBubbleAlign>('start');
16
+ variant = signal<ChatBubbleContentVariant>('default');
17
+ }
18
+
19
+ describe('TonChatBubbleDirective', () => {
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 render with article role', () => {
29
+ const bubble = fixture.nativeElement.querySelector('[tonChatBubble]');
30
+ expect(bubble.getAttribute('role')).toBe('article');
31
+ });
32
+
33
+ it('should apply start alignment by default', () => {
34
+ const bubble = fixture.nativeElement.querySelector('[tonChatBubble]');
35
+ expect(bubble.className).toContain('flex');
36
+ expect(bubble.className).not.toContain('flex-row-reverse');
37
+ });
38
+
39
+ it('should apply end alignment', () => {
40
+ fixture.componentInstance.align.set('end');
41
+ fixture.detectChanges();
42
+ const bubble = fixture.nativeElement.querySelector('[tonChatBubble]');
43
+ expect(bubble.className).toContain('flex-row-reverse');
44
+ });
45
+
46
+ it('should apply primary content variant', () => {
47
+ fixture.componentInstance.variant.set('primary');
48
+ fixture.detectChanges();
49
+ const content = fixture.nativeElement.querySelector('[tonChatBubbleContent]');
50
+ expect(content.className).toContain('bg-primary');
51
+ });
52
+ });
@@ -0,0 +1,96 @@
1
+ import { Directive, InjectionToken, computed, inject, input } from '@angular/core';
2
+ import { cn } from '../core/utils/cn';
3
+
4
+ export type ChatBubbleAlign = 'start' | 'end';
5
+ export type ChatBubbleContentVariant = 'default' | 'primary' | 'secondary' | 'accent';
6
+
7
+ export const TON_CHAT_BUBBLE = new InjectionToken<TonChatBubbleDirective>('TonChatBubble');
8
+
9
+ @Directive({
10
+ selector: '[tonChatBubble]',
11
+ providers: [{ provide: TON_CHAT_BUBBLE, useExisting: TonChatBubbleDirective }],
12
+ host: {
13
+ 'role': 'article',
14
+ '[class]': 'computedClass()',
15
+ },
16
+ })
17
+ export class TonChatBubbleDirective {
18
+ readonly align = input<ChatBubbleAlign>('start');
19
+ readonly class = input<string>('');
20
+
21
+ protected readonly computedClass = computed(() =>
22
+ cn(
23
+ 'flex gap-3 mb-4',
24
+ this.align() === 'end' && 'flex-row-reverse',
25
+ this.class()
26
+ )
27
+ );
28
+ }
29
+
30
+ @Directive({
31
+ selector: '[tonChatBubbleAvatar]',
32
+ host: { '[class]': 'computedClass()' },
33
+ })
34
+ export class TonChatBubbleAvatarDirective {
35
+ readonly class = input<string>('');
36
+ protected readonly computedClass = computed(() =>
37
+ cn('flex-shrink-0 w-10 h-10 rounded-full overflow-hidden', this.class())
38
+ );
39
+ }
40
+
41
+ @Directive({
42
+ selector: '[tonChatBubbleHeader]',
43
+ host: { '[class]': 'computedClass()' },
44
+ })
45
+ export class TonChatBubbleHeaderDirective {
46
+ readonly class = input<string>('');
47
+ protected readonly computedClass = computed(() =>
48
+ cn('text-xs text-muted-foreground mb-1', this.class())
49
+ );
50
+ }
51
+
52
+ @Directive({
53
+ selector: '[tonChatBubbleContent]',
54
+ host: { '[class]': 'computedClass()' },
55
+ })
56
+ export class TonChatBubbleContentDirective {
57
+ readonly variant = input<ChatBubbleContentVariant>('default');
58
+ readonly class = input<string>('');
59
+
60
+ protected readonly computedClass = computed(() => {
61
+ const v = this.variant();
62
+ const variantClass =
63
+ v === 'primary' ? 'bg-primary text-primary-foreground' :
64
+ v === 'secondary' ? 'bg-blue-100 text-blue-900 dark:bg-blue-900 dark:text-blue-100' :
65
+ v === 'accent' ? 'bg-violet-100 text-violet-900 dark:bg-violet-900 dark:text-violet-100' :
66
+ 'bg-muted';
67
+ return cn('rounded-lg px-3 py-2 text-sm max-w-[80%]', variantClass, this.class());
68
+ });
69
+ }
70
+
71
+ @Directive({
72
+ selector: '[tonChatBubbleFooter]',
73
+ host: { '[class]': 'computedClass()' },
74
+ })
75
+ export class TonChatBubbleFooterDirective {
76
+ readonly class = input<string>('');
77
+ protected readonly computedClass = computed(() =>
78
+ cn('text-xs text-muted-foreground mt-1', this.class())
79
+ );
80
+ }
81
+
82
+ @Directive({
83
+ selector: '[tonChatBubbleBody]',
84
+ host: { '[class]': 'computedClass()' },
85
+ })
86
+ export class TonChatBubbleBodyDirective {
87
+ private readonly chatBubble = inject(TON_CHAT_BUBBLE);
88
+ readonly class = input<string>('');
89
+ protected readonly computedClass = computed(() =>
90
+ cn(
91
+ 'flex flex-col',
92
+ this.chatBubble.align() === 'end' && 'items-end',
93
+ this.class()
94
+ )
95
+ );
96
+ }
@@ -0,0 +1,11 @@
1
+ export {
2
+ TonChatBubbleDirective,
3
+ TonChatBubbleAvatarDirective,
4
+ TonChatBubbleHeaderDirective,
5
+ TonChatBubbleContentDirective,
6
+ TonChatBubbleFooterDirective,
7
+ TonChatBubbleBodyDirective,
8
+ TON_CHAT_BUBBLE,
9
+ type ChatBubbleAlign,
10
+ type ChatBubbleContentVariant,
11
+ } from './chat-bubble.directives';
@@ -0,0 +1,57 @@
1
+ import { Component, signal } from '@angular/core';
2
+ import { TestBed, ComponentFixture } from '@angular/core/testing';
3
+ import { TonCheckboxDirective } from './checkbox.directive';
4
+ import type { CheckboxSize } from './checkbox.variants';
5
+
6
+ @Component({
7
+ standalone: true,
8
+ imports: [TonCheckboxDirective],
9
+ template: `<input type="checkbox" tonCheckbox [size]="size()" />`,
10
+ })
11
+ class TestHostComponent {
12
+ size = signal<CheckboxSize>('md');
13
+ }
14
+
15
+ describe('TonCheckboxDirective', () => {
16
+ let fixture: ComponentFixture<TestHostComponent>;
17
+ let el: HTMLInputElement;
18
+
19
+ beforeEach(async () => {
20
+ await TestBed.configureTestingModule({
21
+ imports: [TestHostComponent],
22
+ }).compileComponents();
23
+
24
+ fixture = TestBed.createComponent(TestHostComponent);
25
+ fixture.detectChanges();
26
+ el = fixture.nativeElement.querySelector('input[type="checkbox"]');
27
+ });
28
+
29
+ it('should apply default classes', () => {
30
+ expect(el.className).toContain('appearance-none');
31
+ expect(el.className).toContain('rounded-sm');
32
+ expect(el.className).toContain('border');
33
+ });
34
+
35
+ it('should apply default md size', () => {
36
+ expect(el.className).toContain('h-4');
37
+ expect(el.className).toContain('w-4');
38
+ });
39
+
40
+ it('should apply sm size', () => {
41
+ fixture.componentInstance.size.set('sm');
42
+ fixture.detectChanges();
43
+ expect(el.className).toContain('h-3.5');
44
+ expect(el.className).toContain('w-3.5');
45
+ });
46
+
47
+ it('should apply lg size', () => {
48
+ fixture.componentInstance.size.set('lg');
49
+ fixture.detectChanges();
50
+ expect(el.className).toContain('h-5');
51
+ expect(el.className).toContain('w-5');
52
+ });
53
+
54
+ it('should have checked styles in class list', () => {
55
+ expect(el.className).toContain('checked:bg-primary');
56
+ });
57
+ });