@marianmeres/stuic 3.186.0 → 3.187.0

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.
package/API.md CHANGED
@@ -153,38 +153,38 @@ Navigation wrapper component.
153
153
 
154
154
  Responsive navigation header with leading slot, logo, nav items, locale switcher, action icon buttons, avatar, and configurable responsive collapse (`"hamburger"` fold or `"hide"` for app-like shells). Renders as `<header>`.
155
155
 
156
- | Prop | Type | Default | Description |
157
- | ----------------------- | ------------------------------------------------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
158
- | `leading` | `Snippet<[{ isCollapsed }]>` | — | Leading (left-side) slot. Overrides `leadingHamburger`. |
159
- | `leadingHamburger` | `boolean \| "collapsed"` | `false` | Built-in hamburger in the leading slot (`"collapsed"` = only below threshold). Ignored when `leading` is provided. |
160
- | `onLeadingHamburger` | `() => void` | — | Click handler for the built-in leading hamburger (typically opens a drawer). |
161
- | `leadingHamburgerIcon` | `THC` | menu icon | Icon override for the built-in leading hamburger. |
162
- | `leadingHamburgerLabel` | `string` | `"Open menu"` | Aria-label for the leading hamburger. |
163
- | `logo` | `Snippet` | — | Logo/brand snippet. |
164
- | `projectName` | `string` | — | Simple text logo alternative. |
165
- | `navVariant` | `ButtonVariant` | `"ghost"` | Button variant for nav items and the locale switcher trigger. |
166
- | `items` | `HeaderNavItem[]` | `[]` | Navigation items — inline when expanded, dropdown when collapsed (hamburger mode). |
167
- | `actions` | `HeaderActionItem[]` | `[]` | Action icon buttons between the locale switcher and the avatar. Always visible — never fold into the dropdown. |
168
- | `onActionSelect` | `(action) => void` | — | Called after the per-item `onclick`. |
169
- | `avatar` | `Snippet` | — | Avatar snippet (far right). |
170
- | `avatarOnClick` | `() => void` | — | Makes the avatar interactive. In `"hamburger"` collapse mode it moves into the dropdown. |
171
- | `avatarLabel` | `THC` | `"Account"` | Label for the avatar entry inside the collapsed dropdown. |
172
- | `locales` | `HeaderLocaleItem[]` | `[]` | Locale items. Switcher only renders when 2+. |
173
- | `activeLocale` | `string` | — | Current locale id. |
174
- | `onLocaleChange` | `(localeId) => void` | — | Locale selection callback. |
175
- | `localeLabel` | `THC` | `"Language"` | Section header inside the collapsed dropdown. |
176
- | `contentMaxWidth` | `string \| number` | — | Max-width of the inner content row (outer header stays 100%). Accepts any CSS length. Maps to `--stuic-header-content-max-width`. |
177
- | `collapseThreshold` | `number` | `768` | Width (px) to collapse; 0 disables. |
178
- | `collapseMode` | `"hamburger" \| "hide"` | `"hamburger"` | Collapse behavior. `"hide"` keeps avatar/actions visible and renders no trailing hamburger (app-shell pattern). |
179
- | `keepLocaleOnCollapse` | `boolean` | `false` | Keep the locale switcher visible in collapsed mode (only `collapseMode === "hide"`). |
180
- | `fixed` | `boolean` | `false` | Fixed positioning at the top. |
181
- | `safeArea` | `boolean` | `false` | PWA: when installed/standalone, offset the **top app bar** below the device safe-area insets (top + side notch). No-op in a browser tab. Set only on the top bar — not in-page/detail/drawer headers. See Header README. |
182
- | `isCollapsed` | `boolean` | — | Bindable: collapsed state. |
183
- | `isMenuOpen` | `boolean` | — | Bindable: hamburger menu open. |
184
- | `dropdownPosition` | `DropdownMenuPosition` | `"bottom-span-right"` | Position of the collapsed dropdown. |
185
- | `iconSize` | `number` | `24` | Hamburger/X icon size in px. |
186
- | `onSelect` | `(item) => void` | — | Item selection callback (both modes). |
187
- | `children` | `Snippet<[{ isCollapsed, items, offsetWidth }]>` | — | Escape hatch: override the entire inner layout. |
156
+ | Prop | Type | Default | Description |
157
+ | ----------------------- | ------------------------------------------------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
158
+ | `leading` | `Snippet<[{ isCollapsed }]>` | — | Leading (left-side) slot. Overrides `leadingHamburger`. |
159
+ | `leadingHamburger` | `boolean \| "collapsed"` | `false` | Built-in hamburger in the leading slot (`"collapsed"` = only below threshold). Ignored when `leading` is provided. |
160
+ | `onLeadingHamburger` | `() => void` | — | Click handler for the built-in leading hamburger (typically opens a drawer). |
161
+ | `leadingHamburgerIcon` | `THC` | menu icon | Icon override for the built-in leading hamburger. |
162
+ | `leadingHamburgerLabel` | `string` | `"Open menu"` | Aria-label for the leading hamburger. |
163
+ | `logo` | `Snippet` | — | Logo/brand snippet. |
164
+ | `projectName` | `string` | — | Simple text logo alternative. |
165
+ | `navVariant` | `ButtonVariant` | `"ghost"` | Button variant for nav items and the locale switcher trigger. |
166
+ | `items` | `HeaderNavItem[]` | `[]` | Navigation items — inline when expanded, dropdown when collapsed (hamburger mode). |
167
+ | `actions` | `HeaderActionItem[]` | `[]` | Action icon buttons between the locale switcher and the avatar. Always visible — never fold into the dropdown. |
168
+ | `onActionSelect` | `(action) => void` | — | Called after the per-item `onclick`. |
169
+ | `avatar` | `Snippet` | — | Avatar snippet (far right). |
170
+ | `avatarOnClick` | `() => void` | — | Makes the avatar interactive. In `"hamburger"` collapse mode it moves into the dropdown. |
171
+ | `avatarLabel` | `THC` | `"Account"` | Label for the avatar entry inside the collapsed dropdown. |
172
+ | `locales` | `HeaderLocaleItem[]` | `[]` | Locale items. Switcher only renders when 2+. |
173
+ | `activeLocale` | `string` | — | Current locale id. |
174
+ | `onLocaleChange` | `(localeId) => void` | — | Locale selection callback. |
175
+ | `localeLabel` | `THC` | `"Language"` | Section header inside the collapsed dropdown. |
176
+ | `contentMaxWidth` | `string \| number` | — | Max-width of the inner content row (outer header stays 100%). Accepts any CSS length. Maps to `--stuic-header-content-max-width`. |
177
+ | `collapseThreshold` | `number` | `768` | Width (px) to collapse; 0 disables. |
178
+ | `collapseMode` | `"hamburger" \| "hide"` | `"hamburger"` | Collapse behavior. `"hide"` keeps avatar/actions visible and renders no trailing hamburger (app-shell pattern). |
179
+ | `keepLocaleOnCollapse` | `boolean` | `true` | Keep the locale switcher inline (next to the hamburger) when collapsed — in **both** collapse modes, and without duplicating it inside the dropdown. `false` folds it into the dropdown (`"hamburger"`) or hides it (`"hide"`). |
180
+ | `fixed` | `boolean` | `false` | Fixed positioning at the top. |
181
+ | `safeArea` | `boolean` | `false` | PWA: when installed/standalone, offset the **top app bar** below the device safe-area insets (top + side notch). No-op in a browser tab. Set only on the top bar — not in-page/detail/drawer headers. See Header README. |
182
+ | `isCollapsed` | `boolean` | — | Bindable: collapsed state. |
183
+ | `isMenuOpen` | `boolean` | — | Bindable: hamburger menu open. |
184
+ | `dropdownPosition` | `DropdownMenuPosition` | `"bottom-span-right"` | Position of the collapsed dropdown. |
185
+ | `iconSize` | `number` | `24` | Hamburger/X icon size in px. |
186
+ | `onSelect` | `(item) => void` | — | Item selection callback (both modes). |
187
+ | `children` | `Snippet<[{ isCollapsed, items, offsetWidth }]>` | — | Escape hatch: override the entire inner layout. |
188
188
 
189
189
  Class slots: `class`, `classContent`, `classLeading`, `classLeadingHamburger`, `classLogo`, `classNav`, `classNavItem`, `classNavItemActive`, `classActions`, `classAction`, `classActionActive`, `classEnd`, `classAvatar`, `classLocale`, `classHamburger`, `classDropdown`.
190
190
 
@@ -223,9 +223,9 @@ Class slots: `class`, `classContent`, `classLeading`, `classLeadingHamburger`, `
223
223
 
224
224
  **HeaderActionItem:** `{ id, icon?, label, onclick?, href?, target?, active?, disabled?, class?, render? }` — `render` is an optional `Snippet<[{ action, class, isCollapsed, onclick }]>` that replaces the default `<Button>` for that action (useful for wrapping in a popover/tooltip directive or adding a count badge while keeping default positioning).
225
225
 
226
- **HeaderLocaleItem:** `{ id, label }`
226
+ **HeaderLocaleItem:** `{ id, label, shortLabel? }` — `shortLabel` replaces `label` on the inline trigger **in collapsed mode only** (keeps a long name like "Slovenčina" from crowding a narrow header); the dropdown list always shows the full `label`.
227
227
 
228
- CSS tokens: `--stuic-header-padding-x`, `--stuic-header-padding-y`, `--stuic-header-gap`, `--stuic-header-min-height`, `--stuic-header-nav-gap`, `--stuic-header-content-max-width`, `--stuic-header-bg`, `--stuic-header-text`, `--stuic-header-border-width`, `--stuic-header-border-color`, `--stuic-header-nav-item-bg-active`, `--stuic-header-nav-item-text-active`, `--stuic-header-z-index`.
228
+ CSS tokens: `--stuic-header-padding-x`, `--stuic-header-padding-y`, `--stuic-header-gap`, `--stuic-header-end-gap-collapsed`, `--stuic-header-min-height`, `--stuic-header-nav-gap`, `--stuic-header-content-max-width`, `--stuic-header-bg`, `--stuic-header-text`, `--stuic-header-border-width`, `--stuic-header-border-color`, `--stuic-header-nav-item-bg-active`, `--stuic-header-nav-item-text-active`, `--stuic-header-z-index`.
229
229
 
230
230
  ---
231
231
 
@@ -35,6 +35,12 @@
35
35
  id: string;
36
36
  /** Display label — supports THC (string, html, component, snippet) */
37
37
  label: THC;
38
+ /** Compact label for the inline trigger in COLLAPSED mode only.
39
+ * Falls back to `label`. Use it when the full label ("Slovenčina")
40
+ * would crowd the narrow header next to the hamburger — the dropdown
41
+ * list always shows the full `label`, so the long form stays
42
+ * available where there is room to read it. */
43
+ shortLabel?: THC;
38
44
  }
39
45
 
40
46
  export interface HeaderActionItem {
@@ -86,11 +92,13 @@
86
92
  }
87
93
 
88
94
  /** Collapse behavior when the header drops below `collapseThreshold`:
89
- * - "hamburger": nav items fold into a trailing dropdown along with the
90
- * locale switcher and an interactive avatar.
95
+ * - "hamburger": nav items fold into a trailing dropdown along with an
96
+ * interactive avatar.
91
97
  * - "hide": nav items are hidden entirely. No trailing hamburger renders.
92
- * Avatar stays visible. Locale visibility is controlled by
93
- * `keepLocaleOnCollapse`. */
98
+ * Avatar stays visible.
99
+ *
100
+ * In BOTH modes the locale switcher stays inline next to the hamburger
101
+ * by default — see `keepLocaleOnCollapse`. */
94
102
  export type HeaderCollapseMode = "hamburger" | "hide";
95
103
 
96
104
  /** Visibility for the built-in leading hamburger button:
@@ -152,9 +160,27 @@
152
160
  collapseThreshold?: number;
153
161
  /** Collapse behavior when below threshold (defaults to "hamburger") */
154
162
  collapseMode?: HeaderCollapseMode;
155
- /** When `collapseMode === "hide"`, keep the locale switcher visible in
156
- * collapsed mode. No effect when `collapseMode === "hamburger"`
157
- * (locale already folds into the trailing dropdown there). */
163
+ /** Keep the locale switcher visible — inline, next to the hamburger —
164
+ * when the header is collapsed. Applies to BOTH collapse modes.
165
+ * Defaults to `true`.
166
+ *
167
+ * Why it defaults on: the person who most needs this control is the
168
+ * one who landed in a language they cannot read. For them a visible
169
+ * "EN ▾" trigger is the only self-describing widget in the bar, while
170
+ * a locale entry folded into the hamburger — listed last, under a
171
+ * "Language" heading they also cannot read, often below the fold of a
172
+ * long menu — is effectively invisible.
173
+ *
174
+ * When `true` in `"hamburger"` mode the locale section is NOT also
175
+ * appended to the trailing dropdown, so it is never reachable twice.
176
+ *
177
+ * Set `false` for the alternative: folds into the trailing dropdown
178
+ * in `"hamburger"` mode, hidden entirely in `"hide"` mode (this was
179
+ * the behavior before the prop applied to both modes). Worth doing
180
+ * when the end area is already crowded with actions, or when the
181
+ * app's own drawer owns the language switch. See also
182
+ * `HeaderLocaleItem.shortLabel` for keeping the inline trigger
183
+ * narrow. */
158
184
  keepLocaleOnCollapse?: boolean;
159
185
  /** Fixed positioning (top of viewport) */
160
186
  fixed?: boolean;
@@ -263,7 +289,7 @@
263
289
  contentMaxWidth,
264
290
  collapseThreshold = 768,
265
291
  collapseMode = "hamburger",
266
- keepLocaleOnCollapse = false,
292
+ keepLocaleOnCollapse = true,
267
293
  fixed = false,
268
294
  safeArea = false,
269
295
  isCollapsed = $bindable(false),
@@ -336,17 +362,28 @@
336
362
  // Locale switcher: only render when 2+ locales
337
363
  let _hasLocales = $derived(locales.length > 1);
338
364
 
339
- // Visibility of the inline (expanded-form) locale switcher.
340
- // In "hamburger" mode: visible only when not collapsed (it folds into the
341
- // trailing dropdown when collapsed). In "hide" mode: visible when not
342
- // collapsed, or when collapsed and `keepLocaleOnCollapse` is set.
365
+ // Visibility of the inline (expanded-form) locale switcher. Always visible
366
+ // when expanded; when collapsed it stays inline unless explicitly opted out
367
+ // — in BOTH collapse modes. Deliberately mode-agnostic: a user who cannot
368
+ // read the current language must be able to find the switcher without
369
+ // opening a menu whose trigger tells them nothing.
343
370
  let _showLocaleSwitcher = $derived(
344
- _hasLocales && (!_isCollapsed || (collapseMode === "hide" && keepLocaleOnCollapse))
371
+ _hasLocales && (!_isCollapsed || keepLocaleOnCollapse)
345
372
  );
346
373
 
347
374
  // Active locale object (for trigger label); fallback to first
348
375
  let _activeLocale = $derived(locales.find((l) => l.id === activeLocale) ?? locales[0]);
349
376
 
377
+ // Inline trigger label. `shortLabel` (when provided) is used ONLY in
378
+ // collapsed mode, where horizontal space next to the hamburger is scarce;
379
+ // expanded mode and the dropdown list always show the full `label`.
380
+ let _localeTriggerLabel = $derived.by((): THC | undefined => {
381
+ if (!_activeLocale) return undefined;
382
+ return _isCollapsed
383
+ ? (_activeLocale.shortLabel ?? _activeLocale.label)
384
+ : _activeLocale.label;
385
+ });
386
+
350
387
  // Locale items for the expanded-mode DropdownMenu
351
388
  let _localeDropdownItems = $derived.by((): DropdownMenuItem[] => {
352
389
  return locales.map(
@@ -389,8 +426,10 @@
389
426
  }) satisfies DropdownMenuActionItem
390
427
  );
391
428
 
392
- // Append locale section when locales are available
393
- if (_hasLocales) {
429
+ // Append the locale section ONLY when the inline switcher is hidden.
430
+ // With the inline trigger visible this would be a second, worse route
431
+ // to the same setting — and the one nobody finds.
432
+ if (_hasLocales && !_showLocaleSwitcher) {
394
433
  if (navItems.length > 0) {
395
434
  navItems.push({ type: "divider" });
396
435
  }
@@ -556,7 +595,9 @@
556
595
 
557
596
  <!-- End area: locale + actions + avatar + trailing hamburger -->
558
597
  <div class={_classEnd}>
559
- <!-- Locale switcher (shown when expanded, or in "hide" mode with keepLocaleOnCollapse) -->
598
+ <!-- Locale switcher — inline when expanded and, unless
599
+ `keepLocaleOnCollapse={false}`, still inline when collapsed
600
+ (both collapse modes) rather than folded into the dropdown -->
560
601
  {#if _showLocaleSwitcher}
561
602
  <DropdownMenu
562
603
  items={_localeDropdownItems}
@@ -573,8 +614,8 @@
573
614
  aria-label="Change language"
574
615
  {...triggerProps}
575
616
  >
576
- {#if _activeLocale}
577
- <Thc thc={_activeLocale.label} />
617
+ {#if _localeTriggerLabel !== undefined}
618
+ <Thc thc={_localeTriggerLabel} />
578
619
  {/if}
579
620
  <span
580
621
  class={twMerge(
@@ -28,6 +28,12 @@ export interface HeaderLocaleItem {
28
28
  id: string;
29
29
  /** Display label — supports THC (string, html, component, snippet) */
30
30
  label: THC;
31
+ /** Compact label for the inline trigger in COLLAPSED mode only.
32
+ * Falls back to `label`. Use it when the full label ("Slovenčina")
33
+ * would crowd the narrow header next to the hamburger — the dropdown
34
+ * list always shows the full `label`, so the long form stays
35
+ * available where there is room to read it. */
36
+ shortLabel?: THC;
31
37
  }
32
38
  export interface HeaderActionItem {
33
39
  /** Unique identifier */
@@ -75,11 +81,13 @@ export interface HeaderActionItem {
75
81
  ]>;
76
82
  }
77
83
  /** Collapse behavior when the header drops below `collapseThreshold`:
78
- * - "hamburger": nav items fold into a trailing dropdown along with the
79
- * locale switcher and an interactive avatar.
84
+ * - "hamburger": nav items fold into a trailing dropdown along with an
85
+ * interactive avatar.
80
86
  * - "hide": nav items are hidden entirely. No trailing hamburger renders.
81
- * Avatar stays visible. Locale visibility is controlled by
82
- * `keepLocaleOnCollapse`. */
87
+ * Avatar stays visible.
88
+ *
89
+ * In BOTH modes the locale switcher stays inline next to the hamburger
90
+ * by default — see `keepLocaleOnCollapse`. */
83
91
  export type HeaderCollapseMode = "hamburger" | "hide";
84
92
  /** Visibility for the built-in leading hamburger button:
85
93
  * - false/undefined: not rendered
@@ -141,9 +149,27 @@ export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
141
149
  collapseThreshold?: number;
142
150
  /** Collapse behavior when below threshold (defaults to "hamburger") */
143
151
  collapseMode?: HeaderCollapseMode;
144
- /** When `collapseMode === "hide"`, keep the locale switcher visible in
145
- * collapsed mode. No effect when `collapseMode === "hamburger"`
146
- * (locale already folds into the trailing dropdown there). */
152
+ /** Keep the locale switcher visible — inline, next to the hamburger —
153
+ * when the header is collapsed. Applies to BOTH collapse modes.
154
+ * Defaults to `true`.
155
+ *
156
+ * Why it defaults on: the person who most needs this control is the
157
+ * one who landed in a language they cannot read. For them a visible
158
+ * "EN ▾" trigger is the only self-describing widget in the bar, while
159
+ * a locale entry folded into the hamburger — listed last, under a
160
+ * "Language" heading they also cannot read, often below the fold of a
161
+ * long menu — is effectively invisible.
162
+ *
163
+ * When `true` in `"hamburger"` mode the locale section is NOT also
164
+ * appended to the trailing dropdown, so it is never reachable twice.
165
+ *
166
+ * Set `false` for the alternative: folds into the trailing dropdown
167
+ * in `"hamburger"` mode, hidden entirely in `"hide"` mode (this was
168
+ * the behavior before the prop applied to both modes). Worth doing
169
+ * when the end area is already crowded with actions, or when the
170
+ * app's own drawer owns the language switch. See also
171
+ * `HeaderLocaleItem.shortLabel` for keeping the inline trigger
172
+ * narrow. */
147
173
  keepLocaleOnCollapse?: boolean;
148
174
  /** Fixed positioning (top of viewport) */
149
175
  fixed?: boolean;
@@ -4,17 +4,54 @@ Top-bar component with leading slot, project logo, nav items, locale switcher, o
4
4
 
5
5
  ## Examples
6
6
 
7
+ ### The locale switcher stays visible when collapsed
8
+
9
+ When the header collapses, the locale switcher does **not** fold into the trailing hamburger. It stays inline, immediately before the actions / avatar / hamburger, in **both** collapse modes. Nothing to configure — it is the default.
10
+
11
+ ```svelte
12
+ <Header
13
+ projectName="App"
14
+ items={navItems}
15
+ {locales}
16
+ {activeLocale}
17
+ onLocaleChange={(id) => (activeLocale = id)}
18
+ />
19
+ <!-- collapsed: [App] [EN ▾] [☰] -->
20
+ ```
21
+
22
+ This is the one control `Header` deliberately treats differently from a nav item, and the reason is worth stating because it looks like an inconsistency:
23
+
24
+ > The person who most needs the language switch is the person who landed in a language they cannot read. A visible `EN ▾` trigger is self-describing to them — it shows the current language and it is obviously a control. An entry inside the hamburger is not: the trigger is an unlabeled icon, the section heading says "Language" in a language they do not speak, and past a handful of nav items it sits below the fold of a menu they have to scroll with a finger. It is reliably never found.
25
+
26
+ What follows from it:
27
+
28
+ | Behavior | Why |
29
+ | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
30
+ | The trailing dropdown has **no** locale section while the inline trigger is visible. | Two routes to one setting, one of which nobody finds. `_dropdownItems` appends the locale section only when the inline switcher is hidden. |
31
+ | A header whose only collapsible content was the locale switcher renders **no hamburger at all**. | `_dropdownItems` comes back empty, and the trailing `DropdownMenu` is gated on it being non-empty. |
32
+ | The inline trigger shows `HeaderLocaleItem.shortLabel` (when set) **only** while collapsed. | `"Slovenčina"` next to a hamburger on a 360px phone is what pushed the switcher into the menu in the first place. The dropdown list always shows the full `label`. |
33
+ | `keepLocaleOnCollapse={false}` restores the alternative: folds into the dropdown in `"hamburger"` mode, hidden entirely in `"hide"` mode. | For an end area already crowded with actions, or an app whose own drawer owns the language switch. |
34
+
35
+ ```ts
36
+ // Long locale names on a narrow header: short form on the inline trigger,
37
+ // full form in the dropdown list.
38
+ const locales: HeaderLocaleItem[] = [
39
+ { id: "en", label: "English", shortLabel: "EN" },
40
+ { id: "sk", label: "Slovenčina", shortLabel: "SK" },
41
+ ];
42
+ ```
43
+
7
44
  ### App-like collapse: avatar + actions visible, everything else hidden
8
45
 
9
- Common "app shell" pattern: when the header collapses below `collapseThreshold`, the avatar and a few key actions (search, notifications, cart…) remain visible, the trailing hamburger is NOT shown, and the nav items + locale switcher are hidden entirely (the nav typically lives in a drawer triggered by the leading hamburger instead).
46
+ Common "app shell" pattern: when the header collapses below `collapseThreshold`, the avatar, the locale switcher and a few key actions (search, notifications, cart…) remain visible, the trailing hamburger is NOT shown, and the nav items are hidden entirely (the nav typically lives in a drawer triggered by the leading hamburger instead).
10
47
 
11
- | Requirement | Where it's handled | How |
12
- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
13
- | **Avatar stays visible** | [Header.svelte:626](./Header.svelte#L626) — `{#if avatar && !(_isCollapsed && _avatarInDropdown)}` | `_avatarInDropdown` requires `collapseMode === "hamburger"`. In `"hide"` mode it's always `false`, so the avatar always renders. |
14
- | **Action buttons stay visible** | [Header.svelte:585](./Header.svelte#L585) — `<!-- Actions (icon buttons, always visible) -->` | The actions loop has no collapse gating — items render in both modes. |
15
- | **No trailing hamburger** | [Header.svelte:642](./Header.svelte#L642) — `{#if _isCollapsed && _dropdownItems.length > 0}` | In `"hide"` mode, `_dropdownItems` short-circuits to `[]`, so the `{#if}` is false → no trailing hamburger. |
16
- | **Nav items hidden** | [Header.svelte:516](./Header.svelte#L516) — `{#if !_isCollapsed && items.length > 0}` | Inline nav requires `!_isCollapsed`; combined with the empty `_dropdownItems` above, items don't reappear in a dropdown either. |
17
- | **Locale hidden** | [Header.svelte:335](./Header.svelte#L335) — `!_isCollapsed \|\| (collapseMode === "hide" && keepLocaleOnCollapse)` | Default `keepLocaleOnCollapse={false}` hides the locale switcher in collapsed mode. |
48
+ | Requirement | Where it's handled | How |
49
+ | ------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
50
+ | **Avatar stays visible** | [Header.svelte:673](./Header.svelte#L673) — `{#if avatar && !(_isCollapsed && _avatarInDropdown)}` | `_avatarInDropdown` requires `collapseMode === "hamburger"`. In `"hide"` mode it's always `false`, so the avatar always renders. |
51
+ | **Action buttons stay visible** | [Header.svelte:632](./Header.svelte#L632) — `<!-- Actions (icon buttons, always visible) -->` | The actions loop has no collapse gating — items render in both modes. |
52
+ | **No trailing hamburger** | [Header.svelte:689](./Header.svelte#L689) — `{#if _isCollapsed && _dropdownItems.length > 0}` | In `"hide"` mode, `_dropdownItems` short-circuits to `[]`, so the `{#if}` is false → no trailing hamburger. |
53
+ | **Nav items hidden** | [Header.svelte:564](./Header.svelte#L564) — `{#if !_isCollapsed && items.length > 0}` | Inline nav requires `!_isCollapsed`; combined with the empty `_dropdownItems` above, items don't reappear in a dropdown either. |
54
+ | **Locale stays visible** | [Header.svelte:369](./Header.svelte#L369) — `!_isCollapsed \|\| keepLocaleOnCollapse` | `keepLocaleOnCollapse` defaults to `true` in both modes (see the section above). Pass `false` to hide it, e.g. when the drawer owns the language switch. |
18
55
 
19
56
  Minimal config:
20
57
 
@@ -26,7 +63,7 @@ Minimal config:
26
63
  collapseMode="hide" <!-- no trailing hamburger; avatar stays -->
27
64
  leadingHamburger <!-- optional: drives a drawer for the hidden nav -->
28
65
  onLeadingHamburger={() => (drawerOpen = true)}
29
- {locales} {activeLocale} <!-- hidden in collapsed (keepLocaleOnCollapse defaults to false) -->
66
+ {locales} {activeLocale} <!-- stays visible in collapsed (keepLocaleOnCollapse defaults to true) -->
30
67
  onLocaleChange={(id) => (activeLocale = id)}
31
68
  avatarOnClick={() => alert("Profile")} <!-- safe in "hide" mode — won't move into dropdown -->
32
69
  >
@@ -21,6 +21,14 @@
21
21
  /* Actions (icon buttons in the end region) */
22
22
  --stuic-header-actions-gap: 0.25rem;
23
23
 
24
+ /* Gap between the end-area regions (locale / actions / avatar / hamburger)
25
+ while collapsed. Expanded uses --stuic-header-gap; collapsed is tighter
26
+ because the locale switcher stays inline there instead of folding into
27
+ the hamburger, and at 320px the full gap pushes a locale + 3 actions +
28
+ hamburger row past the content edge. Set to var(--stuic-header-gap) to
29
+ keep both states equal. */
30
+ --stuic-header-end-gap-collapsed: 0.5rem;
31
+
24
32
  /* Hamburger offsets — outdent the built-in hamburger buttons on both
25
33
  inline sides so the *icon* (not the invisible iconButton hit area)
26
34
  sits flush with the header's content padding on one side and tightens
@@ -126,6 +134,14 @@
126
134
  font-size: var(--stuic-header-project-name-font-size, var(--text-lg, 1.125rem));
127
135
  font-weight: var(--stuic-header-project-name-font-weight);
128
136
  white-space: nowrap;
137
+ /* Truncate instead of overflowing the row. `min-width: 0` lets this
138
+ flex item shrink below its content width (auto minimum size would
139
+ otherwise pin it), and `overflow: hidden` is what makes
140
+ `text-overflow` apply. Matters most in collapsed mode, where the
141
+ name shares the row with the locale trigger + hamburger. */
142
+ min-width: 0;
143
+ overflow: hidden;
144
+ text-overflow: ellipsis;
129
145
  }
130
146
 
131
147
  /* ============================================================================
@@ -193,6 +209,10 @@
193
209
  flex-shrink: 0;
194
210
  }
195
211
 
212
+ .stuic-header[data-collapsed] .stuic-header-end {
213
+ gap: var(--stuic-header-end-gap-collapsed);
214
+ }
215
+
196
216
  .stuic-header-avatar {
197
217
  display: flex;
198
218
  align-items: center;
@@ -1515,6 +1515,18 @@ interface HeaderActionItem {
1515
1515
  }
1516
1516
  ```
1517
1517
 
1518
+ ### HeaderLocaleItem
1519
+
1520
+ ```ts
1521
+ interface HeaderLocaleItem {
1522
+ id: string;
1523
+ label: THC;
1524
+ /** Compact label for the inline trigger in COLLAPSED mode only
1525
+ * (falls back to `label`); the dropdown list always shows `label`. */
1526
+ shortLabel?: THC;
1527
+ }
1528
+ ```
1529
+
1518
1530
  ### Key Props
1519
1531
 
1520
1532
  | Prop | Type | Default | Description |
@@ -1540,7 +1552,7 @@ interface HeaderActionItem {
1540
1552
  | `contentMaxWidth` | `string \| number` | — | Inner content row max-width (outer header stays 100%). |
1541
1553
  | `collapseThreshold` | `number` | `768` | Width (px) to collapse; 0 disables. |
1542
1554
  | `collapseMode` | `"hamburger" \| "hide"` | `"hamburger"` | Collapse behavior. See top of section. |
1543
- | `keepLocaleOnCollapse` | `boolean` | `false` | Keep locale switcher visible when collapsed (only `collapseMode === "hide"`). |
1555
+ | `keepLocaleOnCollapse` | `boolean` | `true` | Keep the locale switcher inline when collapsed (both modes). See below. |
1544
1556
  | `fixed` | `boolean` | `false` | Fixed positioning at top. |
1545
1557
  | `isCollapsed` | `boolean` | — | Bindable: collapsed state. |
1546
1558
  | `isMenuOpen` | `boolean` | — | Bindable: hamburger menu open. |
@@ -1575,11 +1587,24 @@ Common pattern for app interfaces: the leading hamburger opens a side drawer for
1575
1587
 
1576
1588
  See [Header/README.md](../../src/lib/components/Header/README.md) for the breakdown of which markup branch handles each requirement of this pattern.
1577
1589
 
1590
+ ### The locale switcher never folds into the hamburger
1591
+
1592
+ By default (`keepLocaleOnCollapse`) the locale switcher stays **inline** when the header collapses, in both collapse modes — it sits just before the actions/avatar/hamburger instead of becoming the last section of the dropdown.
1593
+
1594
+ This is the one control `Header` deliberately treats differently from nav items. The user who most needs the language switch is the one who landed in a language they cannot read, and for them a visible `EN ▾` trigger is self-describing while a hamburger entry is not: the trigger is an unlabeled icon, the section heading says "Language" in a language they do not speak, and with more than a few nav items it sits below the fold of a menu they must scroll.
1595
+
1596
+ Consequences worth knowing:
1597
+
1598
+ - The dropdown never carries a duplicate locale section while the inline trigger is visible.
1599
+ - If the locales were the only thing the trailing dropdown would have held, no hamburger renders at all.
1600
+ - `HeaderLocaleItem.shortLabel` keeps the inline trigger narrow on phones (`"Slovenčina"` → `"SK"`) without shortening the dropdown entries.
1601
+ - Set `keepLocaleOnCollapse={false}` when the end area is already crowded with actions, or when the app's own drawer owns the language switch.
1602
+
1578
1603
  ### CSS Tokens
1579
1604
 
1580
1605
  Prefix: `--stuic-header-*`
1581
1606
 
1582
- `padding-x`, `padding-y`, `gap`, `min-height`, `nav-gap`, `content-max-width`, `project-name-font-weight`, `z-index`, `bg`, `text`, `border-width`, `border-color`, `nav-item-bg-active`, `nav-item-text-active`
1607
+ `padding-x`, `padding-y`, `gap`, `end-gap-collapsed`, `min-height`, `nav-gap`, `content-max-width`, `project-name-font-weight`, `z-index`, `bg`, `text`, `border-width`, `border-color`, `nav-item-bg-active`, `nav-item-text-active`
1583
1608
 
1584
1609
  ---
1585
1610
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.186.0",
3
+ "version": "3.187.0",
4
4
  "packageManager": "pnpm@11.5.0",
5
5
  "scripts": {
6
6
  "dev": "vite dev",