@marianmeres/stuic 3.96.0 → 3.97.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/AGENTS.md +7 -6
- package/API.md +90 -16
- package/README.md +1 -1
- package/dist/components/Header/Header.svelte +61 -22
- package/dist/components/Header/Header.svelte.d.ts +27 -2
- package/dist/components/Header/README.md +35 -0
- package/dist/components/Header/index.css +35 -2
- package/dist/components/IconSwap/README.md +69 -0
- package/dist/components/ImageCycler/README.md +83 -0
- package/dist/components/LoginForm/README.md +148 -0
- package/dist/components/LoginOrRegisterForm/README.md +170 -0
- package/dist/components/RegisterForm/README.md +183 -0
- package/dist/components/Separator/README.md +44 -0
- package/docs/architecture.md +14 -10
- package/docs/domains/components.md +112 -28
- package/docs/domains/utils.md +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -23,15 +23,16 @@
|
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
src/lib/
|
|
26
|
-
├── components/ #
|
|
26
|
+
├── components/ # 57 component directories
|
|
27
27
|
├── actions/ # 15 Svelte actions
|
|
28
|
-
├── utils/ #
|
|
29
|
-
├──
|
|
30
|
-
├── icons/ # Icon re-exports
|
|
28
|
+
├── utils/ # 44 utility modules
|
|
29
|
+
├── icons/ # Icon re-exports from @marianmeres/icons-fns
|
|
31
30
|
├── index.css # Centralized CSS imports
|
|
32
31
|
└── index.ts # Main exports
|
|
33
32
|
```
|
|
34
33
|
|
|
34
|
+
Theme CSS files are not bundled in this package — they're provided by `@marianmeres/design-tokens/css/*.css` (42 themes) and imported by `src/lib/index.css`.
|
|
35
|
+
|
|
35
36
|
---
|
|
36
37
|
|
|
37
38
|
## Critical Conventions
|
|
@@ -116,10 +117,10 @@ Global tokens that control cross-component visual properties. Defined in `src/li
|
|
|
116
117
|
|
|
117
118
|
### Domain Docs
|
|
118
119
|
|
|
119
|
-
- [Components](./docs/domains/components.md) —
|
|
120
|
+
- [Components](./docs/domains/components.md) — 57 component directories, Props pattern, snippets
|
|
120
121
|
- [Theming](./docs/domains/theming.md) — CSS tokens, dark mode, themes
|
|
121
122
|
- [Actions](./docs/domains/actions.md) — 15 Svelte directives
|
|
122
|
-
- [Utils](./docs/domains/utils.md) —
|
|
123
|
+
- [Utils](./docs/domains/utils.md) — 44 utility modules
|
|
123
124
|
|
|
124
125
|
### Reference
|
|
125
126
|
|
package/API.md
CHANGED
|
@@ -132,20 +132,41 @@ Navigation wrapper component.
|
|
|
132
132
|
|
|
133
133
|
#### `Header`
|
|
134
134
|
|
|
135
|
-
Responsive navigation header with logo, nav items, avatar, and
|
|
136
|
-
|
|
137
|
-
| Prop
|
|
138
|
-
|
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
135
|
+
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>`.
|
|
136
|
+
|
|
137
|
+
| Prop | Type | Default | Description |
|
|
138
|
+
| ----------------------- | ----------------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
139
|
+
| `leading` | `Snippet<[{ isCollapsed }]>` | — | Leading (left-side) slot. Overrides `leadingHamburger`. |
|
|
140
|
+
| `leadingHamburger` | `boolean \| "collapsed"` | `false` | Built-in hamburger in the leading slot (`"collapsed"` = only below threshold). Ignored when `leading` is provided. |
|
|
141
|
+
| `onLeadingHamburger` | `() => void` | — | Click handler for the built-in leading hamburger (typically opens a drawer). |
|
|
142
|
+
| `leadingHamburgerIcon` | `THC` | menu icon | Icon override for the built-in leading hamburger. |
|
|
143
|
+
| `leadingHamburgerLabel` | `string` | `"Open menu"` | Aria-label for the leading hamburger. |
|
|
144
|
+
| `logo` | `Snippet` | — | Logo/brand snippet. |
|
|
145
|
+
| `projectName` | `string` | — | Simple text logo alternative. |
|
|
146
|
+
| `navVariant` | `ButtonVariant` | `"ghost"` | Button variant for nav items and the locale switcher trigger. |
|
|
147
|
+
| `items` | `HeaderNavItem[]` | `[]` | Navigation items — inline when expanded, dropdown when collapsed (hamburger mode). |
|
|
148
|
+
| `actions` | `HeaderActionItem[]` | `[]` | Action icon buttons between the locale switcher and the avatar. Always visible — never fold into the dropdown. |
|
|
149
|
+
| `onActionSelect` | `(action) => void` | — | Called after the per-item `onclick`. |
|
|
150
|
+
| `avatar` | `Snippet` | — | Avatar snippet (far right). |
|
|
151
|
+
| `avatarOnClick` | `() => void` | — | Makes the avatar interactive. In `"hamburger"` collapse mode it moves into the dropdown. |
|
|
152
|
+
| `avatarLabel` | `THC` | `"Account"` | Label for the avatar entry inside the collapsed dropdown. |
|
|
153
|
+
| `locales` | `HeaderLocaleItem[]` | `[]` | Locale items. Switcher only renders when 2+. |
|
|
154
|
+
| `activeLocale` | `string` | — | Current locale id. |
|
|
155
|
+
| `onLocaleChange` | `(localeId) => void` | — | Locale selection callback. |
|
|
156
|
+
| `localeLabel` | `THC` | `"Language"` | Section header inside the collapsed dropdown. |
|
|
157
|
+
| `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`. |
|
|
158
|
+
| `collapseThreshold` | `number` | `768` | Width (px) to collapse; 0 disables. |
|
|
159
|
+
| `collapseMode` | `"hamburger" \| "hide"` | `"hamburger"` | Collapse behavior. `"hide"` keeps avatar/actions visible and renders no trailing hamburger (app-shell pattern). |
|
|
160
|
+
| `keepLocaleOnCollapse` | `boolean` | `false` | Keep the locale switcher visible in collapsed mode (only `collapseMode === "hide"`). |
|
|
161
|
+
| `fixed` | `boolean` | `false` | Fixed positioning at the top. |
|
|
162
|
+
| `isCollapsed` | `boolean` | — | Bindable: collapsed state. |
|
|
163
|
+
| `isMenuOpen` | `boolean` | — | Bindable: hamburger menu open. |
|
|
164
|
+
| `dropdownPosition` | `DropdownMenuPosition` | `"bottom-span-right"` | Position of the collapsed dropdown. |
|
|
165
|
+
| `iconSize` | `number` | `24` | Hamburger/X icon size in px. |
|
|
166
|
+
| `onSelect` | `(item) => void` | — | Item selection callback (both modes). |
|
|
167
|
+
| `children` | `Snippet<[{ isCollapsed, items, offsetWidth }]>` | — | Escape hatch: override the entire inner layout. |
|
|
168
|
+
|
|
169
|
+
Class slots: `class`, `classContent`, `classLeading`, `classLeadingHamburger`, `classLogo`, `classNav`, `classNavItem`, `classNavItemActive`, `classActions`, `classAction`, `classActionActive`, `classEnd`, `classAvatar`, `classLocale`, `classHamburger`, `classDropdown`.
|
|
149
170
|
|
|
150
171
|
```svelte
|
|
151
172
|
<Header
|
|
@@ -159,11 +180,32 @@ Responsive navigation header with logo, nav items, avatar, and automatic hamburg
|
|
|
159
180
|
<Avatar src="/me.jpg" alt="User" />
|
|
160
181
|
{/snippet}
|
|
161
182
|
</Header>
|
|
183
|
+
|
|
184
|
+
<!-- App-shell pattern: avatar + actions stay visible in collapsed mode,
|
|
185
|
+
nav items hide, leading hamburger opens a drawer. -->
|
|
186
|
+
<Header
|
|
187
|
+
projectName="App"
|
|
188
|
+
items={navItems}
|
|
189
|
+
actions={[
|
|
190
|
+
{ id: "search", icon: { html: iconSearch() }, label: "Search", onclick: openSearch },
|
|
191
|
+
{ id: "cart", icon: { html: iconCart() }, label: "Cart", onclick: openCart },
|
|
192
|
+
]}
|
|
193
|
+
collapseMode="hide"
|
|
194
|
+
leadingHamburger="collapsed"
|
|
195
|
+
onLeadingHamburger={() => (drawerOpen = true)}
|
|
196
|
+
avatarOnClick={() => goto("/me")}
|
|
197
|
+
>
|
|
198
|
+
{#snippet avatar()}<Avatar initials="MM" autoColor />{/snippet}
|
|
199
|
+
</Header>
|
|
162
200
|
```
|
|
163
201
|
|
|
164
|
-
**HeaderNavItem:** `{ id, label, href?, onclick?, icon?, active?, disabled?, class? }`
|
|
202
|
+
**HeaderNavItem:** `{ id, label, href?, target?, onclick?, icon?, active?, disabled?, class? }`
|
|
203
|
+
|
|
204
|
+
**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).
|
|
165
205
|
|
|
166
|
-
|
|
206
|
+
**HeaderLocaleItem:** `{ id, label }`
|
|
207
|
+
|
|
208
|
+
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`.
|
|
167
209
|
|
|
168
210
|
---
|
|
169
211
|
|
|
@@ -334,6 +376,38 @@ International phone number input with country dial code picker. Parses and compo
|
|
|
334
376
|
|
|
335
377
|
Exports: `FieldPhoneNumber`, `FieldPhoneNumberProps`, `validatePhoneNumber`, `Country`.
|
|
336
378
|
|
|
379
|
+
#### `FieldCountry`
|
|
380
|
+
|
|
381
|
+
Country picker dropdown with searchable, optionally flag-prefixed list. Submits a country ISO alpha-2 code via a hidden input. Pairs naturally with `FieldPhoneNumber` and with checkout/address forms.
|
|
382
|
+
|
|
383
|
+
| Prop | Type | Default | Description |
|
|
384
|
+
| -------------------- | --------------------------------- | ----------- | -------------------------------------------------------------------------- |
|
|
385
|
+
| `value` | `string` | `""` | Bindable ISO alpha-2 code (e.g. `"SK"`). Empty = unselected. |
|
|
386
|
+
| `onChange` | `(iso: string) => void` | — | Called when selection changes. |
|
|
387
|
+
| `countryList` | `Country[] \| string[]` | all | Restrict the list to specific countries (objects or ISO codes). |
|
|
388
|
+
| `preferredCountries` | `string[]` | — | ISO codes pinned at the top of the dropdown. |
|
|
389
|
+
| `countryNames` | `Record<string, string>` | English | Override displayed country names (keyed by ISO code). |
|
|
390
|
+
| `flags` | `boolean` | `true` | Show country flag emoji. |
|
|
391
|
+
| `name` | `string` | — | Hidden input name (enables form submission + native validation). |
|
|
392
|
+
| `placeholder` | `string` | — | Trigger placeholder text when nothing is selected. |
|
|
393
|
+
| `required` | `boolean` | `false` | Required indicator + validation. |
|
|
394
|
+
| `disabled` | `boolean` | `false` | Disable the trigger and dropdown. |
|
|
395
|
+
| `validate` | `boolean \| ValidateOptions` | enabled | Validation behavior (default-on, see Imperative validate API). |
|
|
396
|
+
|
|
397
|
+
Integrates with `InputWrap` — supports `label`, `description`, `renderSize`, `labelLeft`, `labelLeftWidth`, `labelLeftBreakpoint`, `inputBefore`, `inputAfter`, `inputBelow`, `below`, `classInput`, `classDropdown`.
|
|
398
|
+
|
|
399
|
+
```svelte
|
|
400
|
+
<FieldCountry
|
|
401
|
+
bind:value={address.country}
|
|
402
|
+
name="country"
|
|
403
|
+
label="Country"
|
|
404
|
+
preferredCountries={["SK", "CZ", "AT", "DE"]}
|
|
405
|
+
required
|
|
406
|
+
/>
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Exports: `FieldCountry`, `FieldCountryProps`, `Country`, `COUNTRIES`, `ISO_MAP`.
|
|
410
|
+
|
|
337
411
|
#### `FieldObject`
|
|
338
412
|
|
|
339
413
|
Dual-mode JSON object editor with pretty-print and raw edit modes. Validates JSON syntax, supports recursive depth display, auto-grow textarea, and form submission via hidden input.
|
package/README.md
CHANGED
|
@@ -148,7 +148,7 @@ AppShell, Accordion, Backdrop, Modal, ModalDialog, Drawer, Collapsible, Header,
|
|
|
148
148
|
|
|
149
149
|
### Forms & Inputs
|
|
150
150
|
|
|
151
|
-
FieldInput, FieldTextarea, FieldSelect, FieldCheckbox, FieldRadios, FieldFile, FieldAssets, FieldOptions, FieldKeyValues, FieldObject, FieldSwitch, FieldInputLocalized, FieldLikeButton, FieldPhoneNumber, CronInput, Fieldset, LoginForm, LoginFormModal, RegisterForm, LoginOrRegisterForm, LoginOrRegisterFormModal, EmailVerifyForm, OtpInput
|
|
151
|
+
FieldInput, FieldTextarea, FieldSelect, FieldCheckbox, FieldRadios, FieldFile, FieldAssets, FieldOptions, FieldKeyValues, FieldObject, FieldSwitch, FieldInputLocalized, FieldLikeButton, FieldPhoneNumber, FieldCountry, CronInput, Fieldset, LoginForm, LoginFormModal, RegisterForm, RegisterFormModal, LoginOrRegisterForm, LoginOrRegisterFormModal, EmailVerifyForm, OtpInput
|
|
152
152
|
|
|
153
153
|
### Buttons & Controls
|
|
154
154
|
|
|
@@ -40,8 +40,10 @@
|
|
|
40
40
|
export interface HeaderActionItem {
|
|
41
41
|
/** Unique identifier */
|
|
42
42
|
id: string | number;
|
|
43
|
-
/** Icon — THC (string/html/component/snippet). The visible content.
|
|
44
|
-
|
|
43
|
+
/** Icon — THC (string/html/component/snippet). The visible content.
|
|
44
|
+
* Required for the default rendering; ignored when `render` is provided
|
|
45
|
+
* (the snippet owns its own DOM). */
|
|
46
|
+
icon?: THC;
|
|
45
47
|
/** Accessible label (aria-label). */
|
|
46
48
|
label: THC;
|
|
47
49
|
/** Click handler */
|
|
@@ -56,6 +58,31 @@
|
|
|
56
58
|
disabled?: boolean;
|
|
57
59
|
/** Additional CSS classes */
|
|
58
60
|
class?: string;
|
|
61
|
+
/** Optional custom renderer. When provided, replaces the default
|
|
62
|
+
* `<Button>` rendering for this action. The Header still owns
|
|
63
|
+
* positioning (slot in the actions row) and the collapse decision.
|
|
64
|
+
*
|
|
65
|
+
* Use this when an action needs custom DOM around its trigger —
|
|
66
|
+
* e.g. a popover/tooltip directive, or a count/dot badge overlay.
|
|
67
|
+
*
|
|
68
|
+
* Snippet args:
|
|
69
|
+
* - `action` — the item itself (lets a snippet be reused across items)
|
|
70
|
+
* - `class` — the same merged class the default `<Button>` would receive,
|
|
71
|
+
* so consumers can opt into the default look
|
|
72
|
+
* - `isCollapsed`— current collapse state
|
|
73
|
+
* - `onclick` — pre-wired handler that calls `action.onclick` and
|
|
74
|
+
* `onActionSelect`; consumers can wire it to their
|
|
75
|
+
* button or ignore it. */
|
|
76
|
+
render?: Snippet<
|
|
77
|
+
[
|
|
78
|
+
{
|
|
79
|
+
action: HeaderActionItem;
|
|
80
|
+
class: string;
|
|
81
|
+
isCollapsed: boolean;
|
|
82
|
+
onclick: () => void;
|
|
83
|
+
},
|
|
84
|
+
]
|
|
85
|
+
>;
|
|
59
86
|
}
|
|
60
87
|
|
|
61
88
|
/** Collapse behavior when the header drops below `collapseThreshold`:
|
|
@@ -559,26 +586,38 @@
|
|
|
559
586
|
{#if actions.length > 0}
|
|
560
587
|
<div class={_classActions}>
|
|
561
588
|
{#each actions as action (action.id)}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
{
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
589
|
+
{@const actionClass = twMerge(
|
|
590
|
+
!unstyled && HEADER_ACTION_CLASSES,
|
|
591
|
+
!unstyled && action.active && classActionActive,
|
|
592
|
+
classAction,
|
|
593
|
+
action.class
|
|
594
|
+
)}
|
|
595
|
+
{#if action.render}
|
|
596
|
+
{@render action.render({
|
|
597
|
+
action,
|
|
598
|
+
class: actionClass,
|
|
599
|
+
isCollapsed: _isCollapsed,
|
|
600
|
+
onclick: () => handleActionClick(action),
|
|
601
|
+
})}
|
|
602
|
+
{:else}
|
|
603
|
+
<Button
|
|
604
|
+
variant="ghost"
|
|
605
|
+
iconButton
|
|
606
|
+
size="sm"
|
|
607
|
+
href={action.href}
|
|
608
|
+
target={action.target}
|
|
609
|
+
disabled={action.disabled}
|
|
610
|
+
{unstyled}
|
|
611
|
+
class={actionClass}
|
|
612
|
+
data-active={!unstyled && action.active ? "" : undefined}
|
|
613
|
+
aria-label={typeof action.label === "string" ? action.label : undefined}
|
|
614
|
+
onclick={() => handleActionClick(action)}
|
|
615
|
+
>
|
|
616
|
+
{#if action.icon !== undefined}
|
|
617
|
+
<Thc thc={action.icon} />
|
|
618
|
+
{/if}
|
|
619
|
+
</Button>
|
|
620
|
+
{/if}
|
|
582
621
|
{/each}
|
|
583
622
|
</div>
|
|
584
623
|
{/if}
|
|
@@ -32,8 +32,10 @@ export interface HeaderLocaleItem {
|
|
|
32
32
|
export interface HeaderActionItem {
|
|
33
33
|
/** Unique identifier */
|
|
34
34
|
id: string | number;
|
|
35
|
-
/** Icon — THC (string/html/component/snippet). The visible content.
|
|
36
|
-
|
|
35
|
+
/** Icon — THC (string/html/component/snippet). The visible content.
|
|
36
|
+
* Required for the default rendering; ignored when `render` is provided
|
|
37
|
+
* (the snippet owns its own DOM). */
|
|
38
|
+
icon?: THC;
|
|
37
39
|
/** Accessible label (aria-label). */
|
|
38
40
|
label: THC;
|
|
39
41
|
/** Click handler */
|
|
@@ -48,6 +50,29 @@ export interface HeaderActionItem {
|
|
|
48
50
|
disabled?: boolean;
|
|
49
51
|
/** Additional CSS classes */
|
|
50
52
|
class?: string;
|
|
53
|
+
/** Optional custom renderer. When provided, replaces the default
|
|
54
|
+
* `<Button>` rendering for this action. The Header still owns
|
|
55
|
+
* positioning (slot in the actions row) and the collapse decision.
|
|
56
|
+
*
|
|
57
|
+
* Use this when an action needs custom DOM around its trigger —
|
|
58
|
+
* e.g. a popover/tooltip directive, or a count/dot badge overlay.
|
|
59
|
+
*
|
|
60
|
+
* Snippet args:
|
|
61
|
+
* - `action` — the item itself (lets a snippet be reused across items)
|
|
62
|
+
* - `class` — the same merged class the default `<Button>` would receive,
|
|
63
|
+
* so consumers can opt into the default look
|
|
64
|
+
* - `isCollapsed`— current collapse state
|
|
65
|
+
* - `onclick` — pre-wired handler that calls `action.onclick` and
|
|
66
|
+
* `onActionSelect`; consumers can wire it to their
|
|
67
|
+
* button or ignore it. */
|
|
68
|
+
render?: Snippet<[
|
|
69
|
+
{
|
|
70
|
+
action: HeaderActionItem;
|
|
71
|
+
class: string;
|
|
72
|
+
isCollapsed: boolean;
|
|
73
|
+
onclick: () => void;
|
|
74
|
+
}
|
|
75
|
+
]>;
|
|
51
76
|
}
|
|
52
77
|
/** Collapse behavior when the header drops below `collapseThreshold`:
|
|
53
78
|
* - "hamburger": nav items fold into a trailing dropdown along with the
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Header
|
|
2
|
+
|
|
3
|
+
Top-bar component with leading slot, project logo, nav items, locale switcher, optional action buttons, avatar, and responsive collapse (either fold into a trailing hamburger dropdown OR hide nav entirely).
|
|
4
|
+
|
|
5
|
+
## Examples
|
|
6
|
+
|
|
7
|
+
### App-like collapse: avatar + actions visible, everything else hidden
|
|
8
|
+
|
|
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).
|
|
10
|
+
|
|
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. |
|
|
18
|
+
|
|
19
|
+
Minimal config:
|
|
20
|
+
|
|
21
|
+
```svelte
|
|
22
|
+
<Header
|
|
23
|
+
projectName="App"
|
|
24
|
+
items={navItems} <!-- shown expanded, hidden collapsed -->
|
|
25
|
+
actions={[...]} <!-- always visible -->
|
|
26
|
+
collapseMode="hide" <!-- no trailing hamburger; avatar stays -->
|
|
27
|
+
leadingHamburger <!-- optional: drives a drawer for the hidden nav -->
|
|
28
|
+
onLeadingHamburger={() => (drawerOpen = true)}
|
|
29
|
+
{locales} {activeLocale} <!-- hidden in collapsed (keepLocaleOnCollapse defaults to false) -->
|
|
30
|
+
onLocaleChange={(id) => (activeLocale = id)}
|
|
31
|
+
avatarOnClick={() => alert("Profile")} <!-- safe in "hide" mode — won't move into dropdown -->
|
|
32
|
+
>
|
|
33
|
+
{#snippet avatar()}<Avatar initials="MM" autoColor />{/snippet}
|
|
34
|
+
</Header>
|
|
35
|
+
```
|
|
@@ -21,6 +21,29 @@
|
|
|
21
21
|
/* Actions (icon buttons in the end region) */
|
|
22
22
|
--stuic-header-actions-gap: 0.25rem;
|
|
23
23
|
|
|
24
|
+
/* Hamburger offsets — outdent the built-in hamburger buttons on both
|
|
25
|
+
inline sides so the *icon* (not the invisible iconButton hit area)
|
|
26
|
+
sits flush with the header's content padding on one side and tightens
|
|
27
|
+
against its neighbor (logo / avatar) on the other side. The flex
|
|
28
|
+
`gap` between header regions kicks in *after* the button's invisible
|
|
29
|
+
padding, so without these the perceived gap is `--stuic-header-gap` +
|
|
30
|
+
one button padding.
|
|
31
|
+
Default magnitude is derived from the iconButton's own padding so the
|
|
32
|
+
alignment stays correct if button padding ever changes. Set to 0 to
|
|
33
|
+
disable; override each side independently if needed. */
|
|
34
|
+
--stuic-header-leading-hamburger-offset-inline-start: calc(
|
|
35
|
+
-1 * var(--stuic-button-padding-y-sm)
|
|
36
|
+
);
|
|
37
|
+
--stuic-header-leading-hamburger-offset-inline-end: calc(
|
|
38
|
+
-1 * var(--stuic-button-padding-y-sm)
|
|
39
|
+
);
|
|
40
|
+
--stuic-header-trailing-hamburger-offset-inline-start: calc(
|
|
41
|
+
-1 * var(--stuic-button-padding-y-sm)
|
|
42
|
+
);
|
|
43
|
+
--stuic-header-trailing-hamburger-offset-inline-end: calc(
|
|
44
|
+
-1 * var(--stuic-button-padding-y-sm)
|
|
45
|
+
);
|
|
46
|
+
|
|
24
47
|
/* Project name */
|
|
25
48
|
--stuic-header-project-name-font-weight: var(--font-weight-semibold, 600);
|
|
26
49
|
|
|
@@ -79,7 +102,12 @@
|
|
|
79
102
|
}
|
|
80
103
|
|
|
81
104
|
.stuic-header-leading-hamburger {
|
|
82
|
-
/*
|
|
105
|
+
/* Outdent on both inline sides so the icon (not the iconButton hit
|
|
106
|
+
area) aligns with the header's content padding on the start and
|
|
107
|
+
tightens against the logo on the end. Override each side via its
|
|
108
|
+
own token. */
|
|
109
|
+
margin-inline-start: var(--stuic-header-leading-hamburger-offset-inline-start);
|
|
110
|
+
margin-inline-end: var(--stuic-header-leading-hamburger-offset-inline-end);
|
|
83
111
|
}
|
|
84
112
|
|
|
85
113
|
/* ============================================================================
|
|
@@ -214,6 +242,11 @@
|
|
|
214
242
|
============================================================================ */
|
|
215
243
|
|
|
216
244
|
.stuic-header-hamburger {
|
|
217
|
-
/*
|
|
245
|
+
/* Symmetric counterpart to the leading hamburger — outdent on both
|
|
246
|
+
inline sides so the icon tightens against its left neighbor
|
|
247
|
+
(avatar) on the start and aligns with the header's content padding
|
|
248
|
+
on the end. Override each side via its own token. */
|
|
249
|
+
margin-inline-start: var(--stuic-header-trailing-hamburger-offset-inline-start);
|
|
250
|
+
margin-inline-end: var(--stuic-header-trailing-hamburger-offset-inline-end);
|
|
218
251
|
}
|
|
219
252
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# IconSwap
|
|
2
|
+
|
|
3
|
+
Cross-fades between N visual states (HTML strings or Snippets) at a single position. Commonly used for hamburger ⇄ X toggles, play ⇄ pause, sun ⇄ moon, etc. Respects `prefers-reduced-motion` (animation duration is forced to 0).
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
| ------------ | -------------------------- | -------- | -------------------------------------------------------------------------- |
|
|
9
|
+
| `states` | `Array<string \| Snippet>` | required | The visual states to swap between. Strings are rendered with `{@html}`. |
|
|
10
|
+
| `active` | `number` | `0` | Bindable index of the currently visible state. |
|
|
11
|
+
| `duration` | `number` | `300` | Transition duration in ms. Set `0` to disable. |
|
|
12
|
+
| `easing` | `string` | `"ease"` | CSS `transition-timing-function`. |
|
|
13
|
+
| `unstyled` | `boolean` | `false` | Skip default styling. |
|
|
14
|
+
| `class` | `string` | - | Additional CSS classes for the root `<span>`. |
|
|
15
|
+
| `stateClass` | `string` | - | Additional CSS classes for each state wrapper. |
|
|
16
|
+
| `el` | `HTMLSpanElement` | - | Bindable root element. |
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### Hamburger ⇄ X
|
|
21
|
+
|
|
22
|
+
```svelte
|
|
23
|
+
<script lang="ts">
|
|
24
|
+
import { IconSwap, iconMenu, iconX } from "@marianmeres/stuic";
|
|
25
|
+
|
|
26
|
+
let isOpen = $state(false);
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<button onclick={() => (isOpen = !isOpen)}>
|
|
30
|
+
<IconSwap active={isOpen ? 1 : 0} states={[iconMenu(), iconX()]} />
|
|
31
|
+
</button>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### With snippets
|
|
35
|
+
|
|
36
|
+
```svelte
|
|
37
|
+
<IconSwap active={tab}>
|
|
38
|
+
{#snippet states_0()}<span>A</span>{/snippet}
|
|
39
|
+
{#snippet states_1()}<strong>B</strong>{/snippet}
|
|
40
|
+
{#snippet states_2()}<em>C</em>{/snippet}
|
|
41
|
+
</IconSwap>
|
|
42
|
+
|
|
43
|
+
<!-- equivalent with an array of snippets -->
|
|
44
|
+
<IconSwap active={tab} states={[stateA, stateB, stateC]} />
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Custom easing/duration
|
|
48
|
+
|
|
49
|
+
```svelte
|
|
50
|
+
<IconSwap states={frames} active={i} duration={500} easing="cubic-bezier(0.4, 0, 0.2, 1)" />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## CSS Variables
|
|
54
|
+
|
|
55
|
+
| Variable | Default | Description |
|
|
56
|
+
| ----------------------------- | ------- | -------------------------- |
|
|
57
|
+
| `--stuic-icon-swap-duration` | (prop) | Transition duration |
|
|
58
|
+
| `--stuic-icon-swap-easing` | (prop) | Transition timing function |
|
|
59
|
+
|
|
60
|
+
## Data Attributes
|
|
61
|
+
|
|
62
|
+
- `data-active` (root) — current active index
|
|
63
|
+
- `data-visible="true"` (state wrapper) — present on the visible state
|
|
64
|
+
|
|
65
|
+
## Behavior
|
|
66
|
+
|
|
67
|
+
- All states render concurrently; the inactive ones have `opacity: 0` and `aria-hidden="true"`.
|
|
68
|
+
- The root is positioned so all states stack at the same coordinates.
|
|
69
|
+
- `active` is clamped to `[0, states.length - 1]`.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# ImageCycler
|
|
2
|
+
|
|
3
|
+
Auto-cycling background-image carousel with fade transitions. Preloads the next image before swapping, so transitions never reveal a half-loaded asset. Optional `title` and `description` snippets render an absolutely-positioned meta layer on top of the image.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
| -------------------- | ----------------------------------------------------------------- | --------- | ---------------------------------------------------------------------- |
|
|
9
|
+
| `images` | `ImageCyclerImage[]` | required | Images to cycle through. |
|
|
10
|
+
| `fit` | `"cover" \| "contain" \| "fill"` | `"cover"` | Background-size mode. Set via `data-fit` on the image layer. |
|
|
11
|
+
| `minWait` | `number` | `3000` | Minimum wait (ms) on each image before advancing. |
|
|
12
|
+
| `transitionDuration` | `number` | `500` | Fade duration in ms (for both the image and the meta layer). |
|
|
13
|
+
| `onclick` | `(image, index) => void` | - | Click handler. The snippets receive a forwarded version. |
|
|
14
|
+
| `title` | `Snippet<[{ image, index, onclick }]>` | - | Title overlay snippet. |
|
|
15
|
+
| `description` | `Snippet<[{ image, index, onclick }]>` | - | Description overlay snippet. |
|
|
16
|
+
| `unstyled` | `boolean` | `false` | Skip default styling. |
|
|
17
|
+
| `class` | `string` | - | Additional CSS classes. |
|
|
18
|
+
| `el` | `HTMLElement` | - | Bindable root element. |
|
|
19
|
+
|
|
20
|
+
## `ImageCyclerImage`
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
interface ImageCyclerImage {
|
|
24
|
+
src: string;
|
|
25
|
+
alt?: string;
|
|
26
|
+
title?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
[key: string]: unknown; // arbitrary extra fields are preserved
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### Minimal
|
|
35
|
+
|
|
36
|
+
```svelte
|
|
37
|
+
<script lang="ts">
|
|
38
|
+
import { ImageCycler } from "@marianmeres/stuic";
|
|
39
|
+
|
|
40
|
+
const slides = [
|
|
41
|
+
{ src: "/hero/a.jpg", alt: "A" },
|
|
42
|
+
{ src: "/hero/b.jpg", alt: "B" },
|
|
43
|
+
{ src: "/hero/c.jpg", alt: "C" },
|
|
44
|
+
];
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<div style="aspect-ratio: 16/9;">
|
|
48
|
+
<ImageCycler images={slides} />
|
|
49
|
+
</div>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### With overlay + click
|
|
53
|
+
|
|
54
|
+
```svelte
|
|
55
|
+
<ImageCycler
|
|
56
|
+
images={slides}
|
|
57
|
+
minWait={5000}
|
|
58
|
+
transitionDuration={800}
|
|
59
|
+
onclick={(img, i) => console.log("clicked", i, img)}
|
|
60
|
+
>
|
|
61
|
+
{#snippet title({ image, onclick })}
|
|
62
|
+
<button type="button" {onclick} class="absolute bottom-12 left-6 text-white">
|
|
63
|
+
{image.title}
|
|
64
|
+
</button>
|
|
65
|
+
{/snippet}
|
|
66
|
+
{#snippet description({ image })}
|
|
67
|
+
<p class="absolute bottom-4 left-6 text-white/80">{image.description}</p>
|
|
68
|
+
{/snippet}
|
|
69
|
+
</ImageCycler>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## CSS Variables
|
|
73
|
+
|
|
74
|
+
| Variable | Default | Description |
|
|
75
|
+
| ------------------------------------------- | ------- | --------------------- |
|
|
76
|
+
| `--stuic-image-cycler-transition-duration` | `500ms` | (informational; the active transition duration comes from the `transitionDuration` prop) |
|
|
77
|
+
|
|
78
|
+
## Notes
|
|
79
|
+
|
|
80
|
+
- The root has `position: relative; overflow: hidden`. The parent must define a height (`aspect-ratio`, fixed height, etc.) since the image layer is absolutely positioned.
|
|
81
|
+
- Only the next image is preloaded; earlier images aren't kept warm.
|
|
82
|
+
- A single-image `images` array disables the cycler effect.
|
|
83
|
+
- The `aria-label` for the background layer falls back to `alt → title → ""`.
|