@frame-kit/ui-ng 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 (220) hide show
  1. package/COMPONENTS.md +683 -0
  2. package/DEVELOPMENT_GUIDE.md +1102 -0
  3. package/LICENSE +21 -0
  4. package/README.md +69 -0
  5. package/THEMING.md +130 -0
  6. package/core/headline/README.md +121 -0
  7. package/core/icon/README.md +173 -0
  8. package/core/image/README.md +210 -0
  9. package/core/link/README.md +297 -0
  10. package/core/separator/README.md +145 -0
  11. package/core/text/README.md +240 -0
  12. package/directives/infinite-scroll/README.md +102 -0
  13. package/directives/spotlight/README.md +154 -0
  14. package/directives/tooltip/README.md +147 -0
  15. package/docs/endpoint-link/README.md +142 -0
  16. package/docs/method-badge/README.md +154 -0
  17. package/fesm2022/frame-kit-ui-ng-core-headline.mjs +122 -0
  18. package/fesm2022/frame-kit-ui-ng-core-headline.mjs.map +1 -0
  19. package/fesm2022/frame-kit-ui-ng-core-icon.mjs +189 -0
  20. package/fesm2022/frame-kit-ui-ng-core-icon.mjs.map +1 -0
  21. package/fesm2022/frame-kit-ui-ng-core-image.mjs +123 -0
  22. package/fesm2022/frame-kit-ui-ng-core-image.mjs.map +1 -0
  23. package/fesm2022/frame-kit-ui-ng-core-link.mjs +369 -0
  24. package/fesm2022/frame-kit-ui-ng-core-link.mjs.map +1 -0
  25. package/fesm2022/frame-kit-ui-ng-core-separator.mjs +59 -0
  26. package/fesm2022/frame-kit-ui-ng-core-separator.mjs.map +1 -0
  27. package/fesm2022/frame-kit-ui-ng-core-text.mjs +204 -0
  28. package/fesm2022/frame-kit-ui-ng-core-text.mjs.map +1 -0
  29. package/fesm2022/frame-kit-ui-ng-directives-infinite-scroll.mjs +74 -0
  30. package/fesm2022/frame-kit-ui-ng-directives-infinite-scroll.mjs.map +1 -0
  31. package/fesm2022/frame-kit-ui-ng-directives-spotlight.mjs +76 -0
  32. package/fesm2022/frame-kit-ui-ng-directives-spotlight.mjs.map +1 -0
  33. package/fesm2022/frame-kit-ui-ng-directives-tooltip.mjs +425 -0
  34. package/fesm2022/frame-kit-ui-ng-directives-tooltip.mjs.map +1 -0
  35. package/fesm2022/frame-kit-ui-ng-docs-endpoint-link.mjs +63 -0
  36. package/fesm2022/frame-kit-ui-ng-docs-endpoint-link.mjs.map +1 -0
  37. package/fesm2022/frame-kit-ui-ng-docs-method-badge.mjs +43 -0
  38. package/fesm2022/frame-kit-ui-ng-docs-method-badge.mjs.map +1 -0
  39. package/fesm2022/frame-kit-ui-ng-forms.mjs +3632 -0
  40. package/fesm2022/frame-kit-ui-ng-forms.mjs.map +1 -0
  41. package/fesm2022/frame-kit-ui-ng-layouts-app-shell.mjs +239 -0
  42. package/fesm2022/frame-kit-ui-ng-layouts-app-shell.mjs.map +1 -0
  43. package/fesm2022/frame-kit-ui-ng-layouts-content-split.mjs +132 -0
  44. package/fesm2022/frame-kit-ui-ng-layouts-content-split.mjs.map +1 -0
  45. package/fesm2022/frame-kit-ui-ng-services-overlay-orchestrator.mjs +133 -0
  46. package/fesm2022/frame-kit-ui-ng-services-overlay-orchestrator.mjs.map +1 -0
  47. package/fesm2022/frame-kit-ui-ng-services-spotlight.mjs +60 -0
  48. package/fesm2022/frame-kit-ui-ng-services-spotlight.mjs.map +1 -0
  49. package/fesm2022/frame-kit-ui-ng-services-toast.mjs +166 -0
  50. package/fesm2022/frame-kit-ui-ng-services-toast.mjs.map +1 -0
  51. package/fesm2022/frame-kit-ui-ng-ui-accordion.mjs +214 -0
  52. package/fesm2022/frame-kit-ui-ng-ui-accordion.mjs.map +1 -0
  53. package/fesm2022/frame-kit-ui-ng-ui-alert.mjs +82 -0
  54. package/fesm2022/frame-kit-ui-ng-ui-alert.mjs.map +1 -0
  55. package/fesm2022/frame-kit-ui-ng-ui-avatar-stack.mjs +76 -0
  56. package/fesm2022/frame-kit-ui-ng-ui-avatar-stack.mjs.map +1 -0
  57. package/fesm2022/frame-kit-ui-ng-ui-avatar.mjs +81 -0
  58. package/fesm2022/frame-kit-ui-ng-ui-avatar.mjs.map +1 -0
  59. package/fesm2022/frame-kit-ui-ng-ui-badge.mjs +81 -0
  60. package/fesm2022/frame-kit-ui-ng-ui-badge.mjs.map +1 -0
  61. package/fesm2022/frame-kit-ui-ng-ui-breadcrumb.mjs +68 -0
  62. package/fesm2022/frame-kit-ui-ng-ui-breadcrumb.mjs.map +1 -0
  63. package/fesm2022/frame-kit-ui-ng-ui-button.mjs +108 -0
  64. package/fesm2022/frame-kit-ui-ng-ui-button.mjs.map +1 -0
  65. package/fesm2022/frame-kit-ui-ng-ui-callout.mjs +58 -0
  66. package/fesm2022/frame-kit-ui-ng-ui-callout.mjs.map +1 -0
  67. package/fesm2022/frame-kit-ui-ng-ui-card.mjs +70 -0
  68. package/fesm2022/frame-kit-ui-ng-ui-card.mjs.map +1 -0
  69. package/fesm2022/frame-kit-ui-ng-ui-copyable-field.mjs +113 -0
  70. package/fesm2022/frame-kit-ui-ng-ui-copyable-field.mjs.map +1 -0
  71. package/fesm2022/frame-kit-ui-ng-ui-data-table.mjs +1288 -0
  72. package/fesm2022/frame-kit-ui-ng-ui-data-table.mjs.map +1 -0
  73. package/fesm2022/frame-kit-ui-ng-ui-dialog.mjs +456 -0
  74. package/fesm2022/frame-kit-ui-ng-ui-dialog.mjs.map +1 -0
  75. package/fesm2022/frame-kit-ui-ng-ui-drawer.mjs +398 -0
  76. package/fesm2022/frame-kit-ui-ng-ui-drawer.mjs.map +1 -0
  77. package/fesm2022/frame-kit-ui-ng-ui-dropdown-menu.mjs +398 -0
  78. package/fesm2022/frame-kit-ui-ng-ui-dropdown-menu.mjs.map +1 -0
  79. package/fesm2022/frame-kit-ui-ng-ui-editable-field.mjs +125 -0
  80. package/fesm2022/frame-kit-ui-ng-ui-editable-field.mjs.map +1 -0
  81. package/fesm2022/frame-kit-ui-ng-ui-icon-badge.mjs +113 -0
  82. package/fesm2022/frame-kit-ui-ng-ui-icon-badge.mjs.map +1 -0
  83. package/fesm2022/frame-kit-ui-ng-ui-icon-list.mjs +111 -0
  84. package/fesm2022/frame-kit-ui-ng-ui-icon-list.mjs.map +1 -0
  85. package/fesm2022/frame-kit-ui-ng-ui-inline-edit.mjs +103 -0
  86. package/fesm2022/frame-kit-ui-ng-ui-inline-edit.mjs.map +1 -0
  87. package/fesm2022/frame-kit-ui-ng-ui-list-editor.mjs +135 -0
  88. package/fesm2022/frame-kit-ui-ng-ui-list-editor.mjs.map +1 -0
  89. package/fesm2022/frame-kit-ui-ng-ui-loader.mjs +81 -0
  90. package/fesm2022/frame-kit-ui-ng-ui-loader.mjs.map +1 -0
  91. package/fesm2022/frame-kit-ui-ng-ui-menu-item.mjs +79 -0
  92. package/fesm2022/frame-kit-ui-ng-ui-menu-item.mjs.map +1 -0
  93. package/fesm2022/frame-kit-ui-ng-ui-nav-brand.mjs +40 -0
  94. package/fesm2022/frame-kit-ui-ng-ui-nav-brand.mjs.map +1 -0
  95. package/fesm2022/frame-kit-ui-ng-ui-nav-group.mjs +110 -0
  96. package/fesm2022/frame-kit-ui-ng-ui-nav-group.mjs.map +1 -0
  97. package/fesm2022/frame-kit-ui-ng-ui-nav-separator.mjs +91 -0
  98. package/fesm2022/frame-kit-ui-ng-ui-nav-separator.mjs.map +1 -0
  99. package/fesm2022/frame-kit-ui-ng-ui-node-tree-breadcrumb.mjs +86 -0
  100. package/fesm2022/frame-kit-ui-ng-ui-node-tree-breadcrumb.mjs.map +1 -0
  101. package/fesm2022/frame-kit-ui-ng-ui-node-tree.mjs +443 -0
  102. package/fesm2022/frame-kit-ui-ng-ui-node-tree.mjs.map +1 -0
  103. package/fesm2022/frame-kit-ui-ng-ui-note.mjs +56 -0
  104. package/fesm2022/frame-kit-ui-ng-ui-note.mjs.map +1 -0
  105. package/fesm2022/frame-kit-ui-ng-ui-numbered-list.mjs +105 -0
  106. package/fesm2022/frame-kit-ui-ng-ui-numbered-list.mjs.map +1 -0
  107. package/fesm2022/frame-kit-ui-ng-ui-pagination.mjs +110 -0
  108. package/fesm2022/frame-kit-ui-ng-ui-pagination.mjs.map +1 -0
  109. package/fesm2022/frame-kit-ui-ng-ui-progress-bar.mjs +129 -0
  110. package/fesm2022/frame-kit-ui-ng-ui-progress-bar.mjs.map +1 -0
  111. package/fesm2022/frame-kit-ui-ng-ui-sidenav-link.mjs +42 -0
  112. package/fesm2022/frame-kit-ui-ng-ui-sidenav-link.mjs.map +1 -0
  113. package/fesm2022/frame-kit-ui-ng-ui-tabs.mjs +894 -0
  114. package/fesm2022/frame-kit-ui-ng-ui-tabs.mjs.map +1 -0
  115. package/fesm2022/frame-kit-ui-ng-ui-timeline.mjs +81 -0
  116. package/fesm2022/frame-kit-ui-ng-ui-timeline.mjs.map +1 -0
  117. package/fesm2022/frame-kit-ui-ng-ui-toast.mjs +179 -0
  118. package/fesm2022/frame-kit-ui-ng-ui-toast.mjs.map +1 -0
  119. package/fesm2022/frame-kit-ui-ng-ui-user-menu.mjs +143 -0
  120. package/fesm2022/frame-kit-ui-ng-ui-user-menu.mjs.map +1 -0
  121. package/fesm2022/frame-kit-ui-ng-ui-wizard-dialog.mjs +191 -0
  122. package/fesm2022/frame-kit-ui-ng-ui-wizard-dialog.mjs.map +1 -0
  123. package/fesm2022/frame-kit-ui-ng.mjs +58 -0
  124. package/fesm2022/frame-kit-ui-ng.mjs.map +1 -0
  125. package/layouts/app-shell/README.md +357 -0
  126. package/layouts/content-split/README.md +180 -0
  127. package/package.json +253 -0
  128. package/services/overlay-orchestrator/README.md +184 -0
  129. package/services/spotlight/README.md +61 -0
  130. package/services/toast/README.md +118 -0
  131. package/types/frame-kit-ui-ng-core-headline.d.ts +38 -0
  132. package/types/frame-kit-ui-ng-core-icon.d.ts +74 -0
  133. package/types/frame-kit-ui-ng-core-image.d.ts +93 -0
  134. package/types/frame-kit-ui-ng-core-link.d.ts +251 -0
  135. package/types/frame-kit-ui-ng-core-separator.d.ts +28 -0
  136. package/types/frame-kit-ui-ng-core-text.d.ts +186 -0
  137. package/types/frame-kit-ui-ng-directives-infinite-scroll.d.ts +42 -0
  138. package/types/frame-kit-ui-ng-directives-spotlight.d.ts +51 -0
  139. package/types/frame-kit-ui-ng-directives-tooltip.d.ts +70 -0
  140. package/types/frame-kit-ui-ng-docs-endpoint-link.d.ts +43 -0
  141. package/types/frame-kit-ui-ng-docs-method-badge.d.ts +30 -0
  142. package/types/frame-kit-ui-ng-forms.d.ts +1674 -0
  143. package/types/frame-kit-ui-ng-layouts-app-shell.d.ts +75 -0
  144. package/types/frame-kit-ui-ng-layouts-content-split.d.ts +43 -0
  145. package/types/frame-kit-ui-ng-services-overlay-orchestrator.d.ts +96 -0
  146. package/types/frame-kit-ui-ng-services-spotlight.d.ts +32 -0
  147. package/types/frame-kit-ui-ng-services-toast.d.ts +100 -0
  148. package/types/frame-kit-ui-ng-ui-accordion.d.ts +86 -0
  149. package/types/frame-kit-ui-ng-ui-alert.d.ts +34 -0
  150. package/types/frame-kit-ui-ng-ui-avatar-stack.d.ts +38 -0
  151. package/types/frame-kit-ui-ng-ui-avatar.d.ts +36 -0
  152. package/types/frame-kit-ui-ng-ui-badge.d.ts +33 -0
  153. package/types/frame-kit-ui-ng-ui-breadcrumb.d.ts +45 -0
  154. package/types/frame-kit-ui-ng-ui-button.d.ts +48 -0
  155. package/types/frame-kit-ui-ng-ui-callout.d.ts +26 -0
  156. package/types/frame-kit-ui-ng-ui-card.d.ts +30 -0
  157. package/types/frame-kit-ui-ng-ui-copyable-field.d.ts +62 -0
  158. package/types/frame-kit-ui-ng-ui-data-table.d.ts +482 -0
  159. package/types/frame-kit-ui-ng-ui-dialog.d.ts +166 -0
  160. package/types/frame-kit-ui-ng-ui-drawer.d.ts +130 -0
  161. package/types/frame-kit-ui-ng-ui-dropdown-menu.d.ts +77 -0
  162. package/types/frame-kit-ui-ng-ui-editable-field.d.ts +65 -0
  163. package/types/frame-kit-ui-ng-ui-icon-badge.d.ts +45 -0
  164. package/types/frame-kit-ui-ng-ui-icon-list.d.ts +67 -0
  165. package/types/frame-kit-ui-ng-ui-inline-edit.d.ts +44 -0
  166. package/types/frame-kit-ui-ng-ui-list-editor.d.ts +56 -0
  167. package/types/frame-kit-ui-ng-ui-loader.d.ts +32 -0
  168. package/types/frame-kit-ui-ng-ui-menu-item.d.ts +27 -0
  169. package/types/frame-kit-ui-ng-ui-nav-brand.d.ts +25 -0
  170. package/types/frame-kit-ui-ng-ui-nav-group.d.ts +60 -0
  171. package/types/frame-kit-ui-ng-ui-nav-separator.d.ts +33 -0
  172. package/types/frame-kit-ui-ng-ui-node-tree-breadcrumb.d.ts +35 -0
  173. package/types/frame-kit-ui-ng-ui-node-tree.d.ts +135 -0
  174. package/types/frame-kit-ui-ng-ui-note.d.ts +22 -0
  175. package/types/frame-kit-ui-ng-ui-numbered-list.d.ts +52 -0
  176. package/types/frame-kit-ui-ng-ui-pagination.d.ts +49 -0
  177. package/types/frame-kit-ui-ng-ui-progress-bar.d.ts +50 -0
  178. package/types/frame-kit-ui-ng-ui-sidenav-link.d.ts +24 -0
  179. package/types/frame-kit-ui-ng-ui-tabs.d.ts +266 -0
  180. package/types/frame-kit-ui-ng-ui-timeline.d.ts +42 -0
  181. package/types/frame-kit-ui-ng-ui-toast.d.ts +56 -0
  182. package/types/frame-kit-ui-ng-ui-user-menu.d.ts +87 -0
  183. package/types/frame-kit-ui-ng-ui-wizard-dialog.d.ts +116 -0
  184. package/types/frame-kit-ui-ng.d.ts +53 -0
  185. package/ui/accordion/README.md +261 -0
  186. package/ui/alert/README.md +211 -0
  187. package/ui/avatar/README.md +167 -0
  188. package/ui/avatar-stack/README.md +164 -0
  189. package/ui/badge/README.md +162 -0
  190. package/ui/breadcrumb/README.md +240 -0
  191. package/ui/button/README.md +184 -0
  192. package/ui/callout/README.md +159 -0
  193. package/ui/card/README.md +174 -0
  194. package/ui/copyable-field/README.md +235 -0
  195. package/ui/data-table/README.md +408 -0
  196. package/ui/dialog/README.md +222 -0
  197. package/ui/drawer/README.md +274 -0
  198. package/ui/dropdown-menu/README.md +336 -0
  199. package/ui/editable-field/README.md +171 -0
  200. package/ui/icon-badge/README.md +131 -0
  201. package/ui/icon-list/README.md +205 -0
  202. package/ui/inline-edit/README.md +135 -0
  203. package/ui/list-editor/README.md +162 -0
  204. package/ui/loader/README.md +160 -0
  205. package/ui/menu-item/README.md +204 -0
  206. package/ui/nav-brand/README.md +111 -0
  207. package/ui/nav-group/README.md +145 -0
  208. package/ui/nav-separator/README.md +44 -0
  209. package/ui/node-tree/README.md +278 -0
  210. package/ui/node-tree-breadcrumb/README.md +164 -0
  211. package/ui/note/README.md +146 -0
  212. package/ui/numbered-list/README.md +187 -0
  213. package/ui/pagination/README.md +174 -0
  214. package/ui/progress-bar/README.md +223 -0
  215. package/ui/sidenav-link/README.md +214 -0
  216. package/ui/tabs/README.md +204 -0
  217. package/ui/timeline/README.md +285 -0
  218. package/ui/toast/README.md +243 -0
  219. package/ui/user-menu/README.md +260 -0
  220. package/ui/wizard-dialog/README.md +283 -0
@@ -0,0 +1,235 @@
1
+ # fk-copyable-field
2
+
3
+ A read-only value paired with a copy-to-clipboard button. Use it when a
4
+ user needs to copy a secret, token, ID, URL, or any value that's easier to
5
+ copy than to retype — API keys, OAuth client IDs, session tokens, signed
6
+ URLs, etc.
7
+
8
+ ---
9
+
10
+ ## API
11
+
12
+ ### Inputs
13
+
14
+ | Input | Type | Default | Description |
15
+ | -------------------- | ------------------- | ----------------------- | -------------------------------------------------------------------------- |
16
+ | `value` | `string` | _required_ | The text that gets written to the clipboard |
17
+ | `label` | `string \| null` | `null` | Optional label rendered above the field |
18
+ | `size` | `CopyableFieldSize` | `"md"` | Size modifier — `"sm"`, `"md"`, or `"lg"` |
19
+ | `mono` | `boolean` | `true` | Render the value in a monospace font |
20
+ | `copyAriaLabel` | `string` | `"Copy to clipboard"` | Accessible label for the copy button |
21
+ | `copiedAnnouncement` | `string` | `"Copied to clipboard"` | Message announced via `role="status"` after a successful copy |
22
+ | `copiedDuration` | `number` | `2000` | Milliseconds before the icon reverts and the announcement clears |
23
+ | `id` | `string \| null` | `null` | Optional ID applied to the value element |
24
+ | `ariaLabel` | `string \| null` | `null` | Accessible label for the value element (use when the label prop is absent) |
25
+ | `className` | `string` | `""` | Additional CSS classes merged onto the host element |
26
+
27
+ ### Outputs
28
+
29
+ | Output | Type | Description |
30
+ | ------------ | --------- | -------------------------------------------------------- |
31
+ | `copied` | `string` | Emits the copied value when the clipboard write succeeds |
32
+ | `copyFailed` | `unknown` | Emits the rejection error when the clipboard API rejects |
33
+
34
+ ### Content Projection Slots
35
+
36
+ | Attribute | Description |
37
+ | -------------- | ---------------------------------------------------------------------------------------------------------------- |
38
+ | `fkCopyIcon` | Optional custom icon shown on the copy button in the idle state. Defaults to an inline clipboard SVG. |
39
+ | `fkCopiedIcon` | Optional custom icon shown for `copiedDuration` ms after a successful copy. Defaults to an inline checkmark SVG. |
40
+
41
+ ```html
42
+ <fk-copyable-field [value]="apiKey">
43
+ <fk-icon fkCopyIcon name="clipboard-outline" size="sm" />
44
+ <fk-icon fkCopiedIcon name="check-outline" size="sm" />
45
+ </fk-copyable-field>
46
+ ```
47
+
48
+ If a slot is not populated, the library renders its own inline SVG —
49
+ consumers don't have to register anything to get a visible button.
50
+
51
+ ---
52
+
53
+ ## Features
54
+
55
+ - One-click copy to the system clipboard via `navigator.clipboard`
56
+ - Icon swap + screen-reader announcement confirm a successful copy
57
+ - Auto-reverts after `copiedDuration` ms
58
+ - Emits `copied` / `copyFailed` for analytics or custom UX
59
+ - Three sizes (`sm`, `md`, `lg`) and optional monospace value styling
60
+ - Token-driven styling with two-tier fallbacks
61
+ - Renders correctly with no theme file loaded
62
+
63
+ ---
64
+
65
+ ## Quick Start
66
+
67
+ ```html
68
+ <fk-copyable-field label="API key" [value]="apiKey" />
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Import
74
+
75
+ ```ts
76
+ import { CopyableFieldComponent } from '@frame-kit/ui-ng';
77
+ ```
78
+
79
+ ```ts
80
+ @Component({
81
+ selector: 'app-example',
82
+ imports: [CopyableFieldComponent],
83
+ templateUrl: './example.component.html',
84
+ })
85
+ export class ExampleComponent {}
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Selector
91
+
92
+ ```html
93
+ <fk-copyable-field [value]="..." />
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Examples
99
+
100
+ ### Basic API key field
101
+
102
+ ```html
103
+ <fk-copyable-field label="Your new API key" [value]="apiKey" />
104
+ ```
105
+
106
+ ### Non-mono (plain text)
107
+
108
+ ```html
109
+ <fk-copyable-field label="Org name" [mono]="false" value="Acme Realty" />
110
+ ```
111
+
112
+ ### Size variants
113
+
114
+ ```html
115
+ <fk-copyable-field size="sm" value="sm_value" />
116
+ <fk-copyable-field size="md" value="md_value" />
117
+ <fk-copyable-field size="lg" value="lg_value" />
118
+ ```
119
+
120
+ ### Reacting to a successful copy
121
+
122
+ ```html
123
+ <fk-copyable-field label="Client secret" [value]="secret" (copied)="onCopied($event)" (copyFailed)="onCopyFailed($event)" />
124
+ ```
125
+
126
+ ### Icon overrides
127
+
128
+ ```html
129
+ <fk-copyable-field [value]="value">
130
+ <fk-icon fkCopyIcon name="clipboard-outline" size="sm" />
131
+ <fk-icon fkCopiedIcon name="check-outline" size="sm" />
132
+ </fk-copyable-field>
133
+ ```
134
+
135
+ The projected element just needs the `fkCopyIcon` / `fkCopiedIcon`
136
+ attribute — it doesn't have to be `fk-icon`. Any inline SVG, icon
137
+ component, or even plain text works.
138
+
139
+ ---
140
+
141
+ ## Accessibility
142
+
143
+ - The copy button is a real `<button>` element with `aria-label` from the
144
+ `copyAriaLabel` input — keyboard-reachable, Enter / Space activate it
145
+ - After a successful copy, a visually-hidden element with
146
+ `role="status"` and `aria-live="polite"` announces
147
+ `copiedAnnouncement` ("Copied to clipboard" by default)
148
+ - Icons are marked `aria-hidden="true"` — all accessible text lives on
149
+ the button's label
150
+ - Focus ring uses `--fk-focus-ring`
151
+
152
+ ---
153
+
154
+ ## Design Tokens
155
+
156
+ `fk-copyable-field` is styled entirely through design tokens.
157
+
158
+ ### Component-specific tokens
159
+
160
+ ```
161
+ --fk-copyable-field-gap
162
+ --fk-copyable-field-bg
163
+ --fk-copyable-field-border-color
164
+ --fk-copyable-field-radius
165
+ --fk-copyable-field-value-color
166
+
167
+ --fk-copyable-field-label-color
168
+ --fk-copyable-field-label-font-size
169
+ --fk-copyable-field-label-font-weight
170
+ --fk-copyable-field-label-gap
171
+
172
+ --fk-copyable-field-action-color
173
+ --fk-copyable-field-action-color-hover
174
+ --fk-copyable-field-action-bg-hover
175
+ --fk-copyable-field-action-padding
176
+ --fk-copyable-field-action-radius
177
+
178
+ --fk-copyable-field-padding-sm
179
+ --fk-copyable-field-padding-md
180
+ --fk-copyable-field-padding-lg
181
+ --fk-copyable-field-font-size-sm
182
+ --fk-copyable-field-font-size-md
183
+ --fk-copyable-field-font-size-lg
184
+ --fk-copyable-field-mono-font-family
185
+ ```
186
+
187
+ ### Shared tokens
188
+
189
+ ```
190
+ --fk-rhythm-1
191
+ --fk-rhythm-2
192
+ --fk-rhythm-3
193
+ --fk-rhythm-4
194
+ --fk-color-text
195
+ --fk-color-muted
196
+ --fk-color-border
197
+ --fk-color-surface-muted
198
+ --fk-color-surface-dim
199
+ --fk-font-family-mono
200
+ --fk-typography-caption-font-size
201
+ --fk-typography-small-font-size
202
+ --fk-typography-body-font-size
203
+ --fk-font-weight-medium
204
+ --fk-radius-md
205
+ --fk-radius-sm
206
+ --fk-focus-ring
207
+ ```
208
+
209
+ ### Customizing Tokens
210
+
211
+ ```scss
212
+ :root {
213
+ --fk-copyable-field-bg: #0f1626;
214
+ --fk-copyable-field-border-color: #1e2a44;
215
+ --fk-copyable-field-value-color: #e6edf7;
216
+ --fk-copyable-field-action-color: #8b98b5;
217
+ }
218
+ ```
219
+
220
+ ---
221
+
222
+ ## Behavior Notes
223
+
224
+ - Uses `navigator.clipboard.writeText(value)`; when the API is unavailable
225
+ or the user denies clipboard permission, `copyFailed` fires with the
226
+ caught error and the button's icon does not swap
227
+ - The "copied" state is local to the component — rapidly clicking the
228
+ button resets the revert timer so the confirmation always stays for
229
+ `copiedDuration` ms after the most recent click
230
+ - Long values wrap with `word-break: break-all` so the entire value stays
231
+ visible rather than being truncated
232
+ - The component ships with inline SVG icons for both idle and copied
233
+ states, so it renders a visible button with no consumer setup. Override
234
+ either by projecting an element carrying the `fkCopyIcon` or
235
+ `fkCopiedIcon` attribute
@@ -0,0 +1,408 @@
1
+ # fk-data-table
2
+
3
+ A token-driven, responsive data table component for Angular with sorting, virtual scrolling, column resizing, column pinning, multi-row selection (controlled or uncontrolled), and a mobile stacked card view.
4
+
5
+ ---
6
+
7
+ ## API
8
+
9
+ ### Inputs
10
+
11
+ | Input | Type | Default | Description |
12
+ | ------------------------- | ---------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------- |
13
+ | `columns` | `ColumnDef<T>[]` | `[]` | Column definitions describing headers, accessors, and behavior |
14
+ | `rows` | `T[]` | `[]` | Data rows to render |
15
+ | `config` | `Partial<TableConfig<T>>` | `{}` | Table configuration merged with defaults |
16
+ | `loading` | `boolean` | `false` | Shows a loading overlay |
17
+ | `sortState` | `SortState \| null` | `null` | Controlled sort state; `null` uses internal state |
18
+ | `filterState` | `FilterState \| null` | `null` | Controlled filter state; `null` uses internal state |
19
+ | `pageState` | `PageState \| null` | `null` | Controlled page state; `null` uses internal state |
20
+ | `rowSelection` | `boolean` | `false` | Render a leading (or trailing) checkbox column with multi-row selection |
21
+ | `selectedKeys` | `Set<unknown> \| null` | `null` | Controlled selection set; `null` uses internal state. Table never mutates the input — emits new Sets |
22
+ | `getRowId` | `(row: T) => unknown` \| `null` | `null` | Required when `rowSelection` is `true`. Returns each row's stable identity key |
23
+ | `selectionDisabled` | `boolean \| ((row: T) => boolean)` | `false` | Per-row opt-out. Disabled rows can't be selected and are skipped by the header "select all" |
24
+ | `selectionColumnPosition` | `'start' \| 'end'` | `'start'` | Render the checkbox column at the leading or trailing edge |
25
+ | `selectionLabel` | `string` | `'Select row'` | `aria-label` applied to each row checkbox |
26
+ | `className` | `string` | `''` | Additional CSS classes for the host |
27
+ | `id` | `string \| null` | `null` | Optional ID for the host element |
28
+ | `ariaLabel` | `string \| null` | `null` | ARIA label for the table |
29
+
30
+ ### Outputs
31
+
32
+ | Output | Type | Description |
33
+ | ----------------- | -------------------- | ------------------------------------------------------------------------------------ |
34
+ | `sortChange` | `SortState` | Emitted when a sortable column header is clicked |
35
+ | `filterChange` | `FilterState` | Emitted when filter state changes |
36
+ | `pageChange` | `PageState` | Emitted when page state changes |
37
+ | `rowClick` | `TableRowContext<T>` | Emitted when a row is clicked (excludes checkbox clicks) |
38
+ | `selectionChange` | `Set<unknown>` | Emitted on every selection change. The Set is brand-new — the input is never mutated |
39
+ | `columnResize` | `ColumnResizeEvent` | Emitted when a resizable column is resized |
40
+
41
+ ### ColumnDef
42
+
43
+ | Property | Type | Default | Description |
44
+ | ---------------- | -------------------------------- | ---------- | -------------------------------------------------------------- |
45
+ | `id` | `string` | _required_ | Unique column identifier |
46
+ | `header` | `string` | _required_ | Header text |
47
+ | `accessor` | `keyof T \| (row: T) => unknown` | _required_ | Key or function to extract cell value from a row |
48
+ | `sortAccessor` | `(row: T) => unknown` | — | Custom accessor used for sorting instead of `accessor` |
49
+ | `width` | `string` | — | Explicit column width (e.g. `'200px'`) |
50
+ | `minWidth` | `string` | — | Minimum column width |
51
+ | `maxWidth` | `string` | — | Maximum column width |
52
+ | `flex` | `number` | — | Flex value for virtual/div-based render path |
53
+ | `sortable` | `boolean` | — | Enable sorting on this column |
54
+ | `align` | `'start' \| 'center' \| 'end'` | `'start'` | Text alignment |
55
+ | `truncate` | `boolean` | — | Truncate cell text with ellipsis |
56
+ | `visible` | `boolean` | `true` | Whether the column is visible |
57
+ | `cellRenderer` | `TemplateRef` | — | Custom cell template; context: `{ $implicit: value, row: T }` |
58
+ | `headerRenderer` | `TemplateRef` | — | Custom header template; context: `{ $implicit: ColumnDef<T> }` |
59
+ | `priority` | `number` | — | Priority for responsive column hiding (lower = kept longer) |
60
+ | `hideBelow` | `'sm' \| 'md' \| 'lg'` | — | Hide this column below the specified breakpoint |
61
+ | `mobileLabel` | `string` | — | Label used in the stacked mobile card view |
62
+ | `resizable` | `boolean` | — | Enable drag-to-resize on this column |
63
+ | `pinnable` | `boolean` | — | Mark column as eligible for pinning |
64
+ | `pinned` | `'start' \| 'end' \| null` | `null` | Pin column to the start or end of the table |
65
+
66
+ ### TableConfig
67
+
68
+ | Property | Type | Default | Description |
69
+ | ------------------------- | --------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------- |
70
+ | `responsiveMode` | `'auto' \| 'scroll' \| 'stack' \| 'priority'` | `'auto'` | How the table adapts at smaller viewports |
71
+ | `forceScroll` | `boolean` | `false` | Always use horizontal scroll instead of responsive mode |
72
+ | `stickyHeader` | `boolean` | `true` | Stick the header row to the top on scroll |
73
+ | `showHeader` | `boolean` | `true` | Render the header row |
74
+ | `rowExpansion` | `boolean` | `false` | Enable row expansion |
75
+ | `emptyMessage` | `string` | `'No data available'` | Message shown when no rows exist |
76
+ | `trackBy` | `(index: number, row: T) => unknown` | `(index) => index` | Track-by function for row rendering |
77
+ | `virtualization` | `boolean \| 'auto'` | `'auto'` | Virtual scrolling mode |
78
+ | `virtualizationThreshold` | `number` | `200` | Row count threshold for auto-activating virtualization |
79
+ | `rowHeight` | `number` | `48` | Fixed row height in px (required for virtual scrolling) |
80
+ | `overscan` | `number` | `10` | Extra rows rendered above/below the viewport |
81
+ | `breakpoints` | `{ sm: string; md: string }` | `{ sm: '(max-width: 35.999em)', md: '(max-width: 47.999em)' }` | Media query strings for responsive breakpoints |
82
+
83
+ > **Note:** `rowSelection` was previously a `TableConfig` flag but is now a top-level component input alongside the other selection inputs. Selection-related configuration is grouped on the component API for discoverability.
84
+
85
+ ---
86
+
87
+ ## Features
88
+
89
+ - Sortable columns with tri-state cycling (asc → desc → none)
90
+ - **Multi-row selection** with controlled or uncontrolled state
91
+ - **Page-local select-all** header checkbox with `checked` / `unchecked` / `indeterminate` states
92
+ - **Per-row selection-disabled** support via boolean or `(row) => boolean` predicate
93
+ - **Selection column position** — leading (default) or trailing
94
+ - Virtual scrolling for large datasets (auto-activates above threshold)
95
+ - Responsive column hiding by priority or breakpoint
96
+ - Stacked mobile card view with expand/collapse — checkbox surfaces above each card
97
+ - Column resizing via drag handle
98
+ - Column pinning (sticky start/end)
99
+ - Custom cell and header templates via `TemplateRef`
100
+ - Controlled or uncontrolled state for sort, filter, pagination, and selection
101
+ - Loading overlay
102
+ - Empty state message
103
+ - Sticky header
104
+ - Token-driven styling with dark mode support
105
+
106
+ ---
107
+
108
+ ## Quick Start
109
+
110
+ ```html
111
+ <fk-data-table [columns]="columns" [rows]="rows" (rowClick)="onRowClick($event)" />
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Import
117
+
118
+ ```ts
119
+ import { DataTableComponent } from '@frame-kit/ui-ng';
120
+ ```
121
+
122
+ ```ts
123
+ @Component({
124
+ selector: 'app-orders',
125
+ imports: [DataTableComponent],
126
+ templateUrl: './orders.component.html',
127
+ })
128
+ export class OrdersComponent {
129
+ columns: ColumnDef<Order>[] = [
130
+ { id: 'address', header: 'Address', accessor: 'address', sortable: true },
131
+ { id: 'status', header: 'Status', accessor: 'status' },
132
+ { id: 'price', header: 'Price', accessor: 'price', align: 'end' },
133
+ ];
134
+
135
+ rows: Order[] = [];
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Selector
142
+
143
+ ```html
144
+ <fk-data-table></fk-data-table>
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Examples
150
+
151
+ ### Sorting
152
+
153
+ ```html
154
+ <fk-data-table [columns]="columns" [rows]="rows" (sortChange)="onSort($event)" />
155
+ ```
156
+
157
+ Mark columns as sortable in the column definition:
158
+
159
+ ```ts
160
+ { id: "price", header: "Price", accessor: "price", sortable: true }
161
+ ```
162
+
163
+ ### Controlled sort (server-side)
164
+
165
+ ```html
166
+ <fk-data-table [columns]="columns" [rows]="rows" [sortState]="currentSort" (sortChange)="fetchSorted($event)" />
167
+ ```
168
+
169
+ When `sortState` is provided, the table delegates sorting to the consumer and does not sort internally.
170
+
171
+ ### Row selection — uncontrolled
172
+
173
+ The simplest case. The table manages selection internally; the consumer just opts in.
174
+
175
+ ```html
176
+ <fk-data-table [columns]="columns" [rows]="rows" [rowSelection]="true" [getRowId]="getRowId" (selectionChange)="onSelection($event)" />
177
+ ```
178
+
179
+ ```ts
180
+ getRowId = (row: Order) => row.id;
181
+
182
+ onSelection(keys: Set<unknown>): void {
183
+ console.log("Selected ids:", [...keys]);
184
+ }
185
+ ```
186
+
187
+ ### Row selection — controlled (server-side pagination friendly)
188
+
189
+ When the parent owns the truth, pass `selectedKeys` and update it on every `selectionChange`. Selections persist across pagination because the state lives in the parent.
190
+
191
+ ```ts
192
+ readonly selection = signal<Set<unknown>>(new Set());
193
+ readonly getRowId = (row: Order) => row.id;
194
+
195
+ onSelection(next: Set<unknown>): void {
196
+ this.selection.set(next); // emit a brand-new Set
197
+ }
198
+ ```
199
+
200
+ ```html
201
+ <fk-data-table [columns]="columns" [rows]="rows" [rowSelection]="true" [selectedKeys]="selection()" [getRowId]="getRowId" (selectionChange)="onSelection($event)" />
202
+ ```
203
+
204
+ The table never mutates the `Set` you pass in — every selection action emits a brand-new instance.
205
+
206
+ ### Row selection — disabled rows
207
+
208
+ Disable selection on specific rows (e.g. archived, locked). The header "select all" automatically skips them.
209
+
210
+ ```html
211
+ <fk-data-table [columns]="columns" [rows]="rows" [rowSelection]="true" [getRowId]="getRowId" [selectionDisabled]="isLocked" />
212
+ ```
213
+
214
+ ```ts
215
+ isLocked = (row: Order) => row.status === 'Closed';
216
+ ```
217
+
218
+ `selectionDisabled` accepts a boolean (disable everything) or a `(row) => boolean` predicate. The header still flips to fully checked when **all eligible rows are selected** — disabled rows aren't counted against the all-selected check.
219
+
220
+ ### Selection column on the right
221
+
222
+ ```html
223
+ <fk-data-table [columns]="columns" [rows]="rows" [rowSelection]="true" [getRowId]="getRowId" selectionColumnPosition="end" />
224
+ ```
225
+
226
+ ### Loading
227
+
228
+ ```html
229
+ <fk-data-table [columns]="columns" [rows]="rows" [loading]="isLoading" />
230
+ ```
231
+
232
+ ### Empty state
233
+
234
+ ```html
235
+ <fk-data-table [columns]="columns" [rows]="[]" [config]="{ emptyMessage: 'No records found' }" />
236
+ ```
237
+
238
+ ### Virtual scrolling
239
+
240
+ Virtual scrolling activates automatically when row count exceeds `virtualizationThreshold` (default 200). Force it on or off:
241
+
242
+ ```html
243
+ <fk-data-table [columns]="columns" [rows]="largeDataset" [config]="{ virtualization: true, rowHeight: 48 }" />
244
+ ```
245
+
246
+ ### Responsive mode
247
+
248
+ ```html
249
+ <!-- Auto: switches between table and stack based on viewport -->
250
+ <fk-data-table [columns]="columns" [rows]="rows" [config]="{ responsiveMode: 'auto' }" />
251
+
252
+ <!-- Stack: always render mobile cards -->
253
+ <fk-data-table [columns]="columns" [rows]="rows" [config]="{ responsiveMode: 'stack' }" />
254
+
255
+ <!-- Priority: hide lower-priority columns first -->
256
+ <fk-data-table [columns]="columns" [rows]="rows" [config]="{ responsiveMode: 'priority' }" />
257
+ ```
258
+
259
+ Use `hideBelow` and `priority` on column definitions to control responsive visibility:
260
+
261
+ ```ts
262
+ { id: "notes", header: "Notes", accessor: "notes", hideBelow: "md", priority: 3 }
263
+ ```
264
+
265
+ ### Column resizing
266
+
267
+ ```html
268
+ <fk-data-table [columns]="columns" [rows]="rows" (columnResize)="onResize($event)" />
269
+ ```
270
+
271
+ Enable per column:
272
+
273
+ ```ts
274
+ { id: "address", header: "Address", accessor: "address", width: "200px", resizable: true }
275
+ ```
276
+
277
+ ### Column pinning
278
+
279
+ ```ts
280
+ columns: ColumnDef<Order>[] = [
281
+ { id: "id", header: "ID", accessor: "id", width: "80px", pinned: "start" },
282
+ { id: "address", header: "Address", accessor: "address" },
283
+ { id: "status", header: "Status", accessor: "status" },
284
+ { id: "actions", header: "", accessor: () => "", width: "60px", pinned: "end" },
285
+ ];
286
+ ```
287
+
288
+ Pinned columns stick to the left or right edge when scrolling horizontally.
289
+
290
+ ### Custom cell template
291
+
292
+ ```html
293
+ <ng-template #statusCell let-value let-row="row">
294
+ <fk-badge [variant]="row.status === 'active' ? 'success' : 'neutral'"> {{ value }} </fk-badge>
295
+ </ng-template>
296
+ ```
297
+
298
+ ```ts
299
+ { id: "status", header: "Status", accessor: "status", cellRenderer: statusCell }
300
+ ```
301
+
302
+ ---
303
+
304
+ ## Accessibility
305
+
306
+ - The standard render path uses semantic `<table>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, and `<td>` elements
307
+ - The virtual render path uses `role="table"`, `role="rowgroup"`, `role="row"`, `role="columnheader"`, and `role="cell"` on divs
308
+ - Sortable columns announce sort direction via `aria-sort` (`ascending`, `descending`, or unset)
309
+ - `aria-label` is forwarded to the table element
310
+ - Loading overlay uses `aria-live="polite"`
311
+ - Column resize handles are mouse/touch operated and add `cursor: col-resize` during drag
312
+ - **Selection checkboxes** carry an `aria-label` derived from `selectionLabel` (default `"Select row"`); the header checkbox alternates between `"Select all on page"` and `"Deselect all on page"` based on its current state
313
+ - **Indeterminate state** is set programmatically on the header checkbox when some-but-not-all visible rows are selected (standard browser behavior)
314
+ - **Disabled rows** apply the native `disabled` attribute on their checkboxes — keyboard navigation and screen readers treat them correctly
315
+ - **Click event propagation** is stopped on checkbox clicks so `rowClick` only fires for "real" row clicks, not selection toggles
316
+
317
+ ---
318
+
319
+ ## Design Tokens
320
+
321
+ `fk-data-table` uses the following design tokens:
322
+
323
+ ```scss
324
+ // Head
325
+ --fk-data-table-head-bg
326
+ --fk-data-table-head-color
327
+ --fk-data-table-head-font-weight
328
+ --fk-data-table-head-font-size
329
+ --fk-data-table-head-hover-color
330
+
331
+ // Body
332
+ --fk-data-table-body-bg
333
+ --fk-data-table-body-color
334
+ --fk-data-table-body-font-size
335
+
336
+ // Layout
337
+ --fk-data-table-border-width
338
+ --fk-data-table-border-radius
339
+ --fk-data-table-border-color
340
+ --fk-data-table-cell-padding
341
+ --fk-data-table-row-hover-bg
342
+ --fk-data-table-viewport-height
343
+
344
+ // Empty & Loading
345
+ --fk-data-table-empty-padding
346
+ --fk-data-table-empty-color
347
+ --fk-data-table-loading-bg
348
+ --fk-data-table-loading-color
349
+
350
+ // Resize
351
+ --fk-data-table-resize-handle-width
352
+ --fk-data-table-resize-handle-color
353
+
354
+ // Pinning
355
+ --fk-data-table-pin-offset
356
+
357
+ // Row selection
358
+ --fk-data-table-selection-cell-width
359
+ --fk-data-table-row-selected-bg
360
+ --fk-data-table-row-selected-hover-bg
361
+ --fk-data-table-checkbox-size
362
+ --fk-data-table-checkbox-border-color
363
+ --fk-data-table-checkbox-radius
364
+ --fk-data-table-checkbox-bg
365
+ --fk-data-table-checkbox-hover-border-color
366
+ --fk-data-table-checkbox-checked-bg
367
+ --fk-data-table-checkbox-checked-border-color
368
+ --fk-data-table-checkbox-icon-color
369
+ ```
370
+
371
+ ### Customizing Tokens
372
+
373
+ Override tokens in your application's global stylesheet or a scoped selector:
374
+
375
+ ```css
376
+ :root {
377
+ --fk-data-table-head-bg: #f1f5f9;
378
+ --fk-data-table-border-color: #e2e8f0;
379
+ --fk-data-table-row-hover-bg: #f8fafc;
380
+ --fk-data-table-row-selected-bg: #dbeafe;
381
+ }
382
+ ```
383
+
384
+ Or scope overrides to a specific context:
385
+
386
+ ```css
387
+ .dark-theme fk-data-table {
388
+ --fk-data-table-head-bg: #1e293b;
389
+ --fk-data-table-head-color: #94a3b8;
390
+ --fk-data-table-body-bg: #0f172a;
391
+ --fk-data-table-body-color: #e2e8f0;
392
+ --fk-data-table-border-color: #334155;
393
+ }
394
+ ```
395
+
396
+ ---
397
+
398
+ ## Behavior Notes
399
+
400
+ - When `virtualization` is `'auto'`, the table switches to virtual scrolling when `rows.length` exceeds `virtualizationThreshold`.
401
+ - Virtual scrolling requires a fixed `rowHeight`. The standard path does not enforce fixed heights.
402
+ - When `responsiveMode` is `'auto'`, the table switches to a stacked card view on small viewports and a standard table on larger ones.
403
+ - Column resizing is performed outside Angular's zone for performance. The `columnResize` event fires once on drag end.
404
+ - Pinning uses `position: sticky` and requires explicit `width` or `minWidth` on pinned columns for offset calculation.
405
+ - Passing `sortState`, `filterState`, `pageState`, or `selectedKeys` switches the corresponding concern to controlled mode — the table will not manage that state internally.
406
+ - **Selection scope is page-local** — the header checkbox selects/deselects only the rows currently visible on the page, not the entire dataset. Existing selections on other pages persist when the controlled `selectedKeys` is held by the parent.
407
+ - **`getRowId` is required** when `rowSelection` is `true`. Without it, the table falls back to row-reference equality which silently breaks across server-side pagination (each fetch returns brand-new objects). The component logs a `console.error` in dev when this combination is misconfigured.
408
+ - **The `selectedKeys` input is never mutated.** Every selection action returns a brand-new `Set` via `selectionChange`. The parent owns the next state.