@norconsult-digital-public/isypixel 2.0.1 → 2.0.2-canary.10

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 (121) hide show
  1. package/LICENSE.md +11 -0
  2. package/dist/docs/web-components/ip-avatar/llm.md +172 -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 +180 -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-icon-button/llm.md +113 -0
  10. package/dist/docs/web-components/ip-icon-count/llm.md +126 -0
  11. package/dist/docs/web-components/ip-main-menu/llm.md +219 -0
  12. package/dist/docs/web-components/ip-main-menu-group/llm.md +114 -0
  13. package/dist/docs/web-components/ip-main-menu-item/llm.md +104 -0
  14. package/dist/docs/web-components/ip-main-menu-section/llm.md +95 -0
  15. package/dist/docs/web-components/ip-main-menu-toggle/llm.md +106 -0
  16. package/dist/docs/web-components/ip-status-marker/llm.md +87 -0
  17. package/dist/docs/web-components/ip-tooltip/llm.md +174 -0
  18. package/dist/docs/web-components/services/i18n/llm.md +150 -0
  19. package/dist/docs/web-components/services/locale/llm.md +62 -0
  20. package/dist/llm.md +27 -10
  21. package/dist/types/components/ip-avatar/ip-avatar.d.ts +97 -0
  22. package/dist/types/components/ip-avatar-stack/ip-avatar-stack.d.ts +102 -0
  23. package/dist/types/components/ip-button/ip-button.d.ts +7 -0
  24. package/dist/types/components/ip-card/ip-card.d.ts +52 -0
  25. package/dist/types/components/ip-chip/ip-chip.d.ts +67 -0
  26. package/dist/types/components/ip-chip-group/ip-chip-group.d.ts +80 -0
  27. package/dist/types/components/ip-date-display/i18n.d.ts +4 -0
  28. package/dist/types/components/ip-date-display/ip-date-display.d.ts +29 -0
  29. package/dist/types/components/ip-icon-button/ip-icon-button.d.ts +35 -0
  30. package/dist/types/components/ip-icon-count/ip-icon-count.d.ts +14 -0
  31. package/dist/types/components/ip-main-menu/i18n.d.ts +8 -0
  32. package/dist/types/components/ip-main-menu/ip-main-menu-drawer-styles.d.ts +1 -0
  33. package/dist/types/components/ip-main-menu/ip-main-menu-state-store.d.ts +24 -0
  34. package/dist/types/components/ip-main-menu/ip-main-menu.d.ts +81 -0
  35. package/dist/types/components/ip-main-menu-group/ip-main-menu-group.d.ts +20 -0
  36. package/dist/types/components/ip-main-menu-item/ip-main-menu-item.d.ts +21 -0
  37. package/dist/types/components/ip-main-menu-section/ip-main-menu-section.d.ts +7 -0
  38. package/dist/types/components/ip-main-menu-toggle/i18n.d.ts +4 -0
  39. package/dist/types/components/ip-main-menu-toggle/ip-main-menu-toggle.d.ts +18 -0
  40. package/dist/types/components/ip-status-marker/ip-status-marker.d.ts +11 -0
  41. package/dist/types/components/ip-tooltip/ip-tooltip.d.ts +146 -0
  42. package/dist/types/components.d.ts +1812 -28
  43. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/abbreviate-name.d.ts +2 -0
  44. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/get-initials.d.ts +2 -0
  45. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/icons.d.ts +19 -0
  46. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/media-query.d.ts +2 -0
  47. package/dist/types/index.d.ts +21 -0
  48. package/dist/types/services/i18n/i18n.d.ts +93 -0
  49. package/dist/types/services/index.d.ts +5 -0
  50. package/dist/types/services/locale/locale.d.ts +23 -0
  51. package/dist/web-components/components/index.js +1 -1
  52. package/dist/web-components/components/ip-avatar-stack.d.ts +11 -0
  53. package/dist/web-components/components/ip-avatar-stack.js +1 -0
  54. package/dist/web-components/components/ip-avatar.d.ts +11 -0
  55. package/dist/web-components/components/ip-avatar.js +1 -0
  56. package/dist/web-components/components/ip-button.js +1 -1
  57. package/dist/web-components/components/ip-card.d.ts +11 -0
  58. package/dist/web-components/components/ip-card.js +1 -0
  59. package/dist/web-components/components/ip-chip-group.d.ts +11 -0
  60. package/dist/web-components/components/ip-chip-group.js +1 -0
  61. package/dist/web-components/components/ip-chip.d.ts +11 -0
  62. package/dist/web-components/components/ip-chip.js +1 -0
  63. package/dist/web-components/components/ip-date-display.d.ts +11 -0
  64. package/dist/web-components/components/ip-date-display.js +1 -0
  65. package/dist/web-components/components/ip-icon-button.d.ts +11 -0
  66. package/dist/web-components/components/ip-icon-button.js +1 -0
  67. package/dist/web-components/components/ip-icon-count.d.ts +11 -0
  68. package/dist/web-components/components/ip-icon-count.js +1 -0
  69. package/dist/web-components/components/ip-main-menu-group.d.ts +11 -0
  70. package/dist/web-components/components/ip-main-menu-group.js +1 -0
  71. package/dist/web-components/components/ip-main-menu-item.d.ts +11 -0
  72. package/dist/web-components/components/ip-main-menu-item.js +1 -0
  73. package/dist/web-components/components/ip-main-menu-section.d.ts +11 -0
  74. package/dist/web-components/components/ip-main-menu-section.js +1 -0
  75. package/dist/web-components/components/ip-main-menu-toggle.d.ts +11 -0
  76. package/dist/web-components/components/ip-main-menu-toggle.js +1 -0
  77. package/dist/web-components/components/ip-main-menu.d.ts +11 -0
  78. package/dist/web-components/components/ip-main-menu.js +1 -0
  79. package/dist/web-components/components/ip-status-marker.d.ts +11 -0
  80. package/dist/web-components/components/ip-status-marker.js +1 -0
  81. package/dist/web-components/components/ip-tooltip.d.ts +11 -0
  82. package/dist/web-components/components/ip-tooltip.js +1 -0
  83. package/dist/web-components/components/p-B7OyzFE9.js +1 -0
  84. package/dist/web-components/components/p-BYr1NDvY.js +1 -0
  85. package/dist/web-components/components/p-BdDRGAsq.js +1 -0
  86. package/dist/web-components/components/p-ByGj0dn5.js +1 -0
  87. package/dist/web-components/components/p-C7Xl2j2X.js +1 -0
  88. package/dist/web-components/components/p-CLJ8ojje.js +1 -0
  89. package/dist/web-components/components/p-CPfvEnik.js +1 -0
  90. package/dist/web-components/components/p-CTglqvS6.js +1 -0
  91. package/dist/web-components/components/p-Ci9GGTXq.js +1 -0
  92. package/dist/web-components/components/p-D5Y2cGA7.js +1 -0
  93. package/dist/web-components/components/p-D83crOsE.js +2 -0
  94. package/dist/web-components/components/p-DN5xlF7P.js +1 -0
  95. package/dist/web-components/components/p-DRk3XcAj.js +1 -0
  96. package/dist/web-components/components/p-DcHqui8O.js +1 -0
  97. package/dist/web-components/components/p-DefPpKsB.js +1 -0
  98. package/dist/web-components/components/p-DnTzptWz.js +1 -0
  99. package/dist/web-components/components/p-DsFtU_rr.js +1 -0
  100. package/dist/web-components/components/p-DwhXHkf_.js +1 -0
  101. package/dist/web-components/components/p-Dx6JZBTS.js +1 -0
  102. package/dist/web-components/components/p-E51g34iQ.js +1 -0
  103. package/dist/web-components/components/p-OiQz836n.js +1 -0
  104. package/dist/web-components/components/p-iNLo7cGa.js +1 -0
  105. package/dist/web-components/generated/angular/components.d.ts +634 -2
  106. package/dist/web-components/generated/angular/components.js +1029 -4
  107. package/dist/web-components/generated/angular/components.ts +878 -3
  108. package/dist/web-components/generated/angular/index.d.ts +1 -1
  109. package/dist/web-components/generated/angular/index.js +16 -1
  110. package/dist/web-components/generated/angular/index.ts +16 -1
  111. package/dist/web-components/generated/react/components.d.ts +67 -1
  112. package/dist/web-components/generated/react/components.js +145 -5
  113. package/dist/web-components/generated/react/components.ts +202 -1
  114. package/dist/web-components/generated/svelte/components.d.ts +30 -0
  115. package/dist/web-components/generated/vue/components.d.ts +15 -0
  116. package/dist/web-components/generated/vue/components.js +186 -0
  117. package/dist/web-components/generated/vue/components.ts +216 -0
  118. package/llms.txt +41 -0
  119. package/package.json +14 -3
  120. /package/dist/{migrations → docs/migrations}/v1-to-v2.md +0 -0
  121. /package/dist/{esm → docs}/services/theme-service/llm.md +0 -0
package/LICENSE.md CHANGED
@@ -14,3 +14,14 @@ Norconsult Digital AS.
14
14
 
15
15
  The software is provided "AS IS", without warranties of any kind.
16
16
  Reverse engineering, decompilation, and disassembly are prohibited.
17
+
18
+ ---
19
+
20
+ ## Third-party attribution
21
+
22
+ This package includes SVG icons sourced from Font Awesome Free 7.2.0.
23
+
24
+ - Source: https://fontawesome.com
25
+ - License: https://fontawesome.com/license/free
26
+ - Icons under CC BY 4.0
27
+ - Copyright © Fonticons, Inc.
@@ -0,0 +1,172 @@
1
+ # ip-avatar
2
+
3
+ A theme-aware user avatar. It shows a profile image with a fallback chain (primary image → fallback image → initials → silhouette), an optional one- or two-line label (name + caption), a configurable ring, a forced or automatic loading state, and four positional corner slots. It is domain-agnostic: status, signing, and responsibility semantics are not baked in — consumers place and style their own markers via the corner slots.
4
+
5
+ **Storybook:** https://pixel.isy.no/storybook/?path=/docs/core-ui-ip-avatar-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
+ | `name` | string | `""` | Any string | Full name. Used as the image `alt`, the source for initials/abbreviation, and the tooltip text. |
14
+ | `alt-name` | string | `undefined` | Any string | Explicit short/alternative name shown when `mode="alt-name"`. When unset, the name is abbreviated. |
15
+ | `caption` | string | `undefined` | Any string | Secondary line shown beneath the name, e.g. a role or title. |
16
+ | `placeholder` | string | `undefined` | Any string | Text shown beside the avatar when there is no name, e.g. an unselected-resource hint. |
17
+ | `src` | string | `undefined` | Any image URL | Primary profile image URL, tried first in the fallback chain. |
18
+ | `fallback-src` | string | `undefined` | Any image URL | Fallback image URL, used when `src` is missing or fails to load. |
19
+ | `mode` | string | `"fullname"` | `"fullname"`, `"alt-name"`, `"firstname"`, `"lastname"`, `"initials"`, `"image-only"` | Which name representation is shown as visible text beside the avatar. |
20
+ | `fallback` | string | `"auto"` | `"auto"`, `"initials"`, `"silhouette"` | Circle content when there is no image. `auto` shows initials when a name is set, silhouette when nameless. |
21
+ | `label-position` | string | `"right"` | `"top"`, `"right"`, `"bottom"`, `"left"` | Placement of the text label relative to the circle. |
22
+ | `gap` | string | `undefined` | Any CSS length | Overrides the gap between the avatar and the label. Defaults scale with `size`. |
23
+ | `size` | string | `"medium"` | `"small"`, `"medium"`, `"large"`, `"custom"` | Avatar size: `small` 40px, `medium` 50px (42px image), `large` 64px. Use `custom` with `custom-size`. |
24
+ | `custom-size` | string | `undefined` | Any CSS length | Diameter when `size="custom"`, applied to both width and height so the avatar stays circular. |
25
+ | `image-loading` | string | `"lazy"` | `"lazy"`, `"eager"` | Native image loading strategy forwarded to the inner `<img>`. |
26
+ | `loading` | boolean | `false` | `true`, `false` | Forces the loading indicator regardless of image state, e.g. before `src` is known. |
27
+ | `ring-variant` | string | `"default"` | `"default"`, `"brand"`, `"success"`, `"warning"`, `"error"`, `"custom"` | Predefined, theme-aware ring colour. Use `custom` together with `ring-color` for a raw colour. |
28
+ | `ring-color` | string | `undefined` | Any CSS colour | Raw ring colour, applied only when `ring-variant="custom"`. Does not adapt to light/dark. |
29
+ | `ring-style` | string | `"solid"` | `"solid"`, `"dashed"`, `"dotted"` | Line style of the ring. |
30
+ | `ring-gap` | string | `undefined` | Any CSS length | Gap between the ring and the content. Defaults to a small constant (2px). |
31
+ | `striped` | boolean | `false` | `true`, `false` | Overlays subtle diagonal stripes on the no-image background (initials / silhouette). |
32
+ | `clickable` | boolean | `false` | `true`, `false` | Makes the avatar interactive: focusable, keyboard-activatable, and emits `ipClick`. |
33
+ | `selected` | boolean | `false` | `true`, `false` | Applies the selected (active) container styling, e.g. for the chosen item in a list. |
34
+ | `disabled` | boolean | `false` | `true`, `false` | Greys out the avatar and, when `clickable`, prevents activation. |
35
+ | `full-width` | boolean | `false` | `true`, `false` | Stretches the avatar to fill the row, so stacked avatars span the full width. |
36
+ | `maintain-label-height` | boolean | `false` | `true`, `false` | Keeps both label lines at a constant height even when empty, so avatars in a row stay aligned. |
37
+ | `disable-tooltip` | boolean | `false` | `true`, `false` | Disables the built-in tooltip entirely. |
38
+ | `tooltip-position` | string | `"top"` | `"top"`, `"right"`, `"bottom"`, `"left"` | Placement of the tooltip, forwarded to `ip-tooltip`. |
39
+ | `tooltip-size` | string | `"medium"` | `"small"`, `"medium"`, `"large"` | Size of the tooltip, forwarded to `ip-tooltip`. |
40
+ | `tooltip-variant` | string | `"default"` | `"default"`, `"onLight"`, `"onDark"` | Colour variant of the tooltip, forwarded to `ip-tooltip`. |
41
+
42
+ **Fallback chain** — `src` is tried first. When it fails to load, `fallback-src` is tried next. When both are missing or fail, the avatar shows the user's initials (when a name is set) or a silhouette glyph. `fallback` can force `initials` or `silhouette`. Each image failure emits `ipImageError` with the failed URL.
43
+
44
+ **Tooltip content** — The tooltip surfaces the full `name` whenever the visible text differs from it (e.g. `firstname`, `lastname`, `initials`, or an abbreviated `alt-name`). When the visible text already equals the full name — in `mode="fullname"`, or in `mode="alt-name"` for a name short enough that abbreviation leaves it unchanged — the tooltip is auto-disabled unless custom `tooltip` slot content is provided.
45
+
46
+ ## Slots
47
+
48
+ | Slot | Description |
49
+ | -------------- | --------------------------------------------------------------------------------------------- |
50
+ | `top-left` | Adornment anchored to the top-left corner of the circle. The consumer supplies and styles it. |
51
+ | `top-right` | Adornment anchored to the top-right corner. |
52
+ | `bottom-left` | Adornment anchored to the bottom-left corner. |
53
+ | `bottom-right` | Adornment anchored to the bottom-right corner, e.g. a status badge. |
54
+ | `tooltip` | Optional override of the tooltip content. Defaults to the derived name when not provided. |
55
+
56
+ ## Events
57
+
58
+ | Event | Payload | When |
59
+ | -------------- | --------------------- | ------------------------------------------------------------------------------------ |
60
+ | `ipImageError` | `string` (failed src) | An image fails to load and the component advances down the fallback chain. |
61
+ | `ipClick` | `void` | A `clickable` avatar is activated by mouse or keyboard. Not emitted when `disabled`. |
62
+
63
+ ## Accessibility
64
+
65
+ - The image uses `name` as its `alt`. When the initials fallback is shown, `name` is exposed to assistive technology via visually hidden text and the initials/silhouette are marked `aria-hidden`.
66
+ - While an image loads or `loading` is set, the media sets `aria-busy="true"` and renders visually hidden loading text. Initials/silhouette are the final state, never a stuck spinner.
67
+ - When `clickable`, the avatar exposes `role="button"`, is focusable (`tabindex="0"`), shows a `:focus-visible` ring, and is activated with Enter or Space.
68
+ - A non-clickable avatar with an active tooltip is keyboard-focusable via `ip-tooltip`'s WCAG 1.4.13 behaviour (the tooltip sets `tabindex="0"` on its trigger), so Tab reaches it and focus shows the tooltip, with a `:focus-visible` ring.
69
+ - When `disabled`, a clickable avatar sets `aria-disabled="true"`, drops out of the tab order, and blocks `ipClick`.
70
+ - A `clickable` avatar with no visible label derives its accessible name from `name` (falling back to `placeholder`). When using `mode="image-only"` (or no name/placeholder) on a clickable avatar, supply an accessible name yourself via the `tooltip` slot or a wrapping label, or the button is announced unnamed.
71
+
72
+ ## Design tokens
73
+
74
+ > The avatar reuses generic semantic tokens as theme-aware stand-ins; a dedicated avatar token family (background, foreground, ring, container hover/selected) does not exist yet. Avatar sizes are literal pixel values for the same reason. These are marked `//TODO` in the SCSS and will be replaced once design adds the avatar tokens.
75
+
76
+ | SCSS variable | Role |
77
+ | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
78
+ | `$ip_color_surface_primary_default` | Ring layer background behind the ring gap |
79
+ | `$ip_color_text_primary_blue` | Foreground colour: initials, silhouette, loader, and default ring |
80
+ | `$ip_color_border_opaque_15` | No-image background behind initials / silhouette |
81
+ | `$ip_color_border_opaque_10` | Clickable container hover background and the stripe overlay |
82
+ | `$ip_color_border_opaque_20` | Selected container background |
83
+ | `$ip_color_button_focus_default` | Focus ring when `clickable` |
84
+ | `$ip_color_brand_accent_primary_default` | Ring colour for `ring-variant="brand"` |
85
+ | `$ip_color_extended_green-500` | Ring colour for `ring-variant="success"` |
86
+ | `$ip_color_extended_yellow-500` | Ring colour for `ring-variant="warning"` |
87
+ | `$ip_color_extended_red-500` | Ring colour for `ring-variant="error"` |
88
+ | `$ip_color_text_tertiary_default` | Default label (name) text colour, matching `ip-avatar-stack`; override with `--ip-avatar-text-color` |
89
+ | `$ip_radius_size_small` | Framed container and focus-ring radius |
90
+ | `$ip_spacing_space_050` / `$ip_spacing_space_200` | Framed container padding (applied only when the avatar is `clickable` or `selected`) |
91
+ | `$ip_spacing_space_100` / `_200` / `_300` | Avatar↔label gap defaults for small / medium / large |
92
+ | `$ip_typography_body_font_size_small` / `_medium` / `_large` | Initials and label font size per size |
93
+ | `$ip_typography_body_line_height_compact` | Line height for the name and caption lines |
94
+ | `$ip_typography_font_family_primary` | Font family for initials and label |
95
+ | `$ip_typography_font_weight_bold_700` | Initials font weight |
96
+ | `$ip_typography_font_weight_light_300` | Caption and placeholder font weight (italic) |
97
+
98
+ ## CSS custom properties
99
+
100
+ | Property | Default | Role |
101
+ | ------------------------ | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
102
+ | `--ip-avatar-text-color` | `$ip_color_text_tertiary_default` | Label (name and caption) text colour. Set to `currentColor` so the label inherits its surroundings — e.g. on a dark surface such as inside a tooltip. |
103
+
104
+ ## Usage
105
+
106
+ ### HTML
107
+
108
+ > Attribute names must be **kebab-case** in HTML: use `fallback-src`, not `fallbackSrc`.
109
+
110
+ ```js
111
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar.js';
112
+ ```
113
+
114
+ ```html
115
+ <ip-avatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" src="/users/alexander.jpg" fallback-src="/users/default.png">
116
+ <span slot="bottom-right" class="badge">✓</span>
117
+ </ip-avatar>
118
+ ```
119
+
120
+ ### React
121
+
122
+ ```tsx
123
+ import { IpAvatar } from '@norconsult-digital-public/isypixel/web-components/react';
124
+
125
+ <IpAvatar name='Alexander Wangsvik' caption='Elkraft, ingeniør' src='/users/alexander.jpg' fallbackSrc='/users/default.png' onIpClick={() => {}} />;
126
+ ```
127
+
128
+ ### Vue
129
+
130
+ ```vue
131
+ <script setup lang="ts">
132
+ import { IpAvatar } from '@norconsult-digital-public/isypixel/web-components/vue';
133
+ </script>
134
+
135
+ <template>
136
+ <IpAvatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" src="/users/alexander.jpg" fallback-src="/users/default.png" />
137
+ </template>
138
+ ```
139
+
140
+ ### Angular
141
+
142
+ ```ts
143
+ import { IpAvatar } from '@norconsult-digital-public/isypixel/web-components/angular';
144
+ ```
145
+
146
+ ```html
147
+ <ip-avatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" [src]="imageUrl" ring-variant="brand" (ipClick)="onSelect()"></ip-avatar>
148
+ ```
149
+
150
+ ### Svelte
151
+
152
+ ```svelte
153
+ <script lang="ts">
154
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar.js';
155
+ </script>
156
+
157
+ <ip-avatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" src="/users/alexander.jpg" fallback-src="/users/default.png"></ip-avatar>
158
+ ```
159
+
160
+ ### Blazor
161
+
162
+ > Attribute names must be **kebab-case** in Blazor HTML.
163
+
164
+ Copy `ip-avatar.js` to `wwwroot/lib/isypixel/components/` at build time. See `llm.md` in the package root for the MSBuild target.
165
+
166
+ ```html
167
+ <script type="module" src="lib/isypixel/components/ip-avatar.js"></script>
168
+
169
+ <ip-avatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" src="/users/alexander.jpg" fallback-src="/users/default.png"></ip-avatar>
170
+ ```
171
+
172
+ - Boolean attributes such as `clickable`, `disabled`, `striped`, and `disable-tooltip` must be set as `clickable="true"` (string value) — Blazor does not reliably render bare boolean attributes on custom elements.
@@ -0,0 +1,171 @@
1
+ # ip-avatar-stack
2
+
3
+ A stacked-avatar component that overlaps multiple `ip-avatar` instances and shows a combined names label (the first person's name plus a `+N` count for the rest). Consumers place `ip-avatar` elements in the default slot; the stack manages the overlap, the overflow collapse into a `+N` counter, and the label. It reuses each avatar's own ring to keep the overlapping circles visually distinct.
4
+
5
+ **Storybook:** https://pixel.isy.no/storybook/?path=/docs/core-ui-ip-avatar-stack-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
+ > Place `ip-avatar` elements in the default slot. The stack forces each slotted avatar to `image-only`, so you do not need to set `mode` yourself. In `label-mode="name"` the stack reads the first avatar's `name` for the text label, so set `name` on at least the first avatar. Match each avatar's `size` to the stack's `size` so the circles align.
10
+
11
+ ## Attributes
12
+
13
+ | Attribute | Type | Default | Values | Description |
14
+ | ----------------- | ------- | ----------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
15
+ | `max` | number | `3` | Any integer ≥ 1 | Maximum number of avatars rendered (the hard cap). Surplus avatars are hidden; in `label-mode="counter"` they collapse into a `+N` counter circle (N = total − visible). |
16
+ | `auto-fit` | boolean | `true` | `true`, `false` | Reduces the visible avatars to fit the container width, collapsing the rest like `max` does (`max` stays the cap). Set `false` for a fixed stack that always shows up to `max`. |
17
+ | `size` | string | `"medium"` | `"small"`, `"medium"`, `"large"`, `"custom"` | Stack size: drives the overflow counter size (small 40px, medium 50px, large 64px) and the default overlap. |
18
+ | `custom-size` | string | `undefined` | Any CSS length | Diameter of the overflow counter when `size="custom"`, applied to both width and height. |
19
+ | `overlap` | string | `undefined` | Any CSS length | Overrides the overlap offset between circles. Defaults scale with `size` (small 20px, medium 24px, large 30px). |
20
+ | `label-mode` | string | `"name"` | `"name"`, `"alt-name"`, `"counter"`, `"none"` | How the group is summarised. `name`: first name + `+N` rest text label. `alt-name`: same, but uses the first avatar's short `alt-name` (falling back to `name`). `counter`: a `+N` counter circle in the stack. `none`: nothing. The counter circle and the text label are mutually exclusive. |
21
+ | `label-position` | string | `"right"` | `"top"`, `"right"`, `"bottom"`, `"left"` | Which side of the avatars the text label sits on, mirroring `ip-avatar`. No effect in `counter`/`none` modes. |
22
+ | `gap` | string | `undefined` | Any CSS length | Overrides the gap between the stack and the label. Defaults scale with `size`. |
23
+ | `preview-avatars` | boolean | `true` | `true`, `false` | Whether the tooltip previews (the counter preview and the names-label tooltip) show a small avatar beside each person. Set `false` for a compact, name-only list. |
24
+ | `clickable` | boolean | `false` | `true`, `false` | Makes the `+N` overflow counter (in `label-mode="counter"`) focusable, keyboard-activatable, and emits `ipOverflowClick`. |
25
+ | `disabled` | boolean | `false` | `true`, `false` | Greys out the stack and, when `clickable`, prevents activation of the overflow counter. |
26
+
27
+ **Counter mode** — In `label-mode="counter"`, when more avatars are slotted than are shown, the visible avatars are shown (up to `max`, or fewer with `auto-fit`) and the remainder collapse into a single `+N` counter circle appended to the stack, where N = total − visible. No text label is shown. The collapsed avatars are hidden from the layout and the accessibility tree. Hovering or focusing the counter opens a tooltip previewing the collapsed people. The preview reads `name`, `src`, and `caption` from the collapsed `ip-avatar` elements; by default each row shows a small `ip-avatar` with its name and caption, or just the name and caption when `preview-avatars="false"`.
28
+
29
+ **Auto-fit** — On by default. The stack measures its container width and shows as many avatars as fit, collapsing the rest into the `+N` counter (counter mode) or reflecting them in the `+N` text (name mode). `max` remains the upper cap, so the visible count is never more than `max` and adapts down as the container narrows. It needs a width-constrained ancestor to measure against; place the stack in its own container for best results. Set `auto-fit="false"` for a fixed stack that always shows up to `max`.
30
+
31
+ **Names label** — In `label-mode="name"` the text label reads the first slotted avatar's `name` and appends `+N`, where N is the number of remaining avatars (total − 1), e.g. `Markus Halvorsen +2` for three avatars. `label-mode="alt-name"` behaves the same but uses the first avatar's short `alt-name` (falling back to `name` when unset), e.g. `Markus +2`; the full `name` is still shown in the tooltip preview. This mode shows no counter circle. When more than one avatar is slotted, hovering the label opens a tooltip listing every slotted person (not just the overflow), using the same preview rows as counter mode and honouring `preview-avatars`.
32
+
33
+ ## Slots
34
+
35
+ | Slot | Description |
36
+ | ---------- | ---------------------------------------------------------------------------------- |
37
+ | `default` | The `ip-avatar` elements to stack. Non-`ip-avatar` nodes are ignored. |
38
+ | `label` | Overrides the auto-generated text label. Shown in `label-mode="name"`. |
39
+ | `overflow` | Overrides the content of the `+N` counter circle. Shown in `label-mode="counter"`. |
40
+
41
+ ## Events
42
+
43
+ | Event | Payload | When |
44
+ | ----------------- | ------- | ---------------------------------------------------------------------------------------------- |
45
+ | `ipOverflowClick` | `void` | A `clickable` overflow counter is activated by mouse or keyboard. Not emitted when `disabled`. |
46
+
47
+ ## Accessibility
48
+
49
+ - The overlap and paint order are presentational only; each slotted `ip-avatar` keeps its own accessible name and tooltip.
50
+ - Avatars collapsed into the `+N` counter are hidden from the accessibility tree; their names are surfaced through the counter's tooltip preview instead.
51
+ - When `clickable`, the overflow counter exposes `role="button"`, is focusable (`tabindex="0"`), shows a `:focus-visible` ring, is activated with Enter or Space, and carries an `aria-label` describing how many more there are.
52
+ - When `disabled`, a clickable counter sets `aria-disabled="true"` and blocks `ipOverflowClick`.
53
+ - The names label (when it has a tooltip, i.e. more than one person) and a non-clickable counter are keyboard-focusable via `ip-tooltip`'s WCAG 1.4.13 behaviour: the tooltip sets `tabindex="0"` on its trigger, so Tab reaches them and focus shows the people list. Both show a `:focus-visible` ring.
54
+
55
+ ## Design tokens
56
+
57
+ > The stack reuses generic semantic tokens as theme-aware stand-ins; a dedicated avatar token family (background, foreground, ring, sizes, overlap) does not exist yet. Counter sizes and overlap offsets are literal pixel values for the same reason. These are marked `//TODO` in the SCSS and will be replaced once design adds the avatar tokens.
58
+
59
+ | SCSS variable | Role |
60
+ | ------------------------------------------------------------ | ---------------------------------------------------------- |
61
+ | `$ip_color_surface_primary_default` | Overflow counter ring layer background behind the ring gap |
62
+ | `$ip_color_text_primary_blue` | Overflow counter ring colour and `+N` text colour |
63
+ | `$ip_color_border_opaque_15` | Overflow counter inner background behind the `+N` text |
64
+ | `$ip_color_button_focus_default` | Focus ring of the overflow counter and the names label |
65
+ | `$ip_color_text_tertiary_default` | Names label text colour |
66
+ | `$ip_spacing_space_100` / `_200` / `_300` | Stack↔label gap defaults for small / medium / large |
67
+ | `$ip_typography_body_font_size_small` / `_medium` / `_large` | Counter and label font size per size |
68
+ | `$ip_typography_body_line_height_compact` | Label line height |
69
+ | `$ip_typography_font_family_primary` | Font family for the counter and label |
70
+ | `$ip_typography_font_weight_bold_700` | `+N` counter font weight |
71
+
72
+ ## Usage
73
+
74
+ ### HTML
75
+
76
+ > Attribute names must be **kebab-case** in HTML: use `label-mode`, not `labelMode`.
77
+
78
+ ```js
79
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar-stack.js';
80
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar.js';
81
+ ```
82
+
83
+ ```html
84
+ <ip-avatar-stack max="3" size="medium" label-mode="name">
85
+ <ip-avatar name="Markus Halvorsen" src="/users/markus.jpg"></ip-avatar>
86
+ <ip-avatar name="Mia Berg" src="/users/mia.jpg"></ip-avatar>
87
+ <ip-avatar name="Jonas Lie" src="/users/jonas.jpg"></ip-avatar>
88
+ <ip-avatar name="Sara Nilsen"></ip-avatar>
89
+ </ip-avatar-stack>
90
+ ```
91
+
92
+ ### React
93
+
94
+ ```tsx
95
+ import { IpAvatarStack, IpAvatar } from '@norconsult-digital-public/isypixel/web-components/react';
96
+
97
+ <IpAvatarStack max={3} size='medium' labelMode='name' clickable onIpOverflowClick={() => {}}>
98
+ <IpAvatar name='Markus Halvorsen' src='/users/markus.jpg' />
99
+ <IpAvatar name='Mia Berg' src='/users/mia.jpg' />
100
+ <IpAvatar name='Jonas Lie' src='/users/jonas.jpg' />
101
+ <IpAvatar name='Sara Nilsen' />
102
+ </IpAvatarStack>;
103
+ ```
104
+
105
+ ### Vue
106
+
107
+ ```vue
108
+ <script setup lang="ts">
109
+ import { IpAvatarStack, IpAvatar } from '@norconsult-digital-public/isypixel/web-components/vue';
110
+ </script>
111
+
112
+ <template>
113
+ <IpAvatarStack :max="3" size="medium" label-mode="name">
114
+ <IpAvatar name="Markus Halvorsen" src="/users/markus.jpg" />
115
+ <IpAvatar name="Mia Berg" src="/users/mia.jpg" />
116
+ <IpAvatar name="Jonas Lie" src="/users/jonas.jpg" />
117
+ <IpAvatar name="Sara Nilsen" />
118
+ </IpAvatarStack>
119
+ </template>
120
+ ```
121
+
122
+ ### Angular
123
+
124
+ ```ts
125
+ import { IpAvatarStack, IpAvatar } from '@norconsult-digital-public/isypixel/web-components/angular';
126
+ ```
127
+
128
+ ```html
129
+ <ip-avatar-stack [max]="3" size="medium" label-mode="name" clickable (ipOverflowClick)="onShowAll()">
130
+ <ip-avatar name="Markus Halvorsen" [src]="markusUrl"></ip-avatar>
131
+ <ip-avatar name="Mia Berg" [src]="miaUrl"></ip-avatar>
132
+ <ip-avatar name="Jonas Lie" [src]="jonasUrl"></ip-avatar>
133
+ <ip-avatar name="Sara Nilsen"></ip-avatar>
134
+ </ip-avatar-stack>
135
+ ```
136
+
137
+ ### Svelte
138
+
139
+ ```svelte
140
+ <script lang="ts">
141
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar-stack.js';
142
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar.js';
143
+ </script>
144
+
145
+ <ip-avatar-stack max="3" size="medium" label-mode="name">
146
+ <ip-avatar name="Markus Halvorsen" src="/users/markus.jpg"></ip-avatar>
147
+ <ip-avatar name="Mia Berg" src="/users/mia.jpg"></ip-avatar>
148
+ <ip-avatar name="Jonas Lie" src="/users/jonas.jpg"></ip-avatar>
149
+ <ip-avatar name="Sara Nilsen"></ip-avatar>
150
+ </ip-avatar-stack>
151
+ ```
152
+
153
+ ### Blazor
154
+
155
+ > Attribute names must be **kebab-case** in Blazor HTML.
156
+
157
+ Copy `ip-avatar-stack.js` and `ip-avatar.js` to `wwwroot/lib/isypixel/components/` at build time. See `llm.md` in the package root for the MSBuild target.
158
+
159
+ ```html
160
+ <script type="module" src="lib/isypixel/components/ip-avatar-stack.js"></script>
161
+ <script type="module" src="lib/isypixel/components/ip-avatar.js"></script>
162
+
163
+ <ip-avatar-stack max="3" size="medium" label-mode="name">
164
+ <ip-avatar name="Markus Halvorsen" src="/users/markus.jpg"></ip-avatar>
165
+ <ip-avatar name="Mia Berg" src="/users/mia.jpg"></ip-avatar>
166
+ <ip-avatar name="Jonas Lie" src="/users/jonas.jpg"></ip-avatar>
167
+ <ip-avatar name="Sara Nilsen"></ip-avatar>
168
+ </ip-avatar-stack>
169
+ ```
170
+
171
+ - Boolean attributes such as `clickable` and `disabled` must be set as `clickable="true"` (string value) — Blazor does not reliably render bare boolean attributes on custom elements.
@@ -8,14 +8,15 @@ Clickable button component with support for visual variants, loading state, and
8
8
 
9
9
  ## Attributes
10
10
 
11
- | Attribute | Type | Default | Values |
12
- | ----------- | ------- | ----------- | -------------------------------------------------------------------- |
13
- | `variant` | string | `"primary"` | `"primary"`, `"secondary"`, `"tertiary"`, `"brand"`, `"destructive"` |
14
- | `size` | string | `"medium"` | `"small"`, `"medium"`, `"large"` |
15
- | `loading` | boolean | `false` | `true`, `false` |
16
- | `disabled` | boolean | `false` | `true`, `false` |
17
- | `type` | string | `"submit"` | `"submit"`, `"reset"`, `"button"` |
18
- | `alignIcon` | string | `"start"` | `"start"`, `"end"` |
11
+ | Attribute | Type | Default | Values |
12
+ | ------------ | ------- | ----------- | -------------------------------------------------------------------- |
13
+ | `variant` | string | `"primary"` | `"primary"`, `"secondary"`, `"tertiary"`, `"brand"`, `"destructive"` |
14
+ | `size` | string | `"medium"` | `"small"`, `"medium"`, `"large"` |
15
+ | `loading` | boolean | `false` | `true`, `false` |
16
+ | `disabled` | boolean | `false` | `true`, `false` |
17
+ | `type` | string | `"submit"` | `"submit"`, `"reset"`, `"button"` |
18
+ | `alignIcon` | string | `"start"` | `"start"`, `"end"` |
19
+ | `aria-label` | string | — | Any string |
19
20
 
20
21
  **size** — Controls the size of the button. Defaults to `"medium"`. Affects padding, gap, and font size.
21
22
 
@@ -57,6 +58,8 @@ Clickable button component with support for visual variants, loading state, and
57
58
  | _(default)_ | Button label text |
58
59
  | `icon` | Optional icon. Position is controlled by the `alignIcon` prop (`"start"` or `"end"`) |
59
60
 
61
+ > **Icon-only buttons:** When no label text is slotted, the button has no visible accessible name. In this case, set `aria-label` on the `<ip-button>` host element — it is automatically forwarded to the inner `<button>`. Example: `<ip-button aria-label="Delete item"><i slot="icon">…</i></ip-button>`.
62
+
60
63
  ## Other Tokens
61
64
 
62
65
  | SCSS variable | CSS variable | Description |
@@ -151,10 +154,14 @@ import { IpButton } from '@norconsult-digital-public/isypixel/web-components/rea
151
154
 
152
155
  ### Vue
153
156
 
154
- ```tsx
157
+ ```vue
158
+ <script setup lang="ts">
155
159
  import { IpButton } from '@norconsult-digital-public/isypixel/web-components/vue';
160
+ </script>
156
161
 
157
- <IpButton variant='primary'>Click me</IpButton>;
162
+ <template>
163
+ <IpButton variant="primary">Click me</IpButton>
164
+ </template>
158
165
  ```
159
166
 
160
167
  ### Angular
@@ -0,0 +1,180 @@
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. |
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. Hidden in slim layout, and removed entirely (no gap) when the slot is empty. |
32
+
33
+ ## Events
34
+
35
+ | Event | Payload | Description |
36
+ | ------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
37
+ | `ipCardClick` | `CustomEvent<void>` | Fires when the primary action is activated (click / Enter / Space) in button mode: `clickTarget` set (`'heading'` or `'card'`) and no `href`. |
38
+
39
+ ## Slim layout
40
+
41
+ Slim is a **static layout choice**, not a disclosure: the consumer sets the `slim` prop (there is no built-in toggle button). When `slim`:
42
+
43
+ - The `heading`, `header-end` and body (`default` slot) stay visible, with tighter spacing.
44
+ - The `eyebrow`, `subheading`, `footer` and `actions` are hidden.
45
+ - The `marker` slot stays visible.
46
+
47
+ 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.
48
+
49
+ ## Design tokens
50
+
51
+ | Token | Role |
52
+ | ----------------------------------------- | --------------------------------------------------------------------------------------------------- |
53
+ | `ip_color_surface_primary_default` | Card background |
54
+ | `ip_color_border_opaque_15` | Card border (default) |
55
+ | `ip_color_border_opaque_20` | Card outline on hover (`'card'` mode) |
56
+ | `ip_color_border_primary_default` | Card outline on active/press (`'card'` mode) |
57
+ | `ip_color_border_opaque_10` | Auto-divider above the footer |
58
+ | `ip_color_text_primary_black` | Heading text at rest (non-interactive and `'card'` mode) |
59
+ | `ip_color_hyperlink_text_primary_default` | Heading link colour at rest (`'heading'` mode) |
60
+ | `ip_color_hyperlink_text_primary_hover` | Heading link colour on hover (`'heading'` mode) |
61
+ | `ip_color_text_secondary_default` | Subheading text |
62
+ | `ip_color_text_tertiary_default` | Eyebrow text |
63
+ | `ip_color_button_focus_default` | Focus ring on the heading (`'heading'` mode) and the 2px focus ring around the card (`'card'` mode) |
64
+ | `ip_radius_size_x_large` | Card corner radius (12px stand-in; Figma specifies 16px) |
65
+
66
+ > **Token gaps (flagged to design team, §8):**
67
+ >
68
+ > - A radius token for 16px (Figma spec). Interim: `ip_radius_size_x_large` (12px).
69
+ > - A card-specific focus token; interim reuse of `ip_color_button_focus_default`.
70
+ > - 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.
71
+
72
+ ## Usage examples
73
+
74
+ ### HTML
75
+
76
+ ```html
77
+ <!-- Whole card clickable (table-row style): emits ipCardClick, for SPA routing -->
78
+ <ip-card eyebrow="Work order" heading="Periodic maintenance, turbine 2" subheading="AO-11033380" click-target="card" id="card">
79
+ <ip-status-marker slot="marker" status="warning"></ip-status-marker>
80
+ <span slot="header-end">19.08.2025</span>
81
+ <p>SØR...FOFOL.411.012.300 - Guide vanes</p>
82
+ <div slot="footer">Alexander Wangsvik Olsen +2</div>
83
+ <ip-icon-button slot="actions" label="Edit"></ip-icon-button>
84
+ </ip-card>
85
+ <script>
86
+ document.getElementById('card').addEventListener('ipCardClick', () => router.navigate('/orders/11033380'));
87
+ </script>
88
+
89
+ <!-- Link card: real navigation, no ipCardClick -->
90
+ <ip-card heading="Major leak. Dam is affected." href="/faults/314001" target="_blank">
91
+ <ip-status-marker slot="marker" status="error"></ip-status-marker>
92
+ </ip-card>
93
+ ```
94
+
95
+ ### React
96
+
97
+ ```tsx
98
+ import { IpCard, IpStatusMarker } from '@norconsult-digital-public/isypixel/web-components/react';
99
+
100
+ // Interactive (SPA)
101
+ <IpCard eyebrow='Work order' heading='Periodic maintenance' subheading='AO-11033380' clickTarget='card' onIpCardClick={() => navigate('/orders/11033380')}>
102
+ <IpStatusMarker slot='marker' status='warning' />
103
+ <span slot='header-end'>19.08.2025</span>
104
+ <p>Guide vanes</p>
105
+ <div slot='footer'>Alexander Wangsvik Olsen +2</div>
106
+ </IpCard>
107
+
108
+ // Link
109
+ <IpCard heading='Major leak' href='/faults/314001' target='_blank'>
110
+ <IpStatusMarker slot='marker' status='error' />
111
+ </IpCard>
112
+ ```
113
+
114
+ ### Vue
115
+
116
+ ```vue
117
+ <!-- Interactive (SPA) -->
118
+ <IpCard eyebrow="Work order" heading="Periodic maintenance" click-target="card" @ip-card-click="router.push('/orders/11033380')">
119
+ <IpStatusMarker slot="marker" status="warning" />
120
+ <span slot="header-end">19.08.2025</span>
121
+ <p>Guide vanes</p>
122
+ <div slot="footer">Alexander Wangsvik Olsen +2</div>
123
+ </IpCard>
124
+
125
+ <!-- Link -->
126
+ <IpCard heading="Major leak" href="/faults/314001" target="_blank">
127
+ <IpStatusMarker slot="marker" status="error" />
128
+ </IpCard>
129
+ ```
130
+
131
+ ### Angular
132
+
133
+ ```html
134
+ <!-- Interactive (SPA) -->
135
+ <ip-card eyebrow="Work order" heading="Periodic maintenance" clickTarget="card" (ipCardClick)="router.navigate(['/orders/11033380'])">
136
+ <ip-status-marker slot="marker" status="warning"></ip-status-marker>
137
+ <span slot="header-end">19.08.2025</span>
138
+ <p>Guide vanes</p>
139
+ <div slot="footer">Alexander Wangsvik Olsen +2</div>
140
+ </ip-card>
141
+
142
+ <!-- Link -->
143
+ <ip-card heading="Major leak" href="/faults/314001" target="_blank">
144
+ <ip-status-marker slot="marker" status="error"></ip-status-marker>
145
+ </ip-card>
146
+ ```
147
+
148
+ ### Svelte
149
+
150
+ ```svelte
151
+ <!-- Interactive (SPA) -->
152
+ <ip-card heading="Periodic maintenance" click-target="card" on:ipCardClick={() => goto('/orders/11033380')}>
153
+ <ip-status-marker slot="marker" status="warning"></ip-status-marker>
154
+ <span slot="header-end">19.08.2025</span>
155
+ <p>Guide vanes</p>
156
+ <div slot="footer">Alexander Wangsvik Olsen +2</div>
157
+ </ip-card>
158
+
159
+ <!-- Link -->
160
+ <ip-card heading="Major leak" href="/faults/314001" target="_blank">
161
+ <ip-status-marker slot="marker" status="error"></ip-status-marker>
162
+ </ip-card>
163
+ ```
164
+
165
+ ### Blazor
166
+
167
+ ```razor
168
+ <!-- Interactive (SPA) -->
169
+ <ip-card heading="Periodic maintenance" click-target="card" @onipCardClick="OpenOrder">
170
+ <ip-status-marker slot="marker" status="warning"></ip-status-marker>
171
+ <span slot="header-end">19.08.2025</span>
172
+ <p>Guide vanes</p>
173
+ <div slot="footer">Alexander Wangsvik Olsen +2</div>
174
+ </ip-card>
175
+
176
+ <!-- Link -->
177
+ <ip-card heading="Major leak" href="/faults/314001" target="_blank">
178
+ <ip-status-marker slot="marker" status="error"></ip-status-marker>
179
+ </ip-card>
180
+ ```