@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,622 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ TemplateRef,
5
+ computed,
6
+ contentChild,
7
+ contentChildren,
8
+ effect,
9
+ input,
10
+ model,
11
+ output,
12
+ signal,
13
+ untracked,
14
+ } from '@angular/core';
15
+ import { NgTemplateOutlet } from '@angular/common';
16
+ import {
17
+ TonTableDirective,
18
+ TonTableHeaderDirective,
19
+ TonTableBodyDirective,
20
+ TonTableRowDirective,
21
+ TonTableHeadDirective,
22
+ TonTableCellDirective,
23
+ } from '../table/table.directives';
24
+ import type { TableVariant, TableDensity } from '../table/table.variants';
25
+ import { TonPaginationComponent } from '../pagination/pagination.component';
26
+ import { TonCheckboxDirective } from '../checkbox/checkbox.directive';
27
+ import { TonInputDirective } from '../input/input.directive';
28
+ import { TonButtonDirective } from '../button/button.directive';
29
+ import { TonSelectComponent, type SelectOption } from '../select/select.component';
30
+ import { TonSkeletonDirective } from '../skeleton/skeleton.directive';
31
+ import {
32
+ TonDropdownDirective,
33
+ TonDropdownTriggerDirective,
34
+ TonDropdownContentDirective,
35
+ TonMenuItemDirective,
36
+ } from '../dropdown/dropdown.directives';
37
+ import {
38
+ TonCellDefDirective,
39
+ TonHeaderCellDefDirective,
40
+ TonBulkActionsDefDirective,
41
+ TonRowExpandDefDirective,
42
+ } from './data-table.directives';
43
+ import type {
44
+ DataTableColumn,
45
+ DataTableLabels,
46
+ DataTablePaginationConfig,
47
+ SortState,
48
+ SortDirection,
49
+ } from './data-table.types';
50
+
51
+ const DEFAULT_PAGINATION: DataTablePaginationConfig = {
52
+ pageSize: 10,
53
+ pageSizeOptions: [5, 10, 25, 50],
54
+ };
55
+
56
+ const DEFAULT_LABELS: Required<DataTableLabels> = {
57
+ rowsPerPage: 'Rows per page',
58
+ rowsSelected: (selected, total) => `${selected} of ${total} row(s) selected`,
59
+ totalRows: (total) => `${total} row(s)`,
60
+ };
61
+
62
+ @Component({
63
+ selector: 'ton-data-table',
64
+ changeDetection: ChangeDetectionStrategy.OnPush,
65
+ imports: [
66
+ NgTemplateOutlet,
67
+ TonTableDirective,
68
+ TonTableHeaderDirective,
69
+ TonTableBodyDirective,
70
+ TonTableRowDirective,
71
+ TonTableHeadDirective,
72
+ TonTableCellDirective,
73
+ TonPaginationComponent,
74
+ TonCheckboxDirective,
75
+ TonInputDirective,
76
+ TonButtonDirective,
77
+ TonSelectComponent,
78
+ TonSkeletonDirective,
79
+ TonDropdownDirective,
80
+ TonDropdownTriggerDirective,
81
+ TonDropdownContentDirective,
82
+ TonMenuItemDirective,
83
+ ],
84
+ template: `
85
+ <!-- Toolbar -->
86
+ @if (filterable() || showExport() || showColumnToggle()) {
87
+ <div class="flex items-center justify-between gap-4 mb-4 flex-wrap">
88
+ @if (filterable()) {
89
+ <input
90
+ tonInput
91
+ [value]="filterText()"
92
+ (input)="onFilterInput($event)"
93
+ placeholder="Filter..."
94
+ class="w-full sm:max-w-sm"
95
+ />
96
+ }
97
+ <div class="flex items-center gap-2">
98
+ @if (showExport()) {
99
+ <button tonBtn variant="outline" size="sm" (click)="onExport()">
100
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="sm:mr-2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"/><path d="M14 2v6h6"/><path d="M12 18v-6"/><path d="m9 15 3-3 3 3"/></svg>
101
+ <span class="hidden sm:inline">Export</span>
102
+ </button>
103
+ }
104
+ @if (showColumnToggle()) {
105
+ <div tonDropdown class="relative">
106
+ <button tonBtn variant="outline" size="sm" tonDropdownTrigger>
107
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="sm:mr-2"><path d="M12 3v18"/><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/><path d="M3 15h18"/></svg>
108
+ <span class="hidden sm:inline">Columns</span>
109
+ </button>
110
+ <div tonDropdownContent class="w-48 right-0 left-auto">
111
+ @for (col of columns(); track col.key) {
112
+ <label tonMenuItem class="flex items-center gap-2 cursor-pointer">
113
+ <input
114
+ type="checkbox"
115
+ tonCheckbox
116
+ [checked]="!hiddenColumns().has(col.key)"
117
+ (change)="toggleColumnVisibility(col.key)"
118
+ (click)="$event.stopPropagation()"
119
+ />
120
+ {{ col.label }}
121
+ </label>
122
+ }
123
+ </div>
124
+ </div>
125
+ }
126
+ </div>
127
+ </div>
128
+ }
129
+
130
+ <!-- Bulk Actions Bar -->
131
+ @if (showBulkActions()) {
132
+ @let selected = selectedRows();
133
+ <div class="flex items-center gap-2 mb-4 p-3 bg-muted/50 rounded-sm border border-border flex-wrap">
134
+ <span class="text-sm font-medium text-muted-foreground mr-2">
135
+ {{ selected.length }} selected
136
+ </span>
137
+ <ng-container
138
+ [ngTemplateOutlet]="bulkActionsDef()!.template"
139
+ [ngTemplateOutletContext]="{ $implicit: selected }"
140
+ />
141
+ <button
142
+ tonBtn variant="ghost" size="sm" class="ml-auto"
143
+ (click)="selectedRows.set([])"
144
+ >
145
+ Clear
146
+ </button>
147
+ </div>
148
+ }
149
+
150
+ <!-- Table -->
151
+ <div class="overflow-auto border border-border rounded-sm">
152
+ <table
153
+ tonTable
154
+ [variant]="variant()"
155
+ [density]="density()"
156
+ [hoverable]="hoverable()"
157
+ [stickyHeader]="stickyHeader()"
158
+ >
159
+ <thead tonTableHeader>
160
+ <tr tonTableRow>
161
+ @if (selectable()) {
162
+ <th tonTableHead class="w-12">
163
+ <input
164
+ type="checkbox"
165
+ tonCheckbox
166
+ [checked]="allSelected()"
167
+ [indeterminate]="someSelected() && !allSelected()"
168
+ (change)="toggleSelectAll()"
169
+ />
170
+ </th>
171
+ }
172
+ @if (expandable()) {
173
+ <th tonTableHead class="w-10"></th>
174
+ }
175
+ @let sort = sortState();
176
+ @let headerDefs = headerCellDefMap();
177
+ @for (col of visibleColumns(); track col.key) {
178
+ <th
179
+ tonTableHead
180
+ [style.width]="col.width ?? null"
181
+ [class]="col.sortable ? 'cursor-pointer select-none' : ''"
182
+ (click)="col.sortable ? toggleSort(col.key) : null"
183
+ >
184
+ @if (headerDefs.has(col.key)) {
185
+ <ng-container
186
+ [ngTemplateOutlet]="headerDefs.get(col.key)!"
187
+ [ngTemplateOutletContext]="{ $implicit: col }"
188
+ />
189
+ } @else {
190
+ <div class="flex items-center gap-1">
191
+ <span>{{ col.label }}</span>
192
+ @if (col.sortable) {
193
+ @let isActive = sort.key === col.key;
194
+ @if (isActive && sort.direction === 'asc') {
195
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m5 12 7-7 7 7"/></svg>
196
+ } @else if (isActive && sort.direction === 'desc') {
197
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m19 12-7 7-7-7"/></svg>
198
+ } @else {
199
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-30"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
200
+ }
201
+ }
202
+ </div>
203
+ }
204
+ </th>
205
+ }
206
+ </tr>
207
+ </thead>
208
+ <tbody tonTableBody>
209
+ @if (loading()) {
210
+ @for (i of skeletonRows(); track i) {
211
+ <tr tonTableRow>
212
+ @if (selectable()) {
213
+ <td tonTableCell class="w-12"><div tonSkeleton class="w-4 h-4 rounded"></div></td>
214
+ }
215
+ @if (expandable()) {
216
+ <td tonTableCell class="w-10"><div tonSkeleton class="w-4 h-4 rounded"></div></td>
217
+ }
218
+ @for (col of visibleColumns(); track col.key) {
219
+ <td tonTableCell [style.width]="col.width ?? null">
220
+ <div tonSkeleton class="w-full h-4 rounded"></div>
221
+ </td>
222
+ }
223
+ </tr>
224
+ }
225
+ } @else if (paginatedData().length === 0) {
226
+ <tr tonTableRow>
227
+ <td
228
+ tonTableCell
229
+ [attr.colspan]="totalColSpan()"
230
+ class="text-center text-muted-foreground py-8"
231
+ >
232
+ {{ noDataText() }}
233
+ </td>
234
+ </tr>
235
+ } @else {
236
+ @let cellDefs = cellDefMap();
237
+ @let cols = visibleColumns();
238
+ @let expandTpl = rowExpandDef();
239
+ @for (row of paginatedData(); track trackByFn(row, $index)) {
240
+ <tr
241
+ tonTableRow
242
+ [attr.data-state]="isSelected(row) ? 'selected' : null"
243
+ (click)="onRowClick(row)"
244
+ class="cursor-pointer"
245
+ >
246
+ @if (selectable()) {
247
+ <td tonTableCell class="w-12">
248
+ <input
249
+ type="checkbox"
250
+ tonCheckbox
251
+ [checked]="isSelected(row)"
252
+ (change)="toggleRowSelection(row)"
253
+ (click)="$event.stopPropagation()"
254
+ />
255
+ </td>
256
+ }
257
+ @if (expandable()) {
258
+ <td tonTableCell class="w-10">
259
+ <button
260
+ class="p-0.5 rounded hover:bg-accent transition-transform duration-150"
261
+ [class.rotate-90]="isExpanded(row)"
262
+ (click)="toggleRowExpansion(row); $event.stopPropagation()"
263
+ >
264
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
265
+ </button>
266
+ </td>
267
+ }
268
+ @for (col of cols; track col.key) {
269
+ <td tonTableCell [style.width]="col.width ?? null">
270
+ @if (cellDefs.has(col.key)) {
271
+ <ng-container
272
+ [ngTemplateOutlet]="cellDefs.get(col.key)!"
273
+ [ngTemplateOutletContext]="{ $implicit: row[col.key], row: row }"
274
+ />
275
+ } @else {
276
+ {{ row[col.key] }}
277
+ }
278
+ </td>
279
+ }
280
+ </tr>
281
+ @if (expandable() && isExpanded(row) && expandTpl) {
282
+ <tr tonTableRow>
283
+ <td tonTableCell [attr.colspan]="totalColSpan()" class="bg-muted/30">
284
+ <ng-container
285
+ [ngTemplateOutlet]="expandTpl.template"
286
+ [ngTemplateOutletContext]="{ $implicit: row }"
287
+ />
288
+ </td>
289
+ </tr>
290
+ }
291
+ }
292
+ }
293
+ </tbody>
294
+ </table>
295
+ </div>
296
+
297
+ <!-- Footer -->
298
+ @if (paginated()) {
299
+ <div class="flex flex-col sm:flex-row items-start sm:items-center justify-between mt-4 gap-3 sm:gap-4">
300
+ <span class="text-sm text-muted-foreground">
301
+ @if (selectable()) {
302
+ {{ resolvedLabels().rowsSelected(selectedRows().length, filteredData().length) }}
303
+ } @else {
304
+ {{ resolvedLabels().totalRows(filteredData().length) }}
305
+ }
306
+ </span>
307
+ <div class="flex items-center gap-3 sm:gap-4 flex-wrap">
308
+ <div class="flex items-center gap-2">
309
+ <span class="hidden sm:inline text-sm text-muted-foreground whitespace-nowrap">{{ resolvedLabels().rowsPerPage }}</span>
310
+ <ton-select
311
+ [options]="pageSizeOptions()"
312
+ [value]="pageSizeValue()"
313
+ (valueChange)="onPageSizeChange($event)"
314
+ size="sm"
315
+ class="w-20"
316
+ />
317
+ </div>
318
+ <ton-pagination
319
+ [currentPage]="currentPage()"
320
+ (currentPageChange)="currentPage.set($event)"
321
+ [totalPages]="totalPages()"
322
+ />
323
+ </div>
324
+ </div>
325
+ }
326
+ `,
327
+ })
328
+ export class TonDataTableComponent {
329
+ // Inputs
330
+ readonly columns = input.required<DataTableColumn[]>();
331
+ readonly data = input.required<Record<string, unknown>[]>();
332
+ readonly variant = input<TableVariant>('default');
333
+ readonly density = input<TableDensity>('normal');
334
+ readonly hoverable = input(true);
335
+ readonly stickyHeader = input(false);
336
+ readonly selectable = input(false);
337
+ readonly paginated = input(true);
338
+ readonly filterable = input(true);
339
+ readonly showExport = input(false);
340
+ readonly showColumnToggle = input(false);
341
+ readonly expandable = input(false);
342
+ readonly loading = input(false);
343
+ readonly loadingRows = input(5);
344
+ readonly paginationConfig = input<DataTablePaginationConfig>(DEFAULT_PAGINATION);
345
+ readonly trackBy = input('');
346
+ readonly noDataText = input('No data available');
347
+ readonly labels = input<DataTableLabels>({});
348
+
349
+ // Model
350
+ readonly selectedRows = model<Record<string, unknown>[]>([]);
351
+
352
+ // Outputs
353
+ readonly sortChanged = output<SortState>();
354
+ readonly rowClicked = output<Record<string, unknown>>();
355
+ readonly dataExported = output<Record<string, unknown>[]>();
356
+
357
+ // Content queries
358
+ readonly cellDefs = contentChildren(TonCellDefDirective);
359
+ readonly headerCellDefs = contentChildren(TonHeaderCellDefDirective);
360
+ readonly bulkActionsDef = contentChild(TonBulkActionsDefDirective);
361
+ readonly rowExpandDef = contentChild(TonRowExpandDefDirective);
362
+
363
+ // Resolved labels
364
+ readonly resolvedLabels = computed(() => ({ ...DEFAULT_LABELS, ...this.labels() }));
365
+
366
+ // Internal state
367
+ readonly sortState = signal<SortState>({ key: '', direction: null });
368
+ readonly filterText = signal('');
369
+ readonly currentPage = signal(1);
370
+ readonly pageSize = signal(10);
371
+ readonly hiddenColumns = signal<Set<string>>(new Set());
372
+ readonly expandedRows = signal<Set<unknown>>(new Set());
373
+
374
+ // Template def maps
375
+ readonly cellDefMap = computed(() => {
376
+ const map = new Map<string, TemplateRef<unknown>>();
377
+ for (const def of this.cellDefs()) {
378
+ map.set(def.tonCell(), def.template);
379
+ }
380
+ return map;
381
+ });
382
+
383
+ readonly headerCellDefMap = computed(() => {
384
+ const map = new Map<string, TemplateRef<unknown>>();
385
+ for (const def of this.headerCellDefs()) {
386
+ map.set(def.tonHeaderCell(), def.template);
387
+ }
388
+ return map;
389
+ });
390
+
391
+ // Visible columns
392
+ readonly visibleColumns = computed(() =>
393
+ this.columns().filter(
394
+ (col) => col.visible !== false && !this.hiddenColumns().has(col.key)
395
+ )
396
+ );
397
+
398
+ // Page size options
399
+ readonly pageSizeOptions = computed<SelectOption[]>(() =>
400
+ this.paginationConfig().pageSizeOptions.map((n) => ({
401
+ value: String(n),
402
+ label: String(n),
403
+ }))
404
+ );
405
+
406
+ readonly pageSizeValue = computed(() => String(this.pageSize()));
407
+
408
+ // Skeleton rows
409
+ readonly skeletonRows = computed(() =>
410
+ Array.from({ length: this.loadingRows() }, (_, i) => i)
411
+ );
412
+
413
+ // Bulk actions visibility
414
+ readonly showBulkActions = computed(
415
+ () =>
416
+ this.selectable() &&
417
+ this.selectedRows().length > 0 &&
418
+ this.bulkActionsDef() != null
419
+ );
420
+
421
+ // Data pipeline (filter uses all columns, not just visible)
422
+ readonly filteredData = computed(() => {
423
+ const text = this.filterText().toLowerCase().trim();
424
+ const rows = this.data();
425
+ if (!text) return rows;
426
+ const cols = this.columns().filter((c) => c.filterable !== false);
427
+ return rows.filter((row) =>
428
+ cols.some((col) =>
429
+ String(row[col.key] ?? '').toLowerCase().includes(text)
430
+ )
431
+ );
432
+ });
433
+
434
+ readonly sortedData = computed(() => {
435
+ const { key, direction } = this.sortState();
436
+ const rows = this.filteredData();
437
+ if (!key || !direction) return rows;
438
+ return [...rows].sort((a, b) => {
439
+ const aVal = a[key];
440
+ const bVal = b[key];
441
+ if (aVal == null && bVal == null) return 0;
442
+ if (aVal == null) return direction === 'asc' ? -1 : 1;
443
+ if (bVal == null) return direction === 'asc' ? 1 : -1;
444
+ if (typeof aVal === 'number' && typeof bVal === 'number') {
445
+ return direction === 'asc' ? aVal - bVal : bVal - aVal;
446
+ }
447
+ const cmp = String(aVal).localeCompare(String(bVal));
448
+ return direction === 'asc' ? cmp : -cmp;
449
+ });
450
+ });
451
+
452
+ readonly totalPages = computed(() =>
453
+ Math.max(1, Math.ceil(this.filteredData().length / this.pageSize()))
454
+ );
455
+
456
+ readonly paginatedData = computed(() => {
457
+ if (!this.paginated()) return this.sortedData();
458
+ const start = (this.currentPage() - 1) * this.pageSize();
459
+ return this.sortedData().slice(start, start + this.pageSize());
460
+ });
461
+
462
+ readonly totalColSpan = computed(
463
+ () =>
464
+ this.visibleColumns().length +
465
+ (this.selectable() ? 1 : 0) +
466
+ (this.expandable() ? 1 : 0)
467
+ );
468
+
469
+ // Selection computed
470
+ readonly allSelected = computed(() => {
471
+ const page = this.paginatedData();
472
+ if (page.length === 0) return false;
473
+ const selected = this.selectedRows();
474
+ return page.every((row) => this.isRowInList(row, selected));
475
+ });
476
+
477
+ readonly someSelected = computed(() => {
478
+ const page = this.paginatedData();
479
+ const selected = this.selectedRows();
480
+ return page.some((row) => this.isRowInList(row, selected));
481
+ });
482
+
483
+ constructor() {
484
+ effect(() => {
485
+ const config = this.paginationConfig();
486
+ untracked(() => this.pageSize.set(config.pageSize));
487
+ });
488
+
489
+ effect(() => {
490
+ this.filterText();
491
+ this.pageSize();
492
+ const data = this.data();
493
+ untracked(() => {
494
+ this.currentPage.set(1);
495
+ const selected = this.selectedRows();
496
+ if (selected.length) {
497
+ const cleaned = selected.filter((row) => this.isRowInList(row, data));
498
+ if (cleaned.length !== selected.length) {
499
+ this.selectedRows.set(cleaned);
500
+ }
501
+ }
502
+ });
503
+ });
504
+ }
505
+
506
+ // Sort
507
+ toggleSort(key: string): void {
508
+ const current = this.sortState();
509
+ let direction: SortDirection;
510
+ if (current.key !== key) {
511
+ direction = 'asc';
512
+ } else if (current.direction === 'asc') {
513
+ direction = 'desc';
514
+ } else if (current.direction === 'desc') {
515
+ direction = null;
516
+ } else {
517
+ direction = 'asc';
518
+ }
519
+ const next: SortState = { key: direction ? key : '', direction };
520
+ this.sortState.set(next);
521
+ this.sortChanged.emit(next);
522
+ }
523
+
524
+ // Filter
525
+ onFilterInput(event: Event): void {
526
+ this.filterText.set((event.target as HTMLInputElement).value);
527
+ }
528
+
529
+ // Page size
530
+ onPageSizeChange(value: string): void {
531
+ this.pageSize.set(Number(value));
532
+ }
533
+
534
+ // Selection
535
+ toggleSelectAll(): void {
536
+ if (this.allSelected()) {
537
+ const page = this.paginatedData();
538
+ this.selectedRows.update((sel) =>
539
+ sel.filter((r) => !page.some((p) => this.rowsEqual(r, p)))
540
+ );
541
+ } else {
542
+ const page = this.paginatedData();
543
+ this.selectedRows.update((sel) => {
544
+ const newSel = [...sel];
545
+ for (const row of page) {
546
+ if (!this.isRowInList(row, newSel)) newSel.push(row);
547
+ }
548
+ return newSel;
549
+ });
550
+ }
551
+ }
552
+
553
+ toggleRowSelection(row: Record<string, unknown>): void {
554
+ this.selectedRows.update((sel) =>
555
+ this.isRowInList(row, sel)
556
+ ? sel.filter((r) => !this.rowsEqual(r, row))
557
+ : [...sel, row]
558
+ );
559
+ }
560
+
561
+ // Row click
562
+ onRowClick(row: Record<string, unknown>): void {
563
+ this.rowClicked.emit(row);
564
+ }
565
+
566
+ // Export
567
+ onExport(): void {
568
+ this.dataExported.emit(this.filteredData());
569
+ }
570
+
571
+ // Column visibility
572
+ toggleColumnVisibility(key: string): void {
573
+ this.hiddenColumns.update((set) => {
574
+ const next = new Set(set);
575
+ if (next.has(key)) next.delete(key);
576
+ else next.add(key);
577
+ return next;
578
+ });
579
+ }
580
+
581
+ // Expansion
582
+ toggleRowExpansion(row: Record<string, unknown>): void {
583
+ const key = this.trackBy() ? row[this.trackBy()] : row;
584
+ this.expandedRows.update((set) => {
585
+ const next = new Set(set);
586
+ if (next.has(key)) next.delete(key);
587
+ else next.add(key);
588
+ return next;
589
+ });
590
+ }
591
+
592
+ isExpanded(row: Record<string, unknown>): boolean {
593
+ const key = this.trackBy() ? row[this.trackBy()] : row;
594
+ return this.expandedRows().has(key);
595
+ }
596
+
597
+ // Helpers
598
+ isSelected(row: Record<string, unknown>): boolean {
599
+ return this.isRowInList(row, this.selectedRows());
600
+ }
601
+
602
+ trackByFn(row: Record<string, unknown>, index: number): unknown {
603
+ const key = this.trackBy();
604
+ return key ? row[key] : index;
605
+ }
606
+
607
+ private isRowInList(
608
+ row: Record<string, unknown>,
609
+ list: Record<string, unknown>[]
610
+ ): boolean {
611
+ return list.some((r) => this.rowsEqual(r, row));
612
+ }
613
+
614
+ private rowsEqual(
615
+ a: Record<string, unknown>,
616
+ b: Record<string, unknown>
617
+ ): boolean {
618
+ const key = this.trackBy();
619
+ if (key) return a[key] === b[key];
620
+ return a === b;
621
+ }
622
+ }
@@ -0,0 +1,31 @@
1
+ import { Directive, TemplateRef, inject, input } from '@angular/core';
2
+
3
+ @Directive({
4
+ selector: '[tonCell]',
5
+ })
6
+ export class TonCellDefDirective {
7
+ readonly tonCell = input.required<string>();
8
+ readonly template = inject(TemplateRef);
9
+ }
10
+
11
+ @Directive({
12
+ selector: '[tonHeaderCell]',
13
+ })
14
+ export class TonHeaderCellDefDirective {
15
+ readonly tonHeaderCell = input.required<string>();
16
+ readonly template = inject(TemplateRef);
17
+ }
18
+
19
+ @Directive({
20
+ selector: '[tonBulkActions]',
21
+ })
22
+ export class TonBulkActionsDefDirective {
23
+ readonly template = inject(TemplateRef);
24
+ }
25
+
26
+ @Directive({
27
+ selector: '[tonRowExpand]',
28
+ })
29
+ export class TonRowExpandDefDirective {
30
+ readonly template = inject(TemplateRef);
31
+ }
@@ -0,0 +1,26 @@
1
+ export interface DataTableColumn {
2
+ key: string;
3
+ label: string;
4
+ sortable?: boolean;
5
+ filterable?: boolean;
6
+ width?: string;
7
+ visible?: boolean;
8
+ }
9
+
10
+ export type SortDirection = 'asc' | 'desc' | null;
11
+
12
+ export interface SortState {
13
+ key: string;
14
+ direction: SortDirection;
15
+ }
16
+
17
+ export interface DataTablePaginationConfig {
18
+ pageSize: number;
19
+ pageSizeOptions: number[];
20
+ }
21
+
22
+ export interface DataTableLabels {
23
+ rowsPerPage?: string;
24
+ rowsSelected?: (selected: number, total: number) => string;
25
+ totalRows?: (total: number) => string;
26
+ }
@@ -0,0 +1,14 @@
1
+ export { TonDataTableComponent } from './data-table.component';
2
+ export {
3
+ TonCellDefDirective,
4
+ TonHeaderCellDefDirective,
5
+ TonBulkActionsDefDirective,
6
+ TonRowExpandDefDirective,
7
+ } from './data-table.directives';
8
+ export type {
9
+ DataTableColumn,
10
+ SortState,
11
+ SortDirection,
12
+ DataTableLabels,
13
+ DataTablePaginationConfig,
14
+ } from './data-table.types';