@marianmeres/stuic 3.95.0 → 3.96.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.
@@ -37,17 +37,68 @@
37
37
  label: THC;
38
38
  }
39
39
 
40
+ export interface HeaderActionItem {
41
+ /** Unique identifier */
42
+ id: string | number;
43
+ /** Icon — THC (string/html/component/snippet). The visible content. */
44
+ icon: THC;
45
+ /** Accessible label (aria-label). */
46
+ label: THC;
47
+ /** Click handler */
48
+ onclick?: () => void;
49
+ /** Render as a link instead of a button */
50
+ href?: string;
51
+ /** Link target (e.g., "_blank"). Only relevant when href is set. */
52
+ target?: string;
53
+ /** Active state styling (e.g., when a panel triggered by this action is open) */
54
+ active?: boolean;
55
+ /** Whether this action is disabled */
56
+ disabled?: boolean;
57
+ /** Additional CSS classes */
58
+ class?: string;
59
+ }
60
+
61
+ /** Collapse behavior when the header drops below `collapseThreshold`:
62
+ * - "hamburger": nav items fold into a trailing dropdown along with the
63
+ * locale switcher and an interactive avatar.
64
+ * - "hide": nav items are hidden entirely. No trailing hamburger renders.
65
+ * Avatar stays visible. Locale visibility is controlled by
66
+ * `keepLocaleOnCollapse`. */
67
+ export type HeaderCollapseMode = "hamburger" | "hide";
68
+
69
+ /** Visibility for the built-in leading hamburger button:
70
+ * - false/undefined: not rendered
71
+ * - true: always rendered
72
+ * - "collapsed": only rendered when the header is below the collapse threshold */
73
+ export type HeaderLeadingHamburger = boolean | "collapsed";
74
+
40
75
  export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
76
+ /** Leading (left-side) slot. Renders before the logo/title.
77
+ * Use for a hamburger button, back arrow, breadcrumbs, etc.
78
+ * When provided, overrides the built-in `leadingHamburger`. */
79
+ leading?: Snippet<[{ isCollapsed: boolean }]>;
80
+ /** Convenience: render a built-in hamburger button in the leading slot.
81
+ * Ignored when the `leading` snippet is provided. */
82
+ leadingHamburger?: HeaderLeadingHamburger;
83
+ /** Click handler for the built-in leading hamburger (typically opens a drawer) */
84
+ onLeadingHamburger?: () => void;
85
+ /** Icon for the built-in leading hamburger (defaults to a menu icon) */
86
+ leadingHamburgerIcon?: THC;
87
+ /** Aria-label for the built-in leading hamburger (defaults to "Open menu") */
88
+ leadingHamburgerLabel?: string;
41
89
  /** Logo/brand snippet — full control over the left branding area */
42
90
  logo?: Snippet;
43
- /** Horizontal alignment of the nav items in expanded mode */
44
- navAlign?: "left" | "right";
45
91
  /** Button variant for nav items and locale trigger (defaults to "ghost") */
46
92
  navVariant?: ButtonVariant;
47
93
  /** Simple text alternative to the logo snippet */
48
94
  projectName?: string;
49
95
  /** Navigation items — inline when expanded, DropdownMenu when collapsed */
50
96
  items?: HeaderNavItem[];
97
+ /** Action icon buttons displayed between the locale switcher and the avatar.
98
+ * Always visible — they do not fold into the trailing dropdown. */
99
+ actions?: HeaderActionItem[];
100
+ /** Called when an action is selected (in addition to the per-item onclick) */
101
+ onActionSelect?: (action: HeaderActionItem) => void;
51
102
  /** Avatar/user snippet — rendered at the far right */
52
103
  avatar?: Snippet;
53
104
  /** When provided, makes the avatar interactive. In expanded mode wraps it in a
@@ -63,8 +114,21 @@
63
114
  onLocaleChange?: (localeId: string) => void;
64
115
  /** Section header label for locales in collapsed dropdown (defaults to "Language") */
65
116
  localeLabel?: THC;
117
+ /** Max-width of the inner content row. The outer `<header>` stays 100%
118
+ * wide (background fills the parent); the inner content is centered and
119
+ * capped at this value. Accepts any CSS length: "1024px", "72rem",
120
+ * "100%", "none". Default: undefined → unbounded.
121
+ * Equivalent global override:
122
+ * `:root { --stuic-header-content-max-width: 72rem; }` */
123
+ contentMaxWidth?: string | number;
66
124
  /** Element width (px) below which nav collapses to hamburger. 0 to disable. */
67
125
  collapseThreshold?: number;
126
+ /** Collapse behavior when below threshold (defaults to "hamburger") */
127
+ collapseMode?: HeaderCollapseMode;
128
+ /** When `collapseMode === "hide"`, keep the locale switcher visible in
129
+ * collapsed mode. No effect when `collapseMode === "hamburger"`
130
+ * (locale already folds into the trailing dropdown there). */
131
+ keepLocaleOnCollapse?: boolean;
68
132
  /** Fixed positioning (top of viewport) */
69
133
  fixed?: boolean;
70
134
  /** Bindable: whether the header is currently in collapsed (hamburger) mode */
@@ -81,6 +145,12 @@
81
145
  unstyled?: boolean;
82
146
  /** Additional CSS classes for the root <header> */
83
147
  class?: string;
148
+ /** Classes for the inner content wrapper */
149
+ classContent?: string;
150
+ /** Classes for the leading area */
151
+ classLeading?: string;
152
+ /** Classes for the built-in leading hamburger button */
153
+ classLeadingHamburger?: string;
84
154
  /** Classes for the logo area */
85
155
  classLogo?: string;
86
156
  /** Classes for the nav area (expanded mode) */
@@ -89,13 +159,19 @@
89
159
  classNavItem?: string;
90
160
  /** Classes for active nav items */
91
161
  classNavItemActive?: string;
92
- /** Classes for the end area (avatar + hamburger) */
162
+ /** Classes for the actions wrapper */
163
+ classActions?: string;
164
+ /** Classes for individual action buttons */
165
+ classAction?: string;
166
+ /** Classes for active action buttons */
167
+ classActionActive?: string;
168
+ /** Classes for the end area (locale + avatar + trailing hamburger) */
93
169
  classEnd?: string;
94
170
  /** Classes for the avatar container */
95
171
  classAvatar?: string;
96
172
  /** Classes for the locale switcher trigger (expanded mode) */
97
173
  classLocale?: string;
98
- /** Classes for the hamburger button */
174
+ /** Classes for the trailing (right-side) hamburger button */
99
175
  classHamburger?: string;
100
176
  /** Classes for the dropdown wrapper (collapsed mode) */
101
177
  classDropdown?: string;
@@ -108,9 +184,14 @@
108
184
  }
109
185
 
110
186
  export const HEADER_BASE_CLASSES = "stuic-header";
187
+ export const HEADER_CONTENT_CLASSES = "stuic-header-content";
188
+ export const HEADER_LEADING_CLASSES = "stuic-header-leading";
189
+ export const HEADER_LEADING_HAMBURGER_CLASSES = "stuic-header-leading-hamburger";
111
190
  export const HEADER_LOGO_CLASSES = "stuic-header-logo";
112
191
  export const HEADER_NAV_CLASSES = "stuic-header-nav";
113
192
  export const HEADER_NAV_ITEM_CLASSES = "stuic-header-nav-item";
193
+ export const HEADER_ACTIONS_CLASSES = "stuic-header-actions";
194
+ export const HEADER_ACTION_CLASSES = "stuic-header-action";
114
195
  export const HEADER_END_CLASSES = "stuic-header-end";
115
196
  export const HEADER_HAMBURGER_CLASSES = "stuic-header-hamburger";
116
197
  export const HEADER_LOCALE_CLASSES = "stuic-header-locale";
@@ -125,11 +206,17 @@
125
206
  import IconSwap from "../IconSwap/IconSwap.svelte";
126
207
 
127
208
  let {
209
+ leading,
210
+ leadingHamburger = false,
211
+ onLeadingHamburger,
212
+ leadingHamburgerIcon,
213
+ leadingHamburgerLabel = "Open menu",
128
214
  logo,
129
215
  projectName,
130
- navAlign = "right",
131
216
  navVariant = "ghost",
132
217
  items = [],
218
+ actions = [],
219
+ onActionSelect,
133
220
  avatar,
134
221
  avatarOnClick,
135
222
  avatarLabel = "Account",
@@ -137,7 +224,10 @@
137
224
  activeLocale,
138
225
  onLocaleChange,
139
226
  localeLabel = "Language",
227
+ contentMaxWidth,
140
228
  collapseThreshold = 768,
229
+ collapseMode = "hamburger",
230
+ keepLocaleOnCollapse = false,
141
231
  fixed = false,
142
232
  isCollapsed = $bindable(false),
143
233
  isMenuOpen = $bindable(false),
@@ -146,10 +236,16 @@
146
236
  onSelect,
147
237
  unstyled = false,
148
238
  class: classProp,
239
+ classContent,
240
+ classLeading,
241
+ classLeadingHamburger,
149
242
  classLogo,
150
243
  classNav,
151
244
  classNavItem,
152
245
  classNavItemActive,
246
+ classActions,
247
+ classAction,
248
+ classActionActive,
153
249
  classEnd,
154
250
  classAvatar,
155
251
  classLocale,
@@ -160,13 +256,19 @@
160
256
  ...rest
161
257
  }: Props = $props();
162
258
 
163
- // Width measurement (same pattern as Card)
164
- let _offsetWidth = $state(0);
259
+ // Width measurement. We bind both outer and inner because:
260
+ // - Default layout renders an inner wrapper; the inner row width is what
261
+ // actually determines whether nav items fit, so collapse should key off it.
262
+ // - With the `children` escape hatch there is no inner wrapper, so we
263
+ // fall back to the outer measurement.
264
+ let _outerWidth = $state(0);
265
+ let _innerWidth = $state(0);
266
+ let _measuredWidth = $derived(_innerWidth || _outerWidth);
165
267
 
166
268
  // Collapsed state based on threshold
167
269
  let _isCollapsed = $derived.by(() => {
168
270
  if (!collapseThreshold) return false;
169
- return _offsetWidth > 0 && _offsetWidth < collapseThreshold;
271
+ return _measuredWidth > 0 && _measuredWidth < collapseThreshold;
170
272
  });
171
273
 
172
274
  // Sync bindable
@@ -181,12 +283,31 @@
181
283
  }
182
284
  });
183
285
 
184
- // Whether the avatar moves into the dropdown when collapsed
185
- let _avatarInDropdown = $derived(!!(avatar && avatarOnClick));
286
+ // Whether the avatar moves into the dropdown when collapsed.
287
+ // In "hide" mode the avatar always stays visible (never folds).
288
+ let _avatarInDropdown = $derived(
289
+ collapseMode === "hamburger" && !!(avatar && avatarOnClick)
290
+ );
291
+
292
+ // Whether to render the built-in leading hamburger (ignored when `leading` snippet is set)
293
+ let _showLeadingHamburger = $derived.by(() => {
294
+ if (leading) return false;
295
+ if (leadingHamburger === "collapsed") return _isCollapsed;
296
+ return !!leadingHamburger;
297
+ });
186
298
 
187
299
  // Locale switcher: only render when 2+ locales
188
300
  let _hasLocales = $derived(locales.length > 1);
189
301
 
302
+ // Visibility of the inline (expanded-form) locale switcher.
303
+ // In "hamburger" mode: visible only when not collapsed (it folds into the
304
+ // trailing dropdown when collapsed). In "hide" mode: visible when not
305
+ // collapsed, or when collapsed and `keepLocaleOnCollapse` is set.
306
+ let _showLocaleSwitcher = $derived(
307
+ _hasLocales &&
308
+ (!_isCollapsed || (collapseMode === "hide" && keepLocaleOnCollapse))
309
+ );
310
+
190
311
  // Active locale object (for trigger label); fallback to first
191
312
  let _activeLocale = $derived(locales.find((l) => l.id === activeLocale) ?? locales[0]);
192
313
 
@@ -209,8 +330,11 @@
209
330
  );
210
331
  });
211
332
 
212
- // Map HeaderNavItem[] to DropdownMenuItem[] for collapsed mode
333
+ // Map HeaderNavItem[] to DropdownMenuItem[] for collapsed mode.
334
+ // In "hide" mode the trailing hamburger is suppressed entirely — return
335
+ // an empty list so no dropdown trigger renders.
213
336
  let _dropdownItems = $derived.by((): DropdownMenuItem[] => {
337
+ if (collapseMode === "hide") return [];
214
338
  const navItems: DropdownMenuItem[] = items.map(
215
339
  (item) =>
216
340
  ({
@@ -269,10 +393,25 @@
269
393
 
270
394
  // CSS classes
271
395
  let _class = $derived(unstyled ? classProp : twMerge(HEADER_BASE_CLASSES, classProp));
396
+ let _classContent = $derived(
397
+ unstyled ? classContent : twMerge(HEADER_CONTENT_CLASSES, classContent)
398
+ );
399
+ let _styleContent = $derived.by(() => {
400
+ if (contentMaxWidth == null) return undefined;
401
+ const value =
402
+ typeof contentMaxWidth === "number" ? `${contentMaxWidth}px` : contentMaxWidth;
403
+ return `--stuic-header-content-max-width: ${value}`;
404
+ });
405
+ let _classLeading = $derived(
406
+ unstyled ? classLeading : twMerge(HEADER_LEADING_CLASSES, classLeading)
407
+ );
272
408
  let _classLogo = $derived(
273
409
  unstyled ? classLogo : twMerge(HEADER_LOGO_CLASSES, classLogo)
274
410
  );
275
411
  let _classNav = $derived(unstyled ? classNav : twMerge(HEADER_NAV_CLASSES, classNav));
412
+ let _classActions = $derived(
413
+ unstyled ? classActions : twMerge(HEADER_ACTIONS_CLASSES, classActions)
414
+ );
276
415
  let _classEnd = $derived(unstyled ? classEnd : twMerge(HEADER_END_CLASSES, classEnd));
277
416
  let _classLocale = $derived(
278
417
  unstyled ? classLocale : twMerge(HEADER_LOCALE_CLASSES, classLocale)
@@ -283,11 +422,17 @@
283
422
  item.onclick?.();
284
423
  onSelect?.(item);
285
424
  }
425
+
426
+ function handleActionClick(action: HeaderActionItem) {
427
+ if (action.disabled) return;
428
+ action.onclick?.();
429
+ onActionSelect?.(action);
430
+ }
286
431
  </script>
287
432
 
288
433
  <header
289
434
  bind:this={el}
290
- bind:offsetWidth={_offsetWidth}
435
+ bind:offsetWidth={_outerWidth}
291
436
  class={_class}
292
437
  data-fixed={!unstyled && fixed ? "" : undefined}
293
438
  data-collapsed={!unstyled && _isCollapsed ? "" : undefined}
@@ -297,26 +442,48 @@
297
442
  {@render children({
298
443
  isCollapsed: _isCollapsed,
299
444
  items,
300
- offsetWidth: _offsetWidth,
445
+ offsetWidth: _measuredWidth,
301
446
  })}
302
447
  {:else}
303
- <!-- Logo / Brand -->
304
- {#if logo || projectName}
305
- <div class={_classLogo}>
306
- {#if logo}
307
- {@render logo()}
308
- {:else if projectName}
309
- <span class={unstyled ? undefined : "stuic-header-project-name"}>
310
- {projectName}
311
- </span>
312
- {/if}
448
+ <div bind:offsetWidth={_innerWidth} class={_classContent} style={_styleContent}>
449
+ <!-- Leading slot (left-side) -->
450
+ {#if leading}
451
+ <div class={_classLeading}>
452
+ {@render leading({ isCollapsed: _isCollapsed })}
453
+ </div>
454
+ {:else if _showLeadingHamburger}
455
+ <div class={_classLeading}>
456
+ <Button
457
+ variant="ghost"
458
+ iconButton
459
+ size="sm"
460
+ {unstyled}
461
+ class={twMerge(
462
+ !unstyled && HEADER_LEADING_HAMBURGER_CLASSES,
463
+ classLeadingHamburger
464
+ )}
465
+ onclick={onLeadingHamburger}
466
+ aria-label={leadingHamburgerLabel}
467
+ >
468
+ {#if leadingHamburgerIcon}
469
+ <Thc thc={leadingHamburgerIcon} />
470
+ {:else}
471
+ {@html iconMenu({ size: iconSize })}
472
+ {/if}
473
+ </Button>
313
474
  </div>
314
475
  {/if}
315
476
 
316
- <!-- Spacer (before nav when right-aligned) -->
317
- {#if navAlign !== "left"}
318
- <div class={unstyled ? undefined : "stuic-header-spacer"}></div>
319
- {/if}
477
+ <!-- Logo / Title (flex-1) -->
478
+ <div class={_classLogo}>
479
+ {#if logo}
480
+ {@render logo()}
481
+ {:else if projectName}
482
+ <span class={unstyled ? undefined : "stuic-header-project-name"}>
483
+ {projectName}
484
+ </span>
485
+ {/if}
486
+ </div>
320
487
 
321
488
  <!-- Nav items (expanded mode) -->
322
489
  {#if !_isCollapsed && items.length > 0}
@@ -350,15 +517,10 @@
350
517
  </nav>
351
518
  {/if}
352
519
 
353
- <!-- Spacer (after nav when left-aligned) -->
354
- {#if navAlign === "left"}
355
- <div class={unstyled ? undefined : "stuic-header-spacer"}></div>
356
- {/if}
357
-
358
- <!-- End area: locale + avatar + hamburger -->
520
+ <!-- End area: locale + actions + avatar + trailing hamburger -->
359
521
  <div class={_classEnd}>
360
- <!-- Locale switcher (expanded mode only) -->
361
- {#if !_isCollapsed && _hasLocales}
522
+ <!-- Locale switcher (shown when expanded, or in "hide" mode with keepLocaleOnCollapse) -->
523
+ {#if _showLocaleSwitcher}
362
524
  <DropdownMenu
363
525
  items={_localeDropdownItems}
364
526
  position="bottom-span-right"
@@ -393,6 +555,34 @@
393
555
  </DropdownMenu>
394
556
  {/if}
395
557
 
558
+ <!-- Actions (icon buttons, always visible) -->
559
+ {#if actions.length > 0}
560
+ <div class={_classActions}>
561
+ {#each actions as action (action.id)}
562
+ <Button
563
+ variant="ghost"
564
+ iconButton
565
+ size="sm"
566
+ href={action.href}
567
+ target={action.target}
568
+ disabled={action.disabled}
569
+ {unstyled}
570
+ class={twMerge(
571
+ !unstyled && HEADER_ACTION_CLASSES,
572
+ !unstyled && action.active && classActionActive,
573
+ classAction,
574
+ action.class
575
+ )}
576
+ data-active={!unstyled && action.active ? "" : undefined}
577
+ aria-label={typeof action.label === "string" ? action.label : undefined}
578
+ onclick={() => handleActionClick(action)}
579
+ >
580
+ <Thc thc={action.icon} />
581
+ </Button>
582
+ {/each}
583
+ </div>
584
+ {/if}
585
+
396
586
  <!-- Avatar: hidden when collapsed + avatarOnClick (moves into dropdown) -->
397
587
  {#if avatar && !(_isCollapsed && _avatarInDropdown)}
398
588
  {#if avatarOnClick}
@@ -437,5 +627,6 @@
437
627
  </DropdownMenu>
438
628
  {/if}
439
629
  </div>
630
+ </div>
440
631
  {/if}
441
632
  </header>
@@ -29,17 +29,67 @@ export interface HeaderLocaleItem {
29
29
  /** Display label — supports THC (string, html, component, snippet) */
30
30
  label: THC;
31
31
  }
32
+ export interface HeaderActionItem {
33
+ /** Unique identifier */
34
+ id: string | number;
35
+ /** Icon — THC (string/html/component/snippet). The visible content. */
36
+ icon: THC;
37
+ /** Accessible label (aria-label). */
38
+ label: THC;
39
+ /** Click handler */
40
+ onclick?: () => void;
41
+ /** Render as a link instead of a button */
42
+ href?: string;
43
+ /** Link target (e.g., "_blank"). Only relevant when href is set. */
44
+ target?: string;
45
+ /** Active state styling (e.g., when a panel triggered by this action is open) */
46
+ active?: boolean;
47
+ /** Whether this action is disabled */
48
+ disabled?: boolean;
49
+ /** Additional CSS classes */
50
+ class?: string;
51
+ }
52
+ /** Collapse behavior when the header drops below `collapseThreshold`:
53
+ * - "hamburger": nav items fold into a trailing dropdown along with the
54
+ * locale switcher and an interactive avatar.
55
+ * - "hide": nav items are hidden entirely. No trailing hamburger renders.
56
+ * Avatar stays visible. Locale visibility is controlled by
57
+ * `keepLocaleOnCollapse`. */
58
+ export type HeaderCollapseMode = "hamburger" | "hide";
59
+ /** Visibility for the built-in leading hamburger button:
60
+ * - false/undefined: not rendered
61
+ * - true: always rendered
62
+ * - "collapsed": only rendered when the header is below the collapse threshold */
63
+ export type HeaderLeadingHamburger = boolean | "collapsed";
32
64
  export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
65
+ /** Leading (left-side) slot. Renders before the logo/title.
66
+ * Use for a hamburger button, back arrow, breadcrumbs, etc.
67
+ * When provided, overrides the built-in `leadingHamburger`. */
68
+ leading?: Snippet<[{
69
+ isCollapsed: boolean;
70
+ }]>;
71
+ /** Convenience: render a built-in hamburger button in the leading slot.
72
+ * Ignored when the `leading` snippet is provided. */
73
+ leadingHamburger?: HeaderLeadingHamburger;
74
+ /** Click handler for the built-in leading hamburger (typically opens a drawer) */
75
+ onLeadingHamburger?: () => void;
76
+ /** Icon for the built-in leading hamburger (defaults to a menu icon) */
77
+ leadingHamburgerIcon?: THC;
78
+ /** Aria-label for the built-in leading hamburger (defaults to "Open menu") */
79
+ leadingHamburgerLabel?: string;
33
80
  /** Logo/brand snippet — full control over the left branding area */
34
81
  logo?: Snippet;
35
- /** Horizontal alignment of the nav items in expanded mode */
36
- navAlign?: "left" | "right";
37
82
  /** Button variant for nav items and locale trigger (defaults to "ghost") */
38
83
  navVariant?: ButtonVariant;
39
84
  /** Simple text alternative to the logo snippet */
40
85
  projectName?: string;
41
86
  /** Navigation items — inline when expanded, DropdownMenu when collapsed */
42
87
  items?: HeaderNavItem[];
88
+ /** Action icon buttons displayed between the locale switcher and the avatar.
89
+ * Always visible — they do not fold into the trailing dropdown. */
90
+ actions?: HeaderActionItem[];
91
+ /** Called when an action is selected (in addition to the per-item onclick) */
92
+ onActionSelect?: (action: HeaderActionItem) => void;
43
93
  /** Avatar/user snippet — rendered at the far right */
44
94
  avatar?: Snippet;
45
95
  /** When provided, makes the avatar interactive. In expanded mode wraps it in a
@@ -55,8 +105,21 @@ export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
55
105
  onLocaleChange?: (localeId: string) => void;
56
106
  /** Section header label for locales in collapsed dropdown (defaults to "Language") */
57
107
  localeLabel?: THC;
108
+ /** Max-width of the inner content row. The outer `<header>` stays 100%
109
+ * wide (background fills the parent); the inner content is centered and
110
+ * capped at this value. Accepts any CSS length: "1024px", "72rem",
111
+ * "100%", "none". Default: undefined → unbounded.
112
+ * Equivalent global override:
113
+ * `:root { --stuic-header-content-max-width: 72rem; }` */
114
+ contentMaxWidth?: string | number;
58
115
  /** Element width (px) below which nav collapses to hamburger. 0 to disable. */
59
116
  collapseThreshold?: number;
117
+ /** Collapse behavior when below threshold (defaults to "hamburger") */
118
+ collapseMode?: HeaderCollapseMode;
119
+ /** When `collapseMode === "hide"`, keep the locale switcher visible in
120
+ * collapsed mode. No effect when `collapseMode === "hamburger"`
121
+ * (locale already folds into the trailing dropdown there). */
122
+ keepLocaleOnCollapse?: boolean;
60
123
  /** Fixed positioning (top of viewport) */
61
124
  fixed?: boolean;
62
125
  /** Bindable: whether the header is currently in collapsed (hamburger) mode */
@@ -73,6 +136,12 @@ export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
73
136
  unstyled?: boolean;
74
137
  /** Additional CSS classes for the root <header> */
75
138
  class?: string;
139
+ /** Classes for the inner content wrapper */
140
+ classContent?: string;
141
+ /** Classes for the leading area */
142
+ classLeading?: string;
143
+ /** Classes for the built-in leading hamburger button */
144
+ classLeadingHamburger?: string;
76
145
  /** Classes for the logo area */
77
146
  classLogo?: string;
78
147
  /** Classes for the nav area (expanded mode) */
@@ -81,13 +150,19 @@ export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
81
150
  classNavItem?: string;
82
151
  /** Classes for active nav items */
83
152
  classNavItemActive?: string;
84
- /** Classes for the end area (avatar + hamburger) */
153
+ /** Classes for the actions wrapper */
154
+ classActions?: string;
155
+ /** Classes for individual action buttons */
156
+ classAction?: string;
157
+ /** Classes for active action buttons */
158
+ classActionActive?: string;
159
+ /** Classes for the end area (locale + avatar + trailing hamburger) */
85
160
  classEnd?: string;
86
161
  /** Classes for the avatar container */
87
162
  classAvatar?: string;
88
163
  /** Classes for the locale switcher trigger (expanded mode) */
89
164
  classLocale?: string;
90
- /** Classes for the hamburger button */
165
+ /** Classes for the trailing (right-side) hamburger button */
91
166
  classHamburger?: string;
92
167
  /** Classes for the dropdown wrapper (collapsed mode) */
93
168
  classDropdown?: string;
@@ -103,9 +178,14 @@ export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
103
178
  el?: HTMLElement;
104
179
  }
105
180
  export declare const HEADER_BASE_CLASSES = "stuic-header";
181
+ export declare const HEADER_CONTENT_CLASSES = "stuic-header-content";
182
+ export declare const HEADER_LEADING_CLASSES = "stuic-header-leading";
183
+ export declare const HEADER_LEADING_HAMBURGER_CLASSES = "stuic-header-leading-hamburger";
106
184
  export declare const HEADER_LOGO_CLASSES = "stuic-header-logo";
107
185
  export declare const HEADER_NAV_CLASSES = "stuic-header-nav";
108
186
  export declare const HEADER_NAV_ITEM_CLASSES = "stuic-header-nav-item";
187
+ export declare const HEADER_ACTIONS_CLASSES = "stuic-header-actions";
188
+ export declare const HEADER_ACTION_CLASSES = "stuic-header-action";
109
189
  export declare const HEADER_END_CLASSES = "stuic-header-end";
110
190
  export declare const HEADER_HAMBURGER_CLASSES = "stuic-header-hamburger";
111
191
  export declare const HEADER_LOCALE_CLASSES = "stuic-header-locale";
@@ -11,9 +11,16 @@
11
11
  --stuic-header-gap: 1rem;
12
12
  --stuic-header-min-height: 3.5rem;
13
13
 
14
+ /* Content row max-width. `none` lets the inner row fill the parent.
15
+ Override globally or per-instance via the `contentMaxWidth` prop. */
16
+ --stuic-header-content-max-width: none;
17
+
14
18
  /* Nav items */
15
19
  --stuic-header-nav-gap: 0.25rem;
16
20
 
21
+ /* Actions (icon buttons in the end region) */
22
+ --stuic-header-actions-gap: 0.25rem;
23
+
17
24
  /* Project name */
18
25
  --stuic-header-project-name-font-weight: var(--font-weight-semibold, 600);
19
26
 
@@ -27,14 +34,26 @@
27
34
  ============================================================================ */
28
35
 
29
36
  .stuic-header {
37
+ width: 100%;
38
+ background: var(--stuic-header-bg, var(--color-background, inherit));
39
+ color: var(--stuic-header-text, inherit);
40
+ }
41
+
42
+ /* ============================================================================
43
+ CONTENT ROW (inner wrapper — flex layout, padding, gap, optional max-width)
44
+ The outer .stuic-header keeps full width / background; this inner row
45
+ carries the layout so it can be capped via `contentMaxWidth` and centered.
46
+ ============================================================================ */
47
+
48
+ .stuic-header-content {
30
49
  display: flex;
31
50
  align-items: center;
32
- width: 100%;
51
+ gap: var(--stuic-header-gap);
33
52
  min-height: var(--stuic-header-min-height);
34
53
  padding: var(--stuic-header-padding-y) var(--stuic-header-padding-x);
35
- gap: var(--stuic-header-gap);
36
- background: var(--stuic-header-bg, var(--color-background, inherit));
37
- color: var(--stuic-header-text, inherit);
54
+ width: 100%;
55
+ max-width: var(--stuic-header-content-max-width);
56
+ margin-inline: auto;
38
57
  }
39
58
 
40
59
  /* ============================================================================
@@ -50,15 +69,31 @@
50
69
  }
51
70
 
52
71
  /* ============================================================================
53
- LOGO
72
+ LEADING (left-side slot — hamburger, back arrow, etc.)
54
73
  ============================================================================ */
55
74
 
56
- .stuic-header-logo {
75
+ .stuic-header-leading {
57
76
  display: flex;
58
77
  align-items: center;
59
78
  flex-shrink: 0;
60
79
  }
61
80
 
81
+ .stuic-header-leading-hamburger {
82
+ /* Ghost Button handles hover/focus — only override sizing if needed */
83
+ }
84
+
85
+ /* ============================================================================
86
+ LOGO / TITLE (takes available space — flex-1 with min-width: 0 so long
87
+ content can shrink/ellipsize instead of overflowing the row)
88
+ ============================================================================ */
89
+
90
+ .stuic-header-logo {
91
+ display: flex;
92
+ align-items: center;
93
+ flex: 1;
94
+ min-width: 0;
95
+ }
96
+
62
97
  .stuic-header-project-name {
63
98
  font-size: var(--stuic-header-project-name-font-size, var(--text-lg, 1.125rem));
64
99
  font-weight: var(--stuic-header-project-name-font-weight);
@@ -91,6 +126,26 @@
91
126
  flex-shrink: 0;
92
127
  }
93
128
 
129
+ /* ============================================================================
130
+ ACTIONS (icon buttons in the end region — search, notifications, etc.)
131
+ ============================================================================ */
132
+
133
+ .stuic-header-actions {
134
+ display: flex;
135
+ align-items: center;
136
+ gap: var(--stuic-header-actions-gap);
137
+ flex-shrink: 0;
138
+ }
139
+
140
+ .stuic-header-action {
141
+ /* Ghost iconButton handles base styling — placeholder hook for consumer overrides */
142
+ }
143
+
144
+ .stuic-header-action[data-active] {
145
+ color: var(--stuic-header-action-text-active, var(--color-foreground, inherit));
146
+ background: var(--stuic-header-action-bg-active, var(--color-muted, transparent));
147
+ }
148
+
94
149
  /* ============================================================================
95
150
  SPACER
96
151
  ============================================================================ */
@@ -1 +1 @@
1
- export { default as Header, type Props as HeaderProps, type HeaderNavItem, type HeaderLocaleItem, HEADER_BASE_CLASSES, HEADER_LOGO_CLASSES, HEADER_NAV_CLASSES, HEADER_NAV_ITEM_CLASSES, HEADER_END_CLASSES, HEADER_HAMBURGER_CLASSES, HEADER_LOCALE_CLASSES, } from "./Header.svelte";
1
+ export { default as Header, type Props as HeaderProps, type HeaderNavItem, type HeaderLocaleItem, type HeaderActionItem, type HeaderLeadingHamburger, type HeaderCollapseMode, HEADER_BASE_CLASSES, HEADER_CONTENT_CLASSES, HEADER_LEADING_CLASSES, HEADER_LEADING_HAMBURGER_CLASSES, HEADER_LOGO_CLASSES, HEADER_NAV_CLASSES, HEADER_NAV_ITEM_CLASSES, HEADER_ACTIONS_CLASSES, HEADER_ACTION_CLASSES, HEADER_END_CLASSES, HEADER_HAMBURGER_CLASSES, HEADER_LOCALE_CLASSES, } from "./Header.svelte";
@@ -1 +1 @@
1
- export { default as Header, HEADER_BASE_CLASSES, HEADER_LOGO_CLASSES, HEADER_NAV_CLASSES, HEADER_NAV_ITEM_CLASSES, HEADER_END_CLASSES, HEADER_HAMBURGER_CLASSES, HEADER_LOCALE_CLASSES, } from "./Header.svelte";
1
+ export { default as Header, HEADER_BASE_CLASSES, HEADER_CONTENT_CLASSES, HEADER_LEADING_CLASSES, HEADER_LEADING_HAMBURGER_CLASSES, HEADER_LOGO_CLASSES, HEADER_NAV_CLASSES, HEADER_NAV_ITEM_CLASSES, HEADER_ACTIONS_CLASSES, HEADER_ACTION_CLASSES, HEADER_END_CLASSES, HEADER_HAMBURGER_CLASSES, HEADER_LOCALE_CLASSES, } from "./Header.svelte";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.95.0",
3
+ "version": "3.96.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && pnpm run prepack",
@@ -62,19 +62,19 @@
62
62
  "@tailwindcss/forms": "^0.5.11",
63
63
  "@tailwindcss/typography": "^0.5.19",
64
64
  "@tailwindcss/vite": "^4.3.0",
65
- "@types/node": "^25.8.0",
65
+ "@types/node": "^25.9.1",
66
66
  "dotenv": "^16.6.1",
67
67
  "eslint": "^9.39.4",
68
68
  "globals": "^16.5.0",
69
69
  "prettier": "^3.8.3",
70
70
  "prettier-plugin-svelte": "^3.5.2",
71
71
  "publint": "^0.3.21",
72
- "svelte": "^5.55.7",
72
+ "svelte": "^5.55.9",
73
73
  "svelte-check": "^4.4.8",
74
74
  "tailwindcss": "^4.3.0",
75
- "tsx": "^4.22.1",
75
+ "tsx": "^4.22.3",
76
76
  "typescript": "^5.9.3",
77
- "typescript-eslint": "^8.59.3",
77
+ "typescript-eslint": "^8.59.4",
78
78
  "vite": "^7.3.3",
79
79
  "vitest": "^3.2.4"
80
80
  },