@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,1102 @@
1
+ # FrameKit UI-ng — Component Development Guide
2
+
3
+ This is the single source of truth for building components in `packages/ui-ng`. Follow every section exactly. If a pattern is defined here, it is non-negotiable. The goal is absolute consistency across every component in the library.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Architecture Overview](#architecture-overview)
10
+ 2. [Folder and File Structure](#folder-and-file-structure)
11
+ 3. [Component TypeScript](#component-typescript)
12
+ 4. [Templates](#templates)
13
+ 5. [Styling (SCSS)](#styling-scss)
14
+ 6. [Token Architecture](#token-architecture)
15
+ 7. [Dark Mode](#dark-mode)
16
+ 8. [Spacing Rules](#spacing-rules)
17
+ 9. [Directives](#directives)
18
+ 10. [Services](#services)
19
+ 11. [Types](#types)
20
+ 12. [Tests](#tests)
21
+ 13. [Storybook Stories](#storybook-stories)
22
+ 14. [README Documentation](#readme-documentation)
23
+ 15. [Public API Exports](#public-api-exports)
24
+ 16. [Accessibility](#accessibility)
25
+ 17. [Definition of Done](#definition-of-done)
26
+
27
+ ---
28
+
29
+ ## Architecture Overview
30
+
31
+ FrameKit UI-ng is a style-agnostic, token-driven Angular component library. Components define structure, layout, accessibility, and interaction. They never define brand styling. All visual decisions come from `--fk-*` design tokens provided by the consumer application.
32
+
33
+ ### Core principles
34
+
35
+ 1. **Components never contain brand styling.** No hardcoded colors, typography scales, brand radii, shadows, or visual identity. All visual styling is expressed via CSS custom properties.
36
+ 2. **Components only consume tokens.** Token definitions belong in theme files, never in component SCSS.
37
+ 3. **Components are self-sufficient.** Every component renders correctly with zero external dependencies — no theme file required.
38
+ 4. **Stable public API.** Consumers use `<fk-*>` components with stable inputs. Internal implementations can change without breaking consumers.
39
+
40
+ ### Library structure
41
+
42
+ Components are organized by **purpose**, not Atomic Design. Each layer has clear responsibilities and a strict dependency direction:
43
+
44
+ ```
45
+ packages/ui-ng/
46
+ index.ts — primary barrel; re-exports every entry point as @frame-kit/ui-ng
47
+ core/ — raw primitives, no other lib component deps (icon, text, headline, link, image, separator)
48
+ ui/ — composed reusable UI (button, dialog, drawer, toast, tabs, data-table, etc.)
49
+ directives/ — standalone behavior directives (tooltip, infinite-scroll, spotlight)
50
+ docs/ — API-reference UI (endpoint-link, method-badge)
51
+ forms/ — self-contained form system; packaged as ONE entry point (holds its own primitives)
52
+ engine/ — schema parser, field registry, validation, error messages, field state
53
+ fields/ — 16 form-field components (input, select, checkbox, etc.)
54
+ composition/ — form wrappers and renderer (form, form-field, form-renderer, label, hint, error, etc.)
55
+ layouts/ — structural page scaffolding (app-shell, content-split)
56
+ services/ — runtime services with no UI (overlay-orchestrator, toast, spotlight)
57
+ ```
58
+
59
+ > Color/date helpers are **not** in this library — they live in `@frame-kit/utils` (framework-agnostic `Color` / `DateUtil`). Don't add UI-less utility services here that have no Angular dependency.
60
+
61
+ **Dependency rules:**
62
+
63
+ - `core` imports nothing from the lib.
64
+ - `ui`, `directives`, and `docs` may use `core` and `services`.
65
+ - `forms` may use `core`, `ui`, `directives`, and `services`.
66
+ - `layouts` may use anything; nothing imports from `layouts`.
67
+
68
+ All layers follow the same conventions defined in this guide.
69
+
70
+ ### Packaging: secondary entry points
71
+
72
+ The library is published with **one ng-packagr secondary entry point per component** so consumers can tree-shake and code-split per component. This shapes how source is laid out and how components import each other:
73
+
74
+ - **Each leaf component folder is its own entry point** — `packages/ui-ng/<layer>/<name>/` builds to `@frame-kit/ui-ng/<layer>/<name>` (its own FESM). The one exception is **`forms/`**, which is a single entry point (`@frame-kit/ui-ng/forms`) because `engine`/`fields`/`composition` are mutually dependent — its sub-folders are **not** separate entry points.
75
+ - **The primary `index.ts` barrel** re-exports every entry point, so consumers keep importing from `@frame-kit/ui-ng` and still get per-component splitting.
76
+ - **Cross-component imports must use the package path, not a relative `../`.** ng-packagr forbids relative imports that cross an entry-point boundary. Import a sibling component via its entry point:
77
+
78
+ ```ts
79
+ // ✅ in ui/button — importing the loader (a different entry point)
80
+ import { LoaderComponent } from '@frame-kit/ui-ng/ui/loader';
81
+
82
+ // ❌ crosses an entry-point boundary — ng-packagr build fails
83
+ import { LoaderComponent } from '../loader/loader.component';
84
+ ```
85
+
86
+ Imports **within the same entry point stay relative** (e.g. a forms field importing `../../engine/schema`, or a component importing its own `./button.types`).
87
+
88
+ See [Public API Exports](#public-api-exports) for the files each new component must add.
89
+
90
+ ### Token layers
91
+
92
+ | Layer | Location | Purpose |
93
+ | ------------------------ | ------------------------------------------- | ----------------------------------------------------------------- |
94
+ | 1. Component consumption | `packages/ui-ng/**/*.scss` | Consumes `--fk-*` tokens with two-tier fallbacks |
95
+ | 2. Base tokens | `packages/tokens/src/scss/tokens.base.scss` | Default theme opinion — maps semantic tokens to component tokens |
96
+ | 3. Dark mode | `packages/tokens/src/scss/tokens.dark.scss` | Remaps color-sensitive tokens for dark mode |
97
+ | 4. Consumer overrides | Consumer app stylesheets | Consumers override tokens at `:root`, `.theme-*`, or per-instance |
98
+
99
+ ---
100
+
101
+ ## Folder and File Structure
102
+
103
+ Every component must have this exact file set, placed in the correct layer (`core/`, `ui/`, `directives/`, `docs/`, `forms/{engine,fields,composition}/`, `layouts/`):
104
+
105
+ ```
106
+ packages/ui-ng/<layer>/<name>/
107
+ <name>.component.ts
108
+ <name>.component.html
109
+ <name>.component.scss
110
+ <name>.component.spec.ts
111
+ <name>.component.stories.ts
112
+ <name>.types.ts
113
+ README.md
114
+ index.ts (entry-point barrel — see note below)
115
+ ng-package.json (entry-point marker — see note below)
116
+ ```
117
+
118
+ `index.ts` and `ng-package.json` make the folder a secondary entry point. Their contents are fixed boilerplate:
119
+
120
+ ```ts
121
+ // index.ts — export the component's public files
122
+ export * from './<name>.component';
123
+ export * from './<name>.types';
124
+ // + any associated directives, e.g. export * from './<name>-icon.directive';
125
+ ```
126
+
127
+ ```json
128
+ // ng-package.json
129
+ { "lib": { "entryFile": "index.ts" } }
130
+ ```
131
+
132
+ **Exception — `forms/`:** components under `forms/{engine,fields,composition}/<name>/` do **not** get their own `index.ts`/`ng-package.json`. Forms is a single entry point: add the component's public files to the relevant sub-barrel (`forms/composition/index.ts`, `forms/fields/index.ts`, or `forms/engine/index.ts`) instead.
133
+
134
+ Additional files when the component uses content projection directives:
135
+
136
+ ```
137
+ <name>-<slot>.directive.ts (e.g. alert-icon.directive.ts)
138
+ ```
139
+
140
+ ### Naming conventions
141
+
142
+ - Folder name: kebab-case matching the component name (`badge`, `inline-edit`, `data-table`)
143
+ - File names: always `<folder-name>.component.*`
144
+ - Types file: always `<folder-name>.types.ts`
145
+ - Directive files: `<folder-name>-<purpose>.directive.ts`
146
+
147
+ ---
148
+
149
+ ## Component TypeScript
150
+
151
+ ### Required structure
152
+
153
+ Every component follows this exact pattern. Reference the badge component as the canonical atom example and the alert component as the canonical molecule example.
154
+
155
+ ```ts
156
+ import { ChangeDetectionStrategy, Component, HostBinding, computed, input } from '@angular/core';
157
+
158
+ import type { ExampleVariant } from './example.types';
159
+
160
+ @Component({
161
+ selector: 'fk-example',
162
+ standalone: true,
163
+ imports: [],
164
+ changeDetection: ChangeDetectionStrategy.OnPush,
165
+ templateUrl: './example.component.html',
166
+ styleUrl: './example.component.scss',
167
+ })
168
+ export class ExampleComponent {
169
+ // ===== INPUTS =====
170
+ readonly variant = input<ExampleVariant>('default');
171
+
172
+ // ===== CONTENT CHILDREN =====
173
+ // (only when component uses content projection directives)
174
+
175
+ // ===== BASE PROPS =====
176
+ readonly className = input<string>('');
177
+ readonly id = input<string | null>(null);
178
+ readonly ariaLabel = input<string | null>(null);
179
+
180
+ // ===== COMPUTED =====
181
+ readonly classes = computed(() => {
182
+ return ['fk-example', `fk-example--${this.variant()}`, this.className()].filter(Boolean).join(' ');
183
+ });
184
+
185
+ @HostBinding('class')
186
+ get hostClass() {
187
+ return this.classes();
188
+ }
189
+ }
190
+ ```
191
+
192
+ ### Rules
193
+
194
+ - **Always standalone.** Every component sets `standalone: true`.
195
+ - **Always OnPush.** Every component uses `ChangeDetectionStrategy.OnPush`.
196
+ - **Signal-based inputs.** Use Angular's `input<Type>(default)` API. Never use the `@Input()` decorator.
197
+ - **Selector prefix.** Always `fk-` followed by the component name in kebab-case.
198
+ - **Section comments.** Organize the class body with `// ===== SECTION =====` dividers in this order: INPUTS, CONTENT CHILDREN, BASE PROPS, COMPUTED.
199
+
200
+ ### Base props convention
201
+
202
+ All components should support these inputs when relevant:
203
+
204
+ - `className?: string` — additional CSS classes merged onto the host (default: `""`)
205
+ - `id?: string | null` — optional ID (default: `null`)
206
+ - `ariaLabel?: string | null` — accessible label (default: `null`)
207
+ - `ariaDescribedBy?: string | null` — ID of describing element (default: `null`)
208
+ - `disabled?: boolean` — disables interaction (default: `false`)
209
+
210
+ ### Host class binding
211
+
212
+ All components must use this unified mechanism:
213
+
214
+ 1. Compute a `classes` signal combining base class + modifiers + consumer `className`
215
+ 2. Bind to host via `@HostBinding("class")` getter that returns `this.classes()`
216
+ 3. Base class is always `fk-<name>`
217
+ 4. Modifier classes follow BEM: `fk-<name>--<modifier>`
218
+
219
+ **Do NOT use:**
220
+
221
+ - `host: { "[class]": ... }` in the `@Component` decorator
222
+ - Multiple class systems in one component
223
+ - Binding classes only to inner elements instead of the host
224
+
225
+ ### Semantic variants
226
+
227
+ Variants and sizes must be semantic, library-owned values:
228
+
229
+ - `variant: "primary" | "secondary" | "danger"` — not vendor-specific names
230
+ - `size: "sm" | "md" | "lg"` — not pixel values
231
+
232
+ ---
233
+
234
+ ## Templates
235
+
236
+ ### Content projection
237
+
238
+ Use `<ng-content />` for default slot projection:
239
+
240
+ ```html
241
+ <ng-content />
242
+ ```
243
+
244
+ For named slots, use attribute selectors matching directive selectors:
245
+
246
+ ```html
247
+ <ng-content select="[fkAlertIconStart]" />
248
+ <span class="fk-alert__content">
249
+ <ng-content />
250
+ </span>
251
+ ```
252
+
253
+ ### ID and ARIA binding
254
+
255
+ Pass base props through to the appropriate element in the template:
256
+
257
+ ```html
258
+ <span [id]="id() ?? undefined" [attr.aria-label]="ariaLabel()">
259
+ <ng-content />
260
+ </span>
261
+ ```
262
+
263
+ ### Self-closing syntax
264
+
265
+ Use self-closing tags for void elements:
266
+
267
+ ```html
268
+ <ng-content /> <router-outlet />
269
+ ```
270
+
271
+ ---
272
+
273
+ ## Styling (SCSS)
274
+
275
+ ### Host element styling
276
+
277
+ Style the host element directly with `:host`:
278
+
279
+ ```scss
280
+ :host {
281
+ display: inline-flex;
282
+ align-items: center;
283
+ height: var(--fk-badge-height, 1.5rem);
284
+ border-radius: var(--fk-badge-border-radius, 999px);
285
+ font-size: var(--fk-badge-font-size, 0.75rem);
286
+ font-weight: var(--fk-badge-font-weight, var(--fk-font-weight-medium, 500));
287
+ }
288
+ ```
289
+
290
+ ### Variant styles
291
+
292
+ Apply variant styles using the host class + variant modifier:
293
+
294
+ ```scss
295
+ :host.fk-badge--primary {
296
+ background-color: var(--fk-badge-primary-bg, var(--fk-color-primary-subtle, #e8f4ff));
297
+ color: var(--fk-badge-primary-color, var(--fk-color-primary, #0a84ff));
298
+ }
299
+ ```
300
+
301
+ ### BEM naming for internal elements
302
+
303
+ Internal elements use BEM naming under the component namespace:
304
+
305
+ ```scss
306
+ .fk-alert__content {
307
+ flex: 1;
308
+ }
309
+
310
+ .fk-inline-edit__input { ... }
311
+ .fk-inline-edit__action--save { ... }
312
+ ```
313
+
314
+ ### Section comments in SCSS
315
+
316
+ Use section dividers for readability:
317
+
318
+ ```scss
319
+ // ===== VARIANT COLORS =====
320
+ ```
321
+
322
+ ### What component SCSS must do
323
+
324
+ - Consume tokens with `var(--fk-*)` using the two-tier fallback pattern (see [Token Architecture](#token-architecture))
325
+ - Use hook classes: `fk-<name>` + modifiers like `fk-<name>--primary`
326
+ - Work correctly with `tokens.base.scss` loaded
327
+ - Work correctly with NO theme file loaded (via fallback values)
328
+
329
+ ### What component SCSS must NEVER do
330
+
331
+ - **Define `--fk-*` tokens.** Token definitions belong only in theme files (`tokens.base.scss`, `tokens.dark.scss`), never in component SCSS.
332
+ - **Use `::ng-deep`.** Find an alternative approach.
333
+ - **Hardcode hex colors as primary values.** Hex is only allowed as the last-resort fallback inside `var()`.
334
+ - **Use `margin-bottom` for spacing between siblings.** See [Spacing Rules](#spacing-rules).
335
+
336
+ ---
337
+
338
+ ## Token Architecture
339
+
340
+ ### The two-tier fallback pattern (required)
341
+
342
+ Every token reference in component SCSS must include a fallback chain so the component renders correctly even when no theme file is loaded:
343
+
344
+ ```scss
345
+ /* ✅ Correct — two-tier fallback */
346
+ padding: var(--fk-callout-padding, var(--fk-rhythm-4, 1rem));
347
+ background-color: var(--fk-callout-bg, var(--fk-color-surface-muted, #f7f9fb));
348
+
349
+ /* ❌ Wrong — no fallback, breaks without theme */
350
+ padding: var(--fk-callout-padding);
351
+ ```
352
+
353
+ The fallback chain is:
354
+
355
+ 1. **Component token** (`--fk-callout-padding`) — the consumer override point
356
+ 2. **Semantic token** (`--fk-rhythm-4`) — picks up the global theme if loaded
357
+ 3. **Raw value** (`1rem`) — the hardcoded baseline that always works
358
+
359
+ This guarantees components are never visually broken regardless of which theme layers are present.
360
+
361
+ ### Token naming
362
+
363
+ All tokens are prefixed with `--fk-*`. Component-specific tokens follow the pattern:
364
+
365
+ ```
366
+ --fk-<component>-<property>
367
+ --fk-<component>-<variant>-<property>
368
+ ```
369
+
370
+ Examples:
371
+
372
+ ```
373
+ --fk-badge-height
374
+ --fk-badge-font-size
375
+ --fk-badge-primary-bg
376
+ --fk-badge-primary-color
377
+ --fk-alert-padding
378
+ --fk-alert-info-bg
379
+ --fk-alert-info-color
380
+ ```
381
+
382
+ Semantic tokens (shared across components):
383
+
384
+ ```
385
+ --fk-color-primary
386
+ --fk-color-surface-muted
387
+ --fk-color-text
388
+ --fk-font-weight-medium
389
+ --fk-rhythm-4
390
+ --fk-radius-md
391
+ --fk-focus-ring
392
+ ```
393
+
394
+ ### Where tokens are defined
395
+
396
+ Tokens are defined ONLY in theme files, NEVER in component SCSS:
397
+
398
+ | File | What it defines |
399
+ | ------------------------------------------- | ----------------------------- |
400
+ | `packages/tokens/src/scss/tokens.base.scss` | All token defaults in `:root` |
401
+ | `packages/tokens/src/scss/tokens.dark.scss` | Dark mode color overrides |
402
+
403
+ Token definitions must only appear in:
404
+
405
+ - `:root`
406
+ - `.theme-*`
407
+
408
+ Token definitions must NEVER appear in:
409
+
410
+ - `.fk-*` selectors
411
+ - `:host`
412
+ - Component selectors
413
+
414
+ This ensures consumer overrides always win — component selectors would increase specificity and break the override chain.
415
+
416
+ ### Adding tokens for a new component
417
+
418
+ 1. Define component tokens in `tokens.base.scss` following the ITCSS order (primitives, semantic, component tokens grouped by layer: core, ui, directives, forms, layouts, keyframes)
419
+ 2. If the component has color-sensitive tokens, add dark mode overrides in `tokens.dark.scss` under both the `@media (prefers-color-scheme: dark)` block and the `html.dark` block
420
+ 3. Document the tokens in the component README
421
+
422
+ ---
423
+
424
+ ## Dark Mode
425
+
426
+ ### How it works
427
+
428
+ Dark mode is a token remapping layer. Components are completely unaware of dark mode — they consume the same `--fk-*` tokens regardless of theme. The dark mode file (`tokens.dark.scss`) redefines color-sensitive tokens under two selectors:
429
+
430
+ 1. `@media (prefers-color-scheme: dark)` — system preference, automatic
431
+ 2. `html.dark` — user override via JavaScript, always wins due to higher specificity
432
+
433
+ ### What gets remapped in dark mode
434
+
435
+ Only color-sensitive tokens:
436
+
437
+ - Color primitives (`--fk-color-surface`, `--fk-color-text`, `--fk-color-border`, etc.)
438
+ - Focus ring (`--fk-focus-ring`)
439
+ - Component color tokens (backgrounds, text colors, border colors)
440
+
441
+ ### What stays the same
442
+
443
+ Non-color tokens are NOT redefined:
444
+
445
+ - Typography (font sizes, weights, line heights)
446
+ - Spacing / rhythm
447
+ - Radii
448
+ - Sizing tokens
449
+
450
+ ### Adding dark mode support for a new component
451
+
452
+ When your component introduces new color tokens, add overrides in `tokens.dark.scss` under both blocks:
453
+
454
+ ```scss
455
+ @media (prefers-color-scheme: dark) {
456
+ :root {
457
+ --fk-newcomponent-bg: var(--fk-color-surface-muted);
458
+ --fk-newcomponent-color: var(--fk-color-text);
459
+ }
460
+ }
461
+
462
+ html.dark {
463
+ --fk-newcomponent-bg: var(--fk-color-surface-muted);
464
+ --fk-newcomponent-color: var(--fk-color-text);
465
+ }
466
+ ```
467
+
468
+ ### Verification
469
+
470
+ Always verify appearance in both light and dark modes. Run Storybook (it loads `tokens.base.scss` + `tokens.dark.scss`) and toggle the theme:
471
+
472
+ ```bash
473
+ npm run storybook:ui-ng
474
+ ```
475
+
476
+ ---
477
+
478
+ ## Spacing Rules
479
+
480
+ ### Margin direction convention
481
+
482
+ Use **top margin only** for vertical spacing between elements. Bottom margin is always zero.
483
+
484
+ This matches the token definitions:
485
+
486
+ ```scss
487
+ --fk-text-margin-block-start: var(--fk-rhythm-4); /* top */
488
+ --fk-text-margin-block-end: 0; /* bottom */
489
+ --fk-headline-margin-block-start: var(--fk-rhythm-4);
490
+ --fk-headline-margin-block-end: 0;
491
+ ```
492
+
493
+ ### Rules
494
+
495
+ - Space between elements is created by the **top margin of the next element**, not the bottom margin of the current element
496
+ - Never use `margin-bottom` for spacing between siblings
497
+ - Prefer `gap` (via flex/grid) over directional margins when elements are in a flex or grid container
498
+ - Micro-alignment nudges (e.g. `margin-top: 0.125rem` to align an icon with text) are acceptable
499
+
500
+ ### Examples
501
+
502
+ ```scss
503
+ /* ✅ Correct — gap-based spacing */
504
+ .container {
505
+ display: flex;
506
+ flex-direction: column;
507
+ gap: var(--fk-rhythm-3, 0.75rem);
508
+ }
509
+
510
+ /* ✅ Correct — top margin */
511
+ .next-element {
512
+ margin-top: var(--fk-rhythm-4, 1rem);
513
+ }
514
+
515
+ /* ❌ Wrong — bottom margin for spacing */
516
+ .current-element {
517
+ margin-bottom: var(--fk-rhythm-4, 1rem);
518
+ }
519
+ ```
520
+
521
+ ### Field-level vs container-level spacing
522
+
523
+ For atom-tier components — anything in `forms/fields/` (`fk-input`, `fk-textarea`, `fk-select`, etc.) and the `fk-form-field` wrapper — **do not put margin on the host**. Inter-field rhythm is owned by parent containers (`fk-field-group`, `fk-form-renderer__fields`, or any consumer-supplied `display: flex; flex-direction: column; gap: var(--fk-form-field-gap, …)` wrapper).
524
+
525
+ Why this matters: the `:host { margin-top: ... } / :host:first-child { margin-top: 0 }` pattern fires per-parent, not per-form. When fields are wrapped (e.g. each `fk-form-field` sits inside its own `fk-field-outlet` in the schema-driven path), every wrapped field matches `:first-child` and zeros its margin — so the form ends up flush. When fields are direct siblings of a `<form>` element (the manual path), only the literal first sibling matches the reset and the rest carry full margin. The result is two visually different forms even though the components and validators are identical.
526
+
527
+ ```scss
528
+ /* ❌ Wrong — host-driven inter-field rhythm */
529
+ :host {
530
+ margin-top: var(--fk-form-field-gap, var(--fk-rhythm-4, 1rem));
531
+ }
532
+ :host:first-child {
533
+ margin-top: 0;
534
+ }
535
+
536
+ /* ✅ Correct — parent-driven inter-field rhythm */
537
+ /* In the parent (fk-field-group, fk-form-renderer, etc.): */
538
+ .fk-field-group__content {
539
+ display: flex;
540
+ flex-direction: column;
541
+ row-gap: var(--fk-form-field-gap, var(--fk-rhythm-4, 1rem));
542
+ }
543
+ ```
544
+
545
+ The Storybook regression test for this pattern lives at `Forms/Composition/Form Renderer · SpacingParity` — a side-by-side comparison of a schema-rendered form and a hand-built form with identical fields. They should render pixel-identical; if they ever drift, host-margin has crept back into a field somewhere.
546
+
547
+ ---
548
+
549
+ ## Directives
550
+
551
+ There are two distinct directive kinds in this library — they look similar in code but have different purposes and different documentation requirements.
552
+
553
+ | Kind | Lives in | Has its own README? | Has stories? |
554
+ | --------------------------- | ---------------------------------------------------- | ------------------------------------------- | ----------------- |
555
+ | **Behavior directives** | `packages/ui-ng/directives/` | Yes — full per-kind README (see below) | Yes |
556
+ | **Content-slot directives** | Inside the parent component's folder (e.g. `alert/`) | No — covered in the parent component's docs | No (no UI on own) |
557
+
558
+ ### Behavior directives
559
+
560
+ Standalone directives that add behavior to any element they're applied to (e.g. `fkTooltip`, `fkInfiniteScroll`, `fkSpotlightTrigger`). Each lives in its own folder under `packages/ui-ng/directives/<name>/` and ships its own README + Storybook story alongside the source.
561
+
562
+ #### Required files
563
+
564
+ ```
565
+ packages/ui-ng/directives/<name>/
566
+ <name>.directive.ts
567
+ <name>.directive.spec.ts
568
+ <name>.directive.stories.ts
569
+ README.md
570
+ index.ts (entry-point barrel: export * from './<name>.directive')
571
+ ng-package.json ({ "lib": { "entryFile": "index.ts" } })
572
+ ```
573
+
574
+ A behavior directive is its own secondary entry point (`@frame-kit/ui-ng/directives/<name>`). When the directive needs companion utilities (positioning helpers, types files), keep them in the same folder and export them from `index.ts`; the README points at the public surface.
575
+
576
+ #### README section order
577
+
578
+ Mirrors the component README order so consumers don't have to re-learn structure:
579
+
580
+ 1. **Title + one-line summary** — `# fkTooltip` followed by a sentence describing what the directive does and where it attaches.
581
+ 2. **API** — Inputs (table), Outputs (table or "None."), and a Types subsection if the directive exposes string-literal types.
582
+ 3. **Features** — short bullet list.
583
+ 4. **Quick Start** — smallest useful HTML snippet using the attribute.
584
+ 5. **Import** — `@frame-kit/ui-ng` import + standalone `imports: [...]` registration.
585
+ 6. **Examples** — one subsection per realistic usage pattern.
586
+ 7. **Accessibility** — keyboard behavior, ARIA host bindings, focus interaction, `prefers-reduced-motion` handling, anti-patterns.
587
+ 8. **Design Tokens** (when the directive renders any styled UI like a tooltip popover) — tokens consumed + override snippet.
588
+ 9. **Behavior Notes** — edge cases, lifecycle (when host is destroyed), and how the directive interacts with the `OverlayOrchestrator` if it owns an overlay.
589
+
590
+ #### Storybook coverage
591
+
592
+ - Default usage attached to a single host element.
593
+ - One story per non-trivial input variant (e.g. each `position` for `fkTooltip`).
594
+ - An `AllVariants` composite when the directive has more than two configuration knobs, so the gallery shows them side-by-side.
595
+ - For directive pairs that only make sense together (e.g. `fkSpotlightTrigger` + `fkSpotlightTarget`), one story may suffice — render both in the same fixture.
596
+
597
+ #### JSDoc on the public API
598
+
599
+ Every `input()`, `output()`, `model()`, and public method on the directive class carries a one-line JSDoc above its declaration. Inputs answer "what does this control?", outputs "when does this fire?", methods "what does this do?". The class itself carries a paragraph-level JSDoc explaining the directive's responsibility (one short paragraph; no marketing copy).
600
+
601
+ ### Content-slot directives
602
+
603
+ When a component needs named content slots (e.g. an icon slot in an alert), create a marker directive:
604
+
605
+ ```ts
606
+ import { Directive, InjectionToken } from '@angular/core';
607
+
608
+ export const FK_ALERT_ICON_START = new InjectionToken<FkAlertIconStartDirective>('FK_ALERT_ICON_START');
609
+
610
+ @Directive({
611
+ selector: '[fkAlertIconStart]',
612
+ standalone: true,
613
+ providers: [{ provide: FK_ALERT_ICON_START, useExisting: FkAlertIconStartDirective }],
614
+ host: {
615
+ 'aria-hidden': 'true',
616
+ },
617
+ })
618
+ export class FkAlertIconStartDirective {}
619
+ ```
620
+
621
+ #### Rules
622
+
623
+ - Directive selector is always a camelCase attribute: `[fkComponentSlotName]`
624
+ - Provide an `InjectionToken` so the parent component can detect the directive via `contentChild()`
625
+ - Set appropriate `host` attributes (e.g. `aria-hidden` for decorative icons)
626
+ - Directive is always `standalone: true`
627
+ - The parent component detects the directive using `contentChild(FK_TOKEN_NAME)`:
628
+
629
+ ```ts
630
+ readonly iconStartRef = contentChild(FK_ALERT_ICON_START);
631
+ ```
632
+
633
+ #### Naming conventions
634
+
635
+ - InjectionToken constant: `FK_<COMPONENT>_<SLOT>` (screaming snake case)
636
+ - Directive class: `Fk<Component><Slot>Directive` (PascalCase)
637
+ - Selector attribute: `fk<Component><Slot>` (camelCase)
638
+
639
+ ---
640
+
641
+ ## Services
642
+
643
+ Runtime services with no UI that **depend on Angular** (e.g. `OverlayOrchestrator`, `ToastService`, `SpotlightService`). Each lives in its own folder under `packages/ui-ng/services/<name>/` and ships a README alongside the source. Framework-agnostic helpers (color, date math) do **not** belong here — they go in `@frame-kit/utils`.
644
+
645
+ ### Required files
646
+
647
+ ```
648
+ packages/ui-ng/services/<name>/
649
+ <name>.service.ts
650
+ <name>.service.spec.ts
651
+ README.md
652
+ index.ts (entry-point barrel: export * from './<name>.service')
653
+ ng-package.json ({ "lib": { "entryFile": "index.ts" } })
654
+ ```
655
+
656
+ A service is its own secondary entry point (`@frame-kit/ui-ng/services/<name>`). Services do not ship Storybook stories — they have no rendered surface. Behavior is exercised through their unit spec.
657
+
658
+ ### Provider scope
659
+
660
+ State the provider scope at the top of the README. Two patterns:
661
+
662
+ - **Root-provided** (`@Injectable({ providedIn: "root" })`) — singleton across the app. Default for orchestration, registries, and global mutable state. Document any side effects of construction (subscriptions, listeners) so consumers know what they're opting into.
663
+ - **Component-tree-provided** — when the service must be scoped to a feature (e.g. one toast queue per overlay surface). The README must show where to declare the provider.
664
+
665
+ ### README section order
666
+
667
+ 1. **Title + one-line summary** — `# OverlayOrchestrator` followed by a sentence stating the service's responsibility.
668
+ 2. **Initialization** — provider scope, any setup required, and (if applicable) any built-in integrations the service triggers automatically.
669
+ 3. **API** — Public methods and signals as a table or sub-sections. For each: signature, return type, what it does, when to call it, what it emits/mutates.
670
+ 4. **Usage** — A realistic snippet wiring the service into a component (`inject(...)`, calling the method, reading the signal).
671
+ 5. **Behavior Notes** — Concurrency contract, lifecycle (when handles are released), interaction with other services (e.g. how `DialogService` registers with `OverlayOrchestrator`).
672
+
673
+ Services typically don't have Accessibility or Design Tokens sections. Skip them rather than leaving them empty. If the service exposes a value object that ships with token-driven defaults (e.g. a default toast variant), document that in Behavior Notes.
674
+
675
+ ### JSDoc on the public API
676
+
677
+ Every public method, signal, and exported constant on the service carries a one-line JSDoc above its declaration. The class itself carries a paragraph-level JSDoc explaining the service's responsibility and the singleton vs scoped contract.
678
+
679
+ ---
680
+
681
+ ## Types
682
+
683
+ Every component has a `<name>.types.ts` file that exports its variant and configuration types:
684
+
685
+ ```ts
686
+ export type BadgeVariant = 'default' | 'primary' | 'success' | 'warning' | 'danger';
687
+ ```
688
+
689
+ ### Rules
690
+
691
+ - One types file per component
692
+ - Export union types for variants, sizes, and any string-literal inputs
693
+ - Import types with `import type { ... }` in the component file
694
+ - Keep types minimal — only what the public API needs
695
+
696
+ ---
697
+
698
+ ## Tests
699
+
700
+ ### Required test file structure
701
+
702
+ ```ts
703
+ import { Component } from "@angular/core";
704
+ import { ComponentFixture, TestBed } from "@angular/core/testing";
705
+ import { ExampleComponent } from "./example.component";
706
+
707
+ describe("ExampleComponent", () => {
708
+ let component: ExampleComponent;
709
+ let fixture: ComponentFixture<ExampleComponent>;
710
+
711
+ beforeEach(async () => {
712
+ await TestBed.configureTestingModule({
713
+ imports: [ExampleComponent],
714
+ }).compileComponents();
715
+
716
+ fixture = TestBed.createComponent(ExampleComponent);
717
+ component = fixture.componentInstance;
718
+ fixture.detectChanges();
719
+ });
720
+
721
+ it("should create", () => {
722
+ expect(component).toBeTruthy();
723
+ });
724
+
725
+ describe("Defaults", () => { ... });
726
+ describe("Host classes", () => { ... });
727
+ describe("Variants", () => { ... });
728
+ describe("Style overrides", () => { ... });
729
+ describe("Content projection", () => { ... });
730
+ describe("Base props passthrough", () => { ... });
731
+ });
732
+ ```
733
+
734
+ ### Required test sections
735
+
736
+ Every component spec must include these `describe` blocks:
737
+
738
+ 1. **`should create`** — basic instantiation check
739
+ 2. **`Defaults`** — verify every input's default value using `component.inputName()`
740
+ 3. **`Host classes`** — verify base class is applied, variant class is applied, variant class updates when input changes, custom `className` is merged without replacing hook classes
741
+ 4. **`Variants`** — iterate all variant values and verify each applies the correct class
742
+ 5. **`Style overrides`** — verify inline style bindings (color, backgroundColor, etc.) when applicable
743
+ 6. **`Content projection`** — create a test host component with projected content and verify it renders
744
+ 7. **`Base props passthrough`** — verify `id`, `ariaLabel`, etc. are passed through to the correct DOM element
745
+
746
+ ### Testing patterns
747
+
748
+ - Use `fixture.componentRef.setInput("name", value)` to set signal-based inputs
749
+ - For content projection tests, create an inline `@Component` test host:
750
+
751
+ ```ts
752
+ @Component({
753
+ standalone: true,
754
+ imports: [BadgeComponent],
755
+ template: `<fk-badge>Active</fk-badge>`,
756
+ })
757
+ class TestHostComponent {}
758
+
759
+ const hostFixture = TestBed.createComponent(TestHostComponent);
760
+ hostFixture.detectChanges();
761
+ ```
762
+
763
+ - Access the native element with `fixture.nativeElement as HTMLElement`
764
+ - Check classes with `el.classList.contains("fk-badge")`
765
+ - Check attributes with `el.getAttribute("aria-label")`
766
+ - Check inline styles with `el.style.color`
767
+
768
+ ---
769
+
770
+ ## Storybook Stories
771
+
772
+ ### Required file structure
773
+
774
+ ```ts
775
+ import type { Meta, StoryObj } from '@storybook/angular';
776
+ import { ExampleComponent } from './example.component';
777
+
778
+ const meta: Meta<ExampleComponent> = {
779
+ title: 'Atoms/Example', // or "Molecules/Example" or "Organisms/Example"
780
+ component: ExampleComponent,
781
+ tags: ['autodocs'],
782
+ argTypes: {
783
+ variant: {
784
+ control: 'select',
785
+ options: ['default', 'primary', 'success', 'warning', 'danger'],
786
+ },
787
+ },
788
+ };
789
+
790
+ export default meta;
791
+ type Story = StoryObj<ExampleComponent>;
792
+ ```
793
+
794
+ ### Title convention
795
+
796
+ - Atoms: `"Atoms/<Name>"`
797
+ - Molecules: `"Molecules/<Name>"`
798
+ - Organisms: `"Organisms/<Name>"`
799
+
800
+ ### Story pattern
801
+
802
+ Each story uses a render function with props and template:
803
+
804
+ ```ts
805
+ export const Default: Story = {
806
+ render: (args) => ({
807
+ props: args,
808
+ template: `<fk-example [variant]="variant">Content</fk-example>`,
809
+ }),
810
+ args: {
811
+ variant: 'default',
812
+ },
813
+ };
814
+ ```
815
+
816
+ ### Required stories
817
+
818
+ Every component must include stories for:
819
+
820
+ - Default state
821
+ - Each variant
822
+ - Each size (if applicable)
823
+ - Disabled state (if applicable)
824
+ - Loading state (if applicable)
825
+ - An `AllVariants` story showing all variants side by side
826
+ - Accessibility example (if the component has ARIA considerations)
827
+
828
+ ### When using other components or directives
829
+
830
+ Use `moduleMetadata` and `applicationConfig` decorators:
831
+
832
+ ```ts
833
+ decorators: [
834
+ moduleMetadata({
835
+ imports: [IconComponent, FkAlertIconStartDirective],
836
+ }),
837
+ applicationConfig({
838
+ providers: [
839
+ provideIcons({
840
+ "shield-alert": shieldAlert,
841
+ }),
842
+ ],
843
+ }),
844
+ ],
845
+ ```
846
+
847
+ ### Storybook theming
848
+
849
+ The `ui-ng` Storybook target loads `packages/tokens/src/scss/tokens.base.scss` and `tokens.dark.scss` (configured in `project.json`), so components render with the **default `@frame-kit/tokens` theme** applied — the same opinion a consumer gets out of the box. To sanity-check the no-theme baseline (fallback values only), temporarily remove those entries from the storybook `styles` array; every component must still render correctly because every token reference carries a two-tier fallback.
850
+
851
+ ---
852
+
853
+ ## README Documentation
854
+
855
+ Every component must have a `README.md` following this exact section order. Do not skip sections. Do not reorder.
856
+
857
+ ### 1. Title + One-Line Summary
858
+
859
+ ```markdown
860
+ # fk-example
861
+
862
+ A token-driven example component for [describe primary use case].
863
+ ```
864
+
865
+ ### 2. API
866
+
867
+ Break into subsections:
868
+
869
+ - **Inputs** — table with Input, Type, Default, Description columns
870
+ - **Outputs** — table with Output, Type, Description columns (or "None." if no outputs)
871
+ - **Content** — describe projection slots with code example
872
+
873
+ ```markdown
874
+ ## API
875
+
876
+ ### Inputs
877
+
878
+ | Input | Type | Default | Description |
879
+ | ----------- | -------------- | ----------- | ------------------------------ |
880
+ | `variant` | `BadgeVariant` | `"default"` | Visual style of the badge |
881
+ | `className` | `string` | `''` | Additional CSS classes on host |
882
+
883
+ ### Outputs
884
+
885
+ None.
886
+
887
+ ### Content
888
+
889
+ `fk-badge` projects its content:
890
+
891
+ \`\`\`html
892
+ <fk-badge>New</fk-badge>
893
+ \`\`\`
894
+ ```
895
+
896
+ ### 3. Features
897
+
898
+ Short bullet list of capabilities.
899
+
900
+ ### 4. Quick Start
901
+
902
+ The smallest useful example.
903
+
904
+ ### 5. Import
905
+
906
+ Show import path and component registration:
907
+
908
+ ```markdown
909
+ ## Import
910
+
911
+ \`\`\`ts
912
+ import { BadgeComponent } from "@frame-kit/ui-ng";
913
+ \`\`\`
914
+
915
+ \`\`\`ts
916
+ @Component({
917
+ selector: "app-example",
918
+ imports: [BadgeComponent],
919
+ templateUrl: "./example.component.html",
920
+ })
921
+ export class ExampleComponent {}
922
+ \`\`\`
923
+ ```
924
+
925
+ The import path is always `@frame-kit/ui-ng`.
926
+
927
+ ### 6. Selector
928
+
929
+ Show the element selector.
930
+
931
+ ### 7. Examples
932
+
933
+ One subsection per variant or use case with code blocks.
934
+
935
+ ### 8. Accessibility
936
+
937
+ Document semantic element, keyboard behavior, ARIA attributes, disabled behavior, recommendations and anti-patterns.
938
+
939
+ ### 9. Design Tokens
940
+
941
+ List the tokens the component consumes. Show how to override them:
942
+
943
+ ```markdown
944
+ ## Design Tokens
945
+
946
+ \`\`\`scss
947
+ --fk-badge-font-size
948
+ --fk-badge-primary-bg
949
+ --fk-badge-primary-color
950
+ \`\`\`
951
+
952
+ Override in your app stylesheet:
953
+
954
+ \`\`\`scss
955
+ :root {
956
+ --fk-badge-radius: 9999px;
957
+ }
958
+ \`\`\`
959
+ ```
960
+
961
+ ### 10. Behavior Notes
962
+
963
+ Edge cases, default behaviors, and recommendations. Keep short.
964
+
965
+ ---
966
+
967
+ ## Public API Exports
968
+
969
+ There are two levels of barrel: the component's own entry-point `index.ts`, and the primary `index.ts` that aggregates all entry points.
970
+
971
+ ### Adding a new component to the public API
972
+
973
+ For a component in `core/ui/directives/docs/layouts/services` (its own entry point):
974
+
975
+ 1. **Write the entry-point `index.ts`** in the component folder, exporting its public files:
976
+
977
+ ```ts
978
+ export * from './<name>.component';
979
+ export * from './<name>.types';
980
+ // + associated directives
981
+ ```
982
+
983
+ 2. **Add `ng-package.json`** in the same folder: `{ "lib": { "entryFile": "index.ts" } }`.
984
+
985
+ 3. **Add one line to the primary `packages/ui-ng/index.ts`**, re-exporting the new entry point so it's reachable via the `@frame-kit/ui-ng` barrel:
986
+
987
+ ```ts
988
+ export * from '@frame-kit/ui-ng/<layer>/<name>';
989
+ ```
990
+
991
+ For a component under `forms/` (part of the single `forms` entry point): skip steps 1–3. Instead add its public files to the relevant sub-barrel (`forms/composition/index.ts`, `forms/fields/index.ts`, or `forms/engine/index.ts`). The primary barrel already re-exports `@frame-kit/ui-ng/forms`.
992
+
993
+ ng-packagr discovers entry points by globbing for `ng-package.json`, so once the two files exist the new entry point is built automatically — no other config changes needed.
994
+
995
+ ### What to export (from the entry-point `index.ts`)
996
+
997
+ - The component class
998
+ - The types file
999
+ - Any directives associated with the component
1000
+
1001
+ ### What NOT to export
1002
+
1003
+ - Internal utilities or helpers
1004
+ - Test fixtures
1005
+ - Storybook stories
1006
+
1007
+ ---
1008
+
1009
+ ## Accessibility
1010
+
1011
+ ### Required for every component
1012
+
1013
+ - Use semantic HTML elements (`button`, `a`, headings, `nav`, `dialog`)
1014
+ - Support `ariaLabel` and `ariaDescribedBy` inputs
1015
+ - Use tokenized focus styles via `--fk-focus-ring`
1016
+ - Apply `disabled` or `aria-disabled` correctly
1017
+ - Ensure keyboard navigation works (Enter, Space, Escape, Tab as appropriate)
1018
+ - Mark decorative elements as `aria-hidden="true"` (typically via directive host binding)
1019
+
1020
+ ### Focus styling
1021
+
1022
+ All focusable elements must use the shared focus ring token:
1023
+
1024
+ ```scss
1025
+ &:focus-visible {
1026
+ outline: none;
1027
+ box-shadow: var(--fk-focus-ring, 0 0 0 3px rgba(10, 132, 255, 0.18));
1028
+ }
1029
+ ```
1030
+
1031
+ ---
1032
+
1033
+ ## Definition of Done
1034
+
1035
+ Before a component can be merged, every item must be checked:
1036
+
1037
+ ### Structure
1038
+
1039
+ - [ ] Placed in the correct layer (`core/`, `ui/`, `directives/`, `docs/`, `forms/{engine,fields,composition}/`, or `layouts/`) following the dependency rules
1040
+ - [ ] All required files present (`.ts`, `.html`, `.scss`, `.spec.ts`, `.stories.ts`, `.types.ts`, `README.md`)
1041
+ - [ ] Entry-point files present (`index.ts` + `ng-package.json`) — for non-`forms` layers; `forms` components are added to the forms sub-barrels instead
1042
+ - [ ] Cross-component imports use the `@frame-kit/ui-ng/<layer>/<name>` package path, never a relative `../` across an entry-point boundary
1043
+
1044
+ ### Component
1045
+
1046
+ - [ ] `standalone: true`
1047
+ - [ ] `ChangeDetectionStrategy.OnPush`
1048
+ - [ ] Signal-based inputs with `input<Type>(default)`
1049
+ - [ ] `@HostBinding("class")` with computed classes pattern
1050
+ - [ ] Consumer `className` merged without replacing hook classes
1051
+ - [ ] Base class `fk-<name>` and modifier classes `fk-<name>--<modifier>`
1052
+
1053
+ ### Styling
1054
+
1055
+ - [ ] Consumes only `--fk-*` tokens
1056
+ - [ ] Two-tier fallback on every token reference
1057
+ - [ ] No `::ng-deep`
1058
+ - [ ] No token definitions in component SCSS
1059
+ - [ ] No hardcoded hex colors as primary values
1060
+ - [ ] Top-margin-only spacing convention followed
1061
+ - [ ] No host margin on field-level components — wrap consumers in a flex/grid container and use `gap` instead (see "Field-level vs container-level spacing")
1062
+ - [ ] Renders correctly with no theme file loaded
1063
+
1064
+ ### Theme
1065
+
1066
+ - [ ] Tokens added to `tokens.base.scss` if needed
1067
+ - [ ] Dark mode overrides added to `tokens.dark.scss` if component has color tokens
1068
+ - [ ] Verified in both light and dark modes
1069
+
1070
+ ### Tests
1071
+
1072
+ - [ ] `should create` test
1073
+ - [ ] Default input values tested
1074
+ - [ ] Host class presence tested
1075
+ - [ ] Variant classes tested
1076
+ - [ ] Content projection tested if applicable
1077
+ - [ ] Base props passthrough tested
1078
+ - [ ] All tests pass
1079
+
1080
+ ### Storybook
1081
+
1082
+ - [ ] Default story
1083
+ - [ ] Story for each variant
1084
+ - [ ] Story for each size if applicable
1085
+ - [ ] Disabled story if applicable
1086
+ - [ ] Loading story if applicable
1087
+ - [ ] `AllVariants` composite story
1088
+ - [ ] Stories render correctly in Storybook with fallback defaults
1089
+
1090
+ ### Documentation
1091
+
1092
+ - [ ] README follows the exact section order defined in this guide
1093
+ - [ ] API table is complete with all inputs, outputs, and content slots
1094
+ - [ ] Design tokens documented with override examples
1095
+ - [ ] Accessibility behavior documented
1096
+ - [ ] Import path uses `@frame-kit/ui-ng`
1097
+
1098
+ ### Public API
1099
+
1100
+ - [ ] Component, types, and any associated directives exported from the component's entry-point `index.ts` (or the forms sub-barrel for `forms` components)
1101
+ - [ ] New entry point re-exported from the primary `packages/ui-ng/index.ts` (skip for `forms` — already aggregated)
1102
+ - [ ] `nx build ui-ng` succeeds and emits the new entry point's FESM + `exports` map entry