@marianmeres/stuic 3.94.4 → 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
|
|
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
|
|
164
|
-
|
|
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
|
|
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
|
-
|
|
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={
|
|
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:
|
|
445
|
+
offsetWidth: _measuredWidth,
|
|
301
446
|
})}
|
|
302
447
|
{:else}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
<!--
|
|
317
|
-
{
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
<!--
|
|
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
|
|
361
|
-
{#if
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
72
|
+
LEADING (left-side slot — hamburger, back arrow, etc.)
|
|
54
73
|
============================================================================ */
|
|
55
74
|
|
|
56
|
-
.stuic-header-
|
|
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.
|
|
3
|
+
"version": "3.96.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && pnpm run prepack",
|
|
@@ -62,26 +62,26 @@
|
|
|
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.
|
|
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.
|
|
72
|
+
"svelte": "^5.55.9",
|
|
73
73
|
"svelte-check": "^4.4.8",
|
|
74
74
|
"tailwindcss": "^4.3.0",
|
|
75
|
-
"tsx": "^4.22.
|
|
75
|
+
"tsx": "^4.22.3",
|
|
76
76
|
"typescript": "^5.9.3",
|
|
77
|
-
"typescript-eslint": "^8.59.
|
|
77
|
+
"typescript-eslint": "^8.59.4",
|
|
78
78
|
"vite": "^7.3.3",
|
|
79
79
|
"vitest": "^3.2.4"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@marianmeres/clog": "^3.
|
|
82
|
+
"@marianmeres/clog": "^3.21.0",
|
|
83
83
|
"@marianmeres/cron": "^2.0.1",
|
|
84
|
-
"@marianmeres/design-tokens": "^1.
|
|
84
|
+
"@marianmeres/design-tokens": "^1.5.0",
|
|
85
85
|
"@marianmeres/icons-fns": "^5.0.0",
|
|
86
86
|
"@marianmeres/item-collection": "^1.4.2",
|
|
87
87
|
"@marianmeres/paging-store": "^2.1.1",
|