@marianmeres/stuic 3.51.2 → 3.52.1
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 +1 -1
- package/API.md +151 -0
- package/README.md +3 -3
- package/dist/components/Header/Header.svelte +315 -0
- package/dist/components/Header/Header.svelte.d.ts +90 -0
- package/dist/components/Header/index.css +133 -0
- package/dist/components/Header/index.d.ts +1 -0
- package/dist/components/Header/index.js +1 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/docs/domains/components.md +212 -13
- package/package.json +1 -1
package/AGENTS.md
CHANGED
package/API.md
CHANGED
|
@@ -130,6 +130,41 @@ Panel-based navigation with slide transitions.
|
|
|
130
130
|
|
|
131
131
|
Navigation wrapper component.
|
|
132
132
|
|
|
133
|
+
#### `Header`
|
|
134
|
+
|
|
135
|
+
Responsive navigation header with logo, nav items, avatar, and automatic hamburger collapse. Renders as `<header>`.
|
|
136
|
+
|
|
137
|
+
| Prop | Type | Default | Description |
|
|
138
|
+
| ------------------- | ----------------- | --------------------- | ---------------------------------- |
|
|
139
|
+
| `logo` | `Snippet` | — | Logo/brand snippet |
|
|
140
|
+
| `projectName` | `string` | — | Simple text logo alternative |
|
|
141
|
+
| `items` | `HeaderNavItem[]` | `[]` | Navigation items |
|
|
142
|
+
| `avatar` | `Snippet` | — | Avatar snippet (far right) |
|
|
143
|
+
| `avatarOnClick` | `() => void` | — | Avatar click handler |
|
|
144
|
+
| `collapseThreshold` | `number` | `768` | Width (px) to collapse; 0 disables |
|
|
145
|
+
| `fixed` | `boolean` | `false` | Fixed positioning at top |
|
|
146
|
+
| `isCollapsed` | `boolean` | — | Bindable: collapsed state |
|
|
147
|
+
| `isMenuOpen` | `boolean` | — | Bindable: hamburger menu open |
|
|
148
|
+
| `onSelect` | `(item) => void` | — | Item selection callback |
|
|
149
|
+
|
|
150
|
+
```svelte
|
|
151
|
+
<Header
|
|
152
|
+
projectName="My App"
|
|
153
|
+
items={[
|
|
154
|
+
{ id: "home", label: "Home", href: "/", active: true },
|
|
155
|
+
{ id: "about", label: "About", href: "/about" },
|
|
156
|
+
]}
|
|
157
|
+
>
|
|
158
|
+
{#snippet avatar()}
|
|
159
|
+
<Avatar src="/me.jpg" alt="User" />
|
|
160
|
+
{/snippet}
|
|
161
|
+
</Header>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**HeaderNavItem:** `{ id, label, href?, onclick?, icon?, active?, disabled?, class? }`
|
|
165
|
+
|
|
166
|
+
CSS tokens: `--stuic-header-padding-x`, `--stuic-header-padding-y`, `--stuic-header-gap`, `--stuic-header-min-height`, `--stuic-header-nav-gap`, `--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
|
+
|
|
133
168
|
---
|
|
134
169
|
|
|
135
170
|
### Interactive
|
|
@@ -316,6 +351,42 @@ Dual-mode JSON object editor with pretty-print and raw edit modes. Validates JSO
|
|
|
316
351
|
<FieldObject bind:value={jsonStr} name="metadata" label="Metadata" />
|
|
317
352
|
```
|
|
318
353
|
|
|
354
|
+
#### `CronInput`
|
|
355
|
+
|
|
356
|
+
Cron expression editor with preset selector, manual 5-field editor, raw expression input, human-readable descriptions, and next-run calculation.
|
|
357
|
+
|
|
358
|
+
| Prop | Type | Default | Description |
|
|
359
|
+
| ----------------- | ----------------------------- | ------------- | ------------------------------- |
|
|
360
|
+
| `value` | `string` | `"* * * * *"` | Bindable cron expression |
|
|
361
|
+
| `mode` | `CronInputMode` | — | Bindable; predefined or manual |
|
|
362
|
+
| `showPresets` | `boolean` | `true` | Show preset selector |
|
|
363
|
+
| `showFields` | `boolean` | `true` | Show 5-column field editor |
|
|
364
|
+
| `showRawInput` | `boolean` | `true` | Show raw expression input |
|
|
365
|
+
| `showDescription` | `boolean` | `true` | Show human-readable description |
|
|
366
|
+
| `showNextRun` | `boolean` | `true` | Show next run time |
|
|
367
|
+
| `presets` | `CronPreset[]` | default set | Custom presets |
|
|
368
|
+
| `onchange` | `(expr: string, valid: boolean) => void` | — | Change callback |
|
|
369
|
+
|
|
370
|
+
Integrates with `InputWrap` — supports `label`, `description`, `renderSize`, `required`, `disabled`, `validate`, `labelLeft`, `below`.
|
|
371
|
+
|
|
372
|
+
```svelte
|
|
373
|
+
<CronInput bind:value={cronExpr} label="Schedule" showNextRun />
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Exports: `CronInput`, `CronInputProps`, `CronPreset`, `CronInputMode`, `CRON_DEFAULT_PRESETS`, `CronNextRun`.
|
|
377
|
+
|
|
378
|
+
**CronNextRun** — reactive helper class for standalone next-run calculations:
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
const next = new CronNextRun("0 9 * * 1-5");
|
|
382
|
+
next.nextRun; // Date | null
|
|
383
|
+
next.nextRunFormatted; // "YYYY-MM-DD HH:MM"
|
|
384
|
+
next.valid; // boolean
|
|
385
|
+
next.destroy(); // cleanup timer
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
CSS tokens: `--stuic-cron-input-fields-gap`, `--stuic-cron-input-section-gap`, `--stuic-cron-input-field-label-text`, `--stuic-cron-input-summary-text`, `--stuic-cron-input-error-text`, `--stuic-cron-input-field-bg`, `--stuic-cron-input-field-border`, `--stuic-cron-input-field-border-focus`.
|
|
389
|
+
|
|
319
390
|
#### `Fieldset`
|
|
320
391
|
|
|
321
392
|
Form field grouping with legend.
|
|
@@ -1090,6 +1161,86 @@ Always-visible (non-modal) variant of AssetsPreview with the same zoom, pan, swi
|
|
|
1090
1161
|
/>
|
|
1091
1162
|
```
|
|
1092
1163
|
|
|
1164
|
+
#### `Card`
|
|
1165
|
+
|
|
1166
|
+
Flexible, responsive card with image, title, description, footer, and interactive states. Renders as `<a>`, `<button>`, or `<div>` depending on props.
|
|
1167
|
+
|
|
1168
|
+
| Prop | Type | Default | Description |
|
|
1169
|
+
| --------------------- | ------------- | ------------ | ------------------------------------------------- |
|
|
1170
|
+
| `image` | `string` | — | Image URL |
|
|
1171
|
+
| `imageAlt` | `string` | `""` | Alt text |
|
|
1172
|
+
| `eyebrow` | `THC` | — | Small label above title |
|
|
1173
|
+
| `title` | `THC` | — | Card title |
|
|
1174
|
+
| `description` | `THC` | — | Short description |
|
|
1175
|
+
| `variant` | `CardVariant` | `"vertical"` | `"vertical"` or `"horizontal"` layout |
|
|
1176
|
+
| `href` | `string` | — | Renders as `<a>` |
|
|
1177
|
+
| `onclick` | `(e) => void` | — | Renders as `<button>` |
|
|
1178
|
+
| `disabled` | `boolean` | `false` | Disable interaction |
|
|
1179
|
+
| `horizontalThreshold` | `number` | `480` | Width (px) to auto-switch to vertical; 0 disables |
|
|
1180
|
+
|
|
1181
|
+
```svelte
|
|
1182
|
+
<Card
|
|
1183
|
+
image="/photo.jpg"
|
|
1184
|
+
eyebrow="New"
|
|
1185
|
+
title="Product Name"
|
|
1186
|
+
description="A brief description."
|
|
1187
|
+
href="/products/1"
|
|
1188
|
+
>
|
|
1189
|
+
{#snippet renderFooter()}
|
|
1190
|
+
<Button variant="ghost" size="sm">Details</Button>
|
|
1191
|
+
{/snippet}
|
|
1192
|
+
</Card>
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
Snippets: `children`, `renderImage({ image, imageAlt })`, `renderBadge`, `renderContent({ eyebrow?, title?, description? })`, `renderFooter`.
|
|
1196
|
+
|
|
1197
|
+
Exports: `Card`, `CardProps`, `CardVariant`.
|
|
1198
|
+
|
|
1199
|
+
CSS tokens: `--stuic-card-bg`, `--stuic-card-bg-hover`, `--stuic-card-border`, `--stuic-card-border-hover`, `--stuic-card-padding`, `--stuic-card-content-gap`, `--stuic-card-image-aspect-ratio`, `--stuic-card-image-object-fit`, `--stuic-card-image-width-horizontal`, `--stuic-card-radius`, `--stuic-card-shadow`, `--stuic-card-shadow-hover`, `--stuic-card-ring-width`, `--stuic-card-ring-color`, `--stuic-card-eyebrow-font-size`, `--stuic-card-title-font-size`, `--stuic-card-title-font-weight`, `--stuic-card-description-font-size`, `--stuic-card-opacity-disabled`.
|
|
1200
|
+
|
|
1201
|
+
#### `Tree`
|
|
1202
|
+
|
|
1203
|
+
Accessible, keyboard-navigable hierarchical tree view with optional drag-and-drop reordering, expand/collapse, and localStorage persistence.
|
|
1204
|
+
|
|
1205
|
+
| Prop | Type | Default | Description |
|
|
1206
|
+
| ----------------- | ---------------------------------------- | ------------ | ------------------------------------ |
|
|
1207
|
+
| `items` | `TreeNodeDTO<T>[]` | required | Tree data |
|
|
1208
|
+
| `renderItem` | `Snippet<[item, depth, isExpanded]>` | — | Custom item content |
|
|
1209
|
+
| `renderIcon` | `Snippet<[item, depth, isExpanded]>` | — | Custom item icon |
|
|
1210
|
+
| `activeId` | `string` | — | Selected item ID |
|
|
1211
|
+
| `isActive` | `(item) => boolean` | — | Custom active check |
|
|
1212
|
+
| `onSelect` | `(item) => void` | — | Item selection callback |
|
|
1213
|
+
| `onToggle` | `(item, expanded) => void` | — | Branch toggle callback |
|
|
1214
|
+
| `sort` | `(a, b) => number` | — | Sort comparator (per-level) |
|
|
1215
|
+
| `defaultExpanded` | `boolean` | `false` | Default expand state |
|
|
1216
|
+
| `expandedIds` | `Set<string>` | — | Initially expanded IDs |
|
|
1217
|
+
| `persistState` | `boolean` | `false` | Save expand state to localStorage |
|
|
1218
|
+
| `draggable` | `boolean` | `false` | Enable drag-and-drop |
|
|
1219
|
+
| `isDraggable` | `(item) => boolean` | — | Per-item drag control |
|
|
1220
|
+
| `isDropTarget` | `(item) => boolean` | — | Per-item drop target control |
|
|
1221
|
+
| `onMove` | `(event: TreeMoveEvent) => void \| false`| — | Drop handler; return false to reject |
|
|
1222
|
+
| `dragExpandDelay` | `number` | `800` | Auto-expand delay (ms) on drag hover |
|
|
1223
|
+
|
|
1224
|
+
```svelte
|
|
1225
|
+
<Tree
|
|
1226
|
+
items={treeData}
|
|
1227
|
+
activeId={selectedId}
|
|
1228
|
+
onSelect={(item) => (selectedId = item.id)}
|
|
1229
|
+
draggable
|
|
1230
|
+
onMove={handleMove}
|
|
1231
|
+
>
|
|
1232
|
+
{#snippet renderItem(item, depth, isExpanded)}
|
|
1233
|
+
<span>{item.value}</span>
|
|
1234
|
+
{/snippet}
|
|
1235
|
+
</Tree>
|
|
1236
|
+
```
|
|
1237
|
+
|
|
1238
|
+
Keyboard: Arrow keys (up/down/left/right), Enter/Space, Home/End.
|
|
1239
|
+
|
|
1240
|
+
Exports: `Tree`, `TreeProps`, `TreeMoveEvent`, `TreeDropPosition`, `TreeNodeDTO`, `TREE_BASE_CLASSES`, `TREE_ITEM_CLASSES`, `TREE_CHILDREN_CLASSES`.
|
|
1241
|
+
|
|
1242
|
+
CSS tokens: `--stuic-tree-indent`, `--stuic-tree-item-padding-x`, `--stuic-tree-item-padding-y`, `--stuic-tree-item-height`, `--stuic-tree-item-font-size`, `--stuic-tree-item-gap`, `--stuic-tree-chevron-opacity`, `--stuic-tree-icon-opacity`, `--stuic-tree-item-opacity-dragging`, `--stuic-tree-item-bg`, `--stuic-tree-item-text`, `--stuic-tree-item-bg-hover`, `--stuic-tree-item-text-hover`, `--stuic-tree-item-bg-active`, `--stuic-tree-item-text-active`, `--stuic-tree-item-bg-focus`, `--stuic-tree-item-text-focus`, `--stuic-tree-drop-indicator-color`, `--stuic-tree-drop-indicator-height`, `--stuic-tree-item-bg-dragover`.
|
|
1243
|
+
|
|
1093
1244
|
#### `X`
|
|
1094
1245
|
|
|
1095
1246
|
Styled SVG close/multiply icon with configurable stroke width.
|
package/README.md
CHANGED
|
@@ -144,11 +144,11 @@ See [API.md](API.md) for the full list of exported theme types (`ThemeSchema`, `
|
|
|
144
144
|
|
|
145
145
|
### Layout & Overlays
|
|
146
146
|
|
|
147
|
-
AppShell, Accordion, Backdrop, Modal, ModalDialog, Drawer, Collapsible, SlidingPanels, Nav, WithSidePanel
|
|
147
|
+
AppShell, Accordion, Backdrop, Modal, ModalDialog, Drawer, Collapsible, Header, SlidingPanels, Nav, WithSidePanel
|
|
148
148
|
|
|
149
149
|
### Forms & Inputs
|
|
150
150
|
|
|
151
|
-
FieldInput, FieldTextarea, FieldSelect, FieldCheckbox, FieldRadios, FieldFile, FieldAssets, FieldOptions, FieldKeyValues, FieldObject, FieldSwitch, FieldInputLocalized, FieldLikeButton, FieldPhoneNumber, Fieldset, LoginForm, LoginFormModal
|
|
151
|
+
FieldInput, FieldTextarea, FieldSelect, FieldCheckbox, FieldRadios, FieldFile, FieldAssets, FieldOptions, FieldKeyValues, FieldObject, FieldSwitch, FieldInputLocalized, FieldLikeButton, FieldPhoneNumber, CronInput, Fieldset, LoginForm, LoginFormModal
|
|
152
152
|
|
|
153
153
|
### Buttons & Controls
|
|
154
154
|
|
|
@@ -164,7 +164,7 @@ CommandMenu, DropdownMenu, TabbedMenu, TypeaheadInput, KbdShortcut
|
|
|
164
164
|
|
|
165
165
|
### Display & Utility
|
|
166
166
|
|
|
167
|
-
Avatar, Book, BookResponsive, Carousel, Circle, AnimatedElipsis, H, IconSwap, ImageCycler, Separator, ThemePreview, ColorScheme, Thc, HoverExpandableWidth, AssetsPreview, AssetsPreviewInline, DataTable
|
|
167
|
+
Avatar, Book, BookResponsive, Card, Carousel, Circle, AnimatedElipsis, H, IconSwap, ImageCycler, Separator, ThemePreview, Tree, ColorScheme, Thc, HoverExpandableWidth, AssetsPreview, AssetsPreviewInline, DataTable
|
|
168
168
|
|
|
169
169
|
### E-commerce
|
|
170
170
|
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
5
|
+
import type {
|
|
6
|
+
DropdownMenuPosition,
|
|
7
|
+
DropdownMenuItem,
|
|
8
|
+
DropdownMenuActionItem,
|
|
9
|
+
} from "../DropdownMenu/DropdownMenu.svelte";
|
|
10
|
+
|
|
11
|
+
export interface HeaderNavItem {
|
|
12
|
+
/** Unique identifier */
|
|
13
|
+
id: string | number;
|
|
14
|
+
/** Display label — supports THC (string, html, component, snippet) */
|
|
15
|
+
label: THC;
|
|
16
|
+
/** Navigation URL (renders as <a> in expanded mode) */
|
|
17
|
+
href?: string;
|
|
18
|
+
/** Click handler (alternative to href) */
|
|
19
|
+
onclick?: () => void;
|
|
20
|
+
/** Icon before the label — supports THC */
|
|
21
|
+
icon?: THC;
|
|
22
|
+
/** Whether this item is the current/active page */
|
|
23
|
+
active?: boolean;
|
|
24
|
+
/** Whether this item is disabled */
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
/** Additional CSS classes */
|
|
27
|
+
class?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
31
|
+
/** Logo/brand snippet — full control over the left branding area */
|
|
32
|
+
logo?: Snippet;
|
|
33
|
+
/** Simple text alternative to the logo snippet */
|
|
34
|
+
projectName?: string;
|
|
35
|
+
/** Navigation items — inline when expanded, DropdownMenu when collapsed */
|
|
36
|
+
items?: HeaderNavItem[];
|
|
37
|
+
/** Avatar/user snippet — rendered at the far right */
|
|
38
|
+
avatar?: Snippet;
|
|
39
|
+
/** When provided, makes the avatar interactive. In expanded mode wraps it in a
|
|
40
|
+
* button; in collapsed mode moves it into the dropdown as an action item. */
|
|
41
|
+
avatarOnClick?: () => void;
|
|
42
|
+
/** Label for the avatar dropdown item in collapsed mode (defaults to "Account") */
|
|
43
|
+
avatarLabel?: THC;
|
|
44
|
+
/** Element width (px) below which nav collapses to hamburger. 0 to disable. */
|
|
45
|
+
collapseThreshold?: number;
|
|
46
|
+
/** Fixed positioning (top of viewport) */
|
|
47
|
+
fixed?: boolean;
|
|
48
|
+
/** Bindable: whether the header is currently in collapsed (hamburger) mode */
|
|
49
|
+
isCollapsed?: boolean;
|
|
50
|
+
/** Bindable: whether the hamburger dropdown is currently open */
|
|
51
|
+
isMenuOpen?: boolean;
|
|
52
|
+
/** Position for the collapsed dropdown menu */
|
|
53
|
+
dropdownPosition?: DropdownMenuPosition;
|
|
54
|
+
/** Hamburger/X icon size in px */
|
|
55
|
+
iconSize?: number;
|
|
56
|
+
/** Called when a nav item is selected (both modes) */
|
|
57
|
+
onSelect?: (item: HeaderNavItem) => void;
|
|
58
|
+
/** Skip all default styling */
|
|
59
|
+
unstyled?: boolean;
|
|
60
|
+
/** Additional CSS classes for the root <header> */
|
|
61
|
+
class?: string;
|
|
62
|
+
/** Classes for the logo area */
|
|
63
|
+
classLogo?: string;
|
|
64
|
+
/** Classes for the nav area (expanded mode) */
|
|
65
|
+
classNav?: string;
|
|
66
|
+
/** Classes for individual nav items (expanded mode) */
|
|
67
|
+
classNavItem?: string;
|
|
68
|
+
/** Classes for active nav items */
|
|
69
|
+
classNavItemActive?: string;
|
|
70
|
+
/** Classes for the end area (avatar + hamburger) */
|
|
71
|
+
classEnd?: string;
|
|
72
|
+
/** Classes for the avatar container */
|
|
73
|
+
classAvatar?: string;
|
|
74
|
+
/** Classes for the hamburger button */
|
|
75
|
+
classHamburger?: string;
|
|
76
|
+
/** Classes for the dropdown wrapper (collapsed mode) */
|
|
77
|
+
classDropdown?: string;
|
|
78
|
+
/** Escape hatch: override the entire inner layout */
|
|
79
|
+
children?: Snippet<
|
|
80
|
+
[{ isCollapsed: boolean; items: HeaderNavItem[]; offsetWidth: number }]
|
|
81
|
+
>;
|
|
82
|
+
/** Bindable root element reference */
|
|
83
|
+
el?: HTMLElement;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const HEADER_BASE_CLASSES = "stuic-header";
|
|
87
|
+
export const HEADER_LOGO_CLASSES = "stuic-header-logo";
|
|
88
|
+
export const HEADER_NAV_CLASSES = "stuic-header-nav";
|
|
89
|
+
export const HEADER_NAV_ITEM_CLASSES = "stuic-header-nav-item";
|
|
90
|
+
export const HEADER_END_CLASSES = "stuic-header-end";
|
|
91
|
+
export const HEADER_HAMBURGER_CLASSES = "stuic-header-hamburger";
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<script lang="ts">
|
|
95
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
96
|
+
import { iconMenu, iconX } from "../../icons/index.js";
|
|
97
|
+
import Thc from "../Thc/Thc.svelte";
|
|
98
|
+
import Button from "../Button/Button.svelte";
|
|
99
|
+
import DropdownMenu from "../DropdownMenu/DropdownMenu.svelte";
|
|
100
|
+
import IconSwap from "../IconSwap/IconSwap.svelte";
|
|
101
|
+
|
|
102
|
+
let {
|
|
103
|
+
logo,
|
|
104
|
+
projectName,
|
|
105
|
+
items = [],
|
|
106
|
+
avatar,
|
|
107
|
+
avatarOnClick,
|
|
108
|
+
avatarLabel = "Account",
|
|
109
|
+
collapseThreshold = 768,
|
|
110
|
+
fixed = false,
|
|
111
|
+
isCollapsed = $bindable(false),
|
|
112
|
+
isMenuOpen = $bindable(false),
|
|
113
|
+
dropdownPosition = "bottom-span-right",
|
|
114
|
+
iconSize = 24,
|
|
115
|
+
onSelect,
|
|
116
|
+
unstyled = false,
|
|
117
|
+
class: classProp,
|
|
118
|
+
classLogo,
|
|
119
|
+
classNav,
|
|
120
|
+
classNavItem,
|
|
121
|
+
classNavItemActive,
|
|
122
|
+
classEnd,
|
|
123
|
+
classAvatar,
|
|
124
|
+
classHamburger,
|
|
125
|
+
classDropdown,
|
|
126
|
+
children,
|
|
127
|
+
el = $bindable(),
|
|
128
|
+
...rest
|
|
129
|
+
}: Props = $props();
|
|
130
|
+
|
|
131
|
+
// Width measurement (same pattern as Card)
|
|
132
|
+
let _offsetWidth = $state(0);
|
|
133
|
+
|
|
134
|
+
// Collapsed state based on threshold
|
|
135
|
+
let _isCollapsed = $derived.by(() => {
|
|
136
|
+
if (!collapseThreshold) return false;
|
|
137
|
+
return _offsetWidth > 0 && _offsetWidth < collapseThreshold;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Sync bindable
|
|
141
|
+
$effect(() => {
|
|
142
|
+
isCollapsed = _isCollapsed;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Close menu when switching from collapsed to expanded
|
|
146
|
+
$effect(() => {
|
|
147
|
+
if (!_isCollapsed && isMenuOpen) {
|
|
148
|
+
isMenuOpen = false;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Whether the avatar moves into the dropdown when collapsed
|
|
153
|
+
let _avatarInDropdown = $derived(!!(avatar && avatarOnClick));
|
|
154
|
+
|
|
155
|
+
// Map HeaderNavItem[] to DropdownMenuItem[] for collapsed mode
|
|
156
|
+
let _dropdownItems = $derived.by((): DropdownMenuItem[] => {
|
|
157
|
+
const navItems: DropdownMenuItem[] = items.map(
|
|
158
|
+
(item) =>
|
|
159
|
+
({
|
|
160
|
+
type: "action" as const,
|
|
161
|
+
id: item.id,
|
|
162
|
+
label: item.label,
|
|
163
|
+
contentBefore: item.icon,
|
|
164
|
+
disabled: item.disabled,
|
|
165
|
+
class: item.class,
|
|
166
|
+
onSelect: () => {
|
|
167
|
+
if (item.href) window.location.href = item.href;
|
|
168
|
+
item.onclick?.();
|
|
169
|
+
onSelect?.(item);
|
|
170
|
+
},
|
|
171
|
+
}) satisfies DropdownMenuActionItem
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Append avatar as a dropdown item when avatarOnClick is set
|
|
175
|
+
if (_avatarInDropdown) {
|
|
176
|
+
if (navItems.length > 0) {
|
|
177
|
+
navItems.push({ type: "divider" });
|
|
178
|
+
}
|
|
179
|
+
navItems.push({
|
|
180
|
+
type: "action",
|
|
181
|
+
id: "__avatar__",
|
|
182
|
+
label: avatarLabel!,
|
|
183
|
+
onSelect: () => avatarOnClick!(),
|
|
184
|
+
} satisfies DropdownMenuActionItem);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return navItems;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// CSS classes
|
|
191
|
+
let _class = $derived(unstyled ? classProp : twMerge(HEADER_BASE_CLASSES, classProp));
|
|
192
|
+
let _classLogo = $derived(
|
|
193
|
+
unstyled ? classLogo : twMerge(HEADER_LOGO_CLASSES, classLogo)
|
|
194
|
+
);
|
|
195
|
+
let _classNav = $derived(unstyled ? classNav : twMerge(HEADER_NAV_CLASSES, classNav));
|
|
196
|
+
let _classEnd = $derived(unstyled ? classEnd : twMerge(HEADER_END_CLASSES, classEnd));
|
|
197
|
+
|
|
198
|
+
function handleItemClick(item: HeaderNavItem) {
|
|
199
|
+
if (item.disabled) return;
|
|
200
|
+
item.onclick?.();
|
|
201
|
+
onSelect?.(item);
|
|
202
|
+
}
|
|
203
|
+
</script>
|
|
204
|
+
|
|
205
|
+
<header
|
|
206
|
+
bind:this={el}
|
|
207
|
+
bind:offsetWidth={_offsetWidth}
|
|
208
|
+
class={_class}
|
|
209
|
+
data-fixed={!unstyled && fixed ? "" : undefined}
|
|
210
|
+
data-collapsed={!unstyled && _isCollapsed ? "" : undefined}
|
|
211
|
+
{...rest}
|
|
212
|
+
>
|
|
213
|
+
{#if children}
|
|
214
|
+
{@render children({
|
|
215
|
+
isCollapsed: _isCollapsed,
|
|
216
|
+
items,
|
|
217
|
+
offsetWidth: _offsetWidth,
|
|
218
|
+
})}
|
|
219
|
+
{:else}
|
|
220
|
+
<!-- Logo / Brand -->
|
|
221
|
+
{#if logo || projectName}
|
|
222
|
+
<div class={_classLogo}>
|
|
223
|
+
{#if logo}
|
|
224
|
+
{@render logo()}
|
|
225
|
+
{:else if projectName}
|
|
226
|
+
<span class={unstyled ? undefined : "stuic-header-project-name"}>
|
|
227
|
+
{projectName}
|
|
228
|
+
</span>
|
|
229
|
+
{/if}
|
|
230
|
+
</div>
|
|
231
|
+
{/if}
|
|
232
|
+
|
|
233
|
+
<!-- Nav items (expanded mode) -->
|
|
234
|
+
{#if !_isCollapsed && items.length > 0}
|
|
235
|
+
<nav class={_classNav}>
|
|
236
|
+
{#each items as item (item.id)}
|
|
237
|
+
<Button
|
|
238
|
+
variant="ghost"
|
|
239
|
+
size="sm"
|
|
240
|
+
href={item.href}
|
|
241
|
+
disabled={item.disabled}
|
|
242
|
+
{unstyled}
|
|
243
|
+
class={twMerge(
|
|
244
|
+
!unstyled && HEADER_NAV_ITEM_CLASSES,
|
|
245
|
+
!unstyled && item.active && classNavItemActive,
|
|
246
|
+
classNavItem,
|
|
247
|
+
item.class
|
|
248
|
+
)}
|
|
249
|
+
data-active={!unstyled && item.active ? "" : undefined}
|
|
250
|
+
aria-current={item.active ? "page" : undefined}
|
|
251
|
+
onclick={() => handleItemClick(item)}
|
|
252
|
+
>
|
|
253
|
+
{#if item.icon}
|
|
254
|
+
<span class={unstyled ? undefined : "stuic-header-nav-icon"}>
|
|
255
|
+
<Thc thc={item.icon} />
|
|
256
|
+
</span>
|
|
257
|
+
{/if}
|
|
258
|
+
<Thc thc={item.label} />
|
|
259
|
+
</Button>
|
|
260
|
+
{/each}
|
|
261
|
+
</nav>
|
|
262
|
+
{/if}
|
|
263
|
+
|
|
264
|
+
<!-- Spacer -->
|
|
265
|
+
<div class={unstyled ? undefined : "stuic-header-spacer"}></div>
|
|
266
|
+
|
|
267
|
+
<!-- End area: avatar + hamburger -->
|
|
268
|
+
<div class={_classEnd}>
|
|
269
|
+
<!-- Avatar: hidden when collapsed + avatarOnClick (moves into dropdown) -->
|
|
270
|
+
{#if avatar && !(_isCollapsed && _avatarInDropdown)}
|
|
271
|
+
{#if avatarOnClick}
|
|
272
|
+
<button
|
|
273
|
+
type="button"
|
|
274
|
+
class={twMerge(!unstyled && "stuic-header-avatar", classAvatar)}
|
|
275
|
+
onclick={avatarOnClick}
|
|
276
|
+
>
|
|
277
|
+
{@render avatar()}
|
|
278
|
+
</button>
|
|
279
|
+
{:else}
|
|
280
|
+
<div class={twMerge(!unstyled && "stuic-header-avatar", classAvatar)}>
|
|
281
|
+
{@render avatar()}
|
|
282
|
+
</div>
|
|
283
|
+
{/if}
|
|
284
|
+
{/if}
|
|
285
|
+
|
|
286
|
+
{#if _isCollapsed && _dropdownItems.length > 0}
|
|
287
|
+
<DropdownMenu
|
|
288
|
+
items={_dropdownItems}
|
|
289
|
+
bind:isOpen={isMenuOpen}
|
|
290
|
+
position={dropdownPosition}
|
|
291
|
+
class={classDropdown}
|
|
292
|
+
>
|
|
293
|
+
{#snippet trigger({ isOpen, toggle, triggerProps })}
|
|
294
|
+
<Button
|
|
295
|
+
variant="ghost"
|
|
296
|
+
roundedFull
|
|
297
|
+
aspect1
|
|
298
|
+
size="sm"
|
|
299
|
+
{unstyled}
|
|
300
|
+
class={twMerge(!unstyled && HEADER_HAMBURGER_CLASSES, classHamburger)}
|
|
301
|
+
onclick={toggle}
|
|
302
|
+
aria-label={isOpen ? "Close menu" : "Open menu"}
|
|
303
|
+
{...triggerProps}
|
|
304
|
+
>
|
|
305
|
+
<IconSwap
|
|
306
|
+
active={isOpen ? 1 : 0}
|
|
307
|
+
states={[iconMenu({ size: iconSize }), iconX({ size: iconSize })]}
|
|
308
|
+
/>
|
|
309
|
+
</Button>
|
|
310
|
+
{/snippet}
|
|
311
|
+
</DropdownMenu>
|
|
312
|
+
{/if}
|
|
313
|
+
</div>
|
|
314
|
+
{/if}
|
|
315
|
+
</header>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
4
|
+
import type { DropdownMenuPosition } from "../DropdownMenu/DropdownMenu.svelte";
|
|
5
|
+
export interface HeaderNavItem {
|
|
6
|
+
/** Unique identifier */
|
|
7
|
+
id: string | number;
|
|
8
|
+
/** Display label — supports THC (string, html, component, snippet) */
|
|
9
|
+
label: THC;
|
|
10
|
+
/** Navigation URL (renders as <a> in expanded mode) */
|
|
11
|
+
href?: string;
|
|
12
|
+
/** Click handler (alternative to href) */
|
|
13
|
+
onclick?: () => void;
|
|
14
|
+
/** Icon before the label — supports THC */
|
|
15
|
+
icon?: THC;
|
|
16
|
+
/** Whether this item is the current/active page */
|
|
17
|
+
active?: boolean;
|
|
18
|
+
/** Whether this item is disabled */
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
/** Additional CSS classes */
|
|
21
|
+
class?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
24
|
+
/** Logo/brand snippet — full control over the left branding area */
|
|
25
|
+
logo?: Snippet;
|
|
26
|
+
/** Simple text alternative to the logo snippet */
|
|
27
|
+
projectName?: string;
|
|
28
|
+
/** Navigation items — inline when expanded, DropdownMenu when collapsed */
|
|
29
|
+
items?: HeaderNavItem[];
|
|
30
|
+
/** Avatar/user snippet — rendered at the far right */
|
|
31
|
+
avatar?: Snippet;
|
|
32
|
+
/** When provided, makes the avatar interactive. In expanded mode wraps it in a
|
|
33
|
+
* button; in collapsed mode moves it into the dropdown as an action item. */
|
|
34
|
+
avatarOnClick?: () => void;
|
|
35
|
+
/** Label for the avatar dropdown item in collapsed mode (defaults to "Account") */
|
|
36
|
+
avatarLabel?: THC;
|
|
37
|
+
/** Element width (px) below which nav collapses to hamburger. 0 to disable. */
|
|
38
|
+
collapseThreshold?: number;
|
|
39
|
+
/** Fixed positioning (top of viewport) */
|
|
40
|
+
fixed?: boolean;
|
|
41
|
+
/** Bindable: whether the header is currently in collapsed (hamburger) mode */
|
|
42
|
+
isCollapsed?: boolean;
|
|
43
|
+
/** Bindable: whether the hamburger dropdown is currently open */
|
|
44
|
+
isMenuOpen?: boolean;
|
|
45
|
+
/** Position for the collapsed dropdown menu */
|
|
46
|
+
dropdownPosition?: DropdownMenuPosition;
|
|
47
|
+
/** Hamburger/X icon size in px */
|
|
48
|
+
iconSize?: number;
|
|
49
|
+
/** Called when a nav item is selected (both modes) */
|
|
50
|
+
onSelect?: (item: HeaderNavItem) => void;
|
|
51
|
+
/** Skip all default styling */
|
|
52
|
+
unstyled?: boolean;
|
|
53
|
+
/** Additional CSS classes for the root <header> */
|
|
54
|
+
class?: string;
|
|
55
|
+
/** Classes for the logo area */
|
|
56
|
+
classLogo?: string;
|
|
57
|
+
/** Classes for the nav area (expanded mode) */
|
|
58
|
+
classNav?: string;
|
|
59
|
+
/** Classes for individual nav items (expanded mode) */
|
|
60
|
+
classNavItem?: string;
|
|
61
|
+
/** Classes for active nav items */
|
|
62
|
+
classNavItemActive?: string;
|
|
63
|
+
/** Classes for the end area (avatar + hamburger) */
|
|
64
|
+
classEnd?: string;
|
|
65
|
+
/** Classes for the avatar container */
|
|
66
|
+
classAvatar?: string;
|
|
67
|
+
/** Classes for the hamburger button */
|
|
68
|
+
classHamburger?: string;
|
|
69
|
+
/** Classes for the dropdown wrapper (collapsed mode) */
|
|
70
|
+
classDropdown?: string;
|
|
71
|
+
/** Escape hatch: override the entire inner layout */
|
|
72
|
+
children?: Snippet<[
|
|
73
|
+
{
|
|
74
|
+
isCollapsed: boolean;
|
|
75
|
+
items: HeaderNavItem[];
|
|
76
|
+
offsetWidth: number;
|
|
77
|
+
}
|
|
78
|
+
]>;
|
|
79
|
+
/** Bindable root element reference */
|
|
80
|
+
el?: HTMLElement;
|
|
81
|
+
}
|
|
82
|
+
export declare const HEADER_BASE_CLASSES = "stuic-header";
|
|
83
|
+
export declare const HEADER_LOGO_CLASSES = "stuic-header-logo";
|
|
84
|
+
export declare const HEADER_NAV_CLASSES = "stuic-header-nav";
|
|
85
|
+
export declare const HEADER_NAV_ITEM_CLASSES = "stuic-header-nav-item";
|
|
86
|
+
export declare const HEADER_END_CLASSES = "stuic-header-end";
|
|
87
|
+
export declare const HEADER_HAMBURGER_CLASSES = "stuic-header-hamburger";
|
|
88
|
+
declare const Header: import("svelte").Component<Props, {}, "el" | "isCollapsed" | "isMenuOpen">;
|
|
89
|
+
type Header = ReturnType<typeof Header>;
|
|
90
|
+
export default Header;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
HEADER COMPONENT TOKENS
|
|
3
|
+
Override globally: :root { --stuic-header-bg: red; }
|
|
4
|
+
Override locally: <Header style="--stuic-header-bg: red;">
|
|
5
|
+
============================================================================ */
|
|
6
|
+
|
|
7
|
+
:root {
|
|
8
|
+
/* Structure */
|
|
9
|
+
--stuic-header-padding-x: 1rem;
|
|
10
|
+
--stuic-header-padding-y: 0.75rem;
|
|
11
|
+
--stuic-header-gap: 1rem;
|
|
12
|
+
--stuic-header-min-height: 3.5rem;
|
|
13
|
+
|
|
14
|
+
/* Nav items */
|
|
15
|
+
--stuic-header-nav-gap: 0.25rem;
|
|
16
|
+
|
|
17
|
+
/* Project name */
|
|
18
|
+
--stuic-header-project-name-font-weight: var(--font-weight-semibold, 600);
|
|
19
|
+
|
|
20
|
+
/* Fixed mode */
|
|
21
|
+
--stuic-header-z-index: 40;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@layer components {
|
|
25
|
+
/* ============================================================================
|
|
26
|
+
BASE
|
|
27
|
+
============================================================================ */
|
|
28
|
+
|
|
29
|
+
.stuic-header {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
width: 100%;
|
|
33
|
+
min-height: var(--stuic-header-min-height);
|
|
34
|
+
padding: var(--stuic-header-padding-y) var(--stuic-header-padding-x);
|
|
35
|
+
gap: var(--stuic-header-gap);
|
|
36
|
+
background: var(--stuic-header-bg, var(--color-background, inherit));
|
|
37
|
+
color: var(--stuic-header-text, inherit);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* ============================================================================
|
|
41
|
+
FIXED
|
|
42
|
+
============================================================================ */
|
|
43
|
+
|
|
44
|
+
.stuic-header[data-fixed] {
|
|
45
|
+
position: fixed;
|
|
46
|
+
top: 0;
|
|
47
|
+
left: 0;
|
|
48
|
+
right: 0;
|
|
49
|
+
z-index: var(--stuic-header-z-index);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* ============================================================================
|
|
53
|
+
LOGO
|
|
54
|
+
============================================================================ */
|
|
55
|
+
|
|
56
|
+
.stuic-header-logo {
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
flex-shrink: 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.stuic-header-project-name {
|
|
63
|
+
font-size: var(--stuic-header-project-name-font-size, var(--text-lg, 1.125rem));
|
|
64
|
+
font-weight: var(--stuic-header-project-name-font-weight);
|
|
65
|
+
white-space: nowrap;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* ============================================================================
|
|
69
|
+
NAV (expanded mode) — items are ghost Buttons
|
|
70
|
+
============================================================================ */
|
|
71
|
+
|
|
72
|
+
.stuic-header-nav {
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: var(--stuic-header-nav-gap);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Active state highlight for nav Buttons */
|
|
79
|
+
.stuic-header-nav-item[data-active] {
|
|
80
|
+
color: var(--stuic-header-nav-item-text-active, var(--color-foreground, inherit));
|
|
81
|
+
background: var(--stuic-header-nav-item-bg-active, var(--color-muted, transparent));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* ============================================================================
|
|
85
|
+
NAV ICON (inline icon before label)
|
|
86
|
+
============================================================================ */
|
|
87
|
+
|
|
88
|
+
.stuic-header-nav-icon {
|
|
89
|
+
display: inline-flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
flex-shrink: 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ============================================================================
|
|
95
|
+
SPACER
|
|
96
|
+
============================================================================ */
|
|
97
|
+
|
|
98
|
+
.stuic-header-spacer {
|
|
99
|
+
flex: 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* ============================================================================
|
|
103
|
+
END AREA (avatar + hamburger)
|
|
104
|
+
============================================================================ */
|
|
105
|
+
|
|
106
|
+
.stuic-header-end {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
gap: var(--stuic-header-gap);
|
|
110
|
+
flex-shrink: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.stuic-header-avatar {
|
|
114
|
+
display: flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
button.stuic-header-avatar {
|
|
119
|
+
border: none;
|
|
120
|
+
background: none;
|
|
121
|
+
padding: 0;
|
|
122
|
+
cursor: pointer;
|
|
123
|
+
-webkit-tap-highlight-color: transparent;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* ============================================================================
|
|
127
|
+
HAMBURGER
|
|
128
|
+
============================================================================ */
|
|
129
|
+
|
|
130
|
+
.stuic-header-hamburger {
|
|
131
|
+
/* Ghost Button handles hover/focus — only override sizing */
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Header, type Props as HeaderProps, type HeaderNavItem, HEADER_BASE_CLASSES, HEADER_LOGO_CLASSES, HEADER_NAV_CLASSES, HEADER_NAV_ITEM_CLASSES, HEADER_END_CLASSES, HEADER_HAMBURGER_CLASSES, } from "./Header.svelte";
|
|
@@ -0,0 +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, } from "./Header.svelte";
|
package/dist/index.css
CHANGED
|
@@ -68,6 +68,7 @@ In practice:
|
|
|
68
68
|
@import "./components/DismissibleMessage/index.css";
|
|
69
69
|
@import "./components/DropdownMenu/index.css";
|
|
70
70
|
@import "./components/H/index.css";
|
|
71
|
+
@import "./components/Header/index.css";
|
|
71
72
|
@import "./components/ImageCycler/index.css";
|
|
72
73
|
@import "./components/IconSwap/index.css";
|
|
73
74
|
@import "./components/Input/index.css";
|
package/dist/index.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export * from "./components/DismissibleMessage/index.js";
|
|
|
44
44
|
export * from "./components/Drawer/index.js";
|
|
45
45
|
export * from "./components/DropdownMenu/index.js";
|
|
46
46
|
export * from "./components/H/index.js";
|
|
47
|
+
export * from "./components/Header/index.js";
|
|
47
48
|
export * from "./components/ImageCycler/index.js";
|
|
48
49
|
export * from "./components/HoverExpandableWidth/index.js";
|
|
49
50
|
export * from "./components/IconSwap/index.js";
|
package/dist/index.js
CHANGED
|
@@ -45,6 +45,7 @@ export * from "./components/DismissibleMessage/index.js";
|
|
|
45
45
|
export * from "./components/Drawer/index.js";
|
|
46
46
|
export * from "./components/DropdownMenu/index.js";
|
|
47
47
|
export * from "./components/H/index.js";
|
|
48
|
+
export * from "./components/Header/index.js";
|
|
48
49
|
export * from "./components/ImageCycler/index.js";
|
|
49
50
|
export * from "./components/HoverExpandableWidth/index.js";
|
|
50
51
|
export * from "./components/IconSwap/index.js";
|
|
@@ -2,24 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
50 Svelte 5 component directories with consistent API patterns. All use runes-based reactivity.
|
|
6
6
|
|
|
7
7
|
## Component Categories
|
|
8
8
|
|
|
9
9
|
### Layout
|
|
10
10
|
|
|
11
|
-
| Component | Purpose
|
|
12
|
-
| ------------------------ |
|
|
13
|
-
| AppShell, AppShellSimple | Page layouts with header/sidebar/content
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
11
|
+
| Component | Purpose |
|
|
12
|
+
| ------------------------ | ---------------------------------------------------- |
|
|
13
|
+
| AppShell, AppShellSimple | Page layouts with header/sidebar/content |
|
|
14
|
+
| Header | Responsive nav header with hamburger collapse |
|
|
15
|
+
| Modal, ModalDialog | Overlay containers |
|
|
16
|
+
| Drawer | Side panel overlay |
|
|
17
|
+
| Backdrop | Semi-transparent overlay with escape/focus trap |
|
|
18
|
+
| Collapsible | Expandable sections |
|
|
19
|
+
| Accordion | Exclusive/multi-open expandable sections |
|
|
20
|
+
| SlidingPanels | Panel transitions |
|
|
21
|
+
| TabbedMenu | Tab navigation |
|
|
22
|
+
| Nav | Navigation wrapper |
|
|
23
|
+
| WithSidePanel | Two-column layout with collapsible/resizable panel |
|
|
23
24
|
|
|
24
25
|
### Interactive
|
|
25
26
|
|
|
@@ -56,6 +57,7 @@
|
|
|
56
57
|
| Input (FieldInput, FieldSelect, etc.) | Form fields |
|
|
57
58
|
| FieldPhoneNumber | International phone input with country picker |
|
|
58
59
|
| FieldObject | Dual-mode JSON object editor (pretty-print/raw) |
|
|
60
|
+
| CronInput | Cron expression editor with presets and validation |
|
|
59
61
|
| Fieldset | Field grouping with legend |
|
|
60
62
|
| FieldKeyValues | Key-value pair editor |
|
|
61
63
|
| FieldAssets | File/asset management |
|
|
@@ -81,6 +83,8 @@
|
|
|
81
83
|
| H | Semantic heading (h1-h6) with separate visual/semantic levels |
|
|
82
84
|
| Separator | Horizontal/vertical separator line |
|
|
83
85
|
| Thc | Flexible renderer for text, HTML, components, or snippets |
|
|
86
|
+
| Card | Flexible card with image, title, footer; vertical/horizontal layout |
|
|
87
|
+
| Tree | Hierarchical tree view with keyboard nav and drag-and-drop |
|
|
84
88
|
| X | Styled close/multiply SVG icon |
|
|
85
89
|
| ImageCycler | Auto-cycling image carousel with fade transitions and preloading |
|
|
86
90
|
|
|
@@ -373,6 +377,199 @@ Components use private CSS vars (`--_*`) set by intent/variant:
|
|
|
373
377
|
|
|
374
378
|
---
|
|
375
379
|
|
|
380
|
+
## Card
|
|
381
|
+
|
|
382
|
+
Flexible, responsive card for displaying content in a contained box. Supports images (top or side), titles, descriptions, footers, and interactive states.
|
|
383
|
+
|
|
384
|
+
### Exports
|
|
385
|
+
|
|
386
|
+
| Export | Kind | Description |
|
|
387
|
+
| ------------- | --------- | ------------------------------------ |
|
|
388
|
+
| `Card` | component | Main card component |
|
|
389
|
+
| `CardProps` | type | Props type |
|
|
390
|
+
| `CardVariant` | type | `"vertical" \| "horizontal"` |
|
|
391
|
+
|
|
392
|
+
### Key Props
|
|
393
|
+
|
|
394
|
+
| Prop | Type | Default | Description |
|
|
395
|
+
| ---------------------- | ------------------------ | ------------ | -------------------------------------------- |
|
|
396
|
+
| `image` | `string` | — | Image URL |
|
|
397
|
+
| `eyebrow` | `THC` | — | Small label above title |
|
|
398
|
+
| `title` | `THC` | — | Card title |
|
|
399
|
+
| `description` | `THC` | — | Short description |
|
|
400
|
+
| `variant` | `CardVariant` | `"vertical"` | Layout direction |
|
|
401
|
+
| `href` | `string` | — | Renders as `<a>` |
|
|
402
|
+
| `onclick` | `(e) => void` | — | Renders as `<button>` |
|
|
403
|
+
| `disabled` | `boolean` | `false` | Disable interaction |
|
|
404
|
+
| `horizontalThreshold` | `number` | `480` | Width (px) to auto-switch to vertical; 0 disables |
|
|
405
|
+
|
|
406
|
+
Snippets: `children`, `renderImage`, `renderBadge`, `renderContent`, `renderFooter`.
|
|
407
|
+
|
|
408
|
+
### CSS Tokens
|
|
409
|
+
|
|
410
|
+
Prefix: `--stuic-card-*`
|
|
411
|
+
|
|
412
|
+
`bg`, `bg-hover`, `border`, `border-hover`, `padding`, `content-gap`, `image-aspect-ratio`, `image-object-fit`, `image-width-horizontal`, `radius`, `shadow`, `shadow-hover`, `ring-width`, `ring-color`, `eyebrow-font-size`, `eyebrow-text`, `title-font-size`, `title-font-weight`, `title-text`, `description-font-size`, `description-text`, `opacity-disabled`
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## CronInput
|
|
417
|
+
|
|
418
|
+
Cron expression editor with preset selector, manual 5-field editor, raw expression input, human-readable descriptions, and next-run calculation.
|
|
419
|
+
|
|
420
|
+
### Exports
|
|
421
|
+
|
|
422
|
+
| Export | Kind | Description |
|
|
423
|
+
| ---------------------- | --------- | ---------------------------------------- |
|
|
424
|
+
| `CronInput` | component | Main cron editor |
|
|
425
|
+
| `CronInputProps` | type | Props type |
|
|
426
|
+
| `CronPreset` | type | Preset interface `{ label, value }` |
|
|
427
|
+
| `CronInputMode` | type | `"predefined" \| "manual"` |
|
|
428
|
+
| `CRON_DEFAULT_PRESETS` | constant | Default preset array (11 common schedules) |
|
|
429
|
+
| `CronNextRun` | class | Reactive helper for standalone cron calculations |
|
|
430
|
+
|
|
431
|
+
### Key Props
|
|
432
|
+
|
|
433
|
+
| Prop | Type | Default | Description |
|
|
434
|
+
| ----------------- | ----------------- | ------------- | --------------------------------- |
|
|
435
|
+
| `value` | `string` | `"* * * * *"` | Bindable cron expression |
|
|
436
|
+
| `mode` | `CronInputMode` | — | Bindable; predefined or manual |
|
|
437
|
+
| `showPresets` | `boolean` | `true` | Show preset selector |
|
|
438
|
+
| `showFields` | `boolean` | `true` | Show 5-column field editor |
|
|
439
|
+
| `showRawInput` | `boolean` | `true` | Show raw expression input |
|
|
440
|
+
| `showDescription` | `boolean` | `true` | Show human-readable description |
|
|
441
|
+
| `showNextRun` | `boolean` | `true` | Show next run time |
|
|
442
|
+
| `presets` | `CronPreset[]` | default set | Custom presets |
|
|
443
|
+
| `onchange` | `(expr, valid) => void` | — | Change callback |
|
|
444
|
+
|
|
445
|
+
Integrates with `InputWrap` — supports `label`, `description`, `renderSize`, `required`, `disabled`, `validate`, `labelLeft`, `below`.
|
|
446
|
+
|
|
447
|
+
### CronNextRun Class
|
|
448
|
+
|
|
449
|
+
Reactive helper for standalone cron calculations:
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
const next = new CronNextRun("0 9 * * 1-5");
|
|
453
|
+
next.nextRun; // Date | null
|
|
454
|
+
next.nextRunFormatted; // "YYYY-MM-DD HH:MM"
|
|
455
|
+
next.valid; // boolean
|
|
456
|
+
next.destroy(); // cleanup timer
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### CSS Tokens
|
|
460
|
+
|
|
461
|
+
Prefix: `--stuic-cron-input-*`
|
|
462
|
+
|
|
463
|
+
`fields-gap`, `section-gap`, `field-label-text`, `summary-text`, `error-text`, `field-bg`, `field-border`, `field-border-focus`
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Header
|
|
468
|
+
|
|
469
|
+
Responsive navigation header with logo, nav items, avatar, and automatic hamburger collapse.
|
|
470
|
+
|
|
471
|
+
### Exports
|
|
472
|
+
|
|
473
|
+
| Export | Kind | Description |
|
|
474
|
+
| -------------------------- | -------- | ------------------------- |
|
|
475
|
+
| `Header` | component | Main header component |
|
|
476
|
+
| `HeaderProps` | type | Props type |
|
|
477
|
+
| `HeaderNavItem` | type | Nav item interface |
|
|
478
|
+
| `HEADER_BASE_CLASSES` | constant | `"stuic-header"` |
|
|
479
|
+
| `HEADER_LOGO_CLASSES` | constant | `"stuic-header-logo"` |
|
|
480
|
+
| `HEADER_NAV_CLASSES` | constant | `"stuic-header-nav"` |
|
|
481
|
+
| `HEADER_NAV_ITEM_CLASSES` | constant | `"stuic-header-nav-item"` |
|
|
482
|
+
| `HEADER_END_CLASSES` | constant | `"stuic-header-end"` |
|
|
483
|
+
| `HEADER_HAMBURGER_CLASSES` | constant | `"stuic-header-hamburger"` |
|
|
484
|
+
|
|
485
|
+
### HeaderNavItem
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
interface HeaderNavItem {
|
|
489
|
+
id: string | number;
|
|
490
|
+
label: THC;
|
|
491
|
+
href?: string;
|
|
492
|
+
onclick?: () => void;
|
|
493
|
+
icon?: THC;
|
|
494
|
+
active?: boolean;
|
|
495
|
+
disabled?: boolean;
|
|
496
|
+
class?: string;
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Key Props
|
|
501
|
+
|
|
502
|
+
| Prop | Type | Default | Description |
|
|
503
|
+
| ------------------- | --------------------- | ----------------------- | ------------------------------------ |
|
|
504
|
+
| `logo` | `Snippet` | — | Logo/brand snippet |
|
|
505
|
+
| `projectName` | `string` | — | Simple text logo alternative |
|
|
506
|
+
| `items` | `HeaderNavItem[]` | `[]` | Navigation items |
|
|
507
|
+
| `avatar` | `Snippet` | — | Avatar snippet (far right) |
|
|
508
|
+
| `avatarOnClick` | `() => void` | — | Avatar click handler |
|
|
509
|
+
| `collapseThreshold` | `number` | `768` | Width (px) to collapse; 0 disables |
|
|
510
|
+
| `fixed` | `boolean` | `false` | Fixed positioning at top |
|
|
511
|
+
| `isCollapsed` | `boolean` | — | Bindable: collapsed state |
|
|
512
|
+
| `isMenuOpen` | `boolean` | — | Bindable: hamburger menu open |
|
|
513
|
+
| `onSelect` | `(item) => void` | — | Item selection callback |
|
|
514
|
+
|
|
515
|
+
Snippets: `logo`, `avatar`, `children({ isCollapsed, items, offsetWidth })`.
|
|
516
|
+
|
|
517
|
+
### CSS Tokens
|
|
518
|
+
|
|
519
|
+
Prefix: `--stuic-header-*`
|
|
520
|
+
|
|
521
|
+
`padding-x`, `padding-y`, `gap`, `min-height`, `nav-gap`, `project-name-font-weight`, `z-index`, `bg`, `text`, `border-width`, `border-color`, `nav-item-bg-active`, `nav-item-text-active`
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## Tree
|
|
526
|
+
|
|
527
|
+
Accessible, keyboard-navigable hierarchical tree view with optional drag-and-drop reordering and localStorage persistence.
|
|
528
|
+
|
|
529
|
+
### Exports
|
|
530
|
+
|
|
531
|
+
| Export | Kind | Description |
|
|
532
|
+
| ------------------- | --------- | ------------------------------------------ |
|
|
533
|
+
| `Tree` | component | Main tree component |
|
|
534
|
+
| `TreeProps` | type | Props type |
|
|
535
|
+
| `TreeMoveEvent` | type | Drag-drop event interface |
|
|
536
|
+
| `TreeDropPosition` | type | `"before" \| "after" \| "inside"` |
|
|
537
|
+
| `TREE_BASE_CLASSES` | constant | `"stuic-tree"` |
|
|
538
|
+
| `TREE_ITEM_CLASSES` | constant | `"stuic-tree-item"` |
|
|
539
|
+
| `TREE_CHILDREN_CLASSES` | constant | `"stuic-tree-children"` |
|
|
540
|
+
| `TreeNodeDTO` | type | Re-exported from `@marianmeres/tree` |
|
|
541
|
+
|
|
542
|
+
### Key Props
|
|
543
|
+
|
|
544
|
+
| Prop | Type | Default | Description |
|
|
545
|
+
| ----------------- | -------------------------- | -------------- | --------------------------------------- |
|
|
546
|
+
| `items` | `TreeNodeDTO<T>[]` | required | Tree data |
|
|
547
|
+
| `renderItem` | `Snippet<[item, depth, isExpanded]>` | — | Custom item content |
|
|
548
|
+
| `renderIcon` | `Snippet<[item, depth, isExpanded]>` | — | Custom item icon |
|
|
549
|
+
| `activeId` | `string` | — | Selected item ID |
|
|
550
|
+
| `isActive` | `(item) => boolean` | — | Custom active check |
|
|
551
|
+
| `onSelect` | `(item) => void` | — | Item selection callback |
|
|
552
|
+
| `onToggle` | `(item, expanded) => void` | — | Branch toggle callback |
|
|
553
|
+
| `sort` | `(a, b) => number` | — | Sort comparator (per-level) |
|
|
554
|
+
| `defaultExpanded` | `boolean` | `false` | Default expand state |
|
|
555
|
+
| `expandedIds` | `Set<string>` | — | Initially expanded IDs |
|
|
556
|
+
| `persistState` | `boolean` | `false` | Save expand state to localStorage |
|
|
557
|
+
| `draggable` | `boolean` | `false` | Enable drag-and-drop |
|
|
558
|
+
| `isDraggable` | `(item) => boolean` | — | Per-item drag control |
|
|
559
|
+
| `isDropTarget` | `(item) => boolean` | — | Per-item drop target control |
|
|
560
|
+
| `onMove` | `(event) => void \| false` | — | Drop handler; return false to reject |
|
|
561
|
+
| `dragExpandDelay` | `number` | `800` | Auto-expand delay (ms) on drag hover |
|
|
562
|
+
|
|
563
|
+
Features: arrow-key navigation, Home/End, Enter/Space, roving tabindex, slide transitions (respects `prefers-reduced-motion`).
|
|
564
|
+
|
|
565
|
+
### CSS Tokens
|
|
566
|
+
|
|
567
|
+
Prefix: `--stuic-tree-*`
|
|
568
|
+
|
|
569
|
+
`indent`, `item-padding-x`, `item-padding-y`, `item-height`, `item-font-size`, `item-gap`, `chevron-opacity`, `icon-opacity`, `item-opacity-dragging`, `item-bg`, `item-text`, `item-bg-hover`, `item-text-hover`, `item-bg-active`, `item-text-active`, `item-bg-focus`, `item-text-focus`, `drop-indicator-color`, `drop-indicator-height`, `item-bg-dragover`
|
|
570
|
+
|
|
571
|
+
---
|
|
572
|
+
|
|
376
573
|
## Key Files
|
|
377
574
|
|
|
378
575
|
| File | Purpose |
|
|
@@ -382,4 +579,6 @@ Components use private CSS vars (`--_*`) set by intent/variant:
|
|
|
382
579
|
| src/lib/components/Input/ | Form field patterns (incl. FieldObject) |
|
|
383
580
|
| src/lib/components/LoginForm/ | Standalone login form + modal variant |
|
|
384
581
|
| src/lib/components/Checkout/ | E-commerce checkout flow (14 exported sub-components) |
|
|
582
|
+
| src/lib/components/Card/ | Card with image/title/footer variants |
|
|
583
|
+
| src/lib/components/Tree/ | Hierarchical tree with drag-and-drop |
|
|
385
584
|
| src/lib/index.ts | All component exports |
|