@marianmeres/stuic 3.186.0 → 3.188.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/API.md +38 -34
- package/README.md +2 -2
- package/dist/README.md +1 -0
- package/dist/components/ColorPicker/ColorPicker.svelte +26 -1
- package/dist/components/ColorPicker/ColorPicker.svelte.d.ts +9 -0
- package/dist/components/ColorPicker/README.md +19 -7
- package/dist/components/Header/Header.svelte +59 -18
- package/dist/components/Header/Header.svelte.d.ts +33 -7
- package/dist/components/Header/README.md +46 -9
- package/dist/components/Header/index.css +20 -0
- package/dist/components/Input/FieldColorPicker.svelte +201 -0
- package/dist/components/Input/FieldColorPicker.svelte.d.ts +59 -0
- package/dist/components/Input/README.md +45 -0
- package/dist/components/Input/index.css +21 -9
- package/dist/components/Input/index.d.ts +1 -0
- package/dist/components/Input/index.js +1 -0
- package/dist/utils/validate-fields.d.ts +1 -1
- package/docs/domains/components.md +34 -6
- package/package.json +1 -1
package/API.md
CHANGED
|
@@ -153,38 +153,38 @@ Navigation wrapper component.
|
|
|
153
153
|
|
|
154
154
|
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>`.
|
|
155
155
|
|
|
156
|
-
| Prop | Type | Default | Description
|
|
157
|
-
| ----------------------- | ------------------------------------------------ | --------------------- |
|
|
158
|
-
| `leading` | `Snippet<[{ isCollapsed }]>` | — | Leading (left-side) slot. Overrides `leadingHamburger`.
|
|
159
|
-
| `leadingHamburger` | `boolean \| "collapsed"` | `false` | Built-in hamburger in the leading slot (`"collapsed"` = only below threshold). Ignored when `leading` is provided.
|
|
160
|
-
| `onLeadingHamburger` | `() => void` | — | Click handler for the built-in leading hamburger (typically opens a drawer).
|
|
161
|
-
| `leadingHamburgerIcon` | `THC` | menu icon | Icon override for the built-in leading hamburger.
|
|
162
|
-
| `leadingHamburgerLabel` | `string` | `"Open menu"` | Aria-label for the leading hamburger.
|
|
163
|
-
| `logo` | `Snippet` | — | Logo/brand snippet.
|
|
164
|
-
| `projectName` | `string` | — | Simple text logo alternative.
|
|
165
|
-
| `navVariant` | `ButtonVariant` | `"ghost"` | Button variant for nav items and the locale switcher trigger.
|
|
166
|
-
| `items` | `HeaderNavItem[]` | `[]` | Navigation items — inline when expanded, dropdown when collapsed (hamburger mode).
|
|
167
|
-
| `actions` | `HeaderActionItem[]` | `[]` | Action icon buttons between the locale switcher and the avatar. Always visible — never fold into the dropdown.
|
|
168
|
-
| `onActionSelect` | `(action) => void` | — | Called after the per-item `onclick`.
|
|
169
|
-
| `avatar` | `Snippet` | — | Avatar snippet (far right).
|
|
170
|
-
| `avatarOnClick` | `() => void` | — | Makes the avatar interactive. In `"hamburger"` collapse mode it moves into the dropdown.
|
|
171
|
-
| `avatarLabel` | `THC` | `"Account"` | Label for the avatar entry inside the collapsed dropdown.
|
|
172
|
-
| `locales` | `HeaderLocaleItem[]` | `[]` | Locale items. Switcher only renders when 2+.
|
|
173
|
-
| `activeLocale` | `string` | — | Current locale id.
|
|
174
|
-
| `onLocaleChange` | `(localeId) => void` | — | Locale selection callback.
|
|
175
|
-
| `localeLabel` | `THC` | `"Language"` | Section header inside the collapsed dropdown.
|
|
176
|
-
| `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`.
|
|
177
|
-
| `collapseThreshold` | `number` | `768` | Width (px) to collapse; 0 disables.
|
|
178
|
-
| `collapseMode` | `"hamburger" \| "hide"` | `"hamburger"` | Collapse behavior. `"hide"` keeps avatar/actions visible and renders no trailing hamburger (app-shell pattern).
|
|
179
|
-
| `keepLocaleOnCollapse` | `boolean` | `
|
|
180
|
-
| `fixed` | `boolean` | `false` | Fixed positioning at the top.
|
|
181
|
-
| `safeArea` | `boolean` | `false` | PWA: when installed/standalone, offset the **top app bar** below the device safe-area insets (top + side notch). No-op in a browser tab. Set only on the top bar — not in-page/detail/drawer headers. See Header README.
|
|
182
|
-
| `isCollapsed` | `boolean` | — | Bindable: collapsed state.
|
|
183
|
-
| `isMenuOpen` | `boolean` | — | Bindable: hamburger menu open.
|
|
184
|
-
| `dropdownPosition` | `DropdownMenuPosition` | `"bottom-span-right"` | Position of the collapsed dropdown.
|
|
185
|
-
| `iconSize` | `number` | `24` | Hamburger/X icon size in px.
|
|
186
|
-
| `onSelect` | `(item) => void` | — | Item selection callback (both modes).
|
|
187
|
-
| `children` | `Snippet<[{ isCollapsed, items, offsetWidth }]>` | — | Escape hatch: override the entire inner layout.
|
|
156
|
+
| Prop | Type | Default | Description |
|
|
157
|
+
| ----------------------- | ------------------------------------------------ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
158
|
+
| `leading` | `Snippet<[{ isCollapsed }]>` | — | Leading (left-side) slot. Overrides `leadingHamburger`. |
|
|
159
|
+
| `leadingHamburger` | `boolean \| "collapsed"` | `false` | Built-in hamburger in the leading slot (`"collapsed"` = only below threshold). Ignored when `leading` is provided. |
|
|
160
|
+
| `onLeadingHamburger` | `() => void` | — | Click handler for the built-in leading hamburger (typically opens a drawer). |
|
|
161
|
+
| `leadingHamburgerIcon` | `THC` | menu icon | Icon override for the built-in leading hamburger. |
|
|
162
|
+
| `leadingHamburgerLabel` | `string` | `"Open menu"` | Aria-label for the leading hamburger. |
|
|
163
|
+
| `logo` | `Snippet` | — | Logo/brand snippet. |
|
|
164
|
+
| `projectName` | `string` | — | Simple text logo alternative. |
|
|
165
|
+
| `navVariant` | `ButtonVariant` | `"ghost"` | Button variant for nav items and the locale switcher trigger. |
|
|
166
|
+
| `items` | `HeaderNavItem[]` | `[]` | Navigation items — inline when expanded, dropdown when collapsed (hamburger mode). |
|
|
167
|
+
| `actions` | `HeaderActionItem[]` | `[]` | Action icon buttons between the locale switcher and the avatar. Always visible — never fold into the dropdown. |
|
|
168
|
+
| `onActionSelect` | `(action) => void` | — | Called after the per-item `onclick`. |
|
|
169
|
+
| `avatar` | `Snippet` | — | Avatar snippet (far right). |
|
|
170
|
+
| `avatarOnClick` | `() => void` | — | Makes the avatar interactive. In `"hamburger"` collapse mode it moves into the dropdown. |
|
|
171
|
+
| `avatarLabel` | `THC` | `"Account"` | Label for the avatar entry inside the collapsed dropdown. |
|
|
172
|
+
| `locales` | `HeaderLocaleItem[]` | `[]` | Locale items. Switcher only renders when 2+. |
|
|
173
|
+
| `activeLocale` | `string` | — | Current locale id. |
|
|
174
|
+
| `onLocaleChange` | `(localeId) => void` | — | Locale selection callback. |
|
|
175
|
+
| `localeLabel` | `THC` | `"Language"` | Section header inside the collapsed dropdown. |
|
|
176
|
+
| `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`. |
|
|
177
|
+
| `collapseThreshold` | `number` | `768` | Width (px) to collapse; 0 disables. |
|
|
178
|
+
| `collapseMode` | `"hamburger" \| "hide"` | `"hamburger"` | Collapse behavior. `"hide"` keeps avatar/actions visible and renders no trailing hamburger (app-shell pattern). |
|
|
179
|
+
| `keepLocaleOnCollapse` | `boolean` | `true` | Keep the locale switcher inline (next to the hamburger) when collapsed — in **both** collapse modes, and without duplicating it inside the dropdown. `false` folds it into the dropdown (`"hamburger"`) or hides it (`"hide"`). |
|
|
180
|
+
| `fixed` | `boolean` | `false` | Fixed positioning at the top. |
|
|
181
|
+
| `safeArea` | `boolean` | `false` | PWA: when installed/standalone, offset the **top app bar** below the device safe-area insets (top + side notch). No-op in a browser tab. Set only on the top bar — not in-page/detail/drawer headers. See Header README. |
|
|
182
|
+
| `isCollapsed` | `boolean` | — | Bindable: collapsed state. |
|
|
183
|
+
| `isMenuOpen` | `boolean` | — | Bindable: hamburger menu open. |
|
|
184
|
+
| `dropdownPosition` | `DropdownMenuPosition` | `"bottom-span-right"` | Position of the collapsed dropdown. |
|
|
185
|
+
| `iconSize` | `number` | `24` | Hamburger/X icon size in px. |
|
|
186
|
+
| `onSelect` | `(item) => void` | — | Item selection callback (both modes). |
|
|
187
|
+
| `children` | `Snippet<[{ isCollapsed, items, offsetWidth }]>` | — | Escape hatch: override the entire inner layout. |
|
|
188
188
|
|
|
189
189
|
Class slots: `class`, `classContent`, `classLeading`, `classLeadingHamburger`, `classLogo`, `classNav`, `classNavItem`, `classNavItemActive`, `classActions`, `classAction`, `classActionActive`, `classEnd`, `classAvatar`, `classLocale`, `classHamburger`, `classDropdown`.
|
|
190
190
|
|
|
@@ -223,9 +223,9 @@ Class slots: `class`, `classContent`, `classLeading`, `classLeadingHamburger`, `
|
|
|
223
223
|
|
|
224
224
|
**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).
|
|
225
225
|
|
|
226
|
-
**HeaderLocaleItem:** `{ id, label }`
|
|
226
|
+
**HeaderLocaleItem:** `{ id, label, shortLabel? }` — `shortLabel` replaces `label` on the inline trigger **in collapsed mode only** (keeps a long name like "Slovenčina" from crowding a narrow header); the dropdown list always shows the full `label`.
|
|
227
227
|
|
|
228
|
-
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`.
|
|
228
|
+
CSS tokens: `--stuic-header-padding-x`, `--stuic-header-padding-y`, `--stuic-header-gap`, `--stuic-header-end-gap-collapsed`, `--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`.
|
|
229
229
|
|
|
230
230
|
---
|
|
231
231
|
|
|
@@ -467,6 +467,10 @@ File upload input.
|
|
|
467
467
|
|
|
468
468
|
Form-wrapped switch toggle.
|
|
469
469
|
|
|
470
|
+
#### `FieldColorPicker`
|
|
471
|
+
|
|
472
|
+
Form-wrapped `ColorPicker`: visible label (names the swatch group via `aria-labelledby`), description, validation box, label-left layout.
|
|
473
|
+
|
|
470
474
|
#### `FieldOptions`
|
|
471
475
|
|
|
472
476
|
Multi-select options field.
|
package/README.md
CHANGED
|
@@ -175,11 +175,11 @@ AppShell, Accordion, Backdrop, Modal, ModalDialog, Drawer, Collapsible, Header,
|
|
|
175
175
|
|
|
176
176
|
### Forms & Inputs
|
|
177
177
|
|
|
178
|
-
FieldInput, FieldMoney, FieldDate, FieldDateRange, Calendar, FieldTextarea, FieldSelect, FieldCheckbox, FieldRadios, FieldFile, FieldAssets, FieldSingleAsset, FieldOptions, FieldKeyValues, FieldTable, FieldObject, FieldSwitch, FieldInputLocalized, FieldLikeButton, FieldPhoneNumber, FieldCountry, CronInput, Fieldset, LoginForm, LoginFormModal, RegisterForm, RegisterFormModal, LoginOrRegisterForm, LoginOrRegisterFormModal, EmailVerifyForm, OtpInput
|
|
178
|
+
FieldInput, FieldMoney, FieldDate, FieldDateRange, Calendar, FieldTextarea, FieldSelect, FieldCheckbox, FieldRadios, FieldFile, FieldAssets, FieldSingleAsset, FieldOptions, FieldKeyValues, FieldTable, FieldObject, FieldSwitch, FieldColorPicker, FieldInputLocalized, FieldLikeButton, FieldPhoneNumber, FieldCountry, CronInput, Fieldset, LoginForm, LoginFormModal, RegisterForm, RegisterFormModal, LoginOrRegisterForm, LoginOrRegisterFormModal, EmailVerifyForm, OtpInput
|
|
179
179
|
|
|
180
180
|
### Buttons & Controls
|
|
181
181
|
|
|
182
|
-
Button, ButtonGroupRadio, Switch, Slider, RangeSlider, TwCheck, ListItemButton, X
|
|
182
|
+
Button, ButtonGroupRadio, Switch, ColorPicker, Slider, RangeSlider, TwCheck, ListItemButton, X
|
|
183
183
|
|
|
184
184
|
### Feedback & Notifications
|
|
185
185
|
|
package/dist/README.md
CHANGED
|
@@ -46,6 +46,7 @@ npm install @marianmeres/stuic
|
|
|
46
46
|
- **FieldOptions** - Modal-based multi-select picker (optional inline `chips` display)
|
|
47
47
|
- **FieldKeyValues** - Key-value pairs editor with JSON serialization
|
|
48
48
|
- **FieldSwitch** - Toggle switch within a form
|
|
49
|
+
- **FieldColorPicker** - Color picker (swatches + custom color) within a form
|
|
49
50
|
- **Fieldset** - Group of form fields with legend
|
|
50
51
|
|
|
51
52
|
### Buttons & Controls
|
|
@@ -49,6 +49,13 @@
|
|
|
49
49
|
disabled?: boolean;
|
|
50
50
|
/** Accessible name of the swatch group (default `t("color")`, "Color") */
|
|
51
51
|
label?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Id of an element that names the swatch group (`aria-labelledby`). When set,
|
|
54
|
+
* it replaces the group's `aria-label` — what `FieldColorPicker` uses to let
|
|
55
|
+
* its visible label name the swatches. `...rest` cannot do this: it lands on
|
|
56
|
+
* the root, not on the radiogroup.
|
|
57
|
+
*/
|
|
58
|
+
labelledby?: string;
|
|
52
59
|
/** Form field name (hidden input) */
|
|
53
60
|
name?: string;
|
|
54
61
|
/** Require a non-empty value (enforced by the built-in validator) */
|
|
@@ -92,6 +99,7 @@
|
|
|
92
99
|
allowClear = true,
|
|
93
100
|
disabled = false,
|
|
94
101
|
label,
|
|
102
|
+
labelledby,
|
|
95
103
|
name,
|
|
96
104
|
required = false,
|
|
97
105
|
t = t_default,
|
|
@@ -323,6 +331,22 @@
|
|
|
323
331
|
export function getValidation(): ValidationResult | undefined {
|
|
324
332
|
return _validation;
|
|
325
333
|
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Focus the group's tab stop (the checked swatch, or the first one), or —
|
|
337
|
+
* with no swatches rendered — the first custom-color control.
|
|
338
|
+
*/
|
|
339
|
+
export function focus(): void {
|
|
340
|
+
(
|
|
341
|
+
el?.querySelector<HTMLElement>(`[role="radiogroup"] [tabindex="0"]`) ??
|
|
342
|
+
el?.querySelector<HTMLElement>(`input:not([type="hidden"])`)
|
|
343
|
+
)?.focus();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Scroll the picker into view. Defaults to smooth + center. */
|
|
347
|
+
export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
|
|
348
|
+
el?.scrollIntoView?.({ behavior: "smooth", block: "center", ...opts });
|
|
349
|
+
}
|
|
326
350
|
</script>
|
|
327
351
|
|
|
328
352
|
<div
|
|
@@ -340,7 +364,8 @@
|
|
|
340
364
|
<div
|
|
341
365
|
class={unstyled ? undefined : "stuic-color-picker-swatches"}
|
|
342
366
|
role="radiogroup"
|
|
343
|
-
aria-
|
|
367
|
+
aria-labelledby={labelledby || undefined}
|
|
368
|
+
aria-label={labelledby ? undefined : label || t("color", null, "Color")}
|
|
344
369
|
aria-required={required ? "true" : undefined}
|
|
345
370
|
aria-disabled={disabled ? "true" : undefined}
|
|
346
371
|
aria-invalid={_validation && !_validation.valid ? "true" : undefined}
|
|
@@ -40,6 +40,13 @@ export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children" |
|
|
|
40
40
|
disabled?: boolean;
|
|
41
41
|
/** Accessible name of the swatch group (default `t("color")`, "Color") */
|
|
42
42
|
label?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Id of an element that names the swatch group (`aria-labelledby`). When set,
|
|
45
|
+
* it replaces the group's `aria-label` — what `FieldColorPicker` uses to let
|
|
46
|
+
* its visible label name the swatches. `...rest` cannot do this: it lands on
|
|
47
|
+
* the root, not on the radiogroup.
|
|
48
|
+
*/
|
|
49
|
+
labelledby?: string;
|
|
43
50
|
/** Form field name (hidden input) */
|
|
44
51
|
name?: string;
|
|
45
52
|
/** Require a non-empty value (enforced by the built-in validator) */
|
|
@@ -70,6 +77,8 @@ declare const ColorPicker: import("svelte").Component<Props, {
|
|
|
70
77
|
validate: () => ValidationResult | undefined;
|
|
71
78
|
clearValidation: () => void;
|
|
72
79
|
getValidation: () => ValidationResult | undefined;
|
|
80
|
+
focus: () => void;
|
|
81
|
+
scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
|
|
73
82
|
}, "el" | "value" | "inputEl">;
|
|
74
83
|
type ColorPicker = ReturnType<typeof ColorPicker>;
|
|
75
84
|
export default ColorPicker;
|
|
@@ -16,8 +16,12 @@ own full-screen color UI, so there is no anchored popover to fight the on-screen
|
|
|
16
16
|
keyboard. Swatches grow to a ~44px hit target on a coarse pointer, and the hex field
|
|
17
17
|
carries the iOS zoom guard.
|
|
18
18
|
|
|
19
|
+
In a form layout, use [`FieldColorPicker`](../Input/README.md#color-picker): this picker
|
|
20
|
+
inside the standard field shell (visible label, description, validation box, label-left
|
|
21
|
+
layout).
|
|
22
|
+
|
|
19
23
|
Adjacent but different: `Rating` (the same input-with-hidden-input shape),
|
|
20
|
-
`
|
|
24
|
+
`ThemePreview` (theme token swatches).
|
|
21
25
|
|
|
22
26
|
## Props
|
|
23
27
|
|
|
@@ -29,7 +33,8 @@ Adjacent but different: `Rating` (the same input-with-hidden-input shape),
|
|
|
29
33
|
| `custom` | `"both" \| "native" \| "text" \| false` | `"both"` | Which custom-color controls to render under the palette |
|
|
30
34
|
| `allowClear` | `boolean` | `true` | A crossed-out "no color" swatch, Delete / Backspace, and an emptied hex field all set `""` |
|
|
31
35
|
| `disabled` | `boolean` | `false` | No interaction; the hidden input is disabled too (nothing submits) |
|
|
32
|
-
| `label` | `string` | `"Color"` | Accessible name of the swatch group (via `t("color")` by default)
|
|
36
|
+
| `label` | `string` | `"Color"` | Accessible name of the swatch group (via `t("color")` by default). Not a visible label — see `FieldColorPicker` |
|
|
37
|
+
| `labelledby` | `string` | — | Id of the element naming the swatch group (`aria-labelledby`); when set, replaces `label`'s `aria-label` |
|
|
33
38
|
| `name` | `string` | — | Form field name of the hidden input |
|
|
34
39
|
| `required` | `boolean` | `false` | Require a non-empty value — enforced by the built-in validator (hidden inputs skip native constraint validation) |
|
|
35
40
|
| `t` | `TranslateFn` | English | i18n translate function (see below) |
|
|
@@ -46,11 +51,13 @@ Other attributes (`id`, `style`, `data-*`, …) are passed to the root `div`.
|
|
|
46
51
|
|
|
47
52
|
### Imperative API (via `bind:this`)
|
|
48
53
|
|
|
49
|
-
| Method | Description
|
|
50
|
-
| ------------------- |
|
|
51
|
-
| `validate()` | Trigger validation now; returns the `ValidationResult`
|
|
52
|
-
| `clearValidation()` | Clear the stored result and the hidden input's custom validity
|
|
53
|
-
| `getValidation()` | The last validation result (also reported via `setValidationResult`)
|
|
54
|
+
| Method | Description |
|
|
55
|
+
| ------------------- | ----------------------------------------------------------------------- |
|
|
56
|
+
| `validate()` | Trigger validation now; returns the `ValidationResult` |
|
|
57
|
+
| `clearValidation()` | Clear the stored result and the hidden input's custom validity |
|
|
58
|
+
| `getValidation()` | The last validation result (also reported via `setValidationResult`) |
|
|
59
|
+
| `focus()` | Focus the checked swatch (the group's tab stop), else the first control |
|
|
60
|
+
| `scrollIntoView()` | Scroll the picker into view (defaults: `smooth` + `center`) |
|
|
54
61
|
|
|
55
62
|
## Usage
|
|
56
63
|
|
|
@@ -114,6 +121,9 @@ resolve to near-identical greys.
|
|
|
114
121
|
</form>
|
|
115
122
|
```
|
|
116
123
|
|
|
124
|
+
A bare picker only reports the result (`setValidationResult`, `aria-invalid`); it renders no
|
|
125
|
+
message. `FieldColorPicker` shows it in the field's validation box.
|
|
126
|
+
|
|
117
127
|
## Preview vs commit
|
|
118
128
|
|
|
119
129
|
`value` updates **live** while the native picker is being dragged and while a valid
|
|
@@ -144,6 +154,8 @@ value when `allowClear` is on.
|
|
|
144
154
|
Up / Down step by one **rendered** row (measured from the layout, so it stays right
|
|
145
155
|
when a narrow screen wraps to fewer per row) and stop at the edges.
|
|
146
156
|
`aria-required` / `aria-invalid` sit on the group.
|
|
157
|
+
- The group is named by `label` (an `aria-label`, default "Color") or, when `labelledby`
|
|
158
|
+
is set, by that element instead — never both.
|
|
147
159
|
- Every swatch is named: an entry's `label` (through `t`) or, without one, its color
|
|
148
160
|
string. The clear swatch is "No color".
|
|
149
161
|
- Selection is signalled by a ring drawn outside the swatch, not only by color.
|
|
@@ -35,6 +35,12 @@
|
|
|
35
35
|
id: string;
|
|
36
36
|
/** Display label — supports THC (string, html, component, snippet) */
|
|
37
37
|
label: THC;
|
|
38
|
+
/** Compact label for the inline trigger in COLLAPSED mode only.
|
|
39
|
+
* Falls back to `label`. Use it when the full label ("Slovenčina")
|
|
40
|
+
* would crowd the narrow header next to the hamburger — the dropdown
|
|
41
|
+
* list always shows the full `label`, so the long form stays
|
|
42
|
+
* available where there is room to read it. */
|
|
43
|
+
shortLabel?: THC;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
export interface HeaderActionItem {
|
|
@@ -86,11 +92,13 @@
|
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
/** Collapse behavior when the header drops below `collapseThreshold`:
|
|
89
|
-
* - "hamburger": nav items fold into a trailing dropdown along with
|
|
90
|
-
*
|
|
95
|
+
* - "hamburger": nav items fold into a trailing dropdown along with an
|
|
96
|
+
* interactive avatar.
|
|
91
97
|
* - "hide": nav items are hidden entirely. No trailing hamburger renders.
|
|
92
|
-
* Avatar stays visible.
|
|
93
|
-
*
|
|
98
|
+
* Avatar stays visible.
|
|
99
|
+
*
|
|
100
|
+
* In BOTH modes the locale switcher stays inline next to the hamburger
|
|
101
|
+
* by default — see `keepLocaleOnCollapse`. */
|
|
94
102
|
export type HeaderCollapseMode = "hamburger" | "hide";
|
|
95
103
|
|
|
96
104
|
/** Visibility for the built-in leading hamburger button:
|
|
@@ -152,9 +160,27 @@
|
|
|
152
160
|
collapseThreshold?: number;
|
|
153
161
|
/** Collapse behavior when below threshold (defaults to "hamburger") */
|
|
154
162
|
collapseMode?: HeaderCollapseMode;
|
|
155
|
-
/**
|
|
156
|
-
* collapsed
|
|
157
|
-
*
|
|
163
|
+
/** Keep the locale switcher visible — inline, next to the hamburger —
|
|
164
|
+
* when the header is collapsed. Applies to BOTH collapse modes.
|
|
165
|
+
* Defaults to `true`.
|
|
166
|
+
*
|
|
167
|
+
* Why it defaults on: the person who most needs this control is the
|
|
168
|
+
* one who landed in a language they cannot read. For them a visible
|
|
169
|
+
* "EN ▾" trigger is the only self-describing widget in the bar, while
|
|
170
|
+
* a locale entry folded into the hamburger — listed last, under a
|
|
171
|
+
* "Language" heading they also cannot read, often below the fold of a
|
|
172
|
+
* long menu — is effectively invisible.
|
|
173
|
+
*
|
|
174
|
+
* When `true` in `"hamburger"` mode the locale section is NOT also
|
|
175
|
+
* appended to the trailing dropdown, so it is never reachable twice.
|
|
176
|
+
*
|
|
177
|
+
* Set `false` for the alternative: folds into the trailing dropdown
|
|
178
|
+
* in `"hamburger"` mode, hidden entirely in `"hide"` mode (this was
|
|
179
|
+
* the behavior before the prop applied to both modes). Worth doing
|
|
180
|
+
* when the end area is already crowded with actions, or when the
|
|
181
|
+
* app's own drawer owns the language switch. See also
|
|
182
|
+
* `HeaderLocaleItem.shortLabel` for keeping the inline trigger
|
|
183
|
+
* narrow. */
|
|
158
184
|
keepLocaleOnCollapse?: boolean;
|
|
159
185
|
/** Fixed positioning (top of viewport) */
|
|
160
186
|
fixed?: boolean;
|
|
@@ -263,7 +289,7 @@
|
|
|
263
289
|
contentMaxWidth,
|
|
264
290
|
collapseThreshold = 768,
|
|
265
291
|
collapseMode = "hamburger",
|
|
266
|
-
keepLocaleOnCollapse =
|
|
292
|
+
keepLocaleOnCollapse = true,
|
|
267
293
|
fixed = false,
|
|
268
294
|
safeArea = false,
|
|
269
295
|
isCollapsed = $bindable(false),
|
|
@@ -336,17 +362,28 @@
|
|
|
336
362
|
// Locale switcher: only render when 2+ locales
|
|
337
363
|
let _hasLocales = $derived(locales.length > 1);
|
|
338
364
|
|
|
339
|
-
// Visibility of the inline (expanded-form) locale switcher.
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
//
|
|
365
|
+
// Visibility of the inline (expanded-form) locale switcher. Always visible
|
|
366
|
+
// when expanded; when collapsed it stays inline unless explicitly opted out
|
|
367
|
+
// — in BOTH collapse modes. Deliberately mode-agnostic: a user who cannot
|
|
368
|
+
// read the current language must be able to find the switcher without
|
|
369
|
+
// opening a menu whose trigger tells them nothing.
|
|
343
370
|
let _showLocaleSwitcher = $derived(
|
|
344
|
-
_hasLocales && (!_isCollapsed ||
|
|
371
|
+
_hasLocales && (!_isCollapsed || keepLocaleOnCollapse)
|
|
345
372
|
);
|
|
346
373
|
|
|
347
374
|
// Active locale object (for trigger label); fallback to first
|
|
348
375
|
let _activeLocale = $derived(locales.find((l) => l.id === activeLocale) ?? locales[0]);
|
|
349
376
|
|
|
377
|
+
// Inline trigger label. `shortLabel` (when provided) is used ONLY in
|
|
378
|
+
// collapsed mode, where horizontal space next to the hamburger is scarce;
|
|
379
|
+
// expanded mode and the dropdown list always show the full `label`.
|
|
380
|
+
let _localeTriggerLabel = $derived.by((): THC | undefined => {
|
|
381
|
+
if (!_activeLocale) return undefined;
|
|
382
|
+
return _isCollapsed
|
|
383
|
+
? (_activeLocale.shortLabel ?? _activeLocale.label)
|
|
384
|
+
: _activeLocale.label;
|
|
385
|
+
});
|
|
386
|
+
|
|
350
387
|
// Locale items for the expanded-mode DropdownMenu
|
|
351
388
|
let _localeDropdownItems = $derived.by((): DropdownMenuItem[] => {
|
|
352
389
|
return locales.map(
|
|
@@ -389,8 +426,10 @@
|
|
|
389
426
|
}) satisfies DropdownMenuActionItem
|
|
390
427
|
);
|
|
391
428
|
|
|
392
|
-
// Append locale section when
|
|
393
|
-
|
|
429
|
+
// Append the locale section ONLY when the inline switcher is hidden.
|
|
430
|
+
// With the inline trigger visible this would be a second, worse route
|
|
431
|
+
// to the same setting — and the one nobody finds.
|
|
432
|
+
if (_hasLocales && !_showLocaleSwitcher) {
|
|
394
433
|
if (navItems.length > 0) {
|
|
395
434
|
navItems.push({ type: "divider" });
|
|
396
435
|
}
|
|
@@ -556,7 +595,9 @@
|
|
|
556
595
|
|
|
557
596
|
<!-- End area: locale + actions + avatar + trailing hamburger -->
|
|
558
597
|
<div class={_classEnd}>
|
|
559
|
-
<!-- Locale switcher
|
|
598
|
+
<!-- Locale switcher — inline when expanded and, unless
|
|
599
|
+
`keepLocaleOnCollapse={false}`, still inline when collapsed
|
|
600
|
+
(both collapse modes) rather than folded into the dropdown -->
|
|
560
601
|
{#if _showLocaleSwitcher}
|
|
561
602
|
<DropdownMenu
|
|
562
603
|
items={_localeDropdownItems}
|
|
@@ -573,8 +614,8 @@
|
|
|
573
614
|
aria-label="Change language"
|
|
574
615
|
{...triggerProps}
|
|
575
616
|
>
|
|
576
|
-
{#if
|
|
577
|
-
<Thc thc={
|
|
617
|
+
{#if _localeTriggerLabel !== undefined}
|
|
618
|
+
<Thc thc={_localeTriggerLabel} />
|
|
578
619
|
{/if}
|
|
579
620
|
<span
|
|
580
621
|
class={twMerge(
|
|
@@ -28,6 +28,12 @@ export interface HeaderLocaleItem {
|
|
|
28
28
|
id: string;
|
|
29
29
|
/** Display label — supports THC (string, html, component, snippet) */
|
|
30
30
|
label: THC;
|
|
31
|
+
/** Compact label for the inline trigger in COLLAPSED mode only.
|
|
32
|
+
* Falls back to `label`. Use it when the full label ("Slovenčina")
|
|
33
|
+
* would crowd the narrow header next to the hamburger — the dropdown
|
|
34
|
+
* list always shows the full `label`, so the long form stays
|
|
35
|
+
* available where there is room to read it. */
|
|
36
|
+
shortLabel?: THC;
|
|
31
37
|
}
|
|
32
38
|
export interface HeaderActionItem {
|
|
33
39
|
/** Unique identifier */
|
|
@@ -75,11 +81,13 @@ export interface HeaderActionItem {
|
|
|
75
81
|
]>;
|
|
76
82
|
}
|
|
77
83
|
/** Collapse behavior when the header drops below `collapseThreshold`:
|
|
78
|
-
* - "hamburger": nav items fold into a trailing dropdown along with
|
|
79
|
-
*
|
|
84
|
+
* - "hamburger": nav items fold into a trailing dropdown along with an
|
|
85
|
+
* interactive avatar.
|
|
80
86
|
* - "hide": nav items are hidden entirely. No trailing hamburger renders.
|
|
81
|
-
* Avatar stays visible.
|
|
82
|
-
*
|
|
87
|
+
* Avatar stays visible.
|
|
88
|
+
*
|
|
89
|
+
* In BOTH modes the locale switcher stays inline next to the hamburger
|
|
90
|
+
* by default — see `keepLocaleOnCollapse`. */
|
|
83
91
|
export type HeaderCollapseMode = "hamburger" | "hide";
|
|
84
92
|
/** Visibility for the built-in leading hamburger button:
|
|
85
93
|
* - false/undefined: not rendered
|
|
@@ -141,9 +149,27 @@ export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
|
141
149
|
collapseThreshold?: number;
|
|
142
150
|
/** Collapse behavior when below threshold (defaults to "hamburger") */
|
|
143
151
|
collapseMode?: HeaderCollapseMode;
|
|
144
|
-
/**
|
|
145
|
-
* collapsed
|
|
146
|
-
*
|
|
152
|
+
/** Keep the locale switcher visible — inline, next to the hamburger —
|
|
153
|
+
* when the header is collapsed. Applies to BOTH collapse modes.
|
|
154
|
+
* Defaults to `true`.
|
|
155
|
+
*
|
|
156
|
+
* Why it defaults on: the person who most needs this control is the
|
|
157
|
+
* one who landed in a language they cannot read. For them a visible
|
|
158
|
+
* "EN ▾" trigger is the only self-describing widget in the bar, while
|
|
159
|
+
* a locale entry folded into the hamburger — listed last, under a
|
|
160
|
+
* "Language" heading they also cannot read, often below the fold of a
|
|
161
|
+
* long menu — is effectively invisible.
|
|
162
|
+
*
|
|
163
|
+
* When `true` in `"hamburger"` mode the locale section is NOT also
|
|
164
|
+
* appended to the trailing dropdown, so it is never reachable twice.
|
|
165
|
+
*
|
|
166
|
+
* Set `false` for the alternative: folds into the trailing dropdown
|
|
167
|
+
* in `"hamburger"` mode, hidden entirely in `"hide"` mode (this was
|
|
168
|
+
* the behavior before the prop applied to both modes). Worth doing
|
|
169
|
+
* when the end area is already crowded with actions, or when the
|
|
170
|
+
* app's own drawer owns the language switch. See also
|
|
171
|
+
* `HeaderLocaleItem.shortLabel` for keeping the inline trigger
|
|
172
|
+
* narrow. */
|
|
147
173
|
keepLocaleOnCollapse?: boolean;
|
|
148
174
|
/** Fixed positioning (top of viewport) */
|
|
149
175
|
fixed?: boolean;
|
|
@@ -4,17 +4,54 @@ Top-bar component with leading slot, project logo, nav items, locale switcher, o
|
|
|
4
4
|
|
|
5
5
|
## Examples
|
|
6
6
|
|
|
7
|
+
### The locale switcher stays visible when collapsed
|
|
8
|
+
|
|
9
|
+
When the header collapses, the locale switcher does **not** fold into the trailing hamburger. It stays inline, immediately before the actions / avatar / hamburger, in **both** collapse modes. Nothing to configure — it is the default.
|
|
10
|
+
|
|
11
|
+
```svelte
|
|
12
|
+
<Header
|
|
13
|
+
projectName="App"
|
|
14
|
+
items={navItems}
|
|
15
|
+
{locales}
|
|
16
|
+
{activeLocale}
|
|
17
|
+
onLocaleChange={(id) => (activeLocale = id)}
|
|
18
|
+
/>
|
|
19
|
+
<!-- collapsed: [App] [EN ▾] [☰] -->
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This is the one control `Header` deliberately treats differently from a nav item, and the reason is worth stating because it looks like an inconsistency:
|
|
23
|
+
|
|
24
|
+
> The person who most needs the language switch is the person who landed in a language they cannot read. A visible `EN ▾` trigger is self-describing to them — it shows the current language and it is obviously a control. An entry inside the hamburger is not: the trigger is an unlabeled icon, the section heading says "Language" in a language they do not speak, and past a handful of nav items it sits below the fold of a menu they have to scroll with a finger. It is reliably never found.
|
|
25
|
+
|
|
26
|
+
What follows from it:
|
|
27
|
+
|
|
28
|
+
| Behavior | Why |
|
|
29
|
+
| ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
30
|
+
| The trailing dropdown has **no** locale section while the inline trigger is visible. | Two routes to one setting, one of which nobody finds. `_dropdownItems` appends the locale section only when the inline switcher is hidden. |
|
|
31
|
+
| A header whose only collapsible content was the locale switcher renders **no hamburger at all**. | `_dropdownItems` comes back empty, and the trailing `DropdownMenu` is gated on it being non-empty. |
|
|
32
|
+
| The inline trigger shows `HeaderLocaleItem.shortLabel` (when set) **only** while collapsed. | `"Slovenčina"` next to a hamburger on a 360px phone is what pushed the switcher into the menu in the first place. The dropdown list always shows the full `label`. |
|
|
33
|
+
| `keepLocaleOnCollapse={false}` restores the alternative: folds into the dropdown in `"hamburger"` mode, hidden entirely in `"hide"` mode. | For an end area already crowded with actions, or an app whose own drawer owns the language switch. |
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// Long locale names on a narrow header: short form on the inline trigger,
|
|
37
|
+
// full form in the dropdown list.
|
|
38
|
+
const locales: HeaderLocaleItem[] = [
|
|
39
|
+
{ id: "en", label: "English", shortLabel: "EN" },
|
|
40
|
+
{ id: "sk", label: "Slovenčina", shortLabel: "SK" },
|
|
41
|
+
];
|
|
42
|
+
```
|
|
43
|
+
|
|
7
44
|
### App-like collapse: avatar + actions visible, everything else hidden
|
|
8
45
|
|
|
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
|
|
46
|
+
Common "app shell" pattern: when the header collapses below `collapseThreshold`, the avatar, the locale switcher and a few key actions (search, notifications, cart…) remain visible, the trailing hamburger is NOT shown, and the nav items are hidden entirely (the nav typically lives in a drawer triggered by the leading hamburger instead).
|
|
10
47
|
|
|
11
|
-
| Requirement | Where it's handled
|
|
12
|
-
| ------------------------------- |
|
|
13
|
-
| **Avatar stays visible** | [Header.svelte:
|
|
14
|
-
| **Action buttons stay visible** | [Header.svelte:
|
|
15
|
-
| **No trailing hamburger** | [Header.svelte:
|
|
16
|
-
| **Nav items hidden** | [Header.svelte:
|
|
17
|
-
| **Locale
|
|
48
|
+
| Requirement | Where it's handled | How |
|
|
49
|
+
| ------------------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
50
|
+
| **Avatar stays visible** | [Header.svelte:673](./Header.svelte#L673) — `{#if avatar && !(_isCollapsed && _avatarInDropdown)}` | `_avatarInDropdown` requires `collapseMode === "hamburger"`. In `"hide"` mode it's always `false`, so the avatar always renders. |
|
|
51
|
+
| **Action buttons stay visible** | [Header.svelte:632](./Header.svelte#L632) — `<!-- Actions (icon buttons, always visible) -->` | The actions loop has no collapse gating — items render in both modes. |
|
|
52
|
+
| **No trailing hamburger** | [Header.svelte:689](./Header.svelte#L689) — `{#if _isCollapsed && _dropdownItems.length > 0}` | In `"hide"` mode, `_dropdownItems` short-circuits to `[]`, so the `{#if}` is false → no trailing hamburger. |
|
|
53
|
+
| **Nav items hidden** | [Header.svelte:564](./Header.svelte#L564) — `{#if !_isCollapsed && items.length > 0}` | Inline nav requires `!_isCollapsed`; combined with the empty `_dropdownItems` above, items don't reappear in a dropdown either. |
|
|
54
|
+
| **Locale stays visible** | [Header.svelte:369](./Header.svelte#L369) — `!_isCollapsed \|\| keepLocaleOnCollapse` | `keepLocaleOnCollapse` defaults to `true` in both modes (see the section above). Pass `false` to hide it, e.g. when the drawer owns the language switch. |
|
|
18
55
|
|
|
19
56
|
Minimal config:
|
|
20
57
|
|
|
@@ -26,7 +63,7 @@ Minimal config:
|
|
|
26
63
|
collapseMode="hide" <!-- no trailing hamburger; avatar stays -->
|
|
27
64
|
leadingHamburger <!-- optional: drives a drawer for the hidden nav -->
|
|
28
65
|
onLeadingHamburger={() => (drawerOpen = true)}
|
|
29
|
-
{locales} {activeLocale} <!--
|
|
66
|
+
{locales} {activeLocale} <!-- stays visible in collapsed (keepLocaleOnCollapse defaults to true) -->
|
|
30
67
|
onLocaleChange={(id) => (activeLocale = id)}
|
|
31
68
|
avatarOnClick={() => alert("Profile")} <!-- safe in "hide" mode — won't move into dropdown -->
|
|
32
69
|
>
|
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
/* Actions (icon buttons in the end region) */
|
|
22
22
|
--stuic-header-actions-gap: 0.25rem;
|
|
23
23
|
|
|
24
|
+
/* Gap between the end-area regions (locale / actions / avatar / hamburger)
|
|
25
|
+
while collapsed. Expanded uses --stuic-header-gap; collapsed is tighter
|
|
26
|
+
because the locale switcher stays inline there instead of folding into
|
|
27
|
+
the hamburger, and at 320px the full gap pushes a locale + 3 actions +
|
|
28
|
+
hamburger row past the content edge. Set to var(--stuic-header-gap) to
|
|
29
|
+
keep both states equal. */
|
|
30
|
+
--stuic-header-end-gap-collapsed: 0.5rem;
|
|
31
|
+
|
|
24
32
|
/* Hamburger offsets — outdent the built-in hamburger buttons on both
|
|
25
33
|
inline sides so the *icon* (not the invisible iconButton hit area)
|
|
26
34
|
sits flush with the header's content padding on one side and tightens
|
|
@@ -126,6 +134,14 @@
|
|
|
126
134
|
font-size: var(--stuic-header-project-name-font-size, var(--text-lg, 1.125rem));
|
|
127
135
|
font-weight: var(--stuic-header-project-name-font-weight);
|
|
128
136
|
white-space: nowrap;
|
|
137
|
+
/* Truncate instead of overflowing the row. `min-width: 0` lets this
|
|
138
|
+
flex item shrink below its content width (auto minimum size would
|
|
139
|
+
otherwise pin it), and `overflow: hidden` is what makes
|
|
140
|
+
`text-overflow` apply. Matters most in collapsed mode, where the
|
|
141
|
+
name shares the row with the locale trigger + hamburger. */
|
|
142
|
+
min-width: 0;
|
|
143
|
+
overflow: hidden;
|
|
144
|
+
text-overflow: ellipsis;
|
|
129
145
|
}
|
|
130
146
|
|
|
131
147
|
/* ============================================================================
|
|
@@ -193,6 +209,10 @@
|
|
|
193
209
|
flex-shrink: 0;
|
|
194
210
|
}
|
|
195
211
|
|
|
212
|
+
.stuic-header[data-collapsed] .stuic-header-end {
|
|
213
|
+
gap: var(--stuic-header-end-gap-collapsed);
|
|
214
|
+
}
|
|
215
|
+
|
|
196
216
|
.stuic-header-avatar {
|
|
197
217
|
display: flex;
|
|
198
218
|
align-items: center;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { ValidateOptions } from "../../actions/validate.svelte.js";
|
|
4
|
+
import type { TranslateFn } from "../../types.js";
|
|
5
|
+
import type { ColorPickerCustom } from "../ColorPicker/ColorPicker.svelte";
|
|
6
|
+
import type { ColorPickerSwatch } from "../ColorPicker/palettes.js";
|
|
7
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
8
|
+
import type { InputWrapClassProps } from "./types.js";
|
|
9
|
+
|
|
10
|
+
type SnippetWithId = Snippet<[{ id: string }]>;
|
|
11
|
+
|
|
12
|
+
export interface Props extends InputWrapClassProps, Record<string, any> {
|
|
13
|
+
/** Current color (bindable). Any CSS color string; `""` = no color */
|
|
14
|
+
value?: string;
|
|
15
|
+
label?: SnippetWithId | THC;
|
|
16
|
+
description?: SnippetWithId | THC;
|
|
17
|
+
class?: string;
|
|
18
|
+
id?: string;
|
|
19
|
+
renderSize?: "sm" | "md" | "lg" | string;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
|
|
23
|
+
labelAfter?: SnippetWithId | THC;
|
|
24
|
+
inputBefore?: SnippetWithId | THC;
|
|
25
|
+
inputAfter?: SnippetWithId | THC;
|
|
26
|
+
inputBelow?: SnippetWithId | THC;
|
|
27
|
+
below?: SnippetWithId | THC;
|
|
28
|
+
labelLeft?: boolean;
|
|
29
|
+
labelLeftWidth?: "normal" | "wide";
|
|
30
|
+
labelLeftBreakpoint?: number;
|
|
31
|
+
/** Classes for the underlying <ColorPicker> root */
|
|
32
|
+
classInput?: string;
|
|
33
|
+
style?: string;
|
|
34
|
+
//
|
|
35
|
+
// Below: forwarded as-is to the underlying <ColorPicker>.
|
|
36
|
+
//
|
|
37
|
+
/** The swatches: color strings or `{ value, label }` objects */
|
|
38
|
+
palette?: ColorPickerSwatch[];
|
|
39
|
+
/** Cap the palette at this many swatches per row */
|
|
40
|
+
columns?: number;
|
|
41
|
+
/** Which custom-color controls to show (default `"both"`) */
|
|
42
|
+
custom?: ColorPickerCustom;
|
|
43
|
+
/** Offer clearing (the "no color" swatch, Delete / Backspace) */
|
|
44
|
+
allowClear?: boolean;
|
|
45
|
+
/** Form field name (hidden input) */
|
|
46
|
+
name?: string;
|
|
47
|
+
/** i18n translate function (see `createColorPickerT`) */
|
|
48
|
+
t?: TranslateFn;
|
|
49
|
+
/** Fires when the user commits a color (not while dragging the native picker) */
|
|
50
|
+
onchange?: (value: string) => void;
|
|
51
|
+
/** Class for every swatch button */
|
|
52
|
+
classSwatch?: string;
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<script lang="ts">
|
|
57
|
+
import type { ValidationResult } from "../../actions/validate.svelte.js";
|
|
58
|
+
import { getId } from "../../utils/get-id.js";
|
|
59
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
60
|
+
import ColorPicker from "../ColorPicker/ColorPicker.svelte";
|
|
61
|
+
import InputWrap from "./_internal/InputWrap.svelte";
|
|
62
|
+
|
|
63
|
+
let {
|
|
64
|
+
value = $bindable(""),
|
|
65
|
+
label = "",
|
|
66
|
+
id = getId(),
|
|
67
|
+
description,
|
|
68
|
+
class: classProp,
|
|
69
|
+
renderSize = "md",
|
|
70
|
+
//
|
|
71
|
+
required = false,
|
|
72
|
+
disabled = false,
|
|
73
|
+
//
|
|
74
|
+
// Renamed local binding to avoid collision with `export function validate()` below.
|
|
75
|
+
validate: validateProp,
|
|
76
|
+
//
|
|
77
|
+
labelAfter,
|
|
78
|
+
inputBefore,
|
|
79
|
+
inputAfter,
|
|
80
|
+
inputBelow,
|
|
81
|
+
below,
|
|
82
|
+
//
|
|
83
|
+
labelLeft = false,
|
|
84
|
+
labelLeftWidth = "normal",
|
|
85
|
+
labelLeftBreakpoint = 480,
|
|
86
|
+
//
|
|
87
|
+
classInput,
|
|
88
|
+
//
|
|
89
|
+
palette,
|
|
90
|
+
columns,
|
|
91
|
+
custom,
|
|
92
|
+
allowClear,
|
|
93
|
+
name,
|
|
94
|
+
t,
|
|
95
|
+
onchange,
|
|
96
|
+
classSwatch,
|
|
97
|
+
//
|
|
98
|
+
classLabel,
|
|
99
|
+
classLabelBox,
|
|
100
|
+
classInputBox,
|
|
101
|
+
classInputBoxWrap,
|
|
102
|
+
classInputBoxWrapInvalid,
|
|
103
|
+
classDescBox,
|
|
104
|
+
classDescBoxToggle,
|
|
105
|
+
classBelowBox,
|
|
106
|
+
classValidationBox,
|
|
107
|
+
style = "",
|
|
108
|
+
//
|
|
109
|
+
...rest
|
|
110
|
+
}: Props = $props();
|
|
111
|
+
|
|
112
|
+
//
|
|
113
|
+
let validation: ValidationResult | undefined = $state();
|
|
114
|
+
const setValidationResult = (res: ValidationResult) => (validation = res);
|
|
115
|
+
|
|
116
|
+
// Delegate the imperative API to the inner ColorPicker.
|
|
117
|
+
let pickerRef: ColorPicker | undefined = $state();
|
|
118
|
+
|
|
119
|
+
/** Trigger validation now. Renders the inline message if invalid. */
|
|
120
|
+
export function validate(): ValidationResult | undefined {
|
|
121
|
+
pickerRef?.validate();
|
|
122
|
+
return validation;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Clear the inline validation message. */
|
|
126
|
+
export function clearValidation(): void {
|
|
127
|
+
pickerRef?.clearValidation?.();
|
|
128
|
+
validation = undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Current validation state. */
|
|
132
|
+
export function getValidation(): ValidationResult | undefined {
|
|
133
|
+
return validation;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Focus the swatch group's tab stop (or the first custom-color control). */
|
|
137
|
+
export function focus(): void {
|
|
138
|
+
pickerRef?.focus?.();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Scroll the field into view. Defaults to smooth + center. */
|
|
142
|
+
export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
|
|
143
|
+
pickerRef?.scrollIntoView?.(opts);
|
|
144
|
+
}
|
|
145
|
+
</script>
|
|
146
|
+
|
|
147
|
+
<InputWrap
|
|
148
|
+
{description}
|
|
149
|
+
class={classProp}
|
|
150
|
+
size={renderSize}
|
|
151
|
+
{id}
|
|
152
|
+
{label}
|
|
153
|
+
{labelAfter}
|
|
154
|
+
{inputBefore}
|
|
155
|
+
{inputAfter}
|
|
156
|
+
{inputBelow}
|
|
157
|
+
{below}
|
|
158
|
+
{required}
|
|
159
|
+
{disabled}
|
|
160
|
+
{labelLeft}
|
|
161
|
+
{labelLeftWidth}
|
|
162
|
+
{labelLeftBreakpoint}
|
|
163
|
+
{classLabel}
|
|
164
|
+
{classLabelBox}
|
|
165
|
+
{classInputBox}
|
|
166
|
+
{classInputBoxWrapInvalid}
|
|
167
|
+
{classDescBox}
|
|
168
|
+
{classDescBoxToggle}
|
|
169
|
+
{classBelowBox}
|
|
170
|
+
{classValidationBox}
|
|
171
|
+
{validation}
|
|
172
|
+
classInputBoxWrap={twMerge("input-wrap-transparent", classInputBoxWrap)}
|
|
173
|
+
{style}
|
|
174
|
+
>
|
|
175
|
+
<!--
|
|
176
|
+
`labelledby` (not the InputWrap's `for={id}`) is what names the swatch group: a
|
|
177
|
+
radiogroup is a <div>, which a `for` cannot label. Matches InputWrap's own
|
|
178
|
+
`{id}-label`, and only when there is a label to point at — otherwise the picker
|
|
179
|
+
keeps its own `t("color")` name. The picker's string `label` prop is deliberately
|
|
180
|
+
not forwarded: here `label` is the visible one.
|
|
181
|
+
-->
|
|
182
|
+
<ColorPicker
|
|
183
|
+
bind:this={pickerRef}
|
|
184
|
+
bind:value
|
|
185
|
+
{palette}
|
|
186
|
+
{columns}
|
|
187
|
+
{custom}
|
|
188
|
+
{allowClear}
|
|
189
|
+
{name}
|
|
190
|
+
{t}
|
|
191
|
+
{onchange}
|
|
192
|
+
{classSwatch}
|
|
193
|
+
{required}
|
|
194
|
+
{disabled}
|
|
195
|
+
class={classInput}
|
|
196
|
+
labelledby={label ? `${id}-label` : undefined}
|
|
197
|
+
validate={validateProp}
|
|
198
|
+
{setValidationResult}
|
|
199
|
+
{...rest}
|
|
200
|
+
/>
|
|
201
|
+
</InputWrap>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { ValidateOptions } from "../../actions/validate.svelte.js";
|
|
3
|
+
import type { TranslateFn } from "../../types.js";
|
|
4
|
+
import type { ColorPickerCustom } from "../ColorPicker/ColorPicker.svelte";
|
|
5
|
+
import type { ColorPickerSwatch } from "../ColorPicker/palettes.js";
|
|
6
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
7
|
+
import type { InputWrapClassProps } from "./types.js";
|
|
8
|
+
type SnippetWithId = Snippet<[{
|
|
9
|
+
id: string;
|
|
10
|
+
}]>;
|
|
11
|
+
export interface Props extends InputWrapClassProps, Record<string, any> {
|
|
12
|
+
/** Current color (bindable). Any CSS color string; `""` = no color */
|
|
13
|
+
value?: string;
|
|
14
|
+
label?: SnippetWithId | THC;
|
|
15
|
+
description?: SnippetWithId | THC;
|
|
16
|
+
class?: string;
|
|
17
|
+
id?: string;
|
|
18
|
+
renderSize?: "sm" | "md" | "lg" | string;
|
|
19
|
+
required?: boolean;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
|
|
22
|
+
labelAfter?: SnippetWithId | THC;
|
|
23
|
+
inputBefore?: SnippetWithId | THC;
|
|
24
|
+
inputAfter?: SnippetWithId | THC;
|
|
25
|
+
inputBelow?: SnippetWithId | THC;
|
|
26
|
+
below?: SnippetWithId | THC;
|
|
27
|
+
labelLeft?: boolean;
|
|
28
|
+
labelLeftWidth?: "normal" | "wide";
|
|
29
|
+
labelLeftBreakpoint?: number;
|
|
30
|
+
/** Classes for the underlying <ColorPicker> root */
|
|
31
|
+
classInput?: string;
|
|
32
|
+
style?: string;
|
|
33
|
+
/** The swatches: color strings or `{ value, label }` objects */
|
|
34
|
+
palette?: ColorPickerSwatch[];
|
|
35
|
+
/** Cap the palette at this many swatches per row */
|
|
36
|
+
columns?: number;
|
|
37
|
+
/** Which custom-color controls to show (default `"both"`) */
|
|
38
|
+
custom?: ColorPickerCustom;
|
|
39
|
+
/** Offer clearing (the "no color" swatch, Delete / Backspace) */
|
|
40
|
+
allowClear?: boolean;
|
|
41
|
+
/** Form field name (hidden input) */
|
|
42
|
+
name?: string;
|
|
43
|
+
/** i18n translate function (see `createColorPickerT`) */
|
|
44
|
+
t?: TranslateFn;
|
|
45
|
+
/** Fires when the user commits a color (not while dragging the native picker) */
|
|
46
|
+
onchange?: (value: string) => void;
|
|
47
|
+
/** Class for every swatch button */
|
|
48
|
+
classSwatch?: string;
|
|
49
|
+
}
|
|
50
|
+
import type { ValidationResult } from "../../actions/validate.svelte.js";
|
|
51
|
+
declare const FieldColorPicker: import("svelte").Component<Props, {
|
|
52
|
+
validate: () => ValidationResult | undefined;
|
|
53
|
+
clearValidation: () => void;
|
|
54
|
+
getValidation: () => ValidationResult | undefined;
|
|
55
|
+
focus: () => void;
|
|
56
|
+
scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
|
|
57
|
+
}, "value">;
|
|
58
|
+
type FieldColorPicker = ReturnType<typeof FieldColorPicker>;
|
|
59
|
+
export default FieldColorPicker;
|
|
@@ -15,6 +15,7 @@ A comprehensive form input system with multiple field components, validation sup
|
|
|
15
15
|
| `FieldCheckbox` | Single checkbox with label |
|
|
16
16
|
| `FieldRadios` | Radio button group |
|
|
17
17
|
| `FieldSwitch` | Toggle switch field |
|
|
18
|
+
| `FieldColorPicker` | Color picker (swatches + custom color) field — see below |
|
|
18
19
|
| `FieldFile` | File upload input |
|
|
19
20
|
| `FieldAssets` | Asset/image upload with preview |
|
|
20
21
|
| `FieldSingleAsset` | One asset (avatar, logo, cover, one document) — see below |
|
|
@@ -184,6 +185,50 @@ The visible label names the control via `aria-labelledby` (the switch is announc
|
|
|
184
185
|
`switch, on/off` with that name). Clicking the label text does not toggle — the switch's
|
|
185
186
|
own root is a `<label>`, so an HTML `for` association is not possible; click the switch.
|
|
186
187
|
|
|
188
|
+
### Color Picker
|
|
189
|
+
|
|
190
|
+
`FieldColorPicker` wraps a [`ColorPicker`](../ColorPicker/README.md) in the standard field
|
|
191
|
+
scaffolding — visible label, description, validation box, `labelLeft*`, every
|
|
192
|
+
`InputWrapClassProps` member — the way `FieldSwitch` wraps `Switch`.
|
|
193
|
+
|
|
194
|
+
```svelte
|
|
195
|
+
<script lang="ts">
|
|
196
|
+
import { FieldColorPicker } from "stuic";
|
|
197
|
+
|
|
198
|
+
let accent = $state("");
|
|
199
|
+
</script>
|
|
200
|
+
|
|
201
|
+
<FieldColorPicker
|
|
202
|
+
label="Page colour"
|
|
203
|
+
description="Used for buttons and links on the public page."
|
|
204
|
+
name="accent"
|
|
205
|
+
required
|
|
206
|
+
bind:value={accent}
|
|
207
|
+
/>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
| Prop | Goes to |
|
|
211
|
+
| ----------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
|
|
212
|
+
| `value` (bindable), `palette`, `columns`, `custom`, `allowClear`, `name`, `t`, `onchange` | the inner `<ColorPicker>` (same semantics, same defaults) |
|
|
213
|
+
| `classSwatch` | every swatch button |
|
|
214
|
+
| `classInput` | the inner `<ColorPicker>`'s root class |
|
|
215
|
+
| `required`, `disabled`, `validate` | both: the shell (asterisk, dimming, message) and the picker |
|
|
216
|
+
| `renderSize` | the surrounding field shell only — swatch size is a CSS token |
|
|
217
|
+
| everything else unrecognized (`...rest`) | the inner `<ColorPicker>` root |
|
|
218
|
+
|
|
219
|
+
- **Naming.** The visible label names the swatch radiogroup via `aria-labelledby` →
|
|
220
|
+
`{id}-label`, with no competing `aria-label`. Without a `label` the group keeps the
|
|
221
|
+
picker's own `t("color")` name. Clicking the label text focuses nothing (a radiogroup is
|
|
222
|
+
not labelable by `for`) — same as `FieldSwitch`. With `palette={[]}` and
|
|
223
|
+
`allowClear={false}` there is no radiogroup, so the label names nothing; the hex field
|
|
224
|
+
keeps its own "Hex value" name.
|
|
225
|
+
- **Validation.** The picker's hidden input runs the `validate` action; the result renders
|
|
226
|
+
in the field's validation box. `validate()`, `clearValidation()`, `getValidation()`,
|
|
227
|
+
`focus()` (the checked swatch — the group's tab stop) and `scrollIntoView()` are
|
|
228
|
+
available via `bind:this`, so `validateAllFields` / `scrollToFirstInvalidField` work.
|
|
229
|
+
- **Layout.** The input wrap is transparent (no border / focus ring — the picker draws its
|
|
230
|
+
own swatches and hex field). A disabled field dims once (the shell), not twice.
|
|
231
|
+
|
|
187
232
|
### Input with Addons
|
|
188
233
|
|
|
189
234
|
```svelte
|
|
@@ -144,12 +144,18 @@
|
|
|
144
144
|
/* ============================================================================
|
|
145
145
|
BASE INPUT ELEMENT STYLES
|
|
146
146
|
Replaces @tailwindcss/forms plugin for input elements
|
|
147
|
+
|
|
148
|
+
`:where(:not(.stuic-color-picker *))` (here, in the size variants and in the
|
|
149
|
+
iOS zoom guard) exempts a composite control that styles its own raw inputs:
|
|
150
|
+
FieldColorPicker's hex field and native picker would otherwise lose their
|
|
151
|
+
border, font and size to these rules. Inside `:where()`, so the specificity
|
|
152
|
+
every existing override was written against is unchanged.
|
|
147
153
|
============================================================================ */
|
|
148
154
|
|
|
149
155
|
.stuic-input
|
|
150
156
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
|
|
151
157
|
[type="file"]
|
|
152
|
-
),
|
|
158
|
+
):where(:not(.stuic-color-picker *)),
|
|
153
159
|
.stuic-input textarea,
|
|
154
160
|
.stuic-input select {
|
|
155
161
|
/* Reset browser defaults */
|
|
@@ -202,7 +208,7 @@
|
|
|
202
208
|
.stuic-input[data-size="sm"]
|
|
203
209
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
|
|
204
210
|
[type="file"]
|
|
205
|
-
),
|
|
211
|
+
):where(:not(.stuic-color-picker *)),
|
|
206
212
|
.stuic-input[data-size="sm"] textarea,
|
|
207
213
|
.stuic-input[data-size="sm"] select {
|
|
208
214
|
padding: var(--stuic-input-padding-y-sm) var(--stuic-input-padding-x-sm);
|
|
@@ -217,13 +223,13 @@
|
|
|
217
223
|
.stuic-input[data-size="md"]
|
|
218
224
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
|
|
219
225
|
[type="file"]
|
|
220
|
-
),
|
|
226
|
+
):where(:not(.stuic-color-picker *)),
|
|
221
227
|
.stuic-input[data-size="md"] textarea,
|
|
222
228
|
.stuic-input[data-size="md"] select,
|
|
223
229
|
.stuic-input:not([data-size])
|
|
224
230
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
|
|
225
231
|
[type="file"]
|
|
226
|
-
),
|
|
232
|
+
):where(:not(.stuic-color-picker *)),
|
|
227
233
|
.stuic-input:not([data-size]) textarea,
|
|
228
234
|
.stuic-input:not([data-size]) select {
|
|
229
235
|
padding: var(--stuic-input-padding-y-md) var(--stuic-input-padding-x-md);
|
|
@@ -239,7 +245,7 @@
|
|
|
239
245
|
.stuic-input[data-size="lg"]
|
|
240
246
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
|
|
241
247
|
[type="file"]
|
|
242
|
-
),
|
|
248
|
+
):where(:not(.stuic-color-picker *)),
|
|
243
249
|
.stuic-input[data-size="lg"] textarea,
|
|
244
250
|
.stuic-input[data-size="lg"] select {
|
|
245
251
|
padding: var(--stuic-input-padding-y-lg) var(--stuic-input-padding-x-lg);
|
|
@@ -329,6 +335,12 @@
|
|
|
329
335
|
box-shadow: none;
|
|
330
336
|
}
|
|
331
337
|
|
|
338
|
+
/* FieldColorPicker: a disabled shell already dims the wrap — dimming the picker
|
|
339
|
+
again would compound to 0.25 */
|
|
340
|
+
.stuic-input.disabled .stuic-color-picker[data-disabled] {
|
|
341
|
+
opacity: 1;
|
|
342
|
+
}
|
|
343
|
+
|
|
332
344
|
/* Size-specific input-wrap padding */
|
|
333
345
|
.stuic-input[data-size="lg"] .input-wrap {
|
|
334
346
|
padding: calc(var(--spacing) * 1);
|
|
@@ -841,7 +853,7 @@
|
|
|
841
853
|
.stuic-input[data-size="sm"]
|
|
842
854
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
|
|
843
855
|
[type="file"]
|
|
844
|
-
),
|
|
856
|
+
):where(:not(.stuic-color-picker *)),
|
|
845
857
|
.stuic-input[data-size="sm"] textarea,
|
|
846
858
|
.stuic-input[data-size="sm"] select {
|
|
847
859
|
font-size: max(
|
|
@@ -853,13 +865,13 @@
|
|
|
853
865
|
.stuic-input[data-size="md"]
|
|
854
866
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
|
|
855
867
|
[type="file"]
|
|
856
|
-
),
|
|
868
|
+
):where(:not(.stuic-color-picker *)),
|
|
857
869
|
.stuic-input[data-size="md"] textarea,
|
|
858
870
|
.stuic-input[data-size="md"] select,
|
|
859
871
|
.stuic-input:not([data-size])
|
|
860
872
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
|
|
861
873
|
[type="file"]
|
|
862
|
-
),
|
|
874
|
+
):where(:not(.stuic-color-picker *)),
|
|
863
875
|
.stuic-input:not([data-size]) textarea,
|
|
864
876
|
.stuic-input:not([data-size]) select {
|
|
865
877
|
font-size: max(
|
|
@@ -871,7 +883,7 @@
|
|
|
871
883
|
.stuic-input[data-size="lg"]
|
|
872
884
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not(
|
|
873
885
|
[type="file"]
|
|
874
|
-
),
|
|
886
|
+
):where(:not(.stuic-color-picker *)),
|
|
875
887
|
.stuic-input[data-size="lg"] textarea,
|
|
876
888
|
.stuic-input[data-size="lg"] select {
|
|
877
889
|
font-size: max(
|
|
@@ -4,6 +4,7 @@ export { default as FieldSingleAsset, type Props as FieldSingleAssetProps, type
|
|
|
4
4
|
export { createFieldSingleAssetT, FIELD_SINGLE_ASSET_MESSAGES_EN, type FieldSingleAssetMessageKey, type FieldSingleAssetMessages, } from "./field-single-asset-i18n.js";
|
|
5
5
|
export { FIELD_SINGLE_ASSET_MESSAGES_SK } from "./field-single-asset-i18n-sk.js";
|
|
6
6
|
export { default as FieldCheckbox, type Props as FieldCheckboxProps, } from "./FieldCheckbox.svelte";
|
|
7
|
+
export { default as FieldColorPicker, type Props as FieldColorPickerProps, } from "./FieldColorPicker.svelte";
|
|
7
8
|
export { default as Honeypot, type Props as HoneypotProps } from "./Honeypot.svelte";
|
|
8
9
|
export { default as TimeTrap, type Props as TimeTrapProps, type TimeTrapSnapshot, } from "./TimeTrap.svelte";
|
|
9
10
|
export { default as FieldFile, type Props as FieldFileProps } from "./FieldFile.svelte";
|
|
@@ -4,6 +4,7 @@ export { default as FieldSingleAsset, } from "./FieldSingleAsset.svelte";
|
|
|
4
4
|
export { createFieldSingleAssetT, FIELD_SINGLE_ASSET_MESSAGES_EN, } from "./field-single-asset-i18n.js";
|
|
5
5
|
export { FIELD_SINGLE_ASSET_MESSAGES_SK } from "./field-single-asset-i18n-sk.js";
|
|
6
6
|
export { default as FieldCheckbox, } from "./FieldCheckbox.svelte";
|
|
7
|
+
export { default as FieldColorPicker, } from "./FieldColorPicker.svelte";
|
|
7
8
|
export { default as Honeypot } from "./Honeypot.svelte";
|
|
8
9
|
export { default as TimeTrap, } from "./TimeTrap.svelte";
|
|
9
10
|
export { default as FieldFile } from "./FieldFile.svelte";
|
|
@@ -7,7 +7,7 @@ import type { ValidationResult } from "../actions/validate.svelte.js";
|
|
|
7
7
|
* Every STUIC `Field*` component (FieldInput, FieldPhoneNumber, FieldCountry,
|
|
8
8
|
* FieldSelect, FieldCheckbox, FieldTextarea, FieldFile, FieldObject,
|
|
9
9
|
* FieldAssets, FieldInputLocalized, FieldKeyValues, FieldLikeButton,
|
|
10
|
-
* FieldRadios, FieldSwitch) satisfies this interface via `export function`.
|
|
10
|
+
* FieldRadios, FieldSwitch, FieldColorPicker) satisfies this interface via `export function`.
|
|
11
11
|
*/
|
|
12
12
|
export interface ValidatableField {
|
|
13
13
|
/** Run the validator now. Renders the inline error if invalid. */
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
| CronInput | Cron expression editor with presets and validation |
|
|
74
74
|
| Fieldset | Field grouping with legend |
|
|
75
75
|
| FieldKeyValues | Key-value pair editor |
|
|
76
|
+
| FieldColorPicker | ColorPicker in the field shell: visible label names the swatch group, description, validation box |
|
|
76
77
|
| FieldTable | Rows × typed columns editor (text/number/select/checkbox/date/url cells, container layout) |
|
|
77
78
|
| FieldsBuilder | Field-definition list editor ("what properties does a thing have?") |
|
|
78
79
|
| FieldAssets | File/asset management |
|
|
@@ -159,7 +160,7 @@ Use `validate={false}` to bypass stuic's validation entirely.
|
|
|
159
160
|
|
|
160
161
|
> **Why default-on?** Hidden-input field components (`FieldPhoneNumber`,
|
|
161
162
|
> `FieldCountry`, `FieldObject`, `FieldAssets`, `FieldSingleAsset`, `FieldInputLocalized`,
|
|
162
|
-
> `FieldKeyValues`, `FieldTable`, `FieldLikeButton`, `FieldDate`, `FieldDateRange`, `Rating`) _must_ be default-on because hidden
|
|
163
|
+
> `FieldKeyValues`, `FieldTable`, `FieldLikeButton`, `FieldDate`, `FieldDateRange`, `ColorPicker`, `FieldColorPicker`, `Rating`) _must_ be default-on because hidden
|
|
163
164
|
> inputs are excluded from native browser constraint validation — without the
|
|
164
165
|
> stuic action enforcing `required` in a `customValidator`, the attribute is a
|
|
165
166
|
> silent no-op. Plain-input field components were harmonized to the same
|
|
@@ -170,8 +171,8 @@ Use `validate={false}` to bypass stuic's validation entirely.
|
|
|
170
171
|
Available on `FieldInput`, `FieldMoney`, `FieldTextarea`, `FieldCheckbox`,
|
|
171
172
|
`FieldSelect`, `FieldFile`, `FieldObject`, `FieldAssets`, `FieldSingleAsset`, `FieldInputLocalized`,
|
|
172
173
|
`FieldKeyValues`, `FieldTable`, `FieldPhoneNumber`, `FieldCountry`, `FieldLikeButton`,
|
|
173
|
-
`FieldRadios`, `FieldSwitch`, `FieldOptions`, `FieldDate`, `FieldDateRange`,
|
|
174
|
-
`Switch`:
|
|
174
|
+
`FieldRadios`, `FieldSwitch`, `FieldColorPicker`, `FieldOptions`, `FieldDate`, `FieldDateRange`,
|
|
175
|
+
`Switch`, and `ColorPicker`:
|
|
175
176
|
|
|
176
177
|
| Method | Returns | Purpose |
|
|
177
178
|
| ----------------------- | ------------------------------- | ------------------------------------------------------------- |
|
|
@@ -928,7 +929,9 @@ Swatch values are **never parsed** — they go to CSS as `--stuic-color-picker-s
|
|
|
928
929
|
| `onchange` | `(value: string) => void` | — | User **commits** only (see below) |
|
|
929
930
|
| `t` | `TranslateFn` | English | Group label, swatch names, "no color", custom-color labels, required message |
|
|
930
931
|
|
|
931
|
-
Class slots: `class`, `classSwatch`.
|
|
932
|
+
Class slots: `class`, `classSwatch`. `labelledby` (an element id) names the swatch group via `aria-labelledby` instead of `label`'s `aria-label`.
|
|
933
|
+
|
|
934
|
+
**In a form layout** use `FieldColorPicker` (exported from `Input`): the same `InputWrap` shell as every `Field*` (visible label, description, validation box, `labelLeft*`, `InputWrapClassProps`), with the picker props forwarded. The visible label names the radiogroup via `labelledby`; `focus()` / `scrollIntoView()` join the imperative API so `scrollToFirstInvalidField` works. The Input stylesheet exempts inputs inside `.stuic-color-picker` from its field-input rules (`:where(:not(.stuic-color-picker *))`, zero specificity) — otherwise the hex field and native picker would lose their border, font and size inside the shell.
|
|
932
935
|
|
|
933
936
|
### Preview vs commit
|
|
934
937
|
|
|
@@ -1515,6 +1518,18 @@ interface HeaderActionItem {
|
|
|
1515
1518
|
}
|
|
1516
1519
|
```
|
|
1517
1520
|
|
|
1521
|
+
### HeaderLocaleItem
|
|
1522
|
+
|
|
1523
|
+
```ts
|
|
1524
|
+
interface HeaderLocaleItem {
|
|
1525
|
+
id: string;
|
|
1526
|
+
label: THC;
|
|
1527
|
+
/** Compact label for the inline trigger in COLLAPSED mode only
|
|
1528
|
+
* (falls back to `label`); the dropdown list always shows `label`. */
|
|
1529
|
+
shortLabel?: THC;
|
|
1530
|
+
}
|
|
1531
|
+
```
|
|
1532
|
+
|
|
1518
1533
|
### Key Props
|
|
1519
1534
|
|
|
1520
1535
|
| Prop | Type | Default | Description |
|
|
@@ -1540,7 +1555,7 @@ interface HeaderActionItem {
|
|
|
1540
1555
|
| `contentMaxWidth` | `string \| number` | — | Inner content row max-width (outer header stays 100%). |
|
|
1541
1556
|
| `collapseThreshold` | `number` | `768` | Width (px) to collapse; 0 disables. |
|
|
1542
1557
|
| `collapseMode` | `"hamburger" \| "hide"` | `"hamburger"` | Collapse behavior. See top of section. |
|
|
1543
|
-
| `keepLocaleOnCollapse` | `boolean` | `
|
|
1558
|
+
| `keepLocaleOnCollapse` | `boolean` | `true` | Keep the locale switcher inline when collapsed (both modes). See below. |
|
|
1544
1559
|
| `fixed` | `boolean` | `false` | Fixed positioning at top. |
|
|
1545
1560
|
| `isCollapsed` | `boolean` | — | Bindable: collapsed state. |
|
|
1546
1561
|
| `isMenuOpen` | `boolean` | — | Bindable: hamburger menu open. |
|
|
@@ -1575,11 +1590,24 @@ Common pattern for app interfaces: the leading hamburger opens a side drawer for
|
|
|
1575
1590
|
|
|
1576
1591
|
See [Header/README.md](../../src/lib/components/Header/README.md) for the breakdown of which markup branch handles each requirement of this pattern.
|
|
1577
1592
|
|
|
1593
|
+
### The locale switcher never folds into the hamburger
|
|
1594
|
+
|
|
1595
|
+
By default (`keepLocaleOnCollapse`) the locale switcher stays **inline** when the header collapses, in both collapse modes — it sits just before the actions/avatar/hamburger instead of becoming the last section of the dropdown.
|
|
1596
|
+
|
|
1597
|
+
This is the one control `Header` deliberately treats differently from nav items. The user who most needs the language switch is the one who landed in a language they cannot read, and for them a visible `EN ▾` trigger is self-describing while a hamburger entry is not: the trigger is an unlabeled icon, the section heading says "Language" in a language they do not speak, and with more than a few nav items it sits below the fold of a menu they must scroll.
|
|
1598
|
+
|
|
1599
|
+
Consequences worth knowing:
|
|
1600
|
+
|
|
1601
|
+
- The dropdown never carries a duplicate locale section while the inline trigger is visible.
|
|
1602
|
+
- If the locales were the only thing the trailing dropdown would have held, no hamburger renders at all.
|
|
1603
|
+
- `HeaderLocaleItem.shortLabel` keeps the inline trigger narrow on phones (`"Slovenčina"` → `"SK"`) without shortening the dropdown entries.
|
|
1604
|
+
- Set `keepLocaleOnCollapse={false}` when the end area is already crowded with actions, or when the app's own drawer owns the language switch.
|
|
1605
|
+
|
|
1578
1606
|
### CSS Tokens
|
|
1579
1607
|
|
|
1580
1608
|
Prefix: `--stuic-header-*`
|
|
1581
1609
|
|
|
1582
|
-
`padding-x`, `padding-y`, `gap`, `min-height`, `nav-gap`, `content-max-width`, `project-name-font-weight`, `z-index`, `bg`, `text`, `border-width`, `border-color`, `nav-item-bg-active`, `nav-item-text-active`
|
|
1610
|
+
`padding-x`, `padding-y`, `gap`, `end-gap-collapsed`, `min-height`, `nav-gap`, `content-max-width`, `project-name-font-weight`, `z-index`, `bg`, `text`, `border-width`, `border-color`, `nav-item-bg-active`, `nav-item-text-active`
|
|
1583
1611
|
|
|
1584
1612
|
---
|
|
1585
1613
|
|