@marianmeres/stuic 3.115.0 → 3.116.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 +297 -304
- package/dist/actions/dim-behind/index.css +4 -1
- package/dist/actions/focus-trap.js +3 -1
- package/dist/components/Accordion/README.md +17 -17
- package/dist/components/Accordion/index.css +4 -2
- package/dist/components/AssetsPreview/README.md +7 -7
- package/dist/components/AssetsPreview/_internal/assets-preview-types.d.ts +1 -2
- package/dist/components/AssetsPreview/_internal/assets-preview-utils.d.ts +1 -1
- package/dist/components/AssetsPreview/_internal/assets-preview-utils.js +9 -3
- package/dist/components/Avatar/Avatar.svelte +1 -3
- package/dist/components/Avatar/README.md +33 -27
- package/dist/components/Book/Book.svelte +6 -1
- package/dist/components/Book/README.md +22 -20
- package/dist/components/Book/index.css +4 -2
- package/dist/components/Button/README.md +17 -17
- package/dist/components/Card/Card.svelte +25 -8
- package/dist/components/Card/README.md +52 -56
- package/dist/components/Card/index.css +2 -1
- package/dist/components/Carousel/Carousel.svelte +1 -3
- package/dist/components/Carousel/README.md +28 -28
- package/dist/components/Cart/Cart.svelte +2 -1
- package/dist/components/Cart/README.md +25 -25
- package/dist/components/Checkout/CheckoutGuestOrLoginForm.svelte +8 -3
- package/dist/components/Checkout/CheckoutShippingStep.svelte +1 -2
- package/dist/components/Checkout/README.md +143 -130
- package/dist/components/CronInput/CronInput.svelte +64 -60
- package/dist/components/CronInput/README.md +46 -46
- package/dist/components/DataTable/DataTable.svelte +5 -1
- package/dist/components/DataTable/README.md +78 -63
- package/dist/components/DropdownMenu/DropdownMenu.svelte +6 -2
- package/dist/components/DropdownMenu/README.md +33 -27
- package/dist/components/EmailVerifyForm/EmailVerifyForm.svelte +2 -9
- package/dist/components/EmailVerifyForm/README.md +30 -30
- package/dist/components/Header/Header.svelte +161 -165
- package/dist/components/Header/README.md +7 -7
- package/dist/components/IconSwap/README.md +20 -15
- package/dist/components/IconSwap/index.css +2 -1
- package/dist/components/ImageCycler/ImageCycler.svelte +19 -5
- package/dist/components/ImageCycler/ImageCycler.svelte.d.ts +14 -10
- package/dist/components/ImageCycler/README.md +15 -15
- package/dist/components/ImageCycler/index.css +26 -20
- package/dist/components/Input/FieldFile.svelte +1 -3
- package/dist/components/Input/FieldInput.svelte +1 -3
- package/dist/components/Input/FieldKeyValues.svelte +2 -6
- package/dist/components/Input/FieldObject.svelte +2 -1
- package/dist/components/Input/README.md +11 -11
- package/dist/components/KbdShortcut/index.css +2 -1
- package/dist/components/LoginForm/LoginForm.svelte +1 -7
- package/dist/components/LoginForm/README.md +46 -46
- package/dist/components/ModalDialog/index.css +2 -1
- package/dist/components/Notifications/index.css +24 -6
- package/dist/components/OtpInput/OtpInput.svelte +0 -0
- package/dist/components/OtpInput/README.md +15 -19
- package/dist/components/OtpInput/index.css +1 -4
- package/dist/components/OtpInput/index.d.ts +1 -1
- package/dist/components/OtpInput/index.js +1 -1
- package/dist/components/Pill/README.md +41 -40
- package/dist/components/Pill/index.css +3 -6
- package/dist/components/PricingTable/README.md +86 -86
- package/dist/components/PricingTable/index.css +20 -35
- package/dist/components/RegisterForm/README.md +60 -60
- package/dist/components/RegisterForm/RegisterForm.svelte +1 -7
- package/dist/components/Separator/README.md +7 -7
- package/dist/components/TabbedMenu/index.css +6 -3
- package/dist/components/Tree/README.md +67 -67
- package/dist/components/UserAvatarMenu/UserAvatarMenu.svelte +1 -5
- package/dist/components/WithSidePanel/index.css +4 -4
- package/dist/index.css +12 -8
- package/dist/utils/design-tokens.d.ts +1 -1
- package/dist/utils/design-tokens.js +1 -1
- package/docs/architecture.md +7 -7
- package/docs/component-testing/00-overview-and-roadmap.md +19 -19
- package/docs/component-testing/01-framework-setup.md +6 -6
- package/docs/component-testing/02-test-conventions.md +6 -5
- package/docs/component-testing/03-component-coverage-roadmap.md +27 -27
- package/docs/component-testing/04-hard-cases-and-e2e.md +8 -8
- package/docs/component-testing/05-ci.md +3 -3
- package/docs/component-testing/PROGRESS.md +30 -26
- package/docs/component-testing/README.md +8 -8
- package/docs/conventions.md +25 -25
- package/docs/domains/components.md +386 -385
- package/docs/domains/theming.md +24 -24
- package/docs/domains/utils.md +22 -25
- package/docs/testing.md +2 -2
- package/docs/upgrading.md +32 -28
- package/package.json +2 -1
|
@@ -4,32 +4,32 @@ A feature-rich dropdown menu component with CSS Anchor Positioning (with fallbac
|
|
|
4
4
|
|
|
5
5
|
## Props
|
|
6
6
|
|
|
7
|
-
| Prop | Type
|
|
8
|
-
| ------------------------ |
|
|
9
|
-
| `items` | `DropdownMenuItem[]`
|
|
10
|
-
| `isOpen` | `boolean`
|
|
11
|
-
| `position` | `DropdownMenuPosition`
|
|
12
|
-
| `offset` | `string`
|
|
13
|
-
| `maxHeight` | `string`
|
|
14
|
-
| `closeOnSelect` | `boolean`
|
|
15
|
-
| `closeOnClickOutside` | `boolean`
|
|
16
|
-
| `closeOnEscape` | `boolean`
|
|
17
|
-
| `forceFallback` | `boolean`
|
|
18
|
-
| `search` | `boolean \| DropdownMenuSearchConfig` | -
|
|
19
|
-
| `unstyled` | `boolean`
|
|
20
|
-
| `class` | `string`
|
|
21
|
-
| `classTrigger` | `string`
|
|
22
|
-
| `classDropdown` | `string`
|
|
23
|
-
| `classItem` | `string`
|
|
24
|
-
| `classItemActive` | `string`
|
|
25
|
-
| `classItemDisabled` | `string`
|
|
26
|
-
| `classDivider` | `string`
|
|
27
|
-
| `classHeader` | `string`
|
|
28
|
-
| `classExpandable` | `string`
|
|
29
|
-
| `classExpandableContent` | `string`
|
|
30
|
-
| `el` | `HTMLDivElement`
|
|
31
|
-
| `triggerEl` | `HTMLButtonElement`
|
|
32
|
-
| `dropdownEl` | `HTMLDivElement`
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
| ------------------------ | ------------------------------------- | --------------------- | -------------------------------------------------- |
|
|
9
|
+
| `items` | `DropdownMenuItem[]` | - | Menu items to display |
|
|
10
|
+
| `isOpen` | `boolean` | `false` | Controlled open state (bindable) |
|
|
11
|
+
| `position` | `DropdownMenuPosition` | `"bottom-span-right"` | Popover position relative to trigger |
|
|
12
|
+
| `offset` | `string` | `"0.25rem"` | Offset from trigger element (CSS value) |
|
|
13
|
+
| `maxHeight` | `string` | `"300px"` | Max height of dropdown |
|
|
14
|
+
| `closeOnSelect` | `boolean` | `true` | Close menu when action item is selected |
|
|
15
|
+
| `closeOnClickOutside` | `boolean` | `true` | Close on click outside |
|
|
16
|
+
| `closeOnEscape` | `boolean` | `true` | Close on Escape key |
|
|
17
|
+
| `forceFallback` | `boolean` | `false` | Force fallback positioning (for testing) |
|
|
18
|
+
| `search` | `boolean \| DropdownMenuSearchConfig` | - | Enable search/filter input (see [Search](#search)) |
|
|
19
|
+
| `unstyled` | `boolean` | `false` | Opt out of stuic base classes |
|
|
20
|
+
| `class` | `string` | - | Classes for wrapper element |
|
|
21
|
+
| `classTrigger` | `string` | - | Classes for trigger button |
|
|
22
|
+
| `classDropdown` | `string` | - | Classes for dropdown container |
|
|
23
|
+
| `classItem` | `string` | - | Classes for action items |
|
|
24
|
+
| `classItemActive` | `string` | - | Classes for active/focused item |
|
|
25
|
+
| `classItemDisabled` | `string` | - | Classes for disabled items |
|
|
26
|
+
| `classDivider` | `string` | - | Classes for dividers |
|
|
27
|
+
| `classHeader` | `string` | - | Classes for header items |
|
|
28
|
+
| `classExpandable` | `string` | - | Classes for expandable section header |
|
|
29
|
+
| `classExpandableContent` | `string` | - | Classes for expandable section content |
|
|
30
|
+
| `el` | `HTMLDivElement` | - | Wrapper element reference (bindable) |
|
|
31
|
+
| `triggerEl` | `HTMLButtonElement` | - | Trigger element reference (bindable) |
|
|
32
|
+
| `dropdownEl` | `HTMLDivElement` | - | Dropdown element reference (bindable) |
|
|
33
33
|
|
|
34
34
|
## Snippets
|
|
35
35
|
|
|
@@ -199,7 +199,13 @@ interface DropdownMenuSearchConfig {
|
|
|
199
199
|
<DropdownMenu
|
|
200
200
|
items={[
|
|
201
201
|
{ type: "action", id: "docs", label: "Documentation", href: "/docs" },
|
|
202
|
-
{
|
|
202
|
+
{
|
|
203
|
+
type: "action",
|
|
204
|
+
id: "github",
|
|
205
|
+
label: "GitHub",
|
|
206
|
+
href: "https://github.com/...",
|
|
207
|
+
target: "_blank",
|
|
208
|
+
},
|
|
203
209
|
{ type: "divider" },
|
|
204
210
|
{ type: "action", id: "logout", label: "Logout", onSelect: () => handleLogout() },
|
|
205
211
|
]}
|
|
@@ -265,15 +265,8 @@
|
|
|
265
265
|
{@render submitButton({ isSubmitting, disabled: submitDisabled })}
|
|
266
266
|
{:else}
|
|
267
267
|
<div class={unstyled ? undefined : "stuic-email-verify-form-submit"}>
|
|
268
|
-
<Button
|
|
269
|
-
|
|
270
|
-
type="submit"
|
|
271
|
-
disabled={submitDisabled}
|
|
272
|
-
class="w-full"
|
|
273
|
-
>
|
|
274
|
-
{isSubmitting
|
|
275
|
-
? t("email_verify_form.submitting")
|
|
276
|
-
: t("email_verify_form.submit")}
|
|
268
|
+
<Button intent="primary" type="submit" disabled={submitDisabled} class="w-full">
|
|
269
|
+
{isSubmitting ? t("email_verify_form.submitting") : t("email_verify_form.submit")}
|
|
277
270
|
</Button>
|
|
278
271
|
</div>
|
|
279
272
|
{/if}
|
|
@@ -36,39 +36,39 @@ The form owns:
|
|
|
36
36
|
|
|
37
37
|
## Props
|
|
38
38
|
|
|
39
|
-
| Prop | Type
|
|
40
|
-
| ----------------------- |
|
|
41
|
-
| `email` | `string`
|
|
42
|
-
| `onSubmit` | `(code: string) => void`
|
|
43
|
-
| `onResend` | `() => Promise<void> \| void`
|
|
44
|
-
| `resendCooldownSeconds` | `number`
|
|
45
|
-
| `isSubmitting` | `boolean`
|
|
46
|
-
| `error` | `string`
|
|
47
|
-
| `attemptsRemaining` | `number`
|
|
48
|
-
| `codeLength` | `number`
|
|
49
|
-
| `otpInputProps` | `Partial<OtpInputProps>`
|
|
50
|
-
| `notifications` | `NotificationsStack`
|
|
51
|
-
| `submitButton` | `Snippet`
|
|
52
|
-
| `footer` | `Snippet`
|
|
53
|
-
| `heading` | `string \| false`
|
|
54
|
-
| `t` | `TranslateFn`
|
|
55
|
-
| `unstyled` | `boolean`
|
|
56
|
-
| `class` | `string`
|
|
57
|
-
| `el` | `HTMLFormElement`
|
|
39
|
+
| Prop | Type | Default | Description |
|
|
40
|
+
| ----------------------- | ----------------------------- | -------- | -------------------------------------------------------- |
|
|
41
|
+
| `email` | `string` | required | Email address shown in the subhead |
|
|
42
|
+
| `onSubmit` | `(code: string) => void` | required | Called with the entered code (auto on complete + manual) |
|
|
43
|
+
| `onResend` | `() => Promise<void> \| void` | — | When set, renders a resend control |
|
|
44
|
+
| `resendCooldownSeconds` | `number` | `30` | Cooldown after a successful resend |
|
|
45
|
+
| `isSubmitting` | `boolean` | `false` | Disables submit + OtpInput |
|
|
46
|
+
| `error` | `string` | — | General error (renders alert + applies error styling) |
|
|
47
|
+
| `attemptsRemaining` | `number` | — | Inline hint, e.g. "3 attempts remaining" |
|
|
48
|
+
| `codeLength` | `number` | `6` | Forwarded to OtpInput |
|
|
49
|
+
| `otpInputProps` | `Partial<OtpInputProps>` | — | Pass-through props for the inner OtpInput |
|
|
50
|
+
| `notifications` | `NotificationsStack` | — | Route errors to notification system |
|
|
51
|
+
| `submitButton` | `Snippet` | — | Override submit section |
|
|
52
|
+
| `footer` | `Snippet` | — | Content below the resend control |
|
|
53
|
+
| `heading` | `string \| false` | — | Override heading text, or `false` to suppress entirely |
|
|
54
|
+
| `t` | `TranslateFn` | built-in | Translation function |
|
|
55
|
+
| `unstyled` | `boolean` | `false` | Skip default styling |
|
|
56
|
+
| `class` | `string` | — | Additional CSS classes on the form root |
|
|
57
|
+
| `el` | `HTMLFormElement` | — | Bindable form element |
|
|
58
58
|
|
|
59
59
|
## i18n keys
|
|
60
60
|
|
|
61
|
-
| Key
|
|
62
|
-
|
|
|
63
|
-
| `email_verify_form.heading`
|
|
64
|
-
| `email_verify_form.subheading`
|
|
65
|
-
| `email_verify_form.submit`
|
|
66
|
-
| `email_verify_form.submitting`
|
|
67
|
-
| `email_verify_form.resend_prompt`
|
|
68
|
-
| `email_verify_form.resend`
|
|
69
|
-
| `email_verify_form.resend_cooldown`
|
|
70
|
-
| `email_verify_form.resent`
|
|
71
|
-
| `email_verify_form.attempts_remaining`
|
|
61
|
+
| Key | Default |
|
|
62
|
+
| -------------------------------------- | ----------------------------------- |
|
|
63
|
+
| `email_verify_form.heading` | `Check your email` |
|
|
64
|
+
| `email_verify_form.subheading` | `We sent a 6-digit code to {email}` |
|
|
65
|
+
| `email_verify_form.submit` | `Verify` |
|
|
66
|
+
| `email_verify_form.submitting` | `Verifying...` |
|
|
67
|
+
| `email_verify_form.resend_prompt` | `Didn't receive it?` |
|
|
68
|
+
| `email_verify_form.resend` | `Resend code` |
|
|
69
|
+
| `email_verify_form.resend_cooldown` | `Resend available in {seconds}s` |
|
|
70
|
+
| `email_verify_form.resent` | `New code sent` |
|
|
71
|
+
| `email_verify_form.attempts_remaining` | `{count} attempts remaining` |
|
|
72
72
|
|
|
73
73
|
## CSS Tokens
|
|
74
74
|
|
|
@@ -331,8 +331,7 @@
|
|
|
331
331
|
// trailing dropdown when collapsed). In "hide" mode: visible when not
|
|
332
332
|
// collapsed, or when collapsed and `keepLocaleOnCollapse` is set.
|
|
333
333
|
let _showLocaleSwitcher = $derived(
|
|
334
|
-
_hasLocales &&
|
|
335
|
-
(!_isCollapsed || (collapseMode === "hide" && keepLocaleOnCollapse))
|
|
334
|
+
_hasLocales && (!_isCollapsed || (collapseMode === "hide" && keepLocaleOnCollapse))
|
|
336
335
|
);
|
|
337
336
|
|
|
338
337
|
// Active locale object (for trigger label); fallback to first
|
|
@@ -473,199 +472,196 @@
|
|
|
473
472
|
})}
|
|
474
473
|
{:else}
|
|
475
474
|
<div bind:offsetWidth={_innerWidth} class={_classContent} style={_styleContent}>
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
<Button
|
|
484
|
-
variant="ghost"
|
|
485
|
-
iconButton
|
|
486
|
-
size="sm"
|
|
487
|
-
{unstyled}
|
|
488
|
-
class={twMerge(
|
|
489
|
-
!unstyled && HEADER_LEADING_HAMBURGER_CLASSES,
|
|
490
|
-
classLeadingHamburger
|
|
491
|
-
)}
|
|
492
|
-
onclick={onLeadingHamburger}
|
|
493
|
-
aria-label={leadingHamburgerLabel}
|
|
494
|
-
>
|
|
495
|
-
{#if leadingHamburgerIcon}
|
|
496
|
-
<Thc thc={leadingHamburgerIcon} />
|
|
497
|
-
{:else}
|
|
498
|
-
{@html iconMenu({ size: iconSize })}
|
|
499
|
-
{/if}
|
|
500
|
-
</Button>
|
|
501
|
-
</div>
|
|
502
|
-
{/if}
|
|
503
|
-
|
|
504
|
-
<!-- Logo / Title (flex-1) -->
|
|
505
|
-
<div class={_classLogo}>
|
|
506
|
-
{#if logo}
|
|
507
|
-
{@render logo()}
|
|
508
|
-
{:else if projectName}
|
|
509
|
-
<span class={unstyled ? undefined : "stuic-header-project-name"}>
|
|
510
|
-
{projectName}
|
|
511
|
-
</span>
|
|
512
|
-
{/if}
|
|
513
|
-
</div>
|
|
514
|
-
|
|
515
|
-
<!-- Nav items (expanded mode) -->
|
|
516
|
-
{#if !_isCollapsed && items.length > 0}
|
|
517
|
-
<nav class={_classNav}>
|
|
518
|
-
{#each items as item (item.id)}
|
|
475
|
+
<!-- Leading slot (left-side) -->
|
|
476
|
+
{#if leading}
|
|
477
|
+
<div class={_classLeading}>
|
|
478
|
+
{@render leading({ isCollapsed: _isCollapsed })}
|
|
479
|
+
</div>
|
|
480
|
+
{:else if _showLeadingHamburger}
|
|
481
|
+
<div class={_classLeading}>
|
|
519
482
|
<Button
|
|
520
|
-
variant=
|
|
483
|
+
variant="ghost"
|
|
484
|
+
iconButton
|
|
521
485
|
size="sm"
|
|
522
|
-
href={item.href}
|
|
523
|
-
target={item.target}
|
|
524
|
-
disabled={item.disabled}
|
|
525
486
|
{unstyled}
|
|
526
487
|
class={twMerge(
|
|
527
|
-
!unstyled &&
|
|
528
|
-
|
|
529
|
-
classNavItem,
|
|
530
|
-
item.class
|
|
488
|
+
!unstyled && HEADER_LEADING_HAMBURGER_CLASSES,
|
|
489
|
+
classLeadingHamburger
|
|
531
490
|
)}
|
|
532
|
-
|
|
533
|
-
aria-
|
|
534
|
-
onclick={() => handleItemClick(item)}
|
|
491
|
+
onclick={onLeadingHamburger}
|
|
492
|
+
aria-label={leadingHamburgerLabel}
|
|
535
493
|
>
|
|
536
|
-
{#if
|
|
537
|
-
<
|
|
538
|
-
|
|
539
|
-
|
|
494
|
+
{#if leadingHamburgerIcon}
|
|
495
|
+
<Thc thc={leadingHamburgerIcon} />
|
|
496
|
+
{:else}
|
|
497
|
+
{@html iconMenu({ size: iconSize })}
|
|
540
498
|
{/if}
|
|
541
|
-
<Thc thc={item.label} />
|
|
542
499
|
</Button>
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
500
|
+
</div>
|
|
501
|
+
{/if}
|
|
502
|
+
|
|
503
|
+
<!-- Logo / Title (flex-1) -->
|
|
504
|
+
<div class={_classLogo}>
|
|
505
|
+
{#if logo}
|
|
506
|
+
{@render logo()}
|
|
507
|
+
{:else if projectName}
|
|
508
|
+
<span class={unstyled ? undefined : "stuic-header-project-name"}>
|
|
509
|
+
{projectName}
|
|
510
|
+
</span>
|
|
511
|
+
{/if}
|
|
512
|
+
</div>
|
|
513
|
+
|
|
514
|
+
<!-- Nav items (expanded mode) -->
|
|
515
|
+
{#if !_isCollapsed && items.length > 0}
|
|
516
|
+
<nav class={_classNav}>
|
|
517
|
+
{#each items as item (item.id)}
|
|
557
518
|
<Button
|
|
558
519
|
variant={navVariant}
|
|
559
520
|
size="sm"
|
|
521
|
+
href={item.href}
|
|
522
|
+
target={item.target}
|
|
523
|
+
disabled={item.disabled}
|
|
560
524
|
{unstyled}
|
|
561
525
|
class={twMerge(
|
|
562
|
-
!unstyled &&
|
|
563
|
-
|
|
526
|
+
!unstyled && HEADER_NAV_ITEM_CLASSES,
|
|
527
|
+
!unstyled && item.active && classNavItemActive,
|
|
528
|
+
classNavItem,
|
|
529
|
+
item.class
|
|
564
530
|
)}
|
|
565
|
-
|
|
566
|
-
aria-
|
|
567
|
-
{
|
|
531
|
+
data-active={!unstyled && item.active ? "" : undefined}
|
|
532
|
+
aria-current={item.active ? "page" : undefined}
|
|
533
|
+
onclick={() => handleItemClick(item)}
|
|
568
534
|
>
|
|
569
|
-
{#if
|
|
570
|
-
<
|
|
535
|
+
{#if item.icon}
|
|
536
|
+
<span class={unstyled ? undefined : "stuic-header-nav-icon"}>
|
|
537
|
+
<Thc thc={item.icon} />
|
|
538
|
+
</span>
|
|
571
539
|
{/if}
|
|
572
|
-
<
|
|
573
|
-
class={twMerge(
|
|
574
|
-
!unstyled && "stuic-header-locale-chevron",
|
|
575
|
-
isOpen && !unstyled && "stuic-header-locale-chevron-open"
|
|
576
|
-
)}
|
|
577
|
-
>
|
|
578
|
-
{@html iconChevronDown({ size: 14 })}
|
|
579
|
-
</span>
|
|
540
|
+
<Thc thc={item.label} />
|
|
580
541
|
</Button>
|
|
581
|
-
{/
|
|
582
|
-
</
|
|
542
|
+
{/each}
|
|
543
|
+
</nav>
|
|
583
544
|
{/if}
|
|
584
545
|
|
|
585
|
-
<!--
|
|
586
|
-
{
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
)}
|
|
595
|
-
{#if action.render}
|
|
596
|
-
{@render action.render({
|
|
597
|
-
action,
|
|
598
|
-
class: actionClass,
|
|
599
|
-
isCollapsed: _isCollapsed,
|
|
600
|
-
onclick: () => handleActionClick(action),
|
|
601
|
-
})}
|
|
602
|
-
{:else}
|
|
546
|
+
<!-- End area: locale + actions + avatar + trailing hamburger -->
|
|
547
|
+
<div class={_classEnd}>
|
|
548
|
+
<!-- Locale switcher (shown when expanded, or in "hide" mode with keepLocaleOnCollapse) -->
|
|
549
|
+
{#if _showLocaleSwitcher}
|
|
550
|
+
<DropdownMenu
|
|
551
|
+
items={_localeDropdownItems}
|
|
552
|
+
position="bottom-span-right"
|
|
553
|
+
class={_classLocale}
|
|
554
|
+
>
|
|
555
|
+
{#snippet trigger({ isOpen, toggle, triggerProps })}
|
|
603
556
|
<Button
|
|
604
|
-
variant=
|
|
605
|
-
iconButton
|
|
557
|
+
variant={navVariant}
|
|
606
558
|
size="sm"
|
|
607
|
-
href={action.href}
|
|
608
|
-
target={action.target}
|
|
609
|
-
disabled={action.disabled}
|
|
610
559
|
{unstyled}
|
|
611
|
-
class={
|
|
612
|
-
|
|
613
|
-
aria-label=
|
|
614
|
-
|
|
560
|
+
class={twMerge(!unstyled && "stuic-header-locale-trigger", classLocale)}
|
|
561
|
+
onclick={toggle}
|
|
562
|
+
aria-label="Change language"
|
|
563
|
+
{...triggerProps}
|
|
615
564
|
>
|
|
616
|
-
{#if
|
|
617
|
-
<Thc thc={
|
|
565
|
+
{#if _activeLocale}
|
|
566
|
+
<Thc thc={_activeLocale.label} />
|
|
618
567
|
{/if}
|
|
568
|
+
<span
|
|
569
|
+
class={twMerge(
|
|
570
|
+
!unstyled && "stuic-header-locale-chevron",
|
|
571
|
+
isOpen && !unstyled && "stuic-header-locale-chevron-open"
|
|
572
|
+
)}
|
|
573
|
+
>
|
|
574
|
+
{@html iconChevronDown({ size: 14 })}
|
|
575
|
+
</span>
|
|
619
576
|
</Button>
|
|
620
|
-
{/
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
{/if}
|
|
577
|
+
{/snippet}
|
|
578
|
+
</DropdownMenu>
|
|
579
|
+
{/if}
|
|
624
580
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
581
|
+
<!-- Actions (icon buttons, always visible) -->
|
|
582
|
+
{#if actions.length > 0}
|
|
583
|
+
<div class={_classActions}>
|
|
584
|
+
{#each actions as action (action.id)}
|
|
585
|
+
{@const actionClass = twMerge(
|
|
586
|
+
!unstyled && HEADER_ACTION_CLASSES,
|
|
587
|
+
!unstyled && action.active && classActionActive,
|
|
588
|
+
classAction,
|
|
589
|
+
action.class
|
|
590
|
+
)}
|
|
591
|
+
{#if action.render}
|
|
592
|
+
{@render action.render({
|
|
593
|
+
action,
|
|
594
|
+
class: actionClass,
|
|
595
|
+
isCollapsed: _isCollapsed,
|
|
596
|
+
onclick: () => handleActionClick(action),
|
|
597
|
+
})}
|
|
598
|
+
{:else}
|
|
599
|
+
<Button
|
|
600
|
+
variant="ghost"
|
|
601
|
+
iconButton
|
|
602
|
+
size="sm"
|
|
603
|
+
href={action.href}
|
|
604
|
+
target={action.target}
|
|
605
|
+
disabled={action.disabled}
|
|
606
|
+
{unstyled}
|
|
607
|
+
class={actionClass}
|
|
608
|
+
data-active={!unstyled && action.active ? "" : undefined}
|
|
609
|
+
aria-label={typeof action.label === "string" ? action.label : undefined}
|
|
610
|
+
onclick={() => handleActionClick(action)}
|
|
611
|
+
>
|
|
612
|
+
{#if action.icon !== undefined}
|
|
613
|
+
<Thc thc={action.icon} />
|
|
614
|
+
{/if}
|
|
615
|
+
</Button>
|
|
616
|
+
{/if}
|
|
617
|
+
{/each}
|
|
638
618
|
</div>
|
|
639
619
|
{/if}
|
|
640
|
-
{/if}
|
|
641
620
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
{#snippet trigger({ isOpen, toggle, triggerProps })}
|
|
650
|
-
<Button
|
|
651
|
-
variant="ghost"
|
|
652
|
-
iconButton
|
|
653
|
-
size="sm"
|
|
654
|
-
{unstyled}
|
|
655
|
-
class={twMerge(!unstyled && HEADER_HAMBURGER_CLASSES, classHamburger)}
|
|
656
|
-
onclick={toggle}
|
|
657
|
-
aria-label={isOpen ? "Close menu" : "Open menu"}
|
|
658
|
-
{...triggerProps}
|
|
621
|
+
<!-- Avatar: hidden when collapsed + avatarOnClick (moves into dropdown) -->
|
|
622
|
+
{#if avatar && !(_isCollapsed && _avatarInDropdown)}
|
|
623
|
+
{#if avatarOnClick}
|
|
624
|
+
<button
|
|
625
|
+
type="button"
|
|
626
|
+
class={twMerge(!unstyled && "stuic-header-avatar", classAvatar)}
|
|
627
|
+
onclick={avatarOnClick}
|
|
659
628
|
>
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
629
|
+
{@render avatar()}
|
|
630
|
+
</button>
|
|
631
|
+
{:else}
|
|
632
|
+
<div class={twMerge(!unstyled && "stuic-header-avatar", classAvatar)}>
|
|
633
|
+
{@render avatar()}
|
|
634
|
+
</div>
|
|
635
|
+
{/if}
|
|
636
|
+
{/if}
|
|
637
|
+
|
|
638
|
+
{#if _isCollapsed && _dropdownItems.length > 0}
|
|
639
|
+
<DropdownMenu
|
|
640
|
+
items={_dropdownItems}
|
|
641
|
+
bind:isOpen={isMenuOpen}
|
|
642
|
+
position={dropdownPosition}
|
|
643
|
+
class={classDropdown}
|
|
644
|
+
>
|
|
645
|
+
{#snippet trigger({ isOpen, toggle, triggerProps })}
|
|
646
|
+
<Button
|
|
647
|
+
variant="ghost"
|
|
648
|
+
iconButton
|
|
649
|
+
size="sm"
|
|
650
|
+
{unstyled}
|
|
651
|
+
class={twMerge(!unstyled && HEADER_HAMBURGER_CLASSES, classHamburger)}
|
|
652
|
+
onclick={toggle}
|
|
653
|
+
aria-label={isOpen ? "Close menu" : "Open menu"}
|
|
654
|
+
{...triggerProps}
|
|
655
|
+
>
|
|
656
|
+
<IconSwap
|
|
657
|
+
active={isOpen ? 1 : 0}
|
|
658
|
+
states={[iconMenu({ size: iconSize }), iconX({ size: iconSize })]}
|
|
659
|
+
/>
|
|
660
|
+
</Button>
|
|
661
|
+
{/snippet}
|
|
662
|
+
</DropdownMenu>
|
|
663
|
+
{/if}
|
|
664
|
+
</div>
|
|
669
665
|
</div>
|
|
670
666
|
{/if}
|
|
671
667
|
</header>
|
|
@@ -8,13 +8,13 @@ Top-bar component with leading slot, project logo, nav items, locale switcher, o
|
|
|
8
8
|
|
|
9
9
|
Common "app shell" pattern: when the header collapses below `collapseThreshold`, the avatar and a few key actions (search, notifications, cart…) remain visible, the trailing hamburger is NOT shown, and the nav items + locale switcher are hidden entirely (the nav typically lives in a drawer triggered by the leading hamburger instead).
|
|
10
10
|
|
|
11
|
-
| Requirement
|
|
12
|
-
|
|
|
13
|
-
| **Avatar stays visible**
|
|
14
|
-
| **Action buttons stay visible** | [Header.svelte:585](./Header.svelte#L585) — `<!-- Actions (icon buttons, always visible) -->`
|
|
15
|
-
| **No trailing hamburger**
|
|
16
|
-
| **Nav items hidden**
|
|
17
|
-
| **Locale hidden**
|
|
11
|
+
| Requirement | Where it's handled | How |
|
|
12
|
+
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| **Avatar stays visible** | [Header.svelte:626](./Header.svelte#L626) — `{#if avatar && !(_isCollapsed && _avatarInDropdown)}` | `_avatarInDropdown` requires `collapseMode === "hamburger"`. In `"hide"` mode it's always `false`, so the avatar always renders. |
|
|
14
|
+
| **Action buttons stay visible** | [Header.svelte:585](./Header.svelte#L585) — `<!-- Actions (icon buttons, always visible) -->` | The actions loop has no collapse gating — items render in both modes. |
|
|
15
|
+
| **No trailing hamburger** | [Header.svelte:642](./Header.svelte#L642) — `{#if _isCollapsed && _dropdownItems.length > 0}` | In `"hide"` mode, `_dropdownItems` short-circuits to `[]`, so the `{#if}` is false → no trailing hamburger. |
|
|
16
|
+
| **Nav items hidden** | [Header.svelte:516](./Header.svelte#L516) — `{#if !_isCollapsed && items.length > 0}` | Inline nav requires `!_isCollapsed`; combined with the empty `_dropdownItems` above, items don't reappear in a dropdown either. |
|
|
17
|
+
| **Locale hidden** | [Header.svelte:335](./Header.svelte#L335) — `!_isCollapsed \|\| (collapseMode === "hide" && keepLocaleOnCollapse)` | Default `keepLocaleOnCollapse={false}` hides the locale switcher in collapsed mode. |
|
|
18
18
|
|
|
19
19
|
Minimal config:
|
|
20
20
|
|
|
@@ -4,16 +4,16 @@ Cross-fades between N visual states (HTML strings or Snippets) at a single posit
|
|
|
4
4
|
|
|
5
5
|
## Props
|
|
6
6
|
|
|
7
|
-
| Prop | Type | Default | Description
|
|
8
|
-
| ------------ | -------------------------- | -------- |
|
|
9
|
-
| `states` | `Array<string \| Snippet>` | required | The visual states to swap between. Strings are rendered with `{@html}`.
|
|
10
|
-
| `active` | `number` | `0` | Bindable index of the currently visible state.
|
|
11
|
-
| `duration` | `number` | `300` | Transition duration in ms. Set `0` to disable.
|
|
12
|
-
| `easing` | `string` | `"ease"` | CSS `transition-timing-function`.
|
|
13
|
-
| `unstyled` | `boolean` | `false` | Skip default styling.
|
|
14
|
-
| `class` | `string` | - | Additional CSS classes for the root `<span>`.
|
|
15
|
-
| `stateClass` | `string` | - | Additional CSS classes for each state wrapper.
|
|
16
|
-
| `el` | `HTMLSpanElement` | - | Bindable root element.
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
| ------------ | -------------------------- | -------- | ----------------------------------------------------------------------- |
|
|
9
|
+
| `states` | `Array<string \| Snippet>` | required | The visual states to swap between. Strings are rendered with `{@html}`. |
|
|
10
|
+
| `active` | `number` | `0` | Bindable index of the currently visible state. |
|
|
11
|
+
| `duration` | `number` | `300` | Transition duration in ms. Set `0` to disable. |
|
|
12
|
+
| `easing` | `string` | `"ease"` | CSS `transition-timing-function`. |
|
|
13
|
+
| `unstyled` | `boolean` | `false` | Skip default styling. |
|
|
14
|
+
| `class` | `string` | - | Additional CSS classes for the root `<span>`. |
|
|
15
|
+
| `stateClass` | `string` | - | Additional CSS classes for each state wrapper. |
|
|
16
|
+
| `el` | `HTMLSpanElement` | - | Bindable root element. |
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
19
19
|
|
|
@@ -47,15 +47,20 @@ Cross-fades between N visual states (HTML strings or Snippets) at a single posit
|
|
|
47
47
|
### Custom easing/duration
|
|
48
48
|
|
|
49
49
|
```svelte
|
|
50
|
-
<IconSwap
|
|
50
|
+
<IconSwap
|
|
51
|
+
states={frames}
|
|
52
|
+
active={i}
|
|
53
|
+
duration={500}
|
|
54
|
+
easing="cubic-bezier(0.4, 0, 0.2, 1)"
|
|
55
|
+
/>
|
|
51
56
|
```
|
|
52
57
|
|
|
53
58
|
## CSS Variables
|
|
54
59
|
|
|
55
|
-
| Variable
|
|
56
|
-
|
|
|
57
|
-
| `--stuic-icon-swap-duration`
|
|
58
|
-
| `--stuic-icon-swap-easing`
|
|
60
|
+
| Variable | Default | Description |
|
|
61
|
+
| ---------------------------- | ------- | -------------------------- |
|
|
62
|
+
| `--stuic-icon-swap-duration` | (prop) | Transition duration |
|
|
63
|
+
| `--stuic-icon-swap-easing` | (prop) | Transition timing function |
|
|
59
64
|
|
|
60
65
|
## Data Attributes
|
|
61
66
|
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
justify-content: center;
|
|
23
23
|
opacity: 0;
|
|
24
24
|
pointer-events: none;
|
|
25
|
-
transition: opacity var(--stuic-icon-swap-duration, var(--stuic-transition))
|
|
25
|
+
transition: opacity var(--stuic-icon-swap-duration, var(--stuic-transition))
|
|
26
|
+
var(--stuic-icon-swap-easing);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
.stuic-icon-swap > .stuic-icon-swap-state[data-visible="true"] {
|