@marianmeres/stuic 3.95.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 +265 -35
- package/dist/components/Header/Header.svelte.d.ts +109 -4
- package/dist/components/Header/README.md +35 -0
- package/dist/components/Header/index.css +95 -7
- package/dist/components/Header/index.d.ts +1 -1
- package/dist/components/Header/index.js +1 -1
- 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 +5 -5
|
@@ -29,17 +29,92 @@ 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
|
+
* Required for the default rendering; ignored when `render` is provided
|
|
37
|
+
* (the snippet owns its own DOM). */
|
|
38
|
+
icon?: THC;
|
|
39
|
+
/** Accessible label (aria-label). */
|
|
40
|
+
label: THC;
|
|
41
|
+
/** Click handler */
|
|
42
|
+
onclick?: () => void;
|
|
43
|
+
/** Render as a link instead of a button */
|
|
44
|
+
href?: string;
|
|
45
|
+
/** Link target (e.g., "_blank"). Only relevant when href is set. */
|
|
46
|
+
target?: string;
|
|
47
|
+
/** Active state styling (e.g., when a panel triggered by this action is open) */
|
|
48
|
+
active?: boolean;
|
|
49
|
+
/** Whether this action is disabled */
|
|
50
|
+
disabled?: boolean;
|
|
51
|
+
/** Additional CSS classes */
|
|
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
|
+
]>;
|
|
76
|
+
}
|
|
77
|
+
/** Collapse behavior when the header drops below `collapseThreshold`:
|
|
78
|
+
* - "hamburger": nav items fold into a trailing dropdown along with the
|
|
79
|
+
* locale switcher and an interactive avatar.
|
|
80
|
+
* - "hide": nav items are hidden entirely. No trailing hamburger renders.
|
|
81
|
+
* Avatar stays visible. Locale visibility is controlled by
|
|
82
|
+
* `keepLocaleOnCollapse`. */
|
|
83
|
+
export type HeaderCollapseMode = "hamburger" | "hide";
|
|
84
|
+
/** Visibility for the built-in leading hamburger button:
|
|
85
|
+
* - false/undefined: not rendered
|
|
86
|
+
* - true: always rendered
|
|
87
|
+
* - "collapsed": only rendered when the header is below the collapse threshold */
|
|
88
|
+
export type HeaderLeadingHamburger = boolean | "collapsed";
|
|
32
89
|
export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
90
|
+
/** Leading (left-side) slot. Renders before the logo/title.
|
|
91
|
+
* Use for a hamburger button, back arrow, breadcrumbs, etc.
|
|
92
|
+
* When provided, overrides the built-in `leadingHamburger`. */
|
|
93
|
+
leading?: Snippet<[{
|
|
94
|
+
isCollapsed: boolean;
|
|
95
|
+
}]>;
|
|
96
|
+
/** Convenience: render a built-in hamburger button in the leading slot.
|
|
97
|
+
* Ignored when the `leading` snippet is provided. */
|
|
98
|
+
leadingHamburger?: HeaderLeadingHamburger;
|
|
99
|
+
/** Click handler for the built-in leading hamburger (typically opens a drawer) */
|
|
100
|
+
onLeadingHamburger?: () => void;
|
|
101
|
+
/** Icon for the built-in leading hamburger (defaults to a menu icon) */
|
|
102
|
+
leadingHamburgerIcon?: THC;
|
|
103
|
+
/** Aria-label for the built-in leading hamburger (defaults to "Open menu") */
|
|
104
|
+
leadingHamburgerLabel?: string;
|
|
33
105
|
/** Logo/brand snippet — full control over the left branding area */
|
|
34
106
|
logo?: Snippet;
|
|
35
|
-
/** Horizontal alignment of the nav items in expanded mode */
|
|
36
|
-
navAlign?: "left" | "right";
|
|
37
107
|
/** Button variant for nav items and locale trigger (defaults to "ghost") */
|
|
38
108
|
navVariant?: ButtonVariant;
|
|
39
109
|
/** Simple text alternative to the logo snippet */
|
|
40
110
|
projectName?: string;
|
|
41
111
|
/** Navigation items — inline when expanded, DropdownMenu when collapsed */
|
|
42
112
|
items?: HeaderNavItem[];
|
|
113
|
+
/** Action icon buttons displayed between the locale switcher and the avatar.
|
|
114
|
+
* Always visible — they do not fold into the trailing dropdown. */
|
|
115
|
+
actions?: HeaderActionItem[];
|
|
116
|
+
/** Called when an action is selected (in addition to the per-item onclick) */
|
|
117
|
+
onActionSelect?: (action: HeaderActionItem) => void;
|
|
43
118
|
/** Avatar/user snippet — rendered at the far right */
|
|
44
119
|
avatar?: Snippet;
|
|
45
120
|
/** When provided, makes the avatar interactive. In expanded mode wraps it in a
|
|
@@ -55,8 +130,21 @@ export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
|
55
130
|
onLocaleChange?: (localeId: string) => void;
|
|
56
131
|
/** Section header label for locales in collapsed dropdown (defaults to "Language") */
|
|
57
132
|
localeLabel?: THC;
|
|
133
|
+
/** Max-width of the inner content row. The outer `<header>` stays 100%
|
|
134
|
+
* wide (background fills the parent); the inner content is centered and
|
|
135
|
+
* capped at this value. Accepts any CSS length: "1024px", "72rem",
|
|
136
|
+
* "100%", "none". Default: undefined → unbounded.
|
|
137
|
+
* Equivalent global override:
|
|
138
|
+
* `:root { --stuic-header-content-max-width: 72rem; }` */
|
|
139
|
+
contentMaxWidth?: string | number;
|
|
58
140
|
/** Element width (px) below which nav collapses to hamburger. 0 to disable. */
|
|
59
141
|
collapseThreshold?: number;
|
|
142
|
+
/** Collapse behavior when below threshold (defaults to "hamburger") */
|
|
143
|
+
collapseMode?: HeaderCollapseMode;
|
|
144
|
+
/** When `collapseMode === "hide"`, keep the locale switcher visible in
|
|
145
|
+
* collapsed mode. No effect when `collapseMode === "hamburger"`
|
|
146
|
+
* (locale already folds into the trailing dropdown there). */
|
|
147
|
+
keepLocaleOnCollapse?: boolean;
|
|
60
148
|
/** Fixed positioning (top of viewport) */
|
|
61
149
|
fixed?: boolean;
|
|
62
150
|
/** Bindable: whether the header is currently in collapsed (hamburger) mode */
|
|
@@ -73,6 +161,12 @@ export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
|
73
161
|
unstyled?: boolean;
|
|
74
162
|
/** Additional CSS classes for the root <header> */
|
|
75
163
|
class?: string;
|
|
164
|
+
/** Classes for the inner content wrapper */
|
|
165
|
+
classContent?: string;
|
|
166
|
+
/** Classes for the leading area */
|
|
167
|
+
classLeading?: string;
|
|
168
|
+
/** Classes for the built-in leading hamburger button */
|
|
169
|
+
classLeadingHamburger?: string;
|
|
76
170
|
/** Classes for the logo area */
|
|
77
171
|
classLogo?: string;
|
|
78
172
|
/** Classes for the nav area (expanded mode) */
|
|
@@ -81,13 +175,19 @@ export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
|
81
175
|
classNavItem?: string;
|
|
82
176
|
/** Classes for active nav items */
|
|
83
177
|
classNavItemActive?: string;
|
|
84
|
-
/** Classes for the
|
|
178
|
+
/** Classes for the actions wrapper */
|
|
179
|
+
classActions?: string;
|
|
180
|
+
/** Classes for individual action buttons */
|
|
181
|
+
classAction?: string;
|
|
182
|
+
/** Classes for active action buttons */
|
|
183
|
+
classActionActive?: string;
|
|
184
|
+
/** Classes for the end area (locale + avatar + trailing hamburger) */
|
|
85
185
|
classEnd?: string;
|
|
86
186
|
/** Classes for the avatar container */
|
|
87
187
|
classAvatar?: string;
|
|
88
188
|
/** Classes for the locale switcher trigger (expanded mode) */
|
|
89
189
|
classLocale?: string;
|
|
90
|
-
/** Classes for the hamburger button */
|
|
190
|
+
/** Classes for the trailing (right-side) hamburger button */
|
|
91
191
|
classHamburger?: string;
|
|
92
192
|
/** Classes for the dropdown wrapper (collapsed mode) */
|
|
93
193
|
classDropdown?: string;
|
|
@@ -103,9 +203,14 @@ export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
|
103
203
|
el?: HTMLElement;
|
|
104
204
|
}
|
|
105
205
|
export declare const HEADER_BASE_CLASSES = "stuic-header";
|
|
206
|
+
export declare const HEADER_CONTENT_CLASSES = "stuic-header-content";
|
|
207
|
+
export declare const HEADER_LEADING_CLASSES = "stuic-header-leading";
|
|
208
|
+
export declare const HEADER_LEADING_HAMBURGER_CLASSES = "stuic-header-leading-hamburger";
|
|
106
209
|
export declare const HEADER_LOGO_CLASSES = "stuic-header-logo";
|
|
107
210
|
export declare const HEADER_NAV_CLASSES = "stuic-header-nav";
|
|
108
211
|
export declare const HEADER_NAV_ITEM_CLASSES = "stuic-header-nav-item";
|
|
212
|
+
export declare const HEADER_ACTIONS_CLASSES = "stuic-header-actions";
|
|
213
|
+
export declare const HEADER_ACTION_CLASSES = "stuic-header-action";
|
|
109
214
|
export declare const HEADER_END_CLASSES = "stuic-header-end";
|
|
110
215
|
export declare const HEADER_HAMBURGER_CLASSES = "stuic-header-hamburger";
|
|
111
216
|
export declare const HEADER_LOCALE_CLASSES = "stuic-header-locale";
|
|
@@ -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
|
+
```
|
|
@@ -11,9 +11,39 @@
|
|
|
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
|
+
|
|
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
|
+
|
|
17
47
|
/* Project name */
|
|
18
48
|
--stuic-header-project-name-font-weight: var(--font-weight-semibold, 600);
|
|
19
49
|
|
|
@@ -27,14 +57,26 @@
|
|
|
27
57
|
============================================================================ */
|
|
28
58
|
|
|
29
59
|
.stuic-header {
|
|
60
|
+
width: 100%;
|
|
61
|
+
background: var(--stuic-header-bg, var(--color-background, inherit));
|
|
62
|
+
color: var(--stuic-header-text, inherit);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* ============================================================================
|
|
66
|
+
CONTENT ROW (inner wrapper — flex layout, padding, gap, optional max-width)
|
|
67
|
+
The outer .stuic-header keeps full width / background; this inner row
|
|
68
|
+
carries the layout so it can be capped via `contentMaxWidth` and centered.
|
|
69
|
+
============================================================================ */
|
|
70
|
+
|
|
71
|
+
.stuic-header-content {
|
|
30
72
|
display: flex;
|
|
31
73
|
align-items: center;
|
|
32
|
-
|
|
74
|
+
gap: var(--stuic-header-gap);
|
|
33
75
|
min-height: var(--stuic-header-min-height);
|
|
34
76
|
padding: var(--stuic-header-padding-y) var(--stuic-header-padding-x);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
77
|
+
width: 100%;
|
|
78
|
+
max-width: var(--stuic-header-content-max-width);
|
|
79
|
+
margin-inline: auto;
|
|
38
80
|
}
|
|
39
81
|
|
|
40
82
|
/* ============================================================================
|
|
@@ -50,15 +92,36 @@
|
|
|
50
92
|
}
|
|
51
93
|
|
|
52
94
|
/* ============================================================================
|
|
53
|
-
|
|
95
|
+
LEADING (left-side slot — hamburger, back arrow, etc.)
|
|
54
96
|
============================================================================ */
|
|
55
97
|
|
|
56
|
-
.stuic-header-
|
|
98
|
+
.stuic-header-leading {
|
|
57
99
|
display: flex;
|
|
58
100
|
align-items: center;
|
|
59
101
|
flex-shrink: 0;
|
|
60
102
|
}
|
|
61
103
|
|
|
104
|
+
.stuic-header-leading-hamburger {
|
|
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);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* ============================================================================
|
|
114
|
+
LOGO / TITLE (takes available space — flex-1 with min-width: 0 so long
|
|
115
|
+
content can shrink/ellipsize instead of overflowing the row)
|
|
116
|
+
============================================================================ */
|
|
117
|
+
|
|
118
|
+
.stuic-header-logo {
|
|
119
|
+
display: flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
flex: 1;
|
|
122
|
+
min-width: 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
62
125
|
.stuic-header-project-name {
|
|
63
126
|
font-size: var(--stuic-header-project-name-font-size, var(--text-lg, 1.125rem));
|
|
64
127
|
font-weight: var(--stuic-header-project-name-font-weight);
|
|
@@ -91,6 +154,26 @@
|
|
|
91
154
|
flex-shrink: 0;
|
|
92
155
|
}
|
|
93
156
|
|
|
157
|
+
/* ============================================================================
|
|
158
|
+
ACTIONS (icon buttons in the end region — search, notifications, etc.)
|
|
159
|
+
============================================================================ */
|
|
160
|
+
|
|
161
|
+
.stuic-header-actions {
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
gap: var(--stuic-header-actions-gap);
|
|
165
|
+
flex-shrink: 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.stuic-header-action {
|
|
169
|
+
/* Ghost iconButton handles base styling — placeholder hook for consumer overrides */
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.stuic-header-action[data-active] {
|
|
173
|
+
color: var(--stuic-header-action-text-active, var(--color-foreground, inherit));
|
|
174
|
+
background: var(--stuic-header-action-bg-active, var(--color-muted, transparent));
|
|
175
|
+
}
|
|
176
|
+
|
|
94
177
|
/* ============================================================================
|
|
95
178
|
SPACER
|
|
96
179
|
============================================================================ */
|
|
@@ -159,6 +242,11 @@
|
|
|
159
242
|
============================================================================ */
|
|
160
243
|
|
|
161
244
|
.stuic-header-hamburger {
|
|
162
|
-
/*
|
|
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);
|
|
163
251
|
}
|
|
164
252
|
}
|
|
@@ -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";
|
|
@@ -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 → ""`.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# LoginForm
|
|
2
|
+
|
|
3
|
+
Standalone login form with email/password fields, optional social/OAuth buttons, forgot-password link, remember-me checkbox, and client + server validation. Ships with English defaults for 16 i18n keys; pass a `t` function to translate.
|
|
4
|
+
|
|
5
|
+
`LoginForm` is the form-only component; `LoginFormModal` wraps it in a `Modal` with an opener trigger.
|
|
6
|
+
|
|
7
|
+
## Exports
|
|
8
|
+
|
|
9
|
+
| Export | Kind | Description |
|
|
10
|
+
| -------------------------- | --------- | ------------------------------------------------- |
|
|
11
|
+
| `LoginForm` | component | Form component |
|
|
12
|
+
| `LoginFormModal` | component | Modal-wrapped form with optional trigger snippet |
|
|
13
|
+
| `LoginFormProps` | type | Props for `LoginForm` |
|
|
14
|
+
| `LoginFormModalProps` | type | Props for `LoginFormModal` |
|
|
15
|
+
| `LoginFormData` | type | `{ email, password, rememberMe }` |
|
|
16
|
+
| `LoginFormValidationError` | type | `{ field, message }` |
|
|
17
|
+
| `createEmptyLoginFormData` | function | Factory for an empty `LoginFormData` |
|
|
18
|
+
| `validateLoginForm` | function | `(data, t) => LoginFormValidationError[]` |
|
|
19
|
+
|
|
20
|
+
## LoginForm — Props
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
| --------------------- | ------------------------------------------------- | -------- | ------------------------------------------------------------------------------------ |
|
|
24
|
+
| `formData` | `LoginFormData` | empty | Bindable form data. |
|
|
25
|
+
| `onSubmit` | `(data: LoginFormData) => void` | required | Called when client-side validation passes. |
|
|
26
|
+
| `isSubmitting` | `boolean` | `false` | Disables the CTA during submission. |
|
|
27
|
+
| `errors` | `LoginFormValidationError[]` | `[]` | Field-specific server errors (merged with internal validation). |
|
|
28
|
+
| `error` | `string` | - | General error rendered as a `DismissibleMessage` above the form. |
|
|
29
|
+
| `onForgotPassword` | `() => void` | - | Click handler for the "Forgot password?" link. Link is hidden when undefined. |
|
|
30
|
+
| `showRememberMe` | `boolean` | `true` | Render the remember-me checkbox. |
|
|
31
|
+
| `submitLabel` | `string` | i18n | Override the CTA label. |
|
|
32
|
+
| `submittingLabel` | `string` | i18n | Override the CTA label while submitting. |
|
|
33
|
+
| `submitButton` | `Snippet<[{ isSubmitting, disabled }]>` | - | Override the entire CTA section. |
|
|
34
|
+
| `socialLogins` | `Snippet` | - | Social/OAuth buttons rendered below the form. A divider is shown above when set. |
|
|
35
|
+
| `socialDividerLabel` | `string \| false` | i18n | Override (or hide with `false`) the divider above social buttons. |
|
|
36
|
+
| `footer` | `Snippet` | - | Content below the form (e.g., sign-up links). |
|
|
37
|
+
| `notifications` | `NotificationsStack` | - | When set, general errors are also pushed via `notifications.error()`. |
|
|
38
|
+
| `t` | `TranslateFn` | English | i18n function. |
|
|
39
|
+
| `unstyled` / `class` | - | - | Standard styling escape hatches. |
|
|
40
|
+
| `el` | `HTMLFormElement` | - | Bindable form element. |
|
|
41
|
+
|
|
42
|
+
### Imperative methods
|
|
43
|
+
|
|
44
|
+
`bind:this` exposes:
|
|
45
|
+
|
|
46
|
+
| Method | Returns | Purpose |
|
|
47
|
+
| ------------------------------------- | --------- | ------------------------------------------------------------------------ |
|
|
48
|
+
| `validate()` | `boolean` | Forces every inner field's validator to run. `true` if all valid. |
|
|
49
|
+
| `scrollToFirstError(opts?)` | `boolean` | Scrolls + focuses the first invalid field. Call after `validate()`. |
|
|
50
|
+
|
|
51
|
+
These are essential for pristine-form errors (server errors set via the `errors` prop won't render until the user touches each field). See [`docs/domains/components.md#imperative-validate-api`](../../../docs/domains/components.md#imperative-validate-api) for the full pattern.
|
|
52
|
+
|
|
53
|
+
## LoginFormModal — extra props
|
|
54
|
+
|
|
55
|
+
Inherits all `LoginForm` props, plus:
|
|
56
|
+
|
|
57
|
+
| Prop | Type | Default | Description |
|
|
58
|
+
| --------------------- | ----------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------- |
|
|
59
|
+
| `title` | `string` | `"Log In"` (i18n) | Modal title. |
|
|
60
|
+
| `visible` | `boolean` | `false` | Bindable modal visibility. |
|
|
61
|
+
| `trigger` | `Snippet<[{ open }]>` | - | Optional trigger element rendered outside the modal. |
|
|
62
|
+
| `classModal` | `string` | - | Class for the Modal box. |
|
|
63
|
+
| `classInner` | `string` | - | Class for the Modal inner width container. |
|
|
64
|
+
| `classForm` | `string` | - | Class forwarded to the inner `LoginForm`. |
|
|
65
|
+
| `noXClose` | `boolean` | `false` | Hide the close (X) button. |
|
|
66
|
+
| `onClose` | `() => false \| void` | - | Pre-close hook. Return `false` to prevent close. |
|
|
67
|
+
| `noClickOutsideClose` | `boolean` | `true` | Disable backdrop-click close (default-on to protect typed credentials). |
|
|
68
|
+
|
|
69
|
+
**Methods:** `open(openerOrEvent?)`, `close()` — exposed via `bind:this`.
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
### Basic form
|
|
74
|
+
|
|
75
|
+
```svelte
|
|
76
|
+
<script lang="ts">
|
|
77
|
+
import { LoginForm } from "@marianmeres/stuic";
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<LoginForm
|
|
81
|
+
onSubmit={(data) => login(data)}
|
|
82
|
+
onForgotPassword={() => goto("/forgot-password")}
|
|
83
|
+
/>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### With social logins + footer
|
|
87
|
+
|
|
88
|
+
```svelte
|
|
89
|
+
<LoginForm onSubmit={handleLogin}>
|
|
90
|
+
{#snippet socialLogins()}
|
|
91
|
+
<Button variant="outline" onclick={loginWithGoogle}>Google</Button>
|
|
92
|
+
<Button variant="outline" onclick={loginWithGitHub}>GitHub</Button>
|
|
93
|
+
{/snippet}
|
|
94
|
+
{#snippet footer()}
|
|
95
|
+
<p>Don't have an account? <a href="/register">Sign up</a></p>
|
|
96
|
+
{/snippet}
|
|
97
|
+
</LoginForm>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Modal with trigger
|
|
101
|
+
|
|
102
|
+
```svelte
|
|
103
|
+
<LoginFormModal onSubmit={handleLogin}>
|
|
104
|
+
{#snippet trigger({ open })}
|
|
105
|
+
<Button onclick={open}>Log In</Button>
|
|
106
|
+
{/snippet}
|
|
107
|
+
</LoginFormModal>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Imperative validation from a custom CTA
|
|
111
|
+
|
|
112
|
+
```svelte
|
|
113
|
+
<script lang="ts">
|
|
114
|
+
import { LoginForm } from "@marianmeres/stuic";
|
|
115
|
+
let form = $state<LoginForm>();
|
|
116
|
+
|
|
117
|
+
function submit() {
|
|
118
|
+
if (!form?.validate()) {
|
|
119
|
+
form?.scrollToFirstError();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// ... actually submit ...
|
|
123
|
+
}
|
|
124
|
+
</script>
|
|
125
|
+
|
|
126
|
+
<LoginForm bind:this={form} onSubmit={submit} />
|
|
127
|
+
<Button onclick={submit}>Submit from outside</Button>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## CSS Variables
|
|
131
|
+
|
|
132
|
+
Prefix: `--stuic-login-form-*`
|
|
133
|
+
|
|
134
|
+
| Variable | Purpose |
|
|
135
|
+
| ------------------------------------------------- | -------------------------------------- |
|
|
136
|
+
| `--stuic-login-form-gap` | Vertical gap between sections |
|
|
137
|
+
| `--stuic-login-form-gap-row` | Gap inside multi-column rows |
|
|
138
|
+
| `--stuic-login-form-forgot-margin-y` | Forgot-password link vertical margin |
|
|
139
|
+
| `--stuic-login-form-forgot-margin-x` | Forgot-password link horizontal margin |
|
|
140
|
+
| `--stuic-login-form-social-margin-top` | Margin above social buttons block |
|
|
141
|
+
| `--stuic-login-form-social-gap` | Gap between social buttons |
|
|
142
|
+
| `--stuic-login-form-social-divider-color` | Divider text color |
|
|
143
|
+
| `--stuic-login-form-social-divider-font-size` | Divider text size |
|
|
144
|
+
| `--stuic-login-form-social-divider-margin-bottom` | Divider bottom margin |
|
|
145
|
+
|
|
146
|
+
## i18n keys
|
|
147
|
+
|
|
148
|
+
All keys are under the `login_form.*` namespace and cover labels, placeholders, validation messages, the submit/submitting CTA, the social divider, and modal title. See `_internal/login-form-i18n-defaults.ts` for the full list and English defaults.
|