@norconsult-digital-public/isypixel 2.0.2 → 2.0.4-canary.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 (241) hide show
  1. package/LICENSE.md +11 -0
  2. package/dist/docs/web-components/ip-avatar/llm.md +176 -0
  3. package/dist/docs/web-components/ip-avatar-stack/llm.md +171 -0
  4. package/dist/{web-components/components → docs/web-components}/ip-button/llm.md +17 -10
  5. package/dist/docs/web-components/ip-card/llm.md +209 -0
  6. package/dist/docs/web-components/ip-chip/llm.md +157 -0
  7. package/dist/docs/web-components/ip-chip-group/llm.md +165 -0
  8. package/dist/docs/web-components/ip-date-display/llm.md +161 -0
  9. package/dist/docs/web-components/ip-exp-data-grid/llm.md +598 -0
  10. package/dist/docs/web-components/ip-exp-data-grid-action/llm.md +193 -0
  11. package/dist/docs/web-components/ip-exp-data-grid-card/llm.md +133 -0
  12. package/dist/docs/web-components/ip-exp-data-grid-column/llm.md +178 -0
  13. package/dist/docs/web-components/ip-exp-data-grid-column-group/llm.md +181 -0
  14. package/dist/docs/web-components/ip-exp-date-picker/llm.md +287 -0
  15. package/dist/docs/web-components/ip-exp-dialog/llm.md +188 -0
  16. package/dist/docs/web-components/ip-exp-dropdown-menu/llm.md +232 -0
  17. package/dist/docs/web-components/ip-exp-dropdown-menu-item/llm.md +177 -0
  18. package/dist/docs/web-components/ip-exp-filter-builder/llm.md +212 -0
  19. package/dist/docs/web-components/ip-exp-split-button/llm.md +211 -0
  20. package/dist/docs/web-components/ip-exp-switch/llm.md +143 -0
  21. package/dist/docs/web-components/ip-exp-value-list/llm.md +175 -0
  22. package/dist/docs/web-components/ip-icon-button/llm.md +114 -0
  23. package/dist/docs/web-components/ip-icon-count/llm.md +126 -0
  24. package/dist/docs/web-components/ip-main-menu/llm.md +219 -0
  25. package/dist/docs/web-components/ip-main-menu-group/llm.md +114 -0
  26. package/dist/docs/web-components/ip-main-menu-item/llm.md +104 -0
  27. package/dist/docs/web-components/ip-main-menu-section/llm.md +95 -0
  28. package/dist/docs/web-components/ip-main-menu-toggle/llm.md +106 -0
  29. package/dist/docs/web-components/ip-status-marker/llm.md +87 -0
  30. package/dist/docs/web-components/ip-tooltip/llm.md +174 -0
  31. package/dist/docs/web-components/services/i18n/llm.md +151 -0
  32. package/dist/docs/web-components/services/locale/llm.md +62 -0
  33. package/dist/llm.md +68 -10
  34. package/dist/types/components/ip-avatar/ip-avatar.d.ts +105 -0
  35. package/dist/types/components/ip-avatar-stack/ip-avatar-stack.d.ts +102 -0
  36. package/dist/types/components/ip-button/ip-button.d.ts +2 -0
  37. package/dist/types/components/ip-card/ip-card.d.ts +56 -0
  38. package/dist/types/components/ip-chip/ip-chip.d.ts +67 -0
  39. package/dist/types/components/ip-chip-group/ip-chip-group.d.ts +80 -0
  40. package/dist/types/components/ip-date-display/i18n.d.ts +4 -0
  41. package/dist/types/components/ip-date-display/ip-date-display.d.ts +29 -0
  42. package/dist/types/components/ip-icon-button/ip-icon-button.d.ts +35 -0
  43. package/dist/types/components/ip-icon-count/ip-icon-count.d.ts +14 -0
  44. package/dist/types/components/ip-main-menu/i18n.d.ts +8 -0
  45. package/dist/types/components/ip-main-menu/ip-main-menu-drawer-styles.d.ts +1 -0
  46. package/dist/types/components/ip-main-menu/ip-main-menu-state-store.d.ts +24 -0
  47. package/dist/types/components/ip-main-menu/ip-main-menu.d.ts +81 -0
  48. package/dist/types/components/ip-main-menu-group/ip-main-menu-group.d.ts +20 -0
  49. package/dist/types/components/ip-main-menu-item/ip-main-menu-item.d.ts +21 -0
  50. package/dist/types/components/ip-main-menu-section/ip-main-menu-section.d.ts +7 -0
  51. package/dist/types/components/ip-main-menu-toggle/i18n.d.ts +4 -0
  52. package/dist/types/components/ip-main-menu-toggle/ip-main-menu-toggle.d.ts +18 -0
  53. package/dist/types/components/ip-status-marker/ip-status-marker.d.ts +11 -0
  54. package/dist/types/components/ip-tooltip/ip-tooltip.d.ts +146 -0
  55. package/dist/types/components.d.ts +3725 -37
  56. package/dist/types/experimental/components/ip-exp-data-grid/i18n.d.ts +183 -0
  57. package/dist/types/experimental/components/ip-exp-data-grid/ip-exp-data-grid-dataset.d.ts +63 -0
  58. package/dist/types/experimental/components/ip-exp-data-grid/ip-exp-data-grid-date-format.d.ts +20 -0
  59. package/dist/types/experimental/components/ip-exp-data-grid/ip-exp-data-grid-filter-text.d.ts +25 -0
  60. package/dist/types/experimental/components/ip-exp-data-grid/ip-exp-data-grid-setup.d.ts +187 -0
  61. package/dist/types/experimental/components/ip-exp-data-grid/ip-exp-data-grid-template.d.ts +78 -0
  62. package/dist/types/experimental/components/ip-exp-data-grid/ip-exp-data-grid-value-type.d.ts +29 -0
  63. package/dist/types/experimental/components/ip-exp-data-grid/ip-exp-data-grid.d.ts +1262 -0
  64. package/dist/types/experimental/components/ip-exp-data-grid-action/ip-exp-data-grid-action.d.ts +37 -0
  65. package/dist/types/experimental/components/ip-exp-data-grid-card/ip-exp-data-grid-card.d.ts +38 -0
  66. package/dist/types/experimental/components/ip-exp-data-grid-column/ip-exp-data-grid-column.d.ts +72 -0
  67. package/dist/types/experimental/components/ip-exp-data-grid-column-group/ip-exp-data-grid-column-group.d.ts +21 -0
  68. package/dist/types/experimental/components/ip-exp-date-picker/date-picker-model.d.ts +113 -0
  69. package/dist/types/experimental/components/ip-exp-date-picker/i18n.d.ts +44 -0
  70. package/dist/types/experimental/components/ip-exp-date-picker/ip-exp-date-picker.d.ts +307 -0
  71. package/dist/types/experimental/components/ip-exp-dialog/i18n.d.ts +7 -0
  72. package/dist/types/experimental/components/ip-exp-dialog/ip-exp-dialog.d.ts +92 -0
  73. package/dist/types/experimental/components/ip-exp-dropdown-menu/ip-exp-dropdown-menu.d.ts +113 -0
  74. package/dist/types/experimental/components/ip-exp-dropdown-menu-item/ip-exp-dropdown-menu-item.d.ts +33 -0
  75. package/dist/types/experimental/components/ip-exp-filter-builder/i18n.d.ts +65 -0
  76. package/dist/types/experimental/components/ip-exp-filter-builder/ip-exp-filter-builder.d.ts +152 -0
  77. package/dist/types/experimental/components/ip-exp-split-button/i18n.d.ts +7 -0
  78. package/dist/types/experimental/components/ip-exp-split-button/ip-exp-split-button.d.ts +69 -0
  79. package/dist/types/experimental/components/ip-exp-switch/ip-exp-switch.d.ts +74 -0
  80. package/dist/types/experimental/components/ip-exp-value-list/i18n.d.ts +17 -0
  81. package/dist/types/experimental/components/ip-exp-value-list/ip-exp-value-list.d.ts +48 -0
  82. package/dist/types/experimental/grid-engine/aggregate.d.ts +30 -0
  83. package/dist/types/experimental/grid-engine/bitset.d.ts +50 -0
  84. package/dist/types/experimental/grid-engine/collation.d.ts +19 -0
  85. package/dist/types/experimental/grid-engine/column-builder.d.ts +27 -0
  86. package/dist/types/experimental/grid-engine/column-expression.d.ts +40 -0
  87. package/dist/types/experimental/grid-engine/column-store.d.ts +71 -0
  88. package/dist/types/experimental/grid-engine/display-store.d.ts +39 -0
  89. package/dist/types/experimental/grid-engine/facets.d.ts +27 -0
  90. package/dist/types/experimental/grid-engine/filter-ast.d.ts +40 -0
  91. package/dist/types/experimental/grid-engine/filter-builder-model.d.ts +117 -0
  92. package/dist/types/experimental/grid-engine/filter-eval.d.ts +15 -0
  93. package/dist/types/experimental/grid-engine/grouping.d.ts +62 -0
  94. package/dist/types/experimental/grid-engine/highlight.d.ts +29 -0
  95. package/dist/types/experimental/grid-engine/line-metrics.d.ts +37 -0
  96. package/dist/types/experimental/grid-engine/naive-search.d.ts +29 -0
  97. package/dist/types/experimental/grid-engine/query-parser.d.ts +51 -0
  98. package/dist/types/experimental/grid-engine/search.d.ts +35 -0
  99. package/dist/types/experimental/grid-engine/sort.d.ts +34 -0
  100. package/dist/types/experimental/index.d.ts +33 -0
  101. package/dist/types/experimental/shared/field/field-frame.d.ts +43 -0
  102. package/dist/types/experimental/shared/field/i18n.d.ts +14 -0
  103. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/abbreviate-name.d.ts +2 -0
  104. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/get-initials.d.ts +2 -0
  105. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/icons.d.ts +102 -0
  106. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/media-query.d.ts +2 -0
  107. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/xlsx.d.ts +36 -0
  108. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/zip.d.ts +18 -0
  109. package/dist/types/index.d.ts +21 -0
  110. package/dist/types/services/i18n/i18n.d.ts +134 -0
  111. package/dist/types/services/index.d.ts +5 -0
  112. package/dist/types/services/locale/locale.d.ts +23 -0
  113. package/dist/types/stencil-public-runtime.d.ts +74 -55
  114. package/dist/web-components/components/experimental.js +19 -0
  115. package/dist/web-components/components/index.js +1 -1
  116. package/dist/web-components/components/ip-avatar-stack.d.ts +11 -0
  117. package/dist/web-components/components/ip-avatar-stack.js +1 -0
  118. package/dist/web-components/components/ip-avatar.d.ts +11 -0
  119. package/dist/web-components/components/ip-avatar.js +1 -0
  120. package/dist/web-components/components/ip-button.js +1 -1
  121. package/dist/web-components/components/ip-card.d.ts +11 -0
  122. package/dist/web-components/components/ip-card.js +1 -0
  123. package/dist/web-components/components/ip-chip-group.d.ts +11 -0
  124. package/dist/web-components/components/ip-chip-group.js +1 -0
  125. package/dist/web-components/components/ip-chip.d.ts +11 -0
  126. package/dist/web-components/components/ip-chip.js +1 -0
  127. package/dist/web-components/components/ip-date-display.d.ts +11 -0
  128. package/dist/web-components/components/ip-date-display.js +1 -0
  129. package/dist/web-components/components/ip-exp-data-grid-action.d.ts +11 -0
  130. package/dist/web-components/components/ip-exp-data-grid-action.js +1 -0
  131. package/dist/web-components/components/ip-exp-data-grid-card.d.ts +11 -0
  132. package/dist/web-components/components/ip-exp-data-grid-card.js +1 -0
  133. package/dist/web-components/components/ip-exp-data-grid-column-group.d.ts +11 -0
  134. package/dist/web-components/components/ip-exp-data-grid-column-group.js +1 -0
  135. package/dist/web-components/components/ip-exp-data-grid-column.d.ts +11 -0
  136. package/dist/web-components/components/ip-exp-data-grid-column.js +1 -0
  137. package/dist/web-components/components/ip-exp-data-grid.d.ts +11 -0
  138. package/dist/web-components/components/ip-exp-data-grid.js +1 -0
  139. package/dist/web-components/components/ip-exp-date-picker.d.ts +11 -0
  140. package/dist/web-components/components/ip-exp-date-picker.js +1 -0
  141. package/dist/web-components/components/ip-exp-dialog.d.ts +11 -0
  142. package/dist/web-components/components/ip-exp-dialog.js +1 -0
  143. package/dist/web-components/components/ip-exp-dropdown-menu-item.d.ts +11 -0
  144. package/dist/web-components/components/ip-exp-dropdown-menu-item.js +1 -0
  145. package/dist/web-components/components/ip-exp-dropdown-menu.d.ts +11 -0
  146. package/dist/web-components/components/ip-exp-dropdown-menu.js +1 -0
  147. package/dist/web-components/components/ip-exp-filter-builder.d.ts +11 -0
  148. package/dist/web-components/components/ip-exp-filter-builder.js +1 -0
  149. package/dist/web-components/components/ip-exp-split-button.d.ts +11 -0
  150. package/dist/web-components/components/ip-exp-split-button.js +1 -0
  151. package/dist/web-components/components/ip-exp-switch.d.ts +11 -0
  152. package/dist/web-components/components/ip-exp-switch.js +1 -0
  153. package/dist/web-components/components/ip-exp-value-list.d.ts +11 -0
  154. package/dist/web-components/components/ip-exp-value-list.js +1 -0
  155. package/dist/web-components/components/ip-icon-button.d.ts +11 -0
  156. package/dist/web-components/components/ip-icon-button.js +1 -0
  157. package/dist/web-components/components/ip-icon-count.d.ts +11 -0
  158. package/dist/web-components/components/ip-icon-count.js +1 -0
  159. package/dist/web-components/components/ip-main-menu-group.d.ts +11 -0
  160. package/dist/web-components/components/ip-main-menu-group.js +1 -0
  161. package/dist/web-components/components/ip-main-menu-item.d.ts +11 -0
  162. package/dist/web-components/components/ip-main-menu-item.js +1 -0
  163. package/dist/web-components/components/ip-main-menu-section.d.ts +11 -0
  164. package/dist/web-components/components/ip-main-menu-section.js +1 -0
  165. package/dist/web-components/components/ip-main-menu-toggle.d.ts +11 -0
  166. package/dist/web-components/components/ip-main-menu-toggle.js +1 -0
  167. package/dist/web-components/components/ip-main-menu.d.ts +11 -0
  168. package/dist/web-components/components/ip-main-menu.js +1 -0
  169. package/dist/web-components/components/ip-status-marker.d.ts +11 -0
  170. package/dist/web-components/components/ip-status-marker.js +1 -0
  171. package/dist/web-components/components/ip-tooltip.d.ts +11 -0
  172. package/dist/web-components/components/ip-tooltip.js +1 -0
  173. package/dist/web-components/components/p-02Eg1K7Q.js +1 -0
  174. package/dist/web-components/components/p-5lZKL5pU.js +1 -0
  175. package/dist/web-components/components/p-AtiL0xkH.js +1 -0
  176. package/dist/web-components/components/p-B2RTQjbv.js +1 -0
  177. package/dist/web-components/components/p-B7Q0nRIB.js +1 -0
  178. package/dist/web-components/components/p-B8cVaSkf.js +1 -0
  179. package/dist/web-components/components/p-BAVZt5eT.js +1 -0
  180. package/dist/web-components/components/p-BTJLJGxK.js +1 -0
  181. package/dist/web-components/components/p-BaH0ljF8.js +1 -0
  182. package/dist/web-components/components/p-BoqD_XeW.js +1 -0
  183. package/dist/web-components/components/p-BwPaf8qV.js +1 -0
  184. package/dist/web-components/components/p-C-Yd0XyR.js +1 -0
  185. package/dist/web-components/components/p-CFslB2MQ.js +1 -0
  186. package/dist/web-components/components/p-CQV0Bd67.js +1 -0
  187. package/dist/web-components/components/p-CRSkf66C.js +1 -0
  188. package/dist/web-components/components/p-CTPpnp37.js +1 -0
  189. package/dist/web-components/components/p-CXYeHWa6.js +1 -0
  190. package/dist/web-components/components/p-D3ETab0i.js +1 -0
  191. package/dist/web-components/components/p-D71ISsQB.js +3 -0
  192. package/dist/web-components/components/p-DF5j2lnn.js +1 -0
  193. package/dist/web-components/components/p-DYQlk5p6.js +1 -0
  194. package/dist/web-components/components/p-DbAi7XJ5.js +1 -0
  195. package/dist/web-components/components/p-Ddl5OmIu.js +1 -0
  196. package/dist/web-components/components/p-DeFo15B6.js +1 -0
  197. package/dist/web-components/components/p-DefPpKsB.js +1 -0
  198. package/dist/web-components/components/p-RuitbCll.js +1 -0
  199. package/dist/web-components/components/p-VIl8XMWC.js +1 -0
  200. package/dist/web-components/components/p-W1LNEheS.js +1 -0
  201. package/dist/web-components/components/p-i-bxsOWr.js +1 -0
  202. package/dist/web-components/components/p-rLlqXPAQ.js +1 -0
  203. package/dist/web-components/components/p-vAp9QwI5.js +1 -0
  204. package/dist/web-components/generated/angular/angular-component-lib/boolean-attribute.d.ts +35 -0
  205. package/dist/web-components/generated/angular/angular-component-lib/boolean-attribute.js +42 -0
  206. package/dist/web-components/generated/angular/angular-component-lib/boolean-attribute.ts +43 -0
  207. package/dist/web-components/generated/angular/boolean-value-accessor.d.ts +9 -0
  208. package/dist/web-components/generated/angular/boolean-value-accessor.js +37 -0
  209. package/dist/web-components/generated/angular/components.d.ts +1435 -2
  210. package/dist/web-components/generated/angular/components.js +2106 -6
  211. package/dist/web-components/generated/angular/experimental.d.ts +9 -0
  212. package/dist/web-components/generated/angular/experimental.js +9 -0
  213. package/dist/web-components/generated/angular/index.d.ts +1 -1
  214. package/dist/web-components/generated/angular/index.js +29 -1
  215. package/dist/web-components/generated/angular/stable.d.ts +6 -0
  216. package/dist/web-components/generated/angular/stable.js +6 -0
  217. package/dist/web-components/generated/angular/text-value-accessor.d.ts +8 -0
  218. package/dist/web-components/generated/angular/text-value-accessor.js +34 -0
  219. package/dist/web-components/generated/angular/value-accessor.d.ts +18 -0
  220. package/dist/web-components/generated/angular/value-accessor.js +41 -0
  221. package/dist/web-components/generated/react/components.d.ts +151 -2
  222. package/dist/web-components/generated/react/components.js +271 -6
  223. package/dist/web-components/generated/react/experimental.d.ts +8 -0
  224. package/dist/web-components/generated/react/experimental.js +7 -0
  225. package/dist/web-components/generated/react/stable.d.ts +7 -0
  226. package/dist/web-components/generated/react/stable.js +6 -0
  227. package/dist/web-components/generated/svelte/components.d.ts +56 -0
  228. package/dist/web-components/generated/vue/components.d.ts +28 -0
  229. package/dist/web-components/generated/vue/components.js +363 -0
  230. package/dist/web-components/generated/vue/experimental.d.ts +7 -0
  231. package/dist/web-components/generated/vue/experimental.js +7 -0
  232. package/dist/web-components/generated/vue/stable.d.ts +6 -0
  233. package/dist/web-components/generated/vue/stable.js +6 -0
  234. package/llms.txt +61 -0
  235. package/package.json +64 -36
  236. package/dist/web-components/generated/angular/components.ts +0 -56
  237. package/dist/web-components/generated/angular/index.ts +0 -6
  238. package/dist/web-components/generated/react/components.ts +0 -24
  239. package/dist/web-components/generated/vue/components.ts +0 -21
  240. /package/dist/{migrations → docs/migrations}/v1-to-v2.md +0 -0
  241. /package/dist/{esm → docs}/services/theme-service/llm.md +0 -0
@@ -0,0 +1,209 @@
1
+ # ip-card
2
+
3
+ Structural card container that groups related content in a rounded shell with an optional clickable primary action and an optional slim layout. The card owns no domain logic or semantics: the consumer composes the content from existing Pixel components (`ip-chip`, `ip-avatar-stack`, `ip-icon-count`, `ip-date-display`, `ip-icon-button`, `ip-status-marker`) through slots. A semantic status marker (4px stripe on the left edge) is added by slotting an `ip-status-marker` into the `marker` slot. Primary use: mobile rendering of table rows, plus summary, status, and content cards.
4
+
5
+ > **`clickTarget` (where the action lives):** `'none'` (default) is inert. `'heading'` makes just the title the activatable primary action. `'card'` makes the whole card one action, mirroring a clickable table row: every click anywhere on the card is forwarded to the primary action, no exceptions. **Because of that, do not combine `'card'` with standalone interactive controls (including the `actions` slot)** — a slotted control still works, but the card activates as well. A card that needs its own buttons should use `'heading'` mode instead; alternatively a consumer can suppress the card activation per element by calling `event.preventDefault()` in its click handler. A click that ends a text selection does not activate the card. `heading` provides the accessible name and is required when activatable. In `'card'` mode, hovering anywhere on the card outlines the whole card after a short delay, pressing shows a stronger outline, keyboard focus shows the standard 2px focus ring around the card (same style as `ip-button`), and the heading never restyles. In `'heading'` mode the heading is styled as a link (blue), gains an underline while hovered, and keyboard focus rings the heading itself.
6
+
7
+ > **`href` vs button (SPA note):** Setting `href` promotes `clickTarget` from `'none'` to `'heading'` (a link is never inert) and renders the action as a real `<a>` (native navigation, middle-click, open-in-new-tab); it does **not** emit `ipCardClick`. `clickTarget` set without `href` renders a `<button>` that emits `ipCardClick`. An `<a href>` rendered in shadow DOM performs a real browser navigation that framework routers do not intercept. For SPA client-side routing, leave `href` empty, set `clickTarget`, and listen to `ipCardClick`. Set `href` only for real links. This mirrors the `brandHref` / `ipBrandClick` split in `ip-main-menu`.
8
+
9
+ > **Known `'card'`-mode limitations (accepted trade-offs of click delegation):** With `href`, native link affordances (middle-click, Ctrl/Cmd+click for a new tab) work only on the heading itself; a plain click elsewhere on the card is forwarded programmatically and performs a normal navigation. Selecting text on the card does not activate it, but browsers that do not expose shadow-DOM selections through `window.getSelection()` may still activate the card when the selection is inside the heading itself.
10
+
11
+ ## Attributes
12
+
13
+ | Attribute | Type | Default | Allowed values | Description |
14
+ | ------------- | ------------------------------- | ----------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
15
+ | `eyebrow` | `string` | `''` | Any string | Small overline label above the title. |
16
+ | `heading` | `string` | `''` | Any string | Main title. Conditionally required when the card is activatable (`clickTarget` or `href`), because it provides the accessible name. |
17
+ | `subheading` | `string` | `''` | Any string | Secondary line below the title. |
18
+ | `clickTarget` | `'none' \| 'heading' \| 'card'` | `'none'` | `'none'`, `'heading'`, `'card'` | Where the primary action's hit area lives. `'none'` is inert; `'heading'` makes just the title activatable; `'card'` forwards clicks on passive card areas to the primary action. Setting `href` promotes `'none'` to `'heading'`. |
19
+ | `href` | `string \| undefined` | `undefined` | Any URL string | Renders the primary action as a real `<a>` and makes the card activatable (at least `'heading'`). Without `href`, an activatable card is a `<button>` emitting `ipCardClick`. |
20
+ | `target` | `string \| undefined` | `undefined` | Any anchor target (e.g. `_blank`) | Link target, applied only when `href` is set. |
21
+ | `slim` | `boolean` (reflected) | `false` | `true`, `false` | Slim layout. When `true`, the card shows the status marker, `heading`, `header-end` and body (`default` slot); the `eyebrow`, `subheading`, `footer` and `actions` are hidden. The consumer owns this prop; there is no built-in toggle. |
22
+
23
+ ## Slots
24
+
25
+ | Slot | Description |
26
+ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
27
+ | `marker` | Status marker anchored flush to the card's left edge (typically an `ip-status-marker`). Stays visible in slim layout. |
28
+ | `header-end` | Right-aligned content in the top region (date, star, badge). Stays visible in slim layout. |
29
+ | `default` | Body content (chips, meta rows). Stays visible in slim layout. |
30
+ | `footer` | Passive info region below the auto-divider (avatar, counts, meta). Hidden in slim layout, and removed entirely (no gap or divider) when the slot is empty. "Empty" means nothing is assigned to the slot — see the note below. |
31
+ | `actions` | Right-aligned interactive controls (icon buttons, menu) shown as a bottom row. Do not combine with `clickTarget='card'` (clicks on the controls would also activate the card); use `'heading'` mode for cards with actions. Without a footer, the auto-divider sits above this row instead; with a footer there is no divider between footer and actions. Hidden in slim layout, and removed entirely (no gap or divider) when the slot is empty. |
32
+
33
+ > **Empty `footer` and `actions` wrappers still count as content:** the card removes the footer (divider included) and the actions row only when _nothing_ is assigned to the slot. A wrapper element that is always rendered but happens to be empty — `<div slot="footer">` whose own children are all conditional — is assigned content, so the divider is drawn with nothing under it. The card cannot tell the difference; a nested element with no text is not distinguishable from one that renders an icon. Render the wrapper conditionally instead, so the slot is genuinely empty:
34
+ >
35
+ > ```html
36
+ > <!-- Angular -->
37
+ > @if (hasFooterContent) {
38
+ > <div slot="footer">…</div>
39
+ > }
40
+ > ```
41
+ >
42
+ > Alternatively, slot the individual items directly (`<ip-avatar slot="footer">`, `<ip-icon-count slot="footer">`) with the condition on each — several elements may share one slot name, and the slot is empty when none of them render.
43
+
44
+ ## Events
45
+
46
+ | Event | Payload | Description |
47
+ | ------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
48
+ | `ipCardClick` | `CustomEvent<void>` | Fires when the primary action is activated (click / Enter / Space) in button mode: `clickTarget` set (`'heading'` or `'card'`) and no `href`. |
49
+
50
+ ## Slim layout
51
+
52
+ Slim is a **static layout choice**, not a disclosure: the consumer sets the `slim` prop (there is no built-in toggle button). When `slim`:
53
+
54
+ - The `heading`, `header-end` and body (`default` slot) stay visible, with tighter spacing.
55
+ - The `eyebrow`, `subheading`, `footer` and `actions` are hidden.
56
+ - The `marker` slot stays visible.
57
+
58
+ Slim reuses the same `default` slot as the full card, so there is no separate slot to maintain. To drive it from state, bind `slim` to your own value; provide a lighter body in the `default` slot if the full content is too heavy for the compact row.
59
+
60
+ ## Height and equal-height grids
61
+
62
+ The card box fills whatever height the host element (`<ip-card>`) is given, and falls back to its content height when the host is content-sized. Nothing needs to be enabled — there is no `fullHeight` prop.
63
+
64
+ - **In a CSS grid or flex row**, items stretch by default (`align-items: stretch`), so sibling cards in the same row already render at equal height with no extra CSS. Only set `align-items` to something other than `stretch` if you _want_ content-sized cards.
65
+ - **With an explicit height**, style the host: `ip-card { height: 100% }` or `block-size: 100%`.
66
+ - **When the card is wrapped** in another element (a framework component's host, a `<div>`, a repeater item), that wrapper must pass the height on — give it `height: 100%`, or `display: contents` so it disappears from the layout entirely. A wrapper with an auto height is the usual reason a card refuses to fill its row.
67
+
68
+ When a card is taller than its content, the spare height goes to the body (`default` slot), so the `footer` and `actions` rows sit at the card's bottom edge instead of leaving a gap below them. Content that overflows a fixed-height card is clipped (the card sets `overflow: hidden`).
69
+
70
+ ```html
71
+ <!-- Equal-height cards, no per-card CSS needed -->
72
+ <div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px">
73
+ <ip-card heading="Short card"><p>One line.</p></ip-card>
74
+ <ip-card heading="Tall card"><p>Several lines of body content…</p></ip-card>
75
+ </div>
76
+ ```
77
+
78
+ ## Design tokens
79
+
80
+ | Token | Role |
81
+ | ----------------------------------------- | --------------------------------------------------------------------------------------------------- |
82
+ | `ip_color_surface_primary_default` | Card background |
83
+ | `ip_color_border_opaque_15` | Card border (default) |
84
+ | `ip_color_border_opaque_20` | Card outline on hover (`'card'` mode) |
85
+ | `ip_color_border_primary_default` | Card outline on active/press (`'card'` mode) |
86
+ | `ip_color_border_opaque_10` | Auto-divider above the footer, or above the actions when there is no footer |
87
+ | `ip_color_text_primary_black` | Heading text at rest (non-interactive and `'card'` mode) |
88
+ | `ip_color_hyperlink_text_primary_default` | Heading link colour at rest (`'heading'` mode) |
89
+ | `ip_color_hyperlink_text_primary_hover` | Heading link colour on hover (`'heading'` mode) |
90
+ | `ip_color_text_secondary_default` | Subheading text |
91
+ | `ip_color_text_tertiary_default` | Eyebrow text |
92
+ | `ip_color_button_focus_default` | Focus ring on the heading (`'heading'` mode) and the 2px focus ring around the card (`'card'` mode) |
93
+ | `ip_radius_size_x_large` | Card corner radius (12px stand-in; Figma specifies 16px) |
94
+
95
+ > **Token gaps (flagged to design team, §8):**
96
+ >
97
+ > - A radius token for 16px (Figma spec). Interim: `ip_radius_size_x_large` (12px).
98
+ > - A card-specific focus token; interim reuse of `ip_color_button_focus_default`.
99
+ > - Border-width and motion tokens: the 5px interaction outline, 1px borders, and the 150ms transition/delay values are raw values, marked with `//TODO:` in the SCSS.
100
+
101
+ ## Usage examples
102
+
103
+ ### HTML
104
+
105
+ ```html
106
+ <!-- Whole card clickable (table-row style): emits ipCardClick, for SPA routing -->
107
+ <ip-card eyebrow="Work order" heading="Periodic maintenance, turbine 2" subheading="AO-11033380" click-target="card" id="card">
108
+ <ip-status-marker slot="marker" status="warning"></ip-status-marker>
109
+ <span slot="header-end">19.08.2025</span>
110
+ <p>SØR...FOFOL.411.012.300 - Guide vanes</p>
111
+ <div slot="footer">Alexander Wangsvik Olsen +2</div>
112
+ <ip-icon-button slot="actions" label="Edit"></ip-icon-button>
113
+ </ip-card>
114
+ <script>
115
+ document.getElementById('card').addEventListener('ipCardClick', () => router.navigate('/orders/11033380'));
116
+ </script>
117
+
118
+ <!-- Link card: real navigation, no ipCardClick -->
119
+ <ip-card heading="Major leak. Dam is affected." href="/faults/314001" target="_blank">
120
+ <ip-status-marker slot="marker" status="error"></ip-status-marker>
121
+ </ip-card>
122
+ ```
123
+
124
+ ### React
125
+
126
+ ```tsx
127
+ import { IpCard, IpStatusMarker } from '@norconsult-digital-public/isypixel/web-components/react';
128
+
129
+ // Interactive (SPA)
130
+ <IpCard eyebrow='Work order' heading='Periodic maintenance' subheading='AO-11033380' clickTarget='card' onIpCardClick={() => navigate('/orders/11033380')}>
131
+ <IpStatusMarker slot='marker' status='warning' />
132
+ <span slot='header-end'>19.08.2025</span>
133
+ <p>Guide vanes</p>
134
+ <div slot='footer'>Alexander Wangsvik Olsen +2</div>
135
+ </IpCard>
136
+
137
+ // Link
138
+ <IpCard heading='Major leak' href='/faults/314001' target='_blank'>
139
+ <IpStatusMarker slot='marker' status='error' />
140
+ </IpCard>
141
+ ```
142
+
143
+ ### Vue
144
+
145
+ ```vue
146
+ <!-- Interactive (SPA) -->
147
+ <IpCard eyebrow="Work order" heading="Periodic maintenance" click-target="card" @ip-card-click="router.push('/orders/11033380')">
148
+ <IpStatusMarker slot="marker" status="warning" />
149
+ <span slot="header-end">19.08.2025</span>
150
+ <p>Guide vanes</p>
151
+ <div slot="footer">Alexander Wangsvik Olsen +2</div>
152
+ </IpCard>
153
+
154
+ <!-- Link -->
155
+ <IpCard heading="Major leak" href="/faults/314001" target="_blank">
156
+ <IpStatusMarker slot="marker" status="error" />
157
+ </IpCard>
158
+ ```
159
+
160
+ ### Angular
161
+
162
+ ```html
163
+ <!-- Interactive (SPA) -->
164
+ <ip-card eyebrow="Work order" heading="Periodic maintenance" clickTarget="card" (ipCardClick)="router.navigate(['/orders/11033380'])">
165
+ <ip-status-marker slot="marker" status="warning"></ip-status-marker>
166
+ <span slot="header-end">19.08.2025</span>
167
+ <p>Guide vanes</p>
168
+ <div slot="footer">Alexander Wangsvik Olsen +2</div>
169
+ </ip-card>
170
+
171
+ <!-- Link -->
172
+ <ip-card heading="Major leak" href="/faults/314001" target="_blank">
173
+ <ip-status-marker slot="marker" status="error"></ip-status-marker>
174
+ </ip-card>
175
+ ```
176
+
177
+ ### Svelte
178
+
179
+ ```svelte
180
+ <!-- Interactive (SPA) -->
181
+ <ip-card heading="Periodic maintenance" click-target="card" on:ipCardClick={() => goto('/orders/11033380')}>
182
+ <ip-status-marker slot="marker" status="warning"></ip-status-marker>
183
+ <span slot="header-end">19.08.2025</span>
184
+ <p>Guide vanes</p>
185
+ <div slot="footer">Alexander Wangsvik Olsen +2</div>
186
+ </ip-card>
187
+
188
+ <!-- Link -->
189
+ <ip-card heading="Major leak" href="/faults/314001" target="_blank">
190
+ <ip-status-marker slot="marker" status="error"></ip-status-marker>
191
+ </ip-card>
192
+ ```
193
+
194
+ ### Blazor
195
+
196
+ ```razor
197
+ <!-- Interactive (SPA) -->
198
+ <ip-card heading="Periodic maintenance" click-target="card" @onipCardClick="OpenOrder">
199
+ <ip-status-marker slot="marker" status="warning"></ip-status-marker>
200
+ <span slot="header-end">19.08.2025</span>
201
+ <p>Guide vanes</p>
202
+ <div slot="footer">Alexander Wangsvik Olsen +2</div>
203
+ </ip-card>
204
+
205
+ <!-- Link -->
206
+ <ip-card heading="Major leak" href="/faults/314001" target="_blank">
207
+ <ip-status-marker slot="marker" status="error"></ip-status-marker>
208
+ </ip-card>
209
+ ```
@@ -0,0 +1,157 @@
1
+ # ip-chip
2
+
3
+ A compact, presentational chip: a label/tag with a semantic status colour, orthogonal fill and border axes, an optional leading icon, a collapsed icon-only state, and an optional remove button. It has no click or selection behaviour of its own — interactivity and selection are owned by `ip-chip-group`. Standalone, it is a status tag or a removable input tag.
4
+
5
+ **Storybook:** https://pixel.isy.no/storybook/?path=/docs/core-ui-ip-chip-usage--docs
6
+
7
+ > This component requires ISY Pixel global styles to render correctly. Always remind the user to verify that styles are imported in their global stylesheet. See `llm.md` in the package root for style import options.
8
+
9
+ ## Attributes
10
+
11
+ | Attribute | Type | Default | Values | Description |
12
+ | -------------- | ------- | ---------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
13
+ | `variant` | string | `"info"` | `"neutral"`, `"info"`, `"success"`, `"warning"`, `"error"`, `"brand"` | Drives the chip colour (fill and icon). `info` is the default; `neutral` is a status-less greyscale chip. |
14
+ | `border-style` | string | `"solid"` | `"none"`, `"solid"`, `"dashed"` | Border style. |
15
+ | `size` | string | `"medium"` | `"small"`, `"medium"`, `"large"` | Chip size. |
16
+ | `value` | string | `""` | Any string | Passive identity read by `ip-chip-group`. No effect standalone. |
17
+ | `interactive` | boolean | `false` | `true`, `false` | Makes the chip body a toggle button with `aria-pressed`. Set by `ip-chip-group` in single/multiple mode; no effect standalone. |
18
+ | `selected` | boolean | `false` | `true`, `false` | Visual selected state. Set primarily by `ip-chip-group`; styling only. |
19
+ | `collapsed` | boolean | `false` | `true`, `false` | Hides the text visually and shows only the icon. The text is kept for screen readers. Requires the `icon` slot. |
20
+ | `removable` | boolean | `false` | `true`, `false` | Shows a built-in remove button (X). |
21
+ | `remove-label` | string | `"Remove"` | Any string | Accessible label for the remove button. The chip text is appended where available, e.g. "Remove High". |
22
+ | `disabled` | boolean | `false` | `true`, `false` | Disables the chip and its remove button. |
23
+
24
+ **Colour vs border** — `variant` sets the fill tint and icon colour; `border-style` independently controls the border line (`none`, `solid`, or `dashed`). The border is a neutral grey on every variant — the status colour lives in the fill and the icon.
25
+
26
+ ## Slots
27
+
28
+ | Slot | Description |
29
+ | ------------- | -------------------------------------------------------------------------------------------------- |
30
+ | `default` | The text content of the chip. |
31
+ | `icon` | Leading icon or status dot, shown before the text. Required when `collapsed`. |
32
+ | `remove-icon` | Overrides the icon inside the remove button. Defaults to a built-in X (Font Awesome Free `xmark`). |
33
+
34
+ ## Events
35
+
36
+ | Event | Payload | When |
37
+ | ---------- | ------- | --------------------------------------------------------------------- |
38
+ | `ipRemove` | `void` | The built-in remove button is activated. Not emitted when `disabled`. |
39
+
40
+ ## Methods
41
+
42
+ | Method | Signature | Description |
43
+ | ---------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
44
+ | `setFocus` | `setFocus(segment?: 'primary' \| 'remove') => Promise<void>` | Moves focus to a chip segment (default `'primary'`). Falls back to the other segment when the requested one is not focusable. Used by `ip-chip-group` to restore focus after a keyboard removal. |
45
+
46
+ ## Accessibility
47
+
48
+ - The chip is presentational by default. Inside `ip-chip-group` it becomes a **split button**: the primary (body) and the remove button are separate, sibling `<button>`s — never nested. In selection modes the group makes the primary a toggle button with `aria-pressed`; every primary and remove button is a natural tab stop.
49
+ - When a chip is removed via keyboard inside `ip-chip-group`, the group moves focus to a neighbouring chip (see the ip-chip-group docs). Standalone, the consumer is responsible for moving focus after removing the chip from the DOM.
50
+ - When `collapsed`, the text is visually hidden but kept for assistive technology, so the chip retains an accessible name.
51
+ - The remove button is an always-focusable `<button>` with an accessible name combining `remove-label` and the chip text, e.g. "Remove High". It shows a `:focus-visible` ring.
52
+ - When `disabled`, the remove button sets both `disabled` and `aria-disabled="true"`, and the chip is greyed out.
53
+ - Long text is truncated with an ellipsis: the chip is never wider than its container, and `--ip-chip-max-width` can cap it narrower. The full text is then shown in an `ip-tooltip` on hover/focus. A `collapsed` chip always offers its full text in the tooltip.
54
+
55
+ ## Design tokens
56
+
57
+ > The chip reuses generic semantic tokens and non-adaptive primitive extended/core colours as theme stand-ins; a dedicated chip token family (fill, border, text, focus, remove-hover, target size) does not exist yet. These are marked `//TODO` in the SCSS and will be replaced once design adds the chip tokens.
58
+
59
+ | SCSS variable | Role |
60
+ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
61
+ | `$ip_color_core_steel_blue-900` | Text colour on every variant — a non-adaptive near-black (= primary-black's light value) so it stays legible on the light fills in both themes; the label is shown at 60% opacity. A 20%-opacity mix of it is the non-adaptive border on filled chips (visible on the light fill in both themes). |
62
+ | `$ip_color_border_opaque_20` | Adaptive border for the group-unselected (ghost) chip, which sits on the page rather than a fill: near-black at 20% in light mode, flipping to translucent white in dark mode so the outline stays visible. Also the remove-button hover background. |
63
+ | `$ip_color_text_primary_black` | Text colour for a fill-less ghost chip (group-unselected) — adaptive, so it follows the page in dark mode |
64
+ | `$ip_color_extended_indigo-100` / `-700` | `info` (default) fill / icon |
65
+ | `$ip_color_brand_accent_primary_default` | `brand` fill (a pale `color-mix` wash) and icon — the teal brand accent |
66
+ | `$ip_color_extended_green-100` / `-700` | `success` fill / icon |
67
+ | `$ip_color_extended_orange-100` / `-950` | `warning` fill / icon |
68
+ | `$ip_color_extended_red-100` / `-600` | `error` fill / icon |
69
+ | `$ip_color_core_light_gray-200` / `-900` | `neutral` fill / icon (status-less greyscale) |
70
+ | `$ip_color_border_opaque_10` | Resting background of the remove button |
71
+ | `$ip_color_button_focus_default` | Focus ring for the chip and the remove button (borrowed from ip-button) |
72
+ | `$ip_radius_size_medium` | Border radius — fixed across sizes, matching ip-button |
73
+ | `$ip_spacing_space_025` – `$ip_spacing_space_150` | Padding and gaps per size |
74
+ | `$ip_typography_body_font_size_small` / `_medium` / `_large` | Text font size per size |
75
+ | `$ip_typography_font_family_primary` | Font family |
76
+ | `$ip_typography_font_weight_medium_500` | Text font weight |
77
+
78
+ ## CSS custom properties
79
+
80
+ | Property | Default | Role |
81
+ | --------------------- | ------- | -------------------------------------------------------------- |
82
+ | `--ip-chip-max-width` | `100%` | Maximum chip width before the text truncates with an ellipsis. |
83
+
84
+ ## Usage
85
+
86
+ ### HTML
87
+
88
+ > Attribute names must be **kebab-case** in HTML: use `border-style`, not `borderStyle`.
89
+
90
+ ```js
91
+ import '@norconsult-digital-public/isypixel/web-components/ip-chip.js';
92
+ ```
93
+
94
+ ```html
95
+ <ip-chip variant="warning" border-style="dashed">
96
+ <span slot="icon">●</span>
97
+ High
98
+ </ip-chip>
99
+
100
+ <ip-chip variant="info" removable remove-label="Remove">Periodic 2 months</ip-chip>
101
+ ```
102
+
103
+ ### React
104
+
105
+ ```tsx
106
+ import { IpChip } from '@norconsult-digital-public/isypixel/web-components/react';
107
+
108
+ <IpChip variant='warning' borderStyle='dashed' onIpRemove={() => {}}>
109
+ High
110
+ </IpChip>;
111
+ ```
112
+
113
+ ### Vue
114
+
115
+ ```vue
116
+ <script setup lang="ts">
117
+ import { IpChip } from '@norconsult-digital-public/isypixel/web-components/vue';
118
+ </script>
119
+
120
+ <template>
121
+ <IpChip variant="info" removable @ipRemove="onRemove">Periodic 2 months</IpChip>
122
+ </template>
123
+ ```
124
+
125
+ ### Angular
126
+
127
+ ```ts
128
+ import { IpChip } from '@norconsult-digital-public/isypixel/web-components/angular';
129
+ ```
130
+
131
+ ```html
132
+ <ip-chip variant="success" border-style="none" (ipRemove)="onRemove()">Scheduled</ip-chip>
133
+ ```
134
+
135
+ ### Svelte
136
+
137
+ ```svelte
138
+ <script lang="ts">
139
+ import '@norconsult-digital-public/isypixel/web-components/ip-chip.js';
140
+ </script>
141
+
142
+ <ip-chip variant="error" border-style="dashed">Failure</ip-chip>
143
+ ```
144
+
145
+ ### Blazor
146
+
147
+ > Attribute names must be **kebab-case** in Blazor HTML.
148
+
149
+ Copy `ip-chip.js` to `wwwroot/lib/isypixel/components/` at build time. See `llm.md` in the package root for the MSBuild target.
150
+
151
+ ```html
152
+ <script type="module" src="lib/isypixel/components/ip-chip.js"></script>
153
+
154
+ <ip-chip variant="warning" border-style="dashed">High</ip-chip>
155
+ ```
156
+
157
+ - Boolean attributes such as `removable`, `collapsed`, `selected`, and `disabled` must be set as `removable="true"` (string value) — Blazor does not reliably render bare boolean attributes on custom elements.
@@ -0,0 +1,165 @@
1
+ # ip-chip-group
2
+
3
+ Groups `ip-chip` children under one shared interaction model. `selection-mode` decides what activating a chip does — nothing (display/grouping), select one, or select many — while `removable` on individual chips is orthogonal and adds a trailing remove button. Each chip is rendered as a **split button** (a primary action and an optional remove action as separate buttons); every button is a natural tab stop, and the group restores focus to a neighbouring chip after a keyboard-initiated removal.
4
+
5
+ **Storybook:** https://pixel.isy.no/storybook/?path=/docs/core-ui-ip-chip-group-usage--docs
6
+
7
+ > This component requires ISY Pixel global styles to render correctly. Always remind the user to verify that styles are imported in their global stylesheet. See `llm.md` in the package root for style import options.
8
+
9
+ ## The two axes
10
+
11
+ The group composes two independent things — combine them freely:
12
+
13
+ 1. **Selection** — the group's `selection-mode`: `none`, `single`, or `multiple`. A change emits `ipChange`.
14
+ 2. **Removal** — a chip's own `removable` attribute adds a remove button that emits `ipRemove` (on the chip). Independent of `selection-mode`.
15
+
16
+ | Goal | Recipe |
17
+ | ------------------------------- | -------------------------------------------------------------------------- |
18
+ | Read-only status group | `selection-mode="none"` |
19
+ | Removable tags (not selectable) | `selection-mode="none"` + chips `removable` |
20
+ | Choose one / several | `selection-mode="single"` / `"multiple"` |
21
+ | Choose several **and** remove | `selection-mode="multiple"` + `removable` + listen `ipChange` / `ipRemove` |
22
+
23
+ ## Attributes
24
+
25
+ | Attribute | Type | Default | Values | Description |
26
+ | ---------------- | ------------------- | -------------- | ---------------------------------- | ----------------------------------------------------------------------------------------------------- |
27
+ | `selection-mode` | string | `"none"` | `"none"`, `"single"`, `"multiple"` | What activating a chip does: nothing (display), select one, or select many. |
28
+ | `value` | string \| string\[] | `undefined` | Any value(s) | Selected value(s). Binding it makes the group controlled. `string` in single, `string[]` in multiple. |
29
+ | `orientation` | string | `"horizontal"` | `"horizontal"`, `"vertical"` | Visual layout direction of the chips. |
30
+ | `disabled` | boolean | `false` | `true`, `false` | Disables the whole group and all of its chips. |
31
+
32
+ **Control model** — uncontrolled by default: the group owns the selection state (and `value`, if set initially, seeds it). It becomes controlled when the consumer binds `value` and writes the new value back in the `ipChange` handler — like a native `<input>`.
33
+
34
+ **`value` typing** — a `string` in single mode (settable via the attribute), a `string[]` in multiple mode (must be set via the **property**, since an HTML attribute can only carry a string).
35
+
36
+ **Chip identity** — a chip is identified by its `value`; a chip without one falls back to its trimmed text content. Chips that share the same fallback text share one identity — selecting one selects all of them — so always set `value` on selectable chips.
37
+
38
+ ## Slots
39
+
40
+ | Slot | Description |
41
+ | --------- | ------------------------------- |
42
+ | `default` | One or more `ip-chip` elements. |
43
+
44
+ ## Events
45
+
46
+ | Event | Payload | When |
47
+ | ---------- | ------------------------------- | ---------------------------------------------------------------------------------------------- |
48
+ | `ipChange` | `{ value: string \| string[] }` | Selection changes (single/multiple only). `value` is a string in single, an array in multiple. |
49
+
50
+ > Removal is emitted as `ipRemove` on the individual `ip-chip`, not on the group.
51
+
52
+ ## Accessibility
53
+
54
+ - The group is `role="group"`. Provide an accessible name via `aria-label` or `aria-labelledby`. Orientation is visual only (a reflected `orientation` attribute drives the layout); `aria-orientation` is intentionally not set, since it is not a valid property on `role="group"`.
55
+ - **Split button** — each chip exposes a primary button and, when `removable`, a separate remove button; they are never nested, so clicking remove never also toggles selection.
56
+ - **Keyboard** — every primary and remove button is a natural tab stop; Enter/Space and click activate the focused button natively.
57
+ - **Focus restoration on removal** — when a chip is removed while its remove button holds visible keyboard focus (`:focus-visible`), the group moves focus to the next chip's remove button (or the closest previous one when the last chip was removed), so serial keyboard removal never drops focus. Pointer-initiated removals never move focus. Focus moves only after the consumer actually removes the chip from the DOM in its `ipRemove` handler.
58
+ - **Selection** — in single/multiple the primary is a toggle button with `aria-pressed`. Selection is explicit (it does not follow focus).
59
+ - In `none` mode the primary is not interactive or focusable (display only); a `removable` chip's remove button stays focusable.
60
+ - When `disabled`, the group and every chip are disabled; chips re-enable to their own prior state when the group is re-enabled.
61
+
62
+ ## Design tokens
63
+
64
+ | SCSS variable | Role |
65
+ | ----------------------- | ----------------- |
66
+ | `$ip_spacing_space_100` | Gap between chips |
67
+
68
+ ## Usage
69
+
70
+ ### HTML
71
+
72
+ ```js
73
+ import '@norconsult-digital-public/isypixel/web-components/ip-chip-group.js';
74
+ import '@norconsult-digital-public/isypixel/web-components/ip-chip.js';
75
+ ```
76
+
77
+ ```html
78
+ <!-- Filter: choose several, each removable -->
79
+ <ip-chip-group selection-mode="multiple" aria-label="Filter by status">
80
+ <ip-chip value="high" removable>High</ip-chip>
81
+ <ip-chip value="medium" removable>Medium</ip-chip>
82
+ <ip-chip value="low" removable>Low</ip-chip>
83
+ </ip-chip-group>
84
+
85
+ <!-- Removable tags, no selection -->
86
+ <ip-chip-group aria-label="Applied tags">
87
+ <ip-chip value="design" removable>Design</ip-chip>
88
+ <ip-chip value="frontend" removable>Frontend</ip-chip>
89
+ </ip-chip-group>
90
+ ```
91
+
92
+ ### React
93
+
94
+ ```tsx
95
+ import { IpChipGroup, IpChip } from '@norconsult-digital-public/isypixel/web-components/react';
96
+
97
+ <IpChipGroup selectionMode='multiple' aria-label='Tags' onIpChange={(e) => setValue(e.detail.value)}>
98
+ <IpChip value='a' removable onIpRemove={() => remove('a')}>
99
+ Design
100
+ </IpChip>
101
+ <IpChip value='b' removable onIpRemove={() => remove('b')}>
102
+ Frontend
103
+ </IpChip>
104
+ </IpChipGroup>;
105
+ ```
106
+
107
+ ### Vue
108
+
109
+ ```vue
110
+ <script setup lang="ts">
111
+ import { IpChipGroup, IpChip } from '@norconsult-digital-public/isypixel/web-components/vue';
112
+ </script>
113
+
114
+ <template>
115
+ <IpChipGroup selection-mode="single" aria-label="Priority" @ipChange="onChange">
116
+ <IpChip value="high">High</IpChip>
117
+ <IpChip value="low">Low</IpChip>
118
+ </IpChipGroup>
119
+ </template>
120
+ ```
121
+
122
+ ### Angular
123
+
124
+ ```ts
125
+ import { IpChipGroup, IpChip } from '@norconsult-digital-public/isypixel/web-components/angular';
126
+ ```
127
+
128
+ ```html
129
+ <ip-chip-group selection-mode="multiple" aria-label="Filters" (ipChange)="onChange($event)">
130
+ <ip-chip value="a" [removable]="true" (ipRemove)="remove('a')">Design</ip-chip>
131
+ <ip-chip value="b" [removable]="true" (ipRemove)="remove('b')">Frontend</ip-chip>
132
+ </ip-chip-group>
133
+ ```
134
+
135
+ ### Svelte
136
+
137
+ ```svelte
138
+ <script lang="ts">
139
+ import '@norconsult-digital-public/isypixel/web-components/ip-chip-group.js';
140
+ import '@norconsult-digital-public/isypixel/web-components/ip-chip.js';
141
+ </script>
142
+
143
+ <ip-chip-group selection-mode="multiple" aria-label="Filter">
144
+ <ip-chip value="a">A</ip-chip>
145
+ <ip-chip value="b">B</ip-chip>
146
+ </ip-chip-group>
147
+ ```
148
+
149
+ ### Blazor
150
+
151
+ > Attribute names must be **kebab-case** in Blazor HTML.
152
+
153
+ Copy `ip-chip-group.js` and `ip-chip.js` to `wwwroot/lib/isypixel/components/` at build time. See `llm.md` in the package root for the MSBuild target.
154
+
155
+ ```html
156
+ <script type="module" src="lib/isypixel/components/ip-chip-group.js"></script>
157
+ <script type="module" src="lib/isypixel/components/ip-chip.js"></script>
158
+
159
+ <ip-chip-group selection-mode="single" aria-label="Priority">
160
+ <ip-chip value="high">High</ip-chip>
161
+ <ip-chip value="low">Low</ip-chip>
162
+ </ip-chip-group>
163
+ ```
164
+
165
+ - For multiple selection, set `value` as a property (`string[]`) from your view model — an HTML attribute can only carry a single string.