@norconsult-digital-public/isypixel 2.0.2-canary.4 → 2.0.2-canary.6

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 (50) hide show
  1. package/dist/docs/web-components/ip-avatar/llm.md +171 -0
  2. package/dist/docs/web-components/ip-avatar-stack/llm.md +169 -0
  3. package/dist/llm.md +12 -10
  4. package/dist/types/components/ip-avatar/ip-avatar.d.ts +97 -0
  5. package/dist/types/components/ip-avatar-stack/ip-avatar-stack.d.ts +96 -0
  6. package/dist/types/components.d.ts +491 -0
  7. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/abbreviate-name.d.ts +2 -0
  8. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/get-initials.d.ts +2 -0
  9. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/icons.d.ts +2 -0
  10. package/dist/types/index.d.ts +2 -0
  11. package/dist/web-components/components/index.js +1 -1
  12. package/dist/web-components/components/ip-avatar-stack.d.ts +11 -0
  13. package/dist/web-components/components/ip-avatar-stack.js +1 -0
  14. package/dist/web-components/components/ip-avatar.d.ts +11 -0
  15. package/dist/web-components/components/ip-avatar.js +1 -0
  16. package/dist/web-components/components/ip-button.js +1 -1
  17. package/dist/web-components/components/ip-icon-button.js +1 -1
  18. package/dist/web-components/components/ip-main-menu-group.js +1 -1
  19. package/dist/web-components/components/ip-main-menu-item.js +1 -1
  20. package/dist/web-components/components/ip-main-menu-section.js +1 -1
  21. package/dist/web-components/components/ip-main-menu-toggle.js +1 -1
  22. package/dist/web-components/components/ip-main-menu.js +1 -1
  23. package/dist/web-components/components/ip-tooltip.js +1 -1
  24. package/dist/web-components/components/{p-xFc88Xk9.js → p-6Z45CK1I.js} +1 -1
  25. package/dist/web-components/components/{p-BVCpLJHm.js → p-BYKtWIr5.js} +1 -1
  26. package/dist/web-components/components/{p-C5JgSR21.js → p-BkayA00u.js} +1 -1
  27. package/dist/web-components/components/{p-DZvuo-De.js → p-C1tFWpS0.js} +1 -1
  28. package/dist/web-components/components/p-CAPVXUZ_.js +1 -0
  29. package/dist/web-components/components/p-CXMRqnYG.js +1 -0
  30. package/dist/web-components/components/p-D7rK08OB.js +1 -0
  31. package/dist/web-components/components/{p-bEbVkEjw.js → p-DWpUb5rd.js} +1 -1
  32. package/dist/web-components/components/{p-Cdum_HLu.js → p-MvV7276a.js} +1 -1
  33. package/dist/web-components/components/{p-CfZcYTNP.js → p-VLgeZTve.js} +1 -1
  34. package/dist/web-components/components/{p-C46ykm0B.js → p-msgdnxTK.js} +1 -1
  35. package/dist/web-components/generated/angular/components.d.ts +185 -0
  36. package/dist/web-components/generated/angular/components.js +259 -0
  37. package/dist/web-components/generated/angular/components.ts +219 -0
  38. package/dist/web-components/generated/angular/index.d.ts +1 -1
  39. package/dist/web-components/generated/angular/index.js +2 -0
  40. package/dist/web-components/generated/angular/index.ts +2 -0
  41. package/dist/web-components/generated/react/components.d.ts +12 -1
  42. package/dist/web-components/generated/react/components.js +21 -0
  43. package/dist/web-components/generated/react/components.ts +31 -1
  44. package/dist/web-components/generated/svelte/components.d.ts +4 -0
  45. package/dist/web-components/generated/vue/components.d.ts +2 -0
  46. package/dist/web-components/generated/vue/components.js +52 -0
  47. package/dist/web-components/generated/vue/components.ts +56 -0
  48. package/llms.txt +2 -0
  49. package/package.json +1 -1
  50. package/dist/web-components/components/p-Cjml7mZs.js +0 -1
@@ -0,0 +1,171 @@
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
+ - When `disabled`, a clickable avatar sets `aria-disabled="true"`, drops out of the tab order, and blocks `ipClick`.
69
+ - 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.
70
+
71
+ ## Design tokens
72
+
73
+ > 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.
74
+
75
+ | SCSS variable | Role |
76
+ | ------------------------------------------------------------ | ------------------------------------------------------------------------ |
77
+ | `$ip_color_surface_primary_default` | Ring layer background behind the ring gap |
78
+ | `$ip_color_text_primary_blue` | Foreground colour: initials, silhouette, loader, and default ring |
79
+ | `$ip_color_border_opaque_15` | No-image background behind initials / silhouette |
80
+ | `$ip_color_border_opaque_10` | Clickable container hover background and the stripe overlay |
81
+ | `$ip_color_border_opaque_20` | Selected container background |
82
+ | `$ip_color_button_focus_default` | Focus ring when `clickable` |
83
+ | `$ip_color_brand_accent_primary_default` | Ring colour for `ring-variant="brand"` |
84
+ | `$ip_color_extended_green-500` | Ring colour for `ring-variant="success"` |
85
+ | `$ip_color_extended_yellow-500` | Ring colour for `ring-variant="warning"` |
86
+ | `$ip_color_extended_red-500` | Ring colour for `ring-variant="error"` |
87
+ | `$ip_color_text_primary_black` | Default label (name) text colour; override with `--ip-avatar-text-color` |
88
+ | `$ip_radius_size_small` | Framed container and focus-ring radius |
89
+ | `$ip_spacing_space_050` / `$ip_spacing_space_200` | Framed container padding |
90
+ | `$ip_spacing_space_100` / `_200` / `_300` | Avatar↔label gap defaults for small / medium / large |
91
+ | `$ip_typography_body_font_size_small` / `_medium` / `_large` | Initials and label font size per size |
92
+ | `$ip_typography_body_line_height_compact` | Line height for the name and caption lines |
93
+ | `$ip_typography_font_family_primary` | Font family for initials and label |
94
+ | `$ip_typography_font_weight_bold_700` | Initials font weight |
95
+ | `$ip_typography_font_weight_light_300` | Caption and placeholder font weight (italic) |
96
+
97
+ ## CSS custom properties
98
+
99
+ | Property | Default | Role |
100
+ | ------------------------ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
101
+ | `--ip-avatar-text-color` | `$ip_color_text_primary_black` | 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. |
102
+
103
+ ## Usage
104
+
105
+ ### HTML
106
+
107
+ > Attribute names must be **kebab-case** in HTML: use `fallback-src`, not `fallbackSrc`.
108
+
109
+ ```js
110
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar.js';
111
+ ```
112
+
113
+ ```html
114
+ <ip-avatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" src="/users/alexander.jpg" fallback-src="/users/default.png">
115
+ <span slot="bottom-right" class="badge">✓</span>
116
+ </ip-avatar>
117
+ ```
118
+
119
+ ### React
120
+
121
+ ```tsx
122
+ import { IpAvatar } from '@norconsult-digital-public/isypixel/web-components/react';
123
+
124
+ <IpAvatar name='Alexander Wangsvik' caption='Elkraft, ingeniør' src='/users/alexander.jpg' fallbackSrc='/users/default.png' onIpClick={() => {}} />;
125
+ ```
126
+
127
+ ### Vue
128
+
129
+ ```vue
130
+ <script setup lang="ts">
131
+ import { IpAvatar } from '@norconsult-digital-public/isypixel/web-components/vue';
132
+ </script>
133
+
134
+ <template>
135
+ <IpAvatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" src="/users/alexander.jpg" fallback-src="/users/default.png" />
136
+ </template>
137
+ ```
138
+
139
+ ### Angular
140
+
141
+ ```ts
142
+ import { IpAvatar } from '@norconsult-digital-public/isypixel/web-components/angular';
143
+ ```
144
+
145
+ ```html
146
+ <ip-avatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" [src]="imageUrl" ring-variant="brand" (ipClick)="onSelect()"></ip-avatar>
147
+ ```
148
+
149
+ ### Svelte
150
+
151
+ ```svelte
152
+ <script lang="ts">
153
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar.js';
154
+ </script>
155
+
156
+ <ip-avatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" src="/users/alexander.jpg" fallback-src="/users/default.png"></ip-avatar>
157
+ ```
158
+
159
+ ### Blazor
160
+
161
+ > Attribute names must be **kebab-case** in Blazor HTML.
162
+
163
+ Copy `ip-avatar.js` to `wwwroot/lib/isypixel/components/` at build time. See `llm.md` in the package root for the MSBuild target.
164
+
165
+ ```html
166
+ <script type="module" src="lib/isypixel/components/ip-avatar.js"></script>
167
+
168
+ <ip-avatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" src="/users/alexander.jpg" fallback-src="/users/default.png"></ip-avatar>
169
+ ```
170
+
171
+ - 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,169 @@
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"`, `"counter"`, `"none"` | How the group is summarised. `name`: first name + `+N` rest text label. `counter`: a `+N` counter circle in the stack. `none`: nothing. The counter circle and the text label are mutually exclusive. |
21
+ | `gap` | string | `undefined` | Any CSS length | Overrides the gap between the stack and the label. Defaults scale with `size`. |
22
+ | `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. |
23
+ | `clickable` | boolean | `false` | `true`, `false` | Makes the `+N` overflow counter (in `label-mode="counter"`) focusable, keyboard-activatable, and emits `ipOverflowClick`. |
24
+ | `disabled` | boolean | `false` | `true`, `false` | Greys out the stack and, when `clickable`, prevents activation of the overflow counter. |
25
+
26
+ **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"`.
27
+
28
+ **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`.
29
+
30
+ **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. 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`.
31
+
32
+ ## Slots
33
+
34
+ | Slot | Description |
35
+ | ---------- | ---------------------------------------------------------------------------------- |
36
+ | `default` | The `ip-avatar` elements to stack. Non-`ip-avatar` nodes are ignored. |
37
+ | `label` | Overrides the auto-generated text label. Shown in `label-mode="name"`. |
38
+ | `overflow` | Overrides the content of the `+N` counter circle. Shown in `label-mode="counter"`. |
39
+
40
+ ## Events
41
+
42
+ | Event | Payload | When |
43
+ | ----------------- | ------- | ---------------------------------------------------------------------------------------------- |
44
+ | `ipOverflowClick` | `void` | A `clickable` overflow counter is activated by mouse or keyboard. Not emitted when `disabled`. |
45
+
46
+ ## Accessibility
47
+
48
+ - The overlap and paint order are presentational only; each slotted `ip-avatar` keeps its own accessible name and tooltip.
49
+ - Avatars collapsed into the `+N` counter are hidden from the accessibility tree; their names are surfaced through the counter's tooltip preview instead.
50
+ - 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.
51
+ - When `disabled`, a clickable counter sets `aria-disabled="true"` and blocks `ipOverflowClick`.
52
+
53
+ ## Design tokens
54
+
55
+ > 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.
56
+
57
+ | SCSS variable | Role |
58
+ | ------------------------------------------------------------ | ---------------------------------------------------------- |
59
+ | `$ip_color_surface_primary_default` | Overflow counter ring layer background behind the ring gap |
60
+ | `$ip_color_text_primary_blue` | Overflow counter ring colour and `+N` text colour |
61
+ | `$ip_color_border_opaque_15` | Overflow counter inner background behind the `+N` text |
62
+ | `$ip_color_button_focus_default` | Focus ring of the overflow counter when `clickable` |
63
+ | `$ip_color_text_tertiary_default` | Names label text colour |
64
+ | `$ip_spacing_space_100` / `_200` / `_300` | Stack↔label gap defaults for small / medium / large |
65
+ | `$ip_typography_body_font_size_small` / `_medium` / `_large` | Counter and label font size per size |
66
+ | `$ip_typography_body_line_height_compact` | Label line height |
67
+ | `$ip_typography_font_family_primary` | Font family for the counter and label |
68
+ | `$ip_typography_font_weight_bold_700` | `+N` counter font weight |
69
+
70
+ ## Usage
71
+
72
+ ### HTML
73
+
74
+ > Attribute names must be **kebab-case** in HTML: use `label-mode`, not `labelMode`.
75
+
76
+ ```js
77
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar-stack.js';
78
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar.js';
79
+ ```
80
+
81
+ ```html
82
+ <ip-avatar-stack max="3" size="medium" label-mode="name">
83
+ <ip-avatar name="Markus Halvorsen" src="/users/markus.jpg"></ip-avatar>
84
+ <ip-avatar name="Mia Berg" src="/users/mia.jpg"></ip-avatar>
85
+ <ip-avatar name="Jonas Lie" src="/users/jonas.jpg"></ip-avatar>
86
+ <ip-avatar name="Sara Nilsen"></ip-avatar>
87
+ </ip-avatar-stack>
88
+ ```
89
+
90
+ ### React
91
+
92
+ ```tsx
93
+ import { IpAvatarStack, IpAvatar } from '@norconsult-digital-public/isypixel/web-components/react';
94
+
95
+ <IpAvatarStack max={3} size='medium' labelMode='name' clickable onIpOverflowClick={() => {}}>
96
+ <IpAvatar name='Markus Halvorsen' src='/users/markus.jpg' />
97
+ <IpAvatar name='Mia Berg' src='/users/mia.jpg' />
98
+ <IpAvatar name='Jonas Lie' src='/users/jonas.jpg' />
99
+ <IpAvatar name='Sara Nilsen' />
100
+ </IpAvatarStack>;
101
+ ```
102
+
103
+ ### Vue
104
+
105
+ ```vue
106
+ <script setup lang="ts">
107
+ import { IpAvatarStack, IpAvatar } from '@norconsult-digital-public/isypixel/web-components/vue';
108
+ </script>
109
+
110
+ <template>
111
+ <IpAvatarStack :max="3" size="medium" label-mode="name">
112
+ <IpAvatar name="Markus Halvorsen" src="/users/markus.jpg" />
113
+ <IpAvatar name="Mia Berg" src="/users/mia.jpg" />
114
+ <IpAvatar name="Jonas Lie" src="/users/jonas.jpg" />
115
+ <IpAvatar name="Sara Nilsen" />
116
+ </IpAvatarStack>
117
+ </template>
118
+ ```
119
+
120
+ ### Angular
121
+
122
+ ```ts
123
+ import { IpAvatarStack, IpAvatar } from '@norconsult-digital-public/isypixel/web-components/angular';
124
+ ```
125
+
126
+ ```html
127
+ <ip-avatar-stack [max]="3" size="medium" label-mode="name" clickable (ipOverflowClick)="onShowAll()">
128
+ <ip-avatar name="Markus Halvorsen" [src]="markusUrl"></ip-avatar>
129
+ <ip-avatar name="Mia Berg" [src]="miaUrl"></ip-avatar>
130
+ <ip-avatar name="Jonas Lie" [src]="jonasUrl"></ip-avatar>
131
+ <ip-avatar name="Sara Nilsen"></ip-avatar>
132
+ </ip-avatar-stack>
133
+ ```
134
+
135
+ ### Svelte
136
+
137
+ ```svelte
138
+ <script lang="ts">
139
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar-stack.js';
140
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar.js';
141
+ </script>
142
+
143
+ <ip-avatar-stack max="3" size="medium" label-mode="name">
144
+ <ip-avatar name="Markus Halvorsen" src="/users/markus.jpg"></ip-avatar>
145
+ <ip-avatar name="Mia Berg" src="/users/mia.jpg"></ip-avatar>
146
+ <ip-avatar name="Jonas Lie" src="/users/jonas.jpg"></ip-avatar>
147
+ <ip-avatar name="Sara Nilsen"></ip-avatar>
148
+ </ip-avatar-stack>
149
+ ```
150
+
151
+ ### Blazor
152
+
153
+ > Attribute names must be **kebab-case** in Blazor HTML.
154
+
155
+ 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.
156
+
157
+ ```html
158
+ <script type="module" src="lib/isypixel/components/ip-avatar-stack.js"></script>
159
+ <script type="module" src="lib/isypixel/components/ip-avatar.js"></script>
160
+
161
+ <ip-avatar-stack max="3" size="medium" label-mode="name">
162
+ <ip-avatar name="Markus Halvorsen" src="/users/markus.jpg"></ip-avatar>
163
+ <ip-avatar name="Mia Berg" src="/users/mia.jpg"></ip-avatar>
164
+ <ip-avatar name="Jonas Lie" src="/users/jonas.jpg"></ip-avatar>
165
+ <ip-avatar name="Sara Nilsen"></ip-avatar>
166
+ </ip-avatar-stack>
167
+ ```
168
+
169
+ - 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.
package/dist/llm.md CHANGED
@@ -169,16 +169,18 @@ In `index.html` `<body>`:
169
169
 
170
170
  ## Components
171
171
 
172
- | Component | Description | Docs |
173
- | ---------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------- |
174
- | `ip-button` | Clickable button with visual variants, loading state, and icon slot | `docs/web-components/ip-button/llm.md` |
175
- | `ip-icon-button` | Circular icon-only button with CSS ripple, visual variants, and loading state | `docs/web-components/ip-icon-button/llm.md` |
176
- | `ip-tooltip` | Tooltip that wraps a trigger and shows non-interactive content on hover, focus, and Escape | `docs/web-components/ip-tooltip/llm.md` |
177
- | `ip-main-menu` | Sidebar navigation wrapper with expanded/auto-hide/collapsed desktop modes and mobile drawer | `docs/web-components/ip-main-menu/llm.md` |
178
- | `ip-main-menu-toggle` | Hamburger button for the top bar that opens the `ip-main-menu` mobile drawer via a shared store | `docs/web-components/ip-main-menu-toggle/llm.md` |
179
- | `ip-main-menu-section` | Semantic section wrapper that groups navigation items with an optional visible label | `docs/web-components/ip-main-menu-section/llm.md` |
180
- | `ip-main-menu-group` | Collapsible group of navigation items with label, icon, and animated chevron | `docs/web-components/ip-main-menu-group/llm.md` |
181
- | `ip-main-menu-item` | Individual navigation item; renders as `<a>` when `href` is provided, otherwise as `<button>` | `docs/web-components/ip-main-menu-item/llm.md` |
172
+ | Component | Description | Docs |
173
+ | ---------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
174
+ | `ip-button` | Clickable button with visual variants, loading state, and icon slot | `docs/web-components/ip-button/llm.md` |
175
+ | `ip-icon-button` | Circular icon-only button with CSS ripple, visual variants, and loading state | `docs/web-components/ip-icon-button/llm.md` |
176
+ | `ip-tooltip` | Tooltip that wraps a trigger and shows non-interactive content on hover, focus, and Escape | `docs/web-components/ip-tooltip/llm.md` |
177
+ | `ip-avatar` | Theme-aware avatar with image/fallback/initials/silhouette chain, label, ring, loader, corner slots, and tooltip | `docs/web-components/ip-avatar/llm.md` |
178
+ | `ip-avatar-stack` | Overlapping stack of `ip-avatar` elements with overflow collapse into a `+N` counter and a combined names label | `docs/web-components/ip-avatar-stack/llm.md` |
179
+ | `ip-main-menu` | Sidebar navigation wrapper with expanded/auto-hide/collapsed desktop modes and mobile drawer | `docs/web-components/ip-main-menu/llm.md` |
180
+ | `ip-main-menu-toggle` | Hamburger button for the top bar that opens the `ip-main-menu` mobile drawer via a shared store | `docs/web-components/ip-main-menu-toggle/llm.md` |
181
+ | `ip-main-menu-section` | Semantic section wrapper that groups navigation items with an optional visible label | `docs/web-components/ip-main-menu-section/llm.md` |
182
+ | `ip-main-menu-group` | Collapsible group of navigation items with label, icon, and animated chevron | `docs/web-components/ip-main-menu-group/llm.md` |
183
+ | `ip-main-menu-item` | Individual navigation item; renders as `<a>` when `href` is provided, otherwise as `<button>` | `docs/web-components/ip-main-menu-item/llm.md` |
182
184
 
183
185
  ## Services
184
186
 
@@ -0,0 +1,97 @@
1
+ import { EventEmitter, JSX } from '../../stencil-public-runtime';
2
+ export declare class IpAvatar {
3
+ /** User's full name. Used as the image `alt`, the source for every name representation, and the tooltip text. */
4
+ name: string;
5
+ /** Alternative (typically shortened) name shown when `mode="alt-name"`. When unset, the name is abbreviated automatically. */
6
+ altName: string | undefined;
7
+ /** Caption line shown beneath the name, e.g. a role or title. */
8
+ caption: string | undefined;
9
+ /** Text shown beside the avatar when there is no name, e.g. an unselected-resource hint. */
10
+ placeholder: string | undefined;
11
+ /** Primary profile image URL, tried first in the fallback chain. */
12
+ src: string | undefined;
13
+ /** Fallback image URL, used when `src` is missing or fails to load. */
14
+ fallbackSrc: string | undefined;
15
+ /** Which name representation, if any, is displayed as visible text beside the avatar. */
16
+ mode: 'fullname' | 'alt-name' | 'firstname' | 'lastname' | 'initials' | 'image-only';
17
+ /** What to show inside the circle when there is no image: `auto` derives initials from the name (silhouette when nameless), `initials` forces initials, `silhouette` forces the person glyph. */
18
+ fallback: 'auto' | 'initials' | 'silhouette';
19
+ /** Position of the text label relative to the avatar circle. */
20
+ labelPosition: 'top' | 'right' | 'bottom' | 'left';
21
+ /** Overrides the gap between the avatar and the label. Any CSS length; defaults scale with `size`. */
22
+ gap: string | undefined;
23
+ /** Avatar size. Use `custom` together with `customSize` for an arbitrary size. */
24
+ size: 'small' | 'medium' | 'large' | 'custom';
25
+ /** Diameter of the avatar when `size="custom"`. Any CSS length, e.g. `64px` or `4rem`. Applied to both width and height so the avatar stays circular. */
26
+ customSize: string | undefined;
27
+ /** Native image loading strategy forwarded to the inner `<img>`. */
28
+ imageLoading: 'lazy' | 'eager';
29
+ /** Forces the loading spinner regardless of image state — for showing a pending avatar before `src` is known. */
30
+ loading: boolean;
31
+ /** Predefined, theme-aware ring colour. Use `custom` together with `ringColor` for a raw colour. */
32
+ ringVariant: 'default' | 'brand' | 'success' | 'warning' | 'error' | 'custom';
33
+ /** Raw CSS colour for the ring, applied only when `ringVariant="custom"`. Does not adapt to light/dark. */
34
+ ringColor: string | undefined;
35
+ /** Line style of the ring around the circle. */
36
+ ringStyle: 'solid' | 'dashed' | 'dotted';
37
+ /** Gap between the ring and the content (image/initials). Any CSS length; defaults to a small constant. */
38
+ ringGap: string | undefined;
39
+ /** Makes the avatar interactive: focusable, keyboard-activatable, and emits `ipClick`. */
40
+ clickable: boolean;
41
+ /** Applies the selected (active) container styling, e.g. for the chosen item in a list. */
42
+ selected: boolean;
43
+ /** Stretches the avatar to fill the available width, so stacked avatars span the full row. */
44
+ fullWidth: boolean;
45
+ /** Keeps the name and caption lines at a constant height even when empty, so avatars in a row stay aligned regardless of which carry a name/caption. */
46
+ maintainLabelHeight: boolean;
47
+ /** Overlays subtle diagonal stripes on the no-image background (initials / silhouette). */
48
+ striped: boolean;
49
+ /** Greys out the avatar and, when `clickable`, prevents activation. */
50
+ disabled: boolean;
51
+ /** Disables the built-in tooltip entirely. */
52
+ disableTooltip: boolean;
53
+ /** Placement of the tooltip, forwarded to `ip-tooltip`. */
54
+ tooltipPosition: 'top' | 'right' | 'bottom' | 'left';
55
+ /** Size of the tooltip, forwarded to `ip-tooltip`. */
56
+ tooltipSize: 'small' | 'medium' | 'large';
57
+ /** Colour variant of the tooltip, forwarded to `ip-tooltip`. */
58
+ tooltipVariant: 'default' | 'onLight' | 'onDark';
59
+ /** Index into the resolved source chain — advances when an image fails to load. */
60
+ private srcIndex;
61
+ /** Tracks whether the current image is still loading, to show the spinner. */
62
+ private isLoading;
63
+ /** Tracks whether the consumer slotted custom tooltip content, so the tooltip stays enabled even without a derived name. */
64
+ private hasTooltipSlot;
65
+ /** Emitted with the failed src each time an image fails to load and the fallback chain advances. */
66
+ imageError: EventEmitter<string>;
67
+ /** Emitted when a clickable, non-disabled avatar is activated by mouse or keyboard. */
68
+ clickEvent: EventEmitter<void>;
69
+ /** Whitespace-separated words of the name, with empty entries removed. */
70
+ private get nameWords();
71
+ /** Resolved image source chain in priority order, skipping any that are not set and de-duplicating so an identical URL is never retried (which would never fire a second load error). */
72
+ private get sources();
73
+ /** The image source currently being displayed, or an empty string once the chain is exhausted. */
74
+ private get currentSrc();
75
+ /** The name representation displayed as visible text beside the avatar, per the active mode. */
76
+ private get visibleName();
77
+ /** The full name surfaced by the tooltip — shown only when the visible text is not already the full name. */
78
+ private get tooltipName();
79
+ componentWillLoad(): void;
80
+ /** Restarts the fallback chain from the primary source — runs on load and whenever a source prop changes. */
81
+ resetImageState(): void;
82
+ /** Marks the image as loaded so the spinner is removed. */
83
+ private handleImageLoad;
84
+ /** Advances down the fallback chain on load failure, emitting the failed src; falls back to initials or silhouette once exhausted. */
85
+ private handleImageError;
86
+ /** Emits ipClick when the avatar is clickable and not disabled. */
87
+ private handleActivate;
88
+ /** Activates the avatar on Enter or Space, mirroring native button keyboard behaviour. */
89
+ private handleKeyDown;
90
+ /** Tracks custom tooltip slot content so the tooltip is not auto-disabled when the consumer provides their own. */
91
+ private handleTooltipSlotChange;
92
+ /** Whether the initials fallback should render: forced by `fallback`, or chosen automatically when a name is present. */
93
+ private get showsInitials();
94
+ /** Renders the image, the initials fallback, or the silhouette placeholder depending on what is available. */
95
+ private renderMediaContent;
96
+ render(): JSX.Element;
97
+ }
@@ -0,0 +1,96 @@
1
+ import { EventEmitter, JSX } from '../../stencil-public-runtime';
2
+ export declare class IpAvatarStack {
3
+ /** Host element, used to keep the stacking layout in sync when layout props change. */
4
+ private host;
5
+ /** Maximum number of avatars shown before the rest collapse into a `+N` counter. Acts as the hard cap; with `auto-fit` on, fewer may show. */
6
+ max: number;
7
+ /** Reduces the visible avatars to fit the container width, collapsing the rest like `max` does (`max` stays the cap). Set `auto-fit="false"` for a fixed stack that always shows up to `max`. */
8
+ autoFit: boolean;
9
+ /** Stack size. Drives the overflow counter size and the default overlap; slotted avatars should match. */
10
+ size: 'small' | 'medium' | 'large' | 'custom';
11
+ /** Diameter of the overflow counter when `size="custom"`. Any CSS length, applied to both width and height. */
12
+ customSize: string | undefined;
13
+ /** Overrides the overlap offset between circles. Any CSS length; defaults scale with `size`. */
14
+ overlap: string | undefined;
15
+ /** How the group is summarised: the first name with a `+N` rest count, a `+N` overflow counter circle in the stack, or nothing. The counter circle and the text label are mutually exclusive. */
16
+ labelMode: 'name' | 'counter' | 'none';
17
+ /** Overrides the gap between the stack and the label. Any CSS length; defaults scale with `size`. */
18
+ gap: string | undefined;
19
+ /** Whether the tooltip previews (the counter and the names label) show a small avatar beside each person, or just the name and caption. */
20
+ previewAvatars: boolean;
21
+ /** Makes the `+N` overflow counter focusable, keyboard-activatable, and emits `ipOverflowClick`. */
22
+ clickable: boolean;
23
+ /** Greys out the stack and, when `clickable`, prevents activation of the overflow counter. */
24
+ disabled: boolean;
25
+ /** Identity of every slotted avatar, in order. Drives the count, the names label, the counter, and both tooltip previews. */
26
+ private people;
27
+ /** Width-fitted cap computed by the width observer when `autoFit` is on; `null` until first measured (then `effectiveMax` falls back to `max`). */
28
+ private fittedMax;
29
+ /** Emitted when a clickable, non-disabled overflow counter is activated by mouse or keyboard. */
30
+ overflowClick: EventEmitter<void>;
31
+ /** Active width observer driving auto-fit; null when auto-fit is off or the element is detached. */
32
+ private resizeObserver;
33
+ /** True once the first render has measured; gates restart-on-reconnect so the very first connect defers to `componentDidLoad`. */
34
+ private hasLoaded;
35
+ /** Re-reads the slot and re-applies the layout when the collapse threshold changes. */
36
+ relayout(): void;
37
+ /** Re-fits when a dimension-affecting prop changes — the count that fits depends on avatar size and overlap. */
38
+ onDimensionChange(): void;
39
+ /** Starts or stops width observation when auto-fit is toggled, resetting the fitted cap. */
40
+ onAutoFitChange(): void;
41
+ /** Restarts width observation when the element is re-attached after a move; the first connect defers to `componentDidLoad`. */
42
+ connectedCallback(): void;
43
+ /** Begins observing the container width once the element and its slotted avatars exist. */
44
+ componentDidLoad(): void;
45
+ /** Tears down the width observer when the element leaves the DOM. */
46
+ disconnectedCallback(): void;
47
+ /** The `ip-avatar` elements currently assigned to the default slot. */
48
+ private assignedAvatars;
49
+ /** Reads the first avatar's name from the `name` property, falling back to the attribute for plain-HTML consumers. */
50
+ private nameOf;
51
+ /** Reads an avatar's image source from the `src` property, falling back to the attribute for plain-HTML consumers. */
52
+ private srcOf;
53
+ /** Reads an avatar's caption from the `caption` property, falling back to the attribute for plain-HTML consumers. */
54
+ private captionOf;
55
+ /** Records every slotted avatar's identity, then applies the stacking layout. */
56
+ private syncFromSlot;
57
+ /** Re-reads the slotted avatars whenever the assigned set changes. */
58
+ private handleSlotChange;
59
+ /** Forces each avatar to image-only, paints each above the one before it (last on top), and collapses avatars beyond `effectiveMax` into the overflow counter. */
60
+ private applyStacking;
61
+ /** Avatars to show before collapsing: the width-fitted cap (never above `max`) in auto-fit mode, otherwise `max`. */
62
+ private get effectiveMax();
63
+ /** Observes the parent's width (container resize) and the window (ambient resize) so the fitted cap stays in sync. */
64
+ private startObserving;
65
+ /** Disconnects the width observer and the window listener. */
66
+ private stopObserving;
67
+ /** Measures the container and updates the fitted cap, re-applying the stacking layout when it changes. */
68
+ private recomputeFit;
69
+ /** Content-box width available to the stack, measured from its parent. */
70
+ private availableWidth;
71
+ /** Current overlap offset in px, read from the computed custom property (set per size or via the `overlap` prop). */
72
+ private measuredOverlap;
73
+ /** Current stack↔label gap in px, read from the computed custom property (set per size or via the `gap` prop). */
74
+ private measuredGap;
75
+ /** Natural width of the rendered names label, reserved so it is not clipped before avatars collapse in name mode. */
76
+ private measuredLabelWidth;
77
+ /** Pixel width the `+N` counter occupies, matching the per-size circle in the SCSS. */
78
+ private counterWidth;
79
+ /** Largest avatar count that fits the available width, reserving room for the counter when one will show. At least 1. */
80
+ private computeFittedMax;
81
+ /** Emits ipOverflowClick when the counter is clickable and not disabled. */
82
+ private handleOverflowActivate;
83
+ /** Activates the overflow counter on Enter or Space, mirroring native button keyboard behaviour. */
84
+ private handleOverflowKeyDown;
85
+ /** Auto-generated names label: the first name plus a `+N` count of the remaining avatars. */
86
+ private get labelText();
87
+ /** Renders the people preview: ip-avatar's built-in label per row, or a compact name-only list when `previewAvatars` is off. Shared by the counter preview and the names-label tooltip. */
88
+ private renderPeopleList;
89
+ /** A preview row using ip-avatar's own circle, name, and caption. The class makes its label inherit the tooltip bubble's colour. */
90
+ private renderPersonRow;
91
+ /** A compact, avatar-less preview row — the name with an optional caption beneath. Used when `previewAvatars` is off, where ip-avatar's circle-bound label cannot apply. */
92
+ private renderPersonName;
93
+ /** Inline custom properties for the overlap and label gap, set only when the consumer overrides the defaults. */
94
+ private get hostStyle();
95
+ render(): JSX.Element;
96
+ }