@norconsult-digital-public/isypixel 2.0.2-canary.3 → 2.0.2-canary.5

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 (84) hide show
  1. package/LICENSE.md +11 -0
  2. package/dist/docs/web-components/ip-avatar/llm.md +165 -0
  3. package/dist/docs/web-components/ip-icon-button/llm.md +113 -0
  4. package/dist/docs/web-components/ip-main-menu/llm.md +219 -0
  5. package/dist/docs/web-components/ip-main-menu-group/llm.md +114 -0
  6. package/dist/docs/web-components/ip-main-menu-item/llm.md +104 -0
  7. package/dist/docs/web-components/ip-main-menu-section/llm.md +95 -0
  8. package/dist/docs/web-components/ip-main-menu-toggle/llm.md +106 -0
  9. package/dist/docs/web-components/ip-tooltip/llm.md +170 -0
  10. package/dist/docs/web-components/services/i18n/llm.md +150 -0
  11. package/dist/llm.md +19 -12
  12. package/dist/types/components/ip-avatar/ip-avatar.d.ts +97 -0
  13. package/dist/types/components/ip-icon-button/ip-icon-button.d.ts +11 -1
  14. package/dist/types/components/ip-main-menu/i18n.d.ts +8 -0
  15. package/dist/types/components/ip-main-menu/ip-main-menu-drawer-styles.d.ts +1 -0
  16. package/dist/types/components/ip-main-menu/ip-main-menu-state-store.d.ts +24 -0
  17. package/dist/types/components/ip-main-menu/ip-main-menu.d.ts +81 -0
  18. package/dist/types/components/ip-main-menu-group/ip-main-menu-group.d.ts +20 -0
  19. package/dist/types/components/ip-main-menu-item/ip-main-menu-item.d.ts +21 -0
  20. package/dist/types/components/ip-main-menu-section/ip-main-menu-section.d.ts +7 -0
  21. package/dist/types/components/ip-main-menu-toggle/i18n.d.ts +4 -0
  22. package/dist/types/components/ip-main-menu-toggle/ip-main-menu-toggle.d.ts +18 -0
  23. package/dist/types/components/ip-tooltip/ip-tooltip.d.ts +91 -3
  24. package/dist/types/components.d.ts +802 -18
  25. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/abbreviate-name.d.ts +2 -0
  26. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/get-initials.d.ts +2 -0
  27. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/icons.d.ts +14 -0
  28. package/dist/types/home/runner/work/pixel/pixel/.stencil/utils/media-query.d.ts +2 -0
  29. package/dist/types/index.d.ts +10 -0
  30. package/dist/types/services/i18n/i18n.d.ts +90 -0
  31. package/dist/types/services/index.d.ts +3 -0
  32. package/dist/web-components/components/index.js +1 -1
  33. package/dist/web-components/components/ip-avatar.d.ts +11 -0
  34. package/dist/web-components/components/ip-avatar.js +1 -0
  35. package/dist/web-components/components/ip-button.js +1 -1
  36. package/dist/web-components/components/ip-icon-button.js +1 -1
  37. package/dist/web-components/components/ip-main-menu-group.d.ts +11 -0
  38. package/dist/web-components/components/ip-main-menu-group.js +1 -0
  39. package/dist/web-components/components/ip-main-menu-item.d.ts +11 -0
  40. package/dist/web-components/components/ip-main-menu-item.js +1 -0
  41. package/dist/web-components/components/ip-main-menu-section.d.ts +11 -0
  42. package/dist/web-components/components/ip-main-menu-section.js +1 -0
  43. package/dist/web-components/components/ip-main-menu-toggle.d.ts +11 -0
  44. package/dist/web-components/components/ip-main-menu-toggle.js +1 -0
  45. package/dist/web-components/components/ip-main-menu.d.ts +11 -0
  46. package/dist/web-components/components/ip-main-menu.js +1 -0
  47. package/dist/web-components/components/ip-tooltip.js +1 -1
  48. package/dist/web-components/components/p-B1x6AHw9.js +1 -0
  49. package/dist/web-components/components/p-B7DtfC4M.js +1 -0
  50. package/dist/web-components/components/p-BB9-AZXH.js +2 -0
  51. package/dist/web-components/components/p-C7Kf2aED.js +1 -0
  52. package/dist/web-components/components/p-CAPVXUZ_.js +1 -0
  53. package/dist/web-components/components/p-CPd0stBB.js +1 -0
  54. package/dist/web-components/components/p-CSHQqNXM.js +1 -0
  55. package/dist/web-components/components/p-CWbwcDHB.js +1 -0
  56. package/dist/web-components/components/p-D42v9_4Q.js +1 -0
  57. package/dist/web-components/components/p-DHUR9EVo.js +1 -0
  58. package/dist/web-components/components/p-DpQ72I9n.js +1 -0
  59. package/dist/web-components/components/p-DrSqAb5O.js +1 -0
  60. package/dist/web-components/components/p-uroVS1GL.js +1 -0
  61. package/dist/web-components/generated/angular/components.d.ts +299 -7
  62. package/dist/web-components/generated/angular/components.js +467 -12
  63. package/dist/web-components/generated/angular/components.ts +398 -10
  64. package/dist/web-components/generated/angular/index.d.ts +1 -1
  65. package/dist/web-components/generated/angular/index.js +6 -0
  66. package/dist/web-components/generated/angular/index.ts +6 -0
  67. package/dist/web-components/generated/react/components.d.ts +30 -3
  68. package/dist/web-components/generated/react/components.js +63 -2
  69. package/dist/web-components/generated/react/components.ts +91 -5
  70. package/dist/web-components/generated/svelte/components.d.ts +12 -0
  71. package/dist/web-components/generated/vue/components.d.ts +6 -0
  72. package/dist/web-components/generated/vue/components.js +91 -6
  73. package/dist/web-components/generated/vue/components.ts +103 -6
  74. package/llms.txt +33 -0
  75. package/package.json +7 -2
  76. package/dist/web-components/components/ip-icon-button/llm.md +0 -108
  77. package/dist/web-components/components/ip-tooltip/llm.md +0 -151
  78. package/dist/web-components/components/p-BWyvzXuA.js +0 -1
  79. package/dist/web-components/components/p-COraaQeZ.js +0 -1
  80. package/dist/web-components/components/p-Ci0xGmLx.js +0 -1
  81. package/dist/web-components/components/p-D9Ju9vYr.js +0 -1
  82. /package/dist/{migrations → docs/migrations}/v1-to-v2.md +0 -0
  83. /package/dist/{esm → docs}/services/theme-service/llm.md +0 -0
  84. /package/dist/{web-components/components → docs/web-components}/ip-button/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,165 @@
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` | Label (name) text colour |
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
+ ## Usage
98
+
99
+ ### HTML
100
+
101
+ > Attribute names must be **kebab-case** in HTML: use `fallback-src`, not `fallbackSrc`.
102
+
103
+ ```js
104
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar.js';
105
+ ```
106
+
107
+ ```html
108
+ <ip-avatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" src="/users/alexander.jpg" fallback-src="/users/default.png">
109
+ <span slot="bottom-right" class="badge">✓</span>
110
+ </ip-avatar>
111
+ ```
112
+
113
+ ### React
114
+
115
+ ```tsx
116
+ import { IpAvatar } from '@norconsult-digital-public/isypixel/web-components/react';
117
+
118
+ <IpAvatar name='Alexander Wangsvik' caption='Elkraft, ingeniør' src='/users/alexander.jpg' fallbackSrc='/users/default.png' onIpClick={() => {}} />;
119
+ ```
120
+
121
+ ### Vue
122
+
123
+ ```vue
124
+ <script setup lang="ts">
125
+ import { IpAvatar } from '@norconsult-digital-public/isypixel/web-components/vue';
126
+ </script>
127
+
128
+ <template>
129
+ <IpAvatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" src="/users/alexander.jpg" fallback-src="/users/default.png" />
130
+ </template>
131
+ ```
132
+
133
+ ### Angular
134
+
135
+ ```ts
136
+ import { IpAvatar } from '@norconsult-digital-public/isypixel/web-components/angular';
137
+ ```
138
+
139
+ ```html
140
+ <ip-avatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" [src]="imageUrl" ring-variant="brand" (ipClick)="onSelect()"></ip-avatar>
141
+ ```
142
+
143
+ ### Svelte
144
+
145
+ ```svelte
146
+ <script lang="ts">
147
+ import '@norconsult-digital-public/isypixel/web-components/ip-avatar.js';
148
+ </script>
149
+
150
+ <ip-avatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" src="/users/alexander.jpg" fallback-src="/users/default.png"></ip-avatar>
151
+ ```
152
+
153
+ ### Blazor
154
+
155
+ > Attribute names must be **kebab-case** in Blazor HTML.
156
+
157
+ Copy `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.js"></script>
161
+
162
+ <ip-avatar name="Alexander Wangsvik" caption="Elkraft, ingeniør" src="/users/alexander.jpg" fallback-src="/users/default.png"></ip-avatar>
163
+ ```
164
+
165
+ - 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,113 @@
1
+ # ip-icon-button
2
+
3
+ A circular icon-only button with a CSS ripple on click and a built-in tooltip on hover. Available in five visual variants shared with `ip-button`, plus two transparent variants (`transparentOnLight`/`transparentOnDark`) for use on surfaces that do not follow the global theme. Requires a `label` attribute for screen reader accessibility — `label` is also used as the tooltip text by default.
4
+
5
+ ## Attributes
6
+
7
+ | Attribute | Type | Default | Allowed values | Description |
8
+ | -------------------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
9
+ | `variant` | string | `"primary"` | `"primary"` \| `"secondary"` \| `"tertiary"` \| `"brand"` \| `"destructive"` \| `"transparentOnLight"` \| `"transparentOnDark"` | Visual variant of the button. The two transparent variants are interim placeholders used internally by `ip-main-menu` and may change when the dedicated token family lands. |
10
+ | `size` | string | `"medium"` | `"small"` \| `"medium"` \| `"large"` | Controls the size of the button. Affects padding and icon size. |
11
+ | `disabled` | boolean | `false` | — | Disables the button. Also set implicitly when `loading` is `true`. |
12
+ | `loading` | boolean | `false` | — | Replaces the icon with a spinner and disables interaction. Renders a screen-reader-only "Loading..." text. |
13
+ | `type` | string | `"button"` | `"button"` \| `"submit"` \| `"reset"` | Controls form behaviour. Defaults to `"button"` to prevent accidental form submission. |
14
+ | `label` | string | `""` | — | **Required.** Sets `aria-label` for screen readers and is used as the default tooltip text. When empty, the tooltip is implicitly hidden and a development warning is logged. |
15
+ | `disableTooltip` | boolean | `false` | — | Hides the built-in tooltip. Useful when the button is already described by surrounding context. |
16
+ | `tooltipSize` | string | — | `"small"` \| `"medium"` \| `"large"` | Overrides the tooltip size. When unset, the tooltip inherits the button's `size`. |
17
+ | `tooltipPosition` | string | — | `"top"` \| `"right"` \| `"bottom"` \| `"left"` | Position of the tooltip relative to the button. When unset, the tooltip uses its own default (`"top"`). |
18
+ | `tooltipVariant` | string | `"default"` | `"default"` \| `"onLight"` \| `"onDark"` | Color variant of the tooltip. `default` adapts to the active theme. Use `"onLight"`/`"onDark"` when the button sits on a surface whose color does not follow the global theme. |
19
+ | `tooltipHideOnClick` | boolean | `true` | — | When `false`, clicking the button does not dismiss the tooltip. Use for toggle buttons where the tooltip text updates on click (e.g. collapse/expand, pin/unpin). |
20
+ | `aria-expanded` | string | `null` | `"true"` \| `"false"` | Property name: `expanded`. Forwards `aria-expanded` to the inner `<button>` for disclosure patterns (e.g. menu toggles). When `null` (default) the attribute is not rendered. |
21
+
22
+ ## Slots
23
+
24
+ | Slot | Description |
25
+ | --------- | -------------------------------------------------------------------------------------------- |
26
+ | `icon` | The icon element to display. Hidden when `loading` is `true`. Typically an `<i>` or `<svg>`. |
27
+ | `tooltip` | Optional HTML override for the tooltip content. When omitted, the `label` value is used. |
28
+
29
+ ## Size tokens
30
+
31
+ | Size | Padding (all sides) | Icon size |
32
+ | -------- | ----------------------- | ------------------------------------ |
33
+ | `small` | `$ip_spacing_space_100` | `$ip_iconography_size_small` (18px) |
34
+ | `medium` | `$ip_spacing_space_125` | `$ip_iconography_size_medium` (20px) |
35
+ | `large` | `$ip_spacing_space_150` | `$ip_iconography_size_large` (22px) |
36
+
37
+ ## Color tokens
38
+
39
+ | Token | Description |
40
+ | --------------------------------------------------------------------- | ------------------------------------------------------ |
41
+ | `$ip_color_icon_button_background_primary_default` | Primary background, default state |
42
+ | `$ip_color_icon_button_background_primary_hover` | Primary background, hover state |
43
+ | `$ip_color_icon_button_background_primary_active` | Primary background, active state |
44
+ | `$ip_color_button_background_[variant]_default/hover/active/disabled` | Background for secondary, tertiary, brand, destructive |
45
+ | `$ip_color_button_icon_[variant]_default` | Icon color, default state |
46
+ | `$ip_color_button_icon_[variant]_disabled` | Icon color, disabled state |
47
+ | `$ip_color_button_focus_default` | Focus outline color |
48
+
49
+ ## Usage examples
50
+
51
+ ### HTML
52
+
53
+ ```html
54
+ <ip-icon-button label="Save" variant="primary">
55
+ <i slot="icon" class="fas fa-floppy-disk"></i>
56
+ </ip-icon-button>
57
+
58
+ <!-- With HTML tooltip override -->
59
+ <ip-icon-button label="Save" variant="primary">
60
+ <i slot="icon" class="fas fa-floppy-disk"></i>
61
+ <span slot="tooltip">Save <strong>document</strong></span>
62
+ </ip-icon-button>
63
+ ```
64
+
65
+ ### React
66
+
67
+ ```tsx
68
+ import { IpIconButton } from '@norconsult-digital-public/isypixel/web-components/react';
69
+
70
+ <IpIconButton label='Save' variant='primary'>
71
+ <i slot='icon' className='fas fa-floppy-disk' />
72
+ </IpIconButton>;
73
+ ```
74
+
75
+ ### Vue
76
+
77
+ ```html
78
+ <IpIconButton label="Save" variant="primary">
79
+ <i slot="icon" class="fas fa-floppy-disk" />
80
+ </IpIconButton>
81
+ ```
82
+
83
+ ### Angular
84
+
85
+ ```ts
86
+ import { IpIconButton } from '@norconsult-digital-public/isypixel/web-components/angular';
87
+ ```
88
+
89
+ ```html
90
+ <ip-icon-button label="Save" variant="primary">
91
+ <i slot="icon" class="fas fa-floppy-disk"></i>
92
+ </ip-icon-button>
93
+ ```
94
+
95
+ ### Svelte
96
+
97
+ ```html
98
+ <script>
99
+ import '@norconsult-digital-public/isypixel/web-components/ip-icon-button.js';
100
+ </script>
101
+
102
+ <ip-icon-button label="Save" variant="primary">
103
+ <i slot="icon" class="fas fa-floppy-disk"></i>
104
+ </ip-icon-button>
105
+ ```
106
+
107
+ ### Blazor
108
+
109
+ ```html
110
+ <ip-icon-button label="Save" variant="primary">
111
+ <i slot="icon" class="fas fa-floppy-disk"></i>
112
+ </ip-icon-button>
113
+ ```
@@ -0,0 +1,219 @@
1
+ # ip-main-menu
2
+
3
+ Outer sidebar navigation wrapper for ISY applications. Manages three desktop display modes (expanded, auto-hide, collapsed) and a full-height mobile overlay drawer. Must be composed with `ip-main-menu-section`, `ip-main-menu-group`, and `ip-main-menu-item`.
4
+
5
+ > **Opening the drawer on mobile:** The mobile drawer is opened by the separate `ip-main-menu-toggle` component, placed in your top bar. The two communicate through a shared store — no wiring needed. See `ip-main-menu-toggle/llm.md`.
6
+
7
+ > **Layout requirement:** On desktop, the parent element must use `display: flex` or `display: grid` with a defined height, otherwise the sidebar will not fill the viewport. Example: `<div style="display:flex; height:100vh;"><ip-main-menu>...</ip-main-menu><main>...</main></div>`. At the mobile breakpoint the nav becomes a fixed overlay drawer and the host collapses to take no space in the layout.
8
+
9
+ > **Breakpoint:** Mobile drawer behaviour activates at or below the `mobileBreakpoint` prop (default `1024px`). The value is shared through the store so `ip-main-menu-toggle` switches at the exact same width with no extra wiring. Below 768px the drawer spans the full viewport width (a fixed phone-size breakpoint, not configurable). No breakpoint design token is available yet; these values are hardcoded.
10
+
11
+ > **Single-instance:** Use one `<ip-main-menu>` per page. The desktop mode and mobile drawer state are kept in a module-level store, so multiple instances on the same page will share state and stay in sync — there is no per-instance isolation.
12
+
13
+ ## Attributes
14
+
15
+ | Attribute | Type | Default | Allowed values | Description |
16
+ | ------------------ | ------------------------------------------ | ------------ | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
17
+ | `heading` | `string` | — | Any string | Required. App name shown in expanded mode. Used as `aria-label` on `<nav>` and as `aria-label` on the brand element. |
18
+ | `mode` | `'expanded' \| 'auto-hide' \| 'collapsed'` | `'expanded'` | `'expanded'`, `'auto-hide'`, `'collapsed'` | Desktop display mode. Sets initial state; internal state tracks user interactions. Not persisted across refreshes or browser sessions — persist via `ipModeChange` (see below). |
19
+ | `brandHref` | `string` | — | Any URL string | Optional. When set, the brand area (logo + heading) renders as an `<a>` linking to this URL. Without it, it renders as a `<button>` that emits `ipBrandClick`. |
20
+ | `expandDelay` | `number` | `300` | Any non-negative number (ms) | Hover-intent delay before `auto-hide` expands. Increase to ignore longer drift-overs, decrease for snappier expansion. Only applies in `auto-hide` mode. |
21
+ | `collapseDelay` | `number` | `300` | Any non-negative number (ms) | Grace period before `auto-hide` collapses after the mouse leaves. Increase to forgive accidental cursor slips, decrease for snappier collapse. Only applies in `auto-hide` mode. |
22
+ | `mobileBreakpoint` | `string` | `'1024px'` | Any CSS length (e.g. `1024px`, `64rem`) | Viewport width at or below which the menu becomes a mobile drawer and `ip-main-menu-toggle` appears. Shared through the store so both components switch at the same width. |
23
+
24
+ ### Desktop modes
25
+
26
+ | Mode | Width | Behaviour |
27
+ | ----------- | ----- | ----------------------------------------- |
28
+ | `expanded` | 230px | Full labels and icons visible. |
29
+ | `auto-hide` | 64px | Icons only. Expands temporarily on hover. |
30
+ | `collapsed` | 64px | Icons only. No hover expansion. |
31
+
32
+ Mode transitions:
33
+
34
+ - **expanded → auto-hide**: click pin button
35
+ - **expanded → collapsed**: click collapse button
36
+ - **collapsed → expanded**: click collapse button (rotated 180°)
37
+ - **auto-hide → expanded**: click pin/unpin button
38
+
39
+ > The collapse button is only shown in `expanded` and `collapsed` modes. The pin button is only shown in `expanded` and `auto-hide` modes.
40
+
41
+ ## Slots
42
+
43
+ | Slot | Description |
44
+ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
45
+ | `logo` | Custom logo shown in collapsed/auto-hide mode. Falls back to the built-in ISY logo if omitted. |
46
+ | `default` | Main navigation content (`ip-main-menu-section`, `ip-main-menu-group`, `ip-main-menu-item`). |
47
+ | `bottom` | Content pinned to the bottom of the sidebar (e.g. user profile, logout). |
48
+ | `icon-close` | Mobile drawer close icon. Default: Font Awesome Free `xmark` (solid). Replace by slotting your own. |
49
+ | `icon-pin` | Desktop pin button icon — shown when mode is `expanded`. Default: Font Awesome Free `thumbtack` (solid). Replace by slotting your own. |
50
+ | `icon-unpin` | Desktop pin button icon — shown when mode is `auto-hide`. Default: same `thumbtack` SVG as `icon-pin`, rotated 45° via CSS — identical size, no clipping. The intended design icon is FA Pro `thumbtack-angle` (sharp regular); inject it through this slot when a Pro license is available (slotted icons are not auto-rotated). |
51
+ | `icon-collapse` | Desktop collapse/expand button icon. Rotates 180° based on mode. Default: Font Awesome Free `chevron-left` (solid). Replace by slotting your own. |
52
+
53
+ > **Icon fallbacks:** Default SVGs are inlined from Font Awesome Free 7.2.0 (CC BY 4.0 — see LICENSE.md). Each fallback is a separate export in `icons.ts`, so any default a consumer overrides via the matching `icon-*` slot is tree-shaken from the bundle.
54
+
55
+ ## Injecting icons
56
+
57
+ ISY applications use Font Awesome per the design system, and the bundled fallbacks are already FA Free SVGs. Slot injection is therefore only needed in two cases:
58
+
59
+ **1. The unpin icon (recommended when an FA Pro license is available).** The intended design icon for `icon-unpin` is [`thumbtack-angle`](https://fontawesome.com/icons/sharp/regular/thumbtack-angle) (sharp regular), which is FA Pro and cannot be bundled for license reasons. The built-in fallback approximates it with the Free `thumbtack` rotated 45°. Inject the intended icon when the consuming application has a Pro license:
60
+
61
+ ```html
62
+ <ip-main-menu heading="My Application" brand-href="/">
63
+ <i slot="icon-unpin" class="fa-sharp fa-regular fa-thumbtack-angle"></i>
64
+ </ip-main-menu>
65
+ ```
66
+
67
+ **2. A different icon library.** If the application does not use Font Awesome, replace all four control icons:
68
+
69
+ ```html
70
+ <ip-main-menu heading="My Application" brand-href="/">
71
+ <span slot="icon-close">...</span>
72
+ <span slot="icon-pin">...</span>
73
+ <span slot="icon-unpin">...</span>
74
+ <span slot="icon-collapse">...</span>
75
+ </ip-main-menu>
76
+ ```
77
+
78
+ The same pattern applies in all frameworks: slot the element into the matching named slot.
79
+
80
+ ## Events
81
+
82
+ | Event | Payload | Description |
83
+ | -------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
84
+ | `ipModeChange` | `CustomEvent<'expanded' \| 'auto-hide' \| 'collapsed'>` | Fires when the user changes desktop mode. |
85
+ | `ipMobileOpenChange` | `CustomEvent<boolean>` | Fires when the mobile drawer opens or closes. Use this to lock body scroll. |
86
+ | `ipBrandClick` | `CustomEvent<void>` | Fires when the brand area is clicked and `brandHref` is not set. Use for SPA client-side navigation. |
87
+
88
+ ### Persisting mode across sessions
89
+
90
+ The mode lives in memory only; refreshing the page resets it to the `mode` prop. Persist it yourself until a dedicated menu state persistence service is added to Pixel:
91
+
92
+ ```javascript
93
+ const nav = document.querySelector('ip-main-menu');
94
+ nav.mode = localStorage.getItem('nav-mode') ?? 'expanded';
95
+ nav.addEventListener('ipModeChange', (e) => {
96
+ localStorage.setItem('nav-mode', e.detail);
97
+ });
98
+ ```
99
+
100
+ ## Design tokens
101
+
102
+ | Token | Role |
103
+ | ----------------------------------------------- | ----------------------------------------------------------------------------------- |
104
+ | `ip_color_menu_main_background_primary_default` | Sidebar background |
105
+ | `ip_color_button_focus_default` | Focus outline on control buttons (interim until a menu-specific focus token exists) |
106
+
107
+ > **Token gaps (flagged to design team):**
108
+ >
109
+ > - Text/icon colour tokens for sidebar controls (`ip_color_menu_main_*_text`), heading, and logo
110
+ > - Control button hover background and scrollbar colours (hardcoded with `//TODO:` comments)
111
+ > - Width tokens (230px expanded, 64px collapsed)
112
+ > - Mobile breakpoint token (drawer breakpoint defaults to 1024px via `mobileBreakpoint`; full-width phone breakpoint hardcoded to 768px)
113
+ > - Mobile backdrop overlay colour token
114
+
115
+ ## Usage examples
116
+
117
+ ### HTML
118
+
119
+ ```html
120
+ <!-- With brandHref — brand area renders as <a> -->
121
+ <div style="display:flex; height:100vh;">
122
+ <ip-main-menu heading="My Application" brand-href="/">
123
+ <ip-main-menu-section label="Main">
124
+ <ip-main-menu-item label="Dashboard" active>
125
+ <svg slot="icon" ...></svg>
126
+ </ip-main-menu-item>
127
+ </ip-main-menu-section>
128
+ <ip-main-menu-item label="Settings" slot="bottom">
129
+ <svg slot="icon" ...></svg>
130
+ </ip-main-menu-item>
131
+ </ip-main-menu>
132
+ <main>...</main>
133
+ </div>
134
+
135
+ <!-- Without brandHref — brand area renders as <button>, handle ipBrandClick for SPA navigation -->
136
+ <ip-main-menu heading="My Application" id="nav"></ip-main-menu>
137
+ <script>
138
+ document.getElementById('nav').addEventListener('ipBrandClick', () => router.navigate('/'));
139
+ </script>
140
+ ```
141
+
142
+ ### React
143
+
144
+ ```tsx
145
+ import { IpMainMenu, IpMainMenuSection, IpMainMenuItem } from '@norconsult-digital-public/isypixel/web-components/react';
146
+
147
+ // href-based navigation
148
+ <IpMainMenu heading='My Application' brandHref='/'>
149
+ <IpMainMenuSection label='Main'>
150
+ <IpMainMenuItem label='Dashboard' active />
151
+ </IpMainMenuSection>
152
+ </IpMainMenu>
153
+
154
+ // SPA / client-side navigation
155
+ <IpMainMenu heading='My Application' onIpBrandClick={() => navigate('/')}>
156
+ ...
157
+ </IpMainMenu>
158
+ ```
159
+
160
+ ### Vue
161
+
162
+ ```vue
163
+ <!-- href-based -->
164
+ <IpMainMenu heading="My Application" brand-href="/">
165
+ <IpMainMenuSection label="Main">
166
+ <IpMainMenuItem label="Dashboard" :active="true" />
167
+ </IpMainMenuSection>
168
+ </IpMainMenu>
169
+
170
+ <!-- SPA -->
171
+ <IpMainMenu heading="My Application" @ip-brand-click="router.push('/')">
172
+ ...
173
+ </IpMainMenu>
174
+ ```
175
+
176
+ ### Angular
177
+
178
+ ```html
179
+ <!-- href-based -->
180
+ <ip-main-menu heading="My Application" brandHref="/">
181
+ <ip-main-menu-section label="Main">
182
+ <ip-main-menu-item label="Dashboard" [active]="true"></ip-main-menu-item>
183
+ </ip-main-menu-section>
184
+ </ip-main-menu>
185
+
186
+ <!-- SPA -->
187
+ <ip-main-menu heading="My Application" (ipBrandClick)="router.navigate(['/'])"> </ip-main-menu>
188
+ ```
189
+
190
+ ### Svelte
191
+
192
+ ```svelte
193
+ <!-- href-based -->
194
+ <IpMainMenu heading="My Application" brandHref="/">
195
+ <IpMainMenuSection label="Main">
196
+ <IpMainMenuItem label="Dashboard" active />
197
+ </IpMainMenuSection>
198
+ </IpMainMenu>
199
+
200
+ <!-- SPA -->
201
+ <IpMainMenu heading="My Application" on:ipBrandClick={() => goto('/')}>
202
+ ...
203
+ </IpMainMenu>
204
+ ```
205
+
206
+ ### Blazor
207
+
208
+ ```razor
209
+ <!-- href-based -->
210
+ <ip-main-menu heading="My Application" brand-href="/">
211
+ <ip-main-menu-section label="Main">
212
+ <ip-main-menu-item label="Dashboard" active="true"></ip-main-menu-item>
213
+ </ip-main-menu-section>
214
+ </ip-main-menu>
215
+
216
+ <!-- SPA -->
217
+ <ip-main-menu heading="My Application" @onipBrandClick="NavigateHome">
218
+ </ip-main-menu>
219
+ ```
@@ -0,0 +1,114 @@
1
+ # ip-main-menu-group
2
+
3
+ Collapsible group of navigation items with a label, optional icon, and an animated chevron. The chevron rotates 180° on open/close. In collapsed mode the label and chevron are hidden; a tooltip appears on hover/focus.
4
+
5
+ > **Note:** This component must be used inside an `ip-main-menu`.
6
+
7
+ ## Attributes
8
+
9
+ | Attribute | Type | Default | Allowed values | Description |
10
+ | --------- | --------- | ------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | `label` | `string` | — | Any string | Required. Group label in expanded mode. Used as accessible name and tooltip in collapsed mode. |
12
+ | `active` | `boolean` | `false` | `true`, `false` | Whether any child item is active. Applies selected styling to the group trigger. |
13
+ | `open` | `boolean` | `false` | `true`, `false` | Initial expanded state. Internal state takes over after first interaction (uncontrolled pattern). Not persisted across refreshes or browser sessions — listen to `ipOpenChange` to persist. A menu state persistence service may be added to Pixel later. |
14
+
15
+ ## Slots
16
+
17
+ | Slot | Description |
18
+ | -------------- | ------------------------------------------------------------------------------------- |
19
+ | `default` | `ip-main-menu-item` children. |
20
+ | `icon` | Icon displayed to the left of the label. |
21
+ | `icon-chevron` | Chevron icon. Default: inline SVG arrow. Rotates 180° via CSS transition when `open`. |
22
+
23
+ > **Custom chevron:** Slot `icon-chevron` to replace the built-in arrow, e.g. `<i slot="icon-chevron" class="fa-solid fa-chevron-down"></i>`. The slotted icon rotates 180° on open/close just like the default.
24
+
25
+ ## Events
26
+
27
+ | Event | Payload | Description |
28
+ | -------------- | ---------------------- | ----------------------------------------------------- |
29
+ | `ipOpenChange` | `CustomEvent<boolean>` | Fires when the user toggles the group open or closed. |
30
+
31
+ ## Design tokens
32
+
33
+ | Token | Role |
34
+ | ------------------------------------------------------------------- | ----------------------------------------------------------------------- |
35
+ | `ip_color_menu_main_item_background_primary_default` | Group trigger background (default) |
36
+ | `ip_color_menu_main_group_item_background_primary_default` | Group trigger background (open) and child item background |
37
+ | `ip_color_menu_main_group_item_background_primary_hover` | Group trigger background (hover) |
38
+ | `ip_color_menu_main_group_item_background_primary_active` | Group trigger background (pressed) |
39
+ | `ip_color_menu_main_group_item_background_primary_selected_default` | Group trigger background when `active` |
40
+ | `ip_color_menu_main_group_item_background_primary_selected_hover` | Active group trigger background (hover) |
41
+ | `ip_color_menu_main_group_item_background_primary_selected_active` | Active group trigger background (pressed) |
42
+ | `ip_color_menu_main_item_vertical_divider_primary_default` | Left-border accent on active group trigger |
43
+ | `ip_color_button_focus_default` | Focus outline colour (interim until a menu-specific focus token exists) |
44
+
45
+ > **Token gaps:** Text/icon colour tokens for group items are not yet available. Hardcoded values are used with `// TODO` comments.
46
+
47
+ ## Usage examples
48
+
49
+ ### HTML
50
+
51
+ ```html
52
+ <ip-main-menu heading="My App">
53
+ <ip-main-menu-group label="Reports">
54
+ <svg slot="icon" ...></svg>
55
+ <ip-main-menu-item label="Monthly"></ip-main-menu-item>
56
+ <ip-main-menu-item label="Yearly"></ip-main-menu-item>
57
+ </ip-main-menu-group>
58
+ </ip-main-menu>
59
+ ```
60
+
61
+ ### React
62
+
63
+ ```tsx
64
+ <IpMainMenu heading='My App'>
65
+ <IpMainMenuGroup label='Reports' onIpOpenChange={(e) => console.log(e.detail)}>
66
+ <svg slot='icon' />
67
+ <IpMainMenuItem label='Monthly' />
68
+ </IpMainMenuGroup>
69
+ </IpMainMenu>
70
+ ```
71
+
72
+ ### Vue
73
+
74
+ ```vue
75
+ <IpMainMenu heading="My App">
76
+ <IpMainMenuGroup label="Reports" @ip-open-change="handleChange">
77
+ <svg slot="icon" />
78
+ <IpMainMenuItem label="Monthly" />
79
+ </IpMainMenuGroup>
80
+ </IpMainMenu>
81
+ ```
82
+
83
+ ### Angular
84
+
85
+ ```html
86
+ <ip-main-menu heading="My App">
87
+ <ip-main-menu-group label="Reports" (ipOpenChange)="handleChange($event)">
88
+ <svg slot="icon"></svg>
89
+ <ip-main-menu-item label="Monthly"></ip-main-menu-item>
90
+ </ip-main-menu-group>
91
+ </ip-main-menu>
92
+ ```
93
+
94
+ ### Svelte
95
+
96
+ ```svelte
97
+ <IpMainMenu heading="My App">
98
+ <IpMainMenuGroup label="Reports" on:ipOpenChange={handleChange}>
99
+ <svg slot="icon" />
100
+ <IpMainMenuItem label="Monthly" />
101
+ </IpMainMenuGroup>
102
+ </IpMainMenu>
103
+ ```
104
+
105
+ ### Blazor
106
+
107
+ ```razor
108
+ <ip-main-menu heading="My App">
109
+ <ip-main-menu-group label="Reports">
110
+ <svg slot="icon"></svg>
111
+ <ip-main-menu-item label="Monthly"></ip-main-menu-item>
112
+ </ip-main-menu-group>
113
+ </ip-main-menu>
114
+ ```