@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.
Files changed (86) hide show
  1. package/API.md +297 -304
  2. package/dist/actions/dim-behind/index.css +4 -1
  3. package/dist/actions/focus-trap.js +3 -1
  4. package/dist/components/Accordion/README.md +17 -17
  5. package/dist/components/Accordion/index.css +4 -2
  6. package/dist/components/AssetsPreview/README.md +7 -7
  7. package/dist/components/AssetsPreview/_internal/assets-preview-types.d.ts +1 -2
  8. package/dist/components/AssetsPreview/_internal/assets-preview-utils.d.ts +1 -1
  9. package/dist/components/AssetsPreview/_internal/assets-preview-utils.js +9 -3
  10. package/dist/components/Avatar/Avatar.svelte +1 -3
  11. package/dist/components/Avatar/README.md +33 -27
  12. package/dist/components/Book/Book.svelte +6 -1
  13. package/dist/components/Book/README.md +22 -20
  14. package/dist/components/Book/index.css +4 -2
  15. package/dist/components/Button/README.md +17 -17
  16. package/dist/components/Card/Card.svelte +25 -8
  17. package/dist/components/Card/README.md +52 -56
  18. package/dist/components/Card/index.css +2 -1
  19. package/dist/components/Carousel/Carousel.svelte +1 -3
  20. package/dist/components/Carousel/README.md +28 -28
  21. package/dist/components/Cart/Cart.svelte +2 -1
  22. package/dist/components/Cart/README.md +25 -25
  23. package/dist/components/Checkout/CheckoutGuestOrLoginForm.svelte +8 -3
  24. package/dist/components/Checkout/CheckoutShippingStep.svelte +1 -2
  25. package/dist/components/Checkout/README.md +143 -130
  26. package/dist/components/CronInput/CronInput.svelte +64 -60
  27. package/dist/components/CronInput/README.md +46 -46
  28. package/dist/components/DataTable/DataTable.svelte +5 -1
  29. package/dist/components/DataTable/README.md +78 -63
  30. package/dist/components/DropdownMenu/DropdownMenu.svelte +6 -2
  31. package/dist/components/DropdownMenu/README.md +33 -27
  32. package/dist/components/EmailVerifyForm/EmailVerifyForm.svelte +2 -9
  33. package/dist/components/EmailVerifyForm/README.md +30 -30
  34. package/dist/components/Header/Header.svelte +161 -165
  35. package/dist/components/Header/README.md +7 -7
  36. package/dist/components/IconSwap/README.md +20 -15
  37. package/dist/components/IconSwap/index.css +2 -1
  38. package/dist/components/ImageCycler/ImageCycler.svelte +19 -5
  39. package/dist/components/ImageCycler/ImageCycler.svelte.d.ts +14 -10
  40. package/dist/components/ImageCycler/README.md +15 -15
  41. package/dist/components/ImageCycler/index.css +26 -20
  42. package/dist/components/Input/FieldFile.svelte +1 -3
  43. package/dist/components/Input/FieldInput.svelte +1 -3
  44. package/dist/components/Input/FieldKeyValues.svelte +2 -6
  45. package/dist/components/Input/FieldObject.svelte +2 -1
  46. package/dist/components/Input/README.md +11 -11
  47. package/dist/components/KbdShortcut/index.css +2 -1
  48. package/dist/components/LoginForm/LoginForm.svelte +1 -7
  49. package/dist/components/LoginForm/README.md +46 -46
  50. package/dist/components/ModalDialog/index.css +2 -1
  51. package/dist/components/Notifications/index.css +24 -6
  52. package/dist/components/OtpInput/OtpInput.svelte +0 -0
  53. package/dist/components/OtpInput/README.md +15 -19
  54. package/dist/components/OtpInput/index.css +1 -4
  55. package/dist/components/OtpInput/index.d.ts +1 -1
  56. package/dist/components/OtpInput/index.js +1 -1
  57. package/dist/components/Pill/README.md +41 -40
  58. package/dist/components/Pill/index.css +3 -6
  59. package/dist/components/PricingTable/README.md +86 -86
  60. package/dist/components/PricingTable/index.css +20 -35
  61. package/dist/components/RegisterForm/README.md +60 -60
  62. package/dist/components/RegisterForm/RegisterForm.svelte +1 -7
  63. package/dist/components/Separator/README.md +7 -7
  64. package/dist/components/TabbedMenu/index.css +6 -3
  65. package/dist/components/Tree/README.md +67 -67
  66. package/dist/components/UserAvatarMenu/UserAvatarMenu.svelte +1 -5
  67. package/dist/components/WithSidePanel/index.css +4 -4
  68. package/dist/index.css +12 -8
  69. package/dist/utils/design-tokens.d.ts +1 -1
  70. package/dist/utils/design-tokens.js +1 -1
  71. package/docs/architecture.md +7 -7
  72. package/docs/component-testing/00-overview-and-roadmap.md +19 -19
  73. package/docs/component-testing/01-framework-setup.md +6 -6
  74. package/docs/component-testing/02-test-conventions.md +6 -5
  75. package/docs/component-testing/03-component-coverage-roadmap.md +27 -27
  76. package/docs/component-testing/04-hard-cases-and-e2e.md +8 -8
  77. package/docs/component-testing/05-ci.md +3 -3
  78. package/docs/component-testing/PROGRESS.md +30 -26
  79. package/docs/component-testing/README.md +8 -8
  80. package/docs/conventions.md +25 -25
  81. package/docs/domains/components.md +386 -385
  82. package/docs/domains/theming.md +24 -24
  83. package/docs/domains/utils.md +22 -25
  84. package/docs/testing.md +2 -2
  85. package/docs/upgrading.md +32 -28
  86. package/package.json +2 -1
@@ -16,7 +16,10 @@
16
16
  background: var(--stuic-dim-behind-backdrop-bg);
17
17
  opacity: 0;
18
18
  transition-property: opacity;
19
- transition-duration: var(--stuic-dim-behind-transition-duration, var(--stuic-transition));
19
+ transition-duration: var(
20
+ --stuic-dim-behind-transition-duration,
21
+ var(--stuic-transition)
22
+ );
20
23
  pointer-events: auto;
21
24
 
22
25
  &.dim-visible {
@@ -39,7 +39,9 @@ const defaults = { enabled: true, autoFocusFirst: true };
39
39
  * ```
40
40
  */
41
41
  export function focusTrap(node, options = {}) {
42
- let { enabled = true, autoFocusFirst } = { ...defaults, ...(options ?? {}) };
42
+ const merged = { ...defaults, ...(options ?? {}) };
43
+ let { enabled = true } = merged; // reassigned by the update hook below
44
+ const { autoFocusFirst } = merged;
43
45
  const focusableSelectors = [
44
46
  "[contentEditable=true]",
45
47
  //
@@ -93,21 +93,21 @@ Only one item can be open at a time:
93
93
 
94
94
  ## CSS Variables
95
95
 
96
- | Variable | Default | Description |
97
- | --------------------------------------- | ---------------------- | ----------------------------- |
98
- | `--stuic-accordion-border-color` | `--stuic-color-border` | Item separator color |
99
- | `--stuic-accordion-border-width` | `1px` | Separator width |
96
+ | Variable | Default | Description |
97
+ | --------------------------------------- | ---------------------- | --------------------------------------------- |
98
+ | `--stuic-accordion-border-color` | `--stuic-color-border` | Item separator color |
99
+ | `--stuic-accordion-border-width` | `1px` | Separator width |
100
100
  | `--stuic-accordion-divider-inset` | `0` | Inset of divider from L/R edges (e.g. `16px`) |
101
- | `--stuic-accordion-radius` | `--radius-md` | Corner rounding |
102
- | `--stuic-accordion-transition` | `200ms` | Open/close animation duration |
103
- | `--stuic-accordion-trigger-padding-x` | `1rem` | Trigger horizontal padding |
104
- | `--stuic-accordion-trigger-padding-y` | `0.75rem` | Trigger vertical padding |
105
- | `--stuic-accordion-trigger-font-weight` | `--font-weight-medium` | Trigger text weight |
106
- | `--stuic-accordion-trigger-bg` | `transparent` | Trigger background |
107
- | `--stuic-accordion-trigger-bg-hover` | muted mix | Trigger hover background |
108
- | `--stuic-accordion-chevron-size` | `16px` | Chevron icon size |
109
- | `--stuic-accordion-chevron-color` | `currentColor` | Chevron icon color |
110
- | `--stuic-accordion-content-padding-x` | `1rem` | Content horizontal padding |
111
- | `--stuic-accordion-content-padding-y` | `0.75rem` | Content vertical padding |
112
- | `--stuic-accordion-ring-width` | `2px` | Focus ring width |
113
- | `--stuic-accordion-ring-color` | `--stuic-color-ring` | Focus ring color |
101
+ | `--stuic-accordion-radius` | `--radius-md` | Corner rounding |
102
+ | `--stuic-accordion-transition` | `200ms` | Open/close animation duration |
103
+ | `--stuic-accordion-trigger-padding-x` | `1rem` | Trigger horizontal padding |
104
+ | `--stuic-accordion-trigger-padding-y` | `0.75rem` | Trigger vertical padding |
105
+ | `--stuic-accordion-trigger-font-weight` | `--font-weight-medium` | Trigger text weight |
106
+ | `--stuic-accordion-trigger-bg` | `transparent` | Trigger background |
107
+ | `--stuic-accordion-trigger-bg-hover` | muted mix | Trigger hover background |
108
+ | `--stuic-accordion-chevron-size` | `16px` | Chevron icon size |
109
+ | `--stuic-accordion-chevron-color` | `currentColor` | Chevron icon color |
110
+ | `--stuic-accordion-content-padding-x` | `1rem` | Content horizontal padding |
111
+ | `--stuic-accordion-content-padding-y` | `0.75rem` | Content vertical padding |
112
+ | `--stuic-accordion-ring-width` | `2px` | Focus ring width |
113
+ | `--stuic-accordion-ring-color` | `--stuic-color-ring` | Focus ring color |
@@ -41,7 +41,8 @@
41
41
  ============================================================================ */
42
42
 
43
43
  .stuic-accordion {
44
- border: var(--stuic-accordion-border-width, var(--stuic-border-width)) solid var(--stuic-accordion-border-color);
44
+ border: var(--stuic-accordion-border-width, var(--stuic-border-width)) solid
45
+ var(--stuic-accordion-border-color);
45
46
  border-radius: var(--stuic-accordion-radius, var(--stuic-radius-container));
46
47
  overflow: hidden;
47
48
  }
@@ -114,7 +115,8 @@
114
115
  .stuic-accordion-content {
115
116
  display: grid;
116
117
  grid-template-rows: 0fr;
117
- transition: grid-template-rows var(--stuic-accordion-transition, var(--stuic-transition));
118
+ transition: grid-template-rows
119
+ var(--stuic-accordion-transition, var(--stuic-transition));
118
120
  }
119
121
 
120
122
  [data-open="true"] > .stuic-accordion-content {
@@ -4,13 +4,13 @@ A modal-based asset preview component for displaying images and files. Supports
4
4
 
5
5
  ## Props
6
6
 
7
- | Prop | Type | Default | Description |
8
- | --------------- | ---------------------------- | -------- | ----------------------------- |
9
- | `assets` | `string[] \| AssetPreview[]` | - | Array of assets to preview |
10
- | `classControls` | `string` | - | CSS for control buttons |
11
- | `t` | `TranslateFn` | built-in | Translation function for i18n |
12
- | `onDelete` | `(asset, index) => void` | - | Optional delete handler |
13
- | `prevNextBottom`| `boolean` | `false` | Render prev/next arrows at bottom |
7
+ | Prop | Type | Default | Description |
8
+ | ---------------- | ---------------------------- | -------- | --------------------------------- |
9
+ | `assets` | `string[] \| AssetPreview[]` | - | Array of assets to preview |
10
+ | `classControls` | `string` | - | CSS for control buttons |
11
+ | `t` | `TranslateFn` | built-in | Translation function for i18n |
12
+ | `onDelete` | `(asset, index) => void` | - | Optional delete handler |
13
+ | `prevNextBottom` | `boolean` | `false` | Render prev/next arrows at bottom |
14
14
 
15
15
  ## Types
16
16
 
@@ -1,6 +1,5 @@
1
1
  import type { BookPageArea } from "../../Book/Book.svelte";
2
- export interface AssetArea extends BookPageArea {
3
- }
2
+ export type AssetArea = BookPageArea;
4
3
  export type AssetPreviewUrlObj = {
5
4
  thumb: string | URL;
6
5
  full: string | URL;
@@ -1,5 +1,5 @@
1
1
  import type { AssetPreview, AssetPreviewNormalized } from "./assets-preview-types.js";
2
2
  export declare function getAssetIcon(ext?: string): CallableFunction;
3
- export declare function t_default(k: string, values?: false | null | undefined | Record<string, string | number>, fallback?: string | boolean, i18nSpanWrap?: boolean): string;
3
+ export declare function t_default(k: string, values?: false | null | undefined | Record<string, string | number>, fallback?: string | boolean, _i18nSpanWrap?: boolean): string;
4
4
  export declare function ext(name: string): string;
5
5
  export declare function normalizeInput(input: string | AssetPreview): AssetPreviewNormalized | null;
@@ -21,7 +21,9 @@ export function getAssetIcon(ext) {
21
21
  return map[getFileTypeLabel(ext ?? "unknown")] ?? iconFile;
22
22
  }
23
23
  // i18n ready
24
- export function t_default(k, values = null, fallback = "", i18nSpanWrap = true) {
24
+ export function t_default(k, values = null, fallback = "",
25
+ // kept for signature compatibility; not used by this default impl
26
+ _i18nSpanWrap = true) {
25
27
  const m = {
26
28
  unable_to_preview: "Unable to preview",
27
29
  download: "Download",
@@ -30,8 +32,12 @@ export function t_default(k, values = null, fallback = "", i18nSpanWrap = true)
30
32
  zoom_out: "Zoom out",
31
33
  delete: "Delete",
32
34
  };
33
- let out = m[k] ?? fallback ?? k;
34
- return isPlainObject(values) ? replaceMap(out, values) : out;
35
+ const out = m[k] ?? fallback ?? k;
36
+ // values is narrowed to Record<string, string | number>; replaceMap wants
37
+ // string | CallableFunction values (it stringifies), hence the bridge cast.
38
+ return isPlainObject(values)
39
+ ? replaceMap(out, values)
40
+ : out;
35
41
  }
36
42
  // naive best-effort
37
43
  export function ext(name) {
@@ -180,9 +180,7 @@
180
180
  );
181
181
 
182
182
  // When padded: colors go on inner element; otherwise on outer
183
- let outerStyle = $derived(
184
- padding ? `--stuic-avatar-padding: ${padding}` : colorStyle
185
- );
183
+ let outerStyle = $derived(padding ? `--stuic-avatar-padding: ${padding}` : colorStyle);
186
184
  let innerStyle = $derived(padding ? colorStyle : undefined);
187
185
 
188
186
  // Build class string - base class for CSS targeting, allow user overrides via classProp
@@ -4,24 +4,24 @@ A flexible avatar component that displays user photos, initials, or icons with a
4
4
 
5
5
  ## Props
6
6
 
7
- | Prop | Type | Default | Description |
8
- | ---------------- | ------------------------------------------------- | -------- | -------------------------------------------------------------------------------------- |
9
- | `src` | `string` | - | Photo URL - when provided, renders in photo mode |
10
- | `alt` | `string` | - | Alt text for photo mode |
11
- | `initials` | `string` | - | String to extract initials from. Supports: "AB", "John Doe", or "john.doe@example.com" |
12
- | `initialsLength` | `number` | `2` | Maximum length of extracted initials |
13
- | `icon` | `IconFn` | - | Icon function to display - when provided alone, renders in icon mode |
14
- | `fallback` | `AvatarFallback` | `"icon"` | Fallback when photo fails to load |
15
- | `hashSource` | `string` | - | String for color hash calculation (e.g., email, user ID). Falls back to `initials` |
16
- | `size` | `"sm" \| "md" \| "lg" \| "xl" \| "2xl" \| string` | `"md"` | Size preset or custom Tailwind size class |
17
- | `onclick` | `(event: MouseEvent) => void` | - | Click handler - when provided, renders as a button |
18
- | `bg` | `string` | - | Background color (Tailwind class). Ignored if `autoColor=true` |
19
- | `textColor` | `string` | - | Text color (Tailwind class). Ignored if `autoColor=true` |
20
- | `autoColor` | `boolean` | `false` | Generate deterministic pastel colors from hashSource/initials |
7
+ | Prop | Type | Default | Description |
8
+ | ---------------- | ------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
9
+ | `src` | `string` | - | Photo URL - when provided, renders in photo mode |
10
+ | `alt` | `string` | - | Alt text for photo mode |
11
+ | `initials` | `string` | - | String to extract initials from. Supports: "AB", "John Doe", or "john.doe@example.com" |
12
+ | `initialsLength` | `number` | `2` | Maximum length of extracted initials |
13
+ | `icon` | `IconFn` | - | Icon function to display - when provided alone, renders in icon mode |
14
+ | `fallback` | `AvatarFallback` | `"icon"` | Fallback when photo fails to load |
15
+ | `hashSource` | `string` | - | String for color hash calculation (e.g., email, user ID). Falls back to `initials` |
16
+ | `size` | `"sm" \| "md" \| "lg" \| "xl" \| "2xl" \| string` | `"md"` | Size preset or custom Tailwind size class |
17
+ | `onclick` | `(event: MouseEvent) => void` | - | Click handler - when provided, renders as a button |
18
+ | `bg` | `string` | - | Background color (Tailwind class). Ignored if `autoColor=true` |
19
+ | `textColor` | `string` | - | Text color (Tailwind class). Ignored if `autoColor=true` |
20
+ | `autoColor` | `boolean` | `false` | Generate deterministic pastel colors from hashSource/initials |
21
21
  | `padding` | `string` | - | CSS padding around the visual circle. Outer keeps its size, inner circle shrinks (e.g. `"4px"`, `"0.25rem"`). Useful for larger tap targets. |
22
- | `class` | `string` | - | Additional CSS classes |
23
- | `classInner` | `string` | - | Additional CSS classes for the inner element (only used when `padding` is set) |
24
- | `el` | `HTMLElement` | - | Bindable element reference |
22
+ | `class` | `string` | - | Additional CSS classes |
23
+ | `classInner` | `string` | - | Additional CSS classes for the inner element (only used when `padding` is set) |
24
+ | `el` | `HTMLElement` | - | Bindable element reference |
25
25
 
26
26
  ## Usage
27
27
 
@@ -91,7 +91,13 @@ Use `padding` to keep the avatar's outer footprint (e.g. a 44px tap target) whil
91
91
  ```svelte
92
92
  <!-- Outer stays at size="md" (2.75rem), circle shrinks by 4px on each side -->
93
93
  <Avatar size="md" initials="AB" padding="4px" />
94
- <Avatar size="md" autoColor initials="john@example.com" padding="0.25rem" onclick={() => {}} />
94
+ <Avatar
95
+ size="md"
96
+ autoColor
97
+ initials="john@example.com"
98
+ padding="0.25rem"
99
+ onclick={() => {}}
100
+ />
95
101
  ```
96
102
 
97
103
  When `padding` is set, `bg`, `textColor`, and `autoColor` automatically apply to the inner circle (not the outer element).
@@ -112,15 +118,15 @@ When `padding` is set, `bg`, `textColor`, and `autoColor` automatically apply to
112
118
 
113
119
  Override globally in `:root` or locally via `style` prop:
114
120
 
115
- | Variable | Default | Description |
116
- | ---------------------------- | -------------------------------- | --------------------------------- |
117
- | `--stuic-avatar-radius` | `9999px` | Border radius (circle by default) |
118
- | `--stuic-avatar-font-weight` | `--font-weight-medium` | Font weight for initials |
119
- | `--stuic-avatar-transition` | `150ms` | Transition duration |
120
- | `--stuic-avatar-bg` | `--stuic-color-muted` | Default background color |
121
- | `--stuic-avatar-fg` | `--stuic-color-muted-foreground` | Default text/icon color |
122
- | `--stuic-avatar-ring-width` | `3px` | Focus ring width (button mode) |
123
- | `--stuic-avatar-ring-color` | `--stuic-color-ring` | Focus ring color |
121
+ | Variable | Default | Description |
122
+ | ---------------------------- | -------------------------------- | -------------------------------------------------------------- |
123
+ | `--stuic-avatar-radius` | `9999px` | Border radius (circle by default) |
124
+ | `--stuic-avatar-font-weight` | `--font-weight-medium` | Font weight for initials |
125
+ | `--stuic-avatar-transition` | `150ms` | Transition duration |
126
+ | `--stuic-avatar-bg` | `--stuic-color-muted` | Default background color |
127
+ | `--stuic-avatar-fg` | `--stuic-color-muted-foreground` | Default text/icon color |
128
+ | `--stuic-avatar-ring-width` | `3px` | Focus ring width (button mode) |
129
+ | `--stuic-avatar-ring-color` | `--stuic-color-ring` | Focus ring color |
124
130
  | `--stuic-avatar-padding` | - | Set by the `padding` prop; can also be driven directly via CSS |
125
131
 
126
132
  ### Size Tokens
@@ -720,7 +720,12 @@
720
720
 
721
721
  {#if spreads.length}
722
722
  <!-- Measurement wrapper: always 100% width for responsive detection -->
723
- <div bind:clientWidth={containerWidth} style:width="100%" style:display="flex" style:justify-content="center">
723
+ <div
724
+ bind:clientWidth={containerWidth}
725
+ style:width="100%"
726
+ style:display="flex"
727
+ style:justify-content="center"
728
+ >
724
729
  <!-- Visual book wrapper: owns background/shadow/radius -->
725
730
  <div
726
731
  bind:this={el}
@@ -22,7 +22,9 @@ A physical book visualization with CSS 3D page-flipping animation. Displays an o
22
22
  let activeSpread = $state(0);
23
23
  </script>
24
24
 
25
- <div style="--stuic-book-page-width: {size.width}px; --stuic-book-page-height: {size.height}px;">
25
+ <div
26
+ style="--stuic-book-page-width: {size.width}px; --stuic-book-page-height: {size.height}px;"
27
+ >
26
28
  <Book bind:this={book} {pages} bind:activeSpread />
27
29
  </div>
28
30
 
@@ -68,10 +70,10 @@ Pages are grouped into **spreads**:
68
70
  ```typescript
69
71
  interface BookPageArea {
70
72
  id: string | number;
71
- x: number; // X position in natural image pixels
72
- y: number; // Y position in natural image pixels
73
- w: number; // Width in natural image pixels
74
- h: number; // Height in natural image pixels
73
+ x: number; // X position in natural image pixels
74
+ y: number; // Y position in natural image pixels
75
+ w: number; // Width in natural image pixels
76
+ h: number; // Height in natural image pixels
75
77
  [key: string]: any;
76
78
  }
77
79
 
@@ -79,8 +81,8 @@ interface BookPage {
79
81
  id: string | number;
80
82
  src: string;
81
83
  title?: string;
82
- width: number; // Natural image width in px
83
- height: number; // Natural image height in px
84
+ width: number; // Natural image width in px
85
+ height: number; // Natural image height in px
84
86
  areas?: BookPageArea[];
85
87
  [key: string]: any;
86
88
  }
@@ -114,8 +116,8 @@ Computes display dimensions from page image metadata. Finds the largest width/he
114
116
  ```ts
115
117
  import { computeBookPageSize } from "@marianmeres/stuic";
116
118
 
117
- const size = computeBookPageSize(pages); // { width: 283, height: 400 }
118
- const size = computeBookPageSize(pages, 300); // { width: 212, height: 300 }
119
+ const size = computeBookPageSize(pages); // { width: 283, height: 400 }
120
+ const size = computeBookPageSize(pages, 300); // { width: 212, height: 300 }
119
121
  ```
120
122
 
121
123
  ## Clickable Areas
@@ -155,14 +157,14 @@ Pages can define clickable areas (e.g. product hotspots in a catalog). Areas are
155
157
 
156
158
  ## CSS Variables
157
159
 
158
- | Variable | Default | Description |
159
- | -------------------------- | ----------------------------- | ------------------------- |
160
- | `--stuic-book-page-width` | `300px` | Width of a single page |
161
- | `--stuic-book-page-height` | `400px` | Height of the book |
162
- | `--stuic-book-perspective` | `1200px` | CSS perspective depth |
163
- | `--stuic-book-duration` | `600ms` | Flip animation duration |
164
- | `--stuic-book-timing` | `ease-in-out` | Animation timing function |
165
- | `--stuic-book-page-bg` | `var(--stuic-color-surface)` | Page background color |
166
- | `--stuic-book-page-shadow` | `0 2px 16px rgba(0,0,0,0.15)` | Book shadow |
167
- | `--stuic-book-radius` | `var(--radius-sm)` | Page border radius |
168
- | `--stuic-book-area-fill-hover` | `rgba(0, 0, 0, 0.06)` | Area hover highlight fill |
160
+ | Variable | Default | Description |
161
+ | ------------------------------ | ----------------------------- | ------------------------- |
162
+ | `--stuic-book-page-width` | `300px` | Width of a single page |
163
+ | `--stuic-book-page-height` | `400px` | Height of the book |
164
+ | `--stuic-book-perspective` | `1200px` | CSS perspective depth |
165
+ | `--stuic-book-duration` | `600ms` | Flip animation duration |
166
+ | `--stuic-book-timing` | `ease-in-out` | Animation timing function |
167
+ | `--stuic-book-page-bg` | `var(--stuic-color-surface)` | Page background color |
168
+ | `--stuic-book-page-shadow` | `0 2px 16px rgba(0,0,0,0.15)` | Book shadow |
169
+ | `--stuic-book-radius` | `var(--radius-sm)` | Page border radius |
170
+ | `--stuic-book-area-fill-hover` | `rgba(0, 0, 0, 0.06)` | Area hover highlight fill |
@@ -64,7 +64,8 @@
64
64
  backface-visibility: hidden;
65
65
  -webkit-backface-visibility: hidden;
66
66
  background: var(--stuic-book-page-bg);
67
- border-radius: 0 var(--stuic-book-radius, var(--stuic-radius)) var(--stuic-book-radius, var(--stuic-radius)) 0;
67
+ border-radius: 0 var(--stuic-book-radius, var(--stuic-radius))
68
+ var(--stuic-book-radius, var(--stuic-radius)) 0;
68
69
  overflow: hidden;
69
70
  }
70
71
 
@@ -82,7 +83,8 @@
82
83
  -webkit-backface-visibility: hidden;
83
84
  transform: rotateY(180deg);
84
85
  background: var(--stuic-book-page-bg);
85
- border-radius: var(--stuic-book-radius, var(--stuic-radius)) 0 0 var(--stuic-book-radius, var(--stuic-radius));
86
+ border-radius: var(--stuic-book-radius, var(--stuic-radius)) 0 0
87
+ var(--stuic-book-radius, var(--stuic-radius));
86
88
  overflow: hidden;
87
89
  }
88
90
 
@@ -4,24 +4,24 @@ A flexible button component with semantic intents, visual variants, sizes, and o
4
4
 
5
5
  ## Props
6
6
 
7
- | Prop | Type | Default | Description |
8
- | ------------ | ------------------------------------------------------------------ | --------- | ------------------------------------------------- |
9
- | `intent` | `"primary" \| "accent" \| "destructive" \| "warning" \| "success"` | - | Semantic color intent |
10
- | `variant` | `"solid" \| "outline" \| "ghost" \| "soft" \| "link"` | `"solid"` | Visual variant (how colors are applied) |
11
- | `size` | `"sm" \| "md" \| "lg" \| "xl"` | `"md"` | Button size |
12
- | `muted` | `boolean` | `false` | Reduce emphasis (lower opacity) |
13
- | `raised` | `boolean` | `false` | 3D push effect |
14
- | `unstyled` | `boolean` | `false` | Skip all default styling |
15
- | `href` | `string` | - | Render as anchor tag with this URL |
16
- | `roleSwitch` | `boolean` | `false` | Enable toggle/switch behavior |
17
- | `checked` | `boolean` | `false` | Toggle state when `roleSwitch` is true (bindable) |
18
- | `el` | `HTMLElement` | - | Element reference (bindable) |
19
- | `iconButton` | `boolean` | `false` | Icon-only button (implies aspect1, adds CSS hook) |
20
- | `iconSwap` | `[string \| Snippet, string \| Snippet]` | - | Two icon states with swap animation (implies iconButton) |
21
- | `x` | `boolean \| XProps` | - | Normalized "X" icon button shortcut (close/dismiss) |
22
- | `nav` | `"prev" \| "next" \| ButtonNavProps` | - | Normalized prev/next icon button shortcut (arrow by default; `x` wins on conflict) |
7
+ | Prop | Type | Default | Description |
8
+ | ------------ | ------------------------------------------------------------------ | --------- | ----------------------------------------------------------------------------------------- |
9
+ | `intent` | `"primary" \| "accent" \| "destructive" \| "warning" \| "success"` | - | Semantic color intent |
10
+ | `variant` | `"solid" \| "outline" \| "ghost" \| "soft" \| "link"` | `"solid"` | Visual variant (how colors are applied) |
11
+ | `size` | `"sm" \| "md" \| "lg" \| "xl"` | `"md"` | Button size |
12
+ | `muted` | `boolean` | `false` | Reduce emphasis (lower opacity) |
13
+ | `raised` | `boolean` | `false` | 3D push effect |
14
+ | `unstyled` | `boolean` | `false` | Skip all default styling |
15
+ | `href` | `string` | - | Render as anchor tag with this URL |
16
+ | `roleSwitch` | `boolean` | `false` | Enable toggle/switch behavior |
17
+ | `checked` | `boolean` | `false` | Toggle state when `roleSwitch` is true (bindable) |
18
+ | `el` | `HTMLElement` | - | Element reference (bindable) |
19
+ | `iconButton` | `boolean` | `false` | Icon-only button (implies aspect1, adds CSS hook) |
20
+ | `iconSwap` | `[string \| Snippet, string \| Snippet]` | - | Two icon states with swap animation (implies iconButton) |
21
+ | `x` | `boolean \| XProps` | - | Normalized "X" icon button shortcut (close/dismiss) |
22
+ | `nav` | `"prev" \| "next" \| ButtonNavProps` | - | Normalized prev/next icon button shortcut (arrow by default; `x` wins on conflict) |
23
23
  | `iconEdge` | `"leading" \| "trailing"` | - | Trim icon-side padding to the y-padding (pill + edge-flush icon; pair with `roundedFull`) |
24
- | `class` | `string` | - | Additional CSS classes |
24
+ | `class` | `string` | - | Additional CSS classes |
25
25
 
26
26
  ## Snippet Props
27
27
 
@@ -5,7 +5,10 @@
5
5
 
6
6
  export type CardVariant = "vertical" | "horizontal";
7
7
 
8
- export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "title"> {
8
+ export interface Props extends Omit<
9
+ HTMLAttributes<HTMLDivElement>,
10
+ "children" | "title"
11
+ > {
9
12
  /** Image URL for the top (vertical) or side (horizontal) image area */
10
13
  image?: string;
11
14
  /** Alt text for the image */
@@ -82,14 +85,22 @@
82
85
 
83
86
  let _effectiveVariant = $derived.by(() => {
84
87
  if (variant !== "horizontal" || !horizontalThreshold) return variant;
85
- return _offsetWidth > 0 && _offsetWidth < horizontalThreshold ? "vertical" : "horizontal";
88
+ return _offsetWidth > 0 && _offsetWidth < horizontalThreshold
89
+ ? "vertical"
90
+ : "horizontal";
86
91
  });
87
92
 
88
93
  let _class = $derived(unstyled ? classProp : twMerge("stuic-card", classProp));
89
- let _classImage = $derived(unstyled ? classImageProp : twMerge("stuic-card-image", classImageProp));
90
- let _classContent = $derived(unstyled ? classContentProp : twMerge("stuic-card-content", classContentProp));
94
+ let _classImage = $derived(
95
+ unstyled ? classImageProp : twMerge("stuic-card-image", classImageProp)
96
+ );
97
+ let _classContent = $derived(
98
+ unstyled ? classContentProp : twMerge("stuic-card-content", classContentProp)
99
+ );
91
100
  let _classBody = $derived(unstyled ? undefined : "stuic-card-body");
92
- let _classFooter = $derived(unstyled ? classFooterProp : twMerge("stuic-card-footer", classFooterProp));
101
+ let _classFooter = $derived(
102
+ unstyled ? classFooterProp : twMerge("stuic-card-footer", classFooterProp)
103
+ );
93
104
 
94
105
  let _isInteractive = $derived(!!(href || onclick));
95
106
  let _hasImage = $derived(!!(image || renderImage));
@@ -123,13 +134,19 @@
123
134
  {:else if _hasContent}
124
135
  <div class={_classContent}>
125
136
  {#if eyebrow}
126
- <div class={unstyled ? undefined : "stuic-card-eyebrow"}><Thc thc={eyebrow} /></div>
137
+ <div class={unstyled ? undefined : "stuic-card-eyebrow"}>
138
+ <Thc thc={eyebrow} />
139
+ </div>
127
140
  {/if}
128
141
  {#if title}
129
- <div class={unstyled ? undefined : "stuic-card-title"}><Thc thc={title} /></div>
142
+ <div class={unstyled ? undefined : "stuic-card-title"}>
143
+ <Thc thc={title} />
144
+ </div>
130
145
  {/if}
131
146
  {#if description}
132
- <div class={unstyled ? undefined : "stuic-card-description"}><Thc thc={description} /></div>
147
+ <div class={unstyled ? undefined : "stuic-card-description"}>
148
+ <Thc thc={description} />
149
+ </div>
133
150
  {/if}
134
151
  </div>
135
152
  {/if}
@@ -10,11 +10,7 @@ A flexible card component for displaying content in a contained, visually distin
10
10
  </script>
11
11
 
12
12
  <!-- Basic -->
13
- <Card
14
- image="/photo.jpg"
15
- title="Card Title"
16
- description="A brief description."
17
- />
13
+ <Card image="/photo.jpg" title="Card Title" description="A brief description." />
18
14
 
19
15
  <!-- With eyebrow and footer actions -->
20
16
  <Card
@@ -55,63 +51,63 @@ A flexible card component for displaying content in a contained, visually distin
55
51
 
56
52
  ## Props
57
53
 
58
- | Prop | Type | Default | Description |
59
- |------|------|---------|-------------|
60
- | `image` | `string` | - | Image URL for the top (vertical) or side (horizontal) area |
61
- | `imageAlt` | `string` | `""` | Alt text for the image |
62
- | `eyebrow` | `THC` | - | Small label above the title (category, date, tag) |
63
- | `title` | `THC` | - | Card title |
64
- | `description` | `THC` | - | Short description below the title |
65
- | `variant` | `"vertical" \| "horizontal"` | `"vertical"` | Layout variant |
66
- | `horizontalThreshold` | `number` | `480` | Width (px) below which horizontal auto-switches to vertical. Set 0 to disable. |
67
- | `href` | `string` | - | Renders card as `<a>` |
68
- | `onclick` | `(e: MouseEvent) => void` | - | Renders card as `<button>` |
69
- | `disabled` | `boolean` | `false` | Disabled state (reduced opacity, no interaction) |
70
- | `unstyled` | `boolean` | `false` | Skip all default styling |
71
- | `class` | `string` | - | Additional CSS classes for the card shell |
72
- | `classImage` | `string` | - | Additional CSS classes for the image container |
73
- | `classContent` | `string` | - | Additional CSS classes for the content area |
74
- | `classFooter` | `string` | - | Additional CSS classes for the footer |
75
- | `el` | `HTMLElement` | - | Bindable element reference |
54
+ | Prop | Type | Default | Description |
55
+ | --------------------- | ---------------------------- | ------------ | ------------------------------------------------------------------------------ |
56
+ | `image` | `string` | - | Image URL for the top (vertical) or side (horizontal) area |
57
+ | `imageAlt` | `string` | `""` | Alt text for the image |
58
+ | `eyebrow` | `THC` | - | Small label above the title (category, date, tag) |
59
+ | `title` | `THC` | - | Card title |
60
+ | `description` | `THC` | - | Short description below the title |
61
+ | `variant` | `"vertical" \| "horizontal"` | `"vertical"` | Layout variant |
62
+ | `horizontalThreshold` | `number` | `480` | Width (px) below which horizontal auto-switches to vertical. Set 0 to disable. |
63
+ | `href` | `string` | - | Renders card as `<a>` |
64
+ | `onclick` | `(e: MouseEvent) => void` | - | Renders card as `<button>` |
65
+ | `disabled` | `boolean` | `false` | Disabled state (reduced opacity, no interaction) |
66
+ | `unstyled` | `boolean` | `false` | Skip all default styling |
67
+ | `class` | `string` | - | Additional CSS classes for the card shell |
68
+ | `classImage` | `string` | - | Additional CSS classes for the image container |
69
+ | `classContent` | `string` | - | Additional CSS classes for the content area |
70
+ | `classFooter` | `string` | - | Additional CSS classes for the footer |
71
+ | `el` | `HTMLElement` | - | Bindable element reference |
76
72
 
77
73
  ## Snippets
78
74
 
79
- | Snippet | Parameters | Description |
80
- |---------|------------|-------------|
81
- | `children` | - | Overrides the entire card body |
82
- | `renderImage` | `{ image, imageAlt }` | Overrides the image area |
83
- | `renderBadge` | - | Badge/overlay positioned over the image |
84
- | `renderContent` | `{ eyebrow?, title?, description? }` | Overrides the content area |
85
- | `renderFooter` | - | Footer area (action buttons, metadata) |
75
+ | Snippet | Parameters | Description |
76
+ | --------------- | ------------------------------------ | --------------------------------------- |
77
+ | `children` | - | Overrides the entire card body |
78
+ | `renderImage` | `{ image, imageAlt }` | Overrides the image area |
79
+ | `renderBadge` | - | Badge/overlay positioned over the image |
80
+ | `renderContent` | `{ eyebrow?, title?, description? }` | Overrides the content area |
81
+ | `renderFooter` | - | Footer area (action buttons, metadata) |
86
82
 
87
83
  ## CSS Variables
88
84
 
89
- | Variable | Default | Description |
90
- |----------|---------|-------------|
91
- | `--stuic-card-bg` | `var(--stuic-color-card, var(--stuic-color-background))` | Background color |
92
- | `--stuic-card-bg-hover` | `var(--stuic-color-card-hover, var(--stuic-color-muted))` | Hover background (interactive only) |
93
- | `--stuic-card-border-width` | `1px` | Border width |
94
- | `--stuic-card-border` | `var(--stuic-color-border)` | Border color |
95
- | `--stuic-card-border-hover` | `var(--stuic-color-border-hover)` | Hover border color |
96
- | `--stuic-card-radius` | `var(--radius-lg)` | Border radius |
97
- | `--stuic-card-shadow` | `var(--shadow-sm)` | Box shadow |
98
- | `--stuic-card-shadow-hover` | `var(--shadow-md)` | Hover box shadow |
99
- | `--stuic-card-transition` | `150ms` | Transition duration |
100
- | `--stuic-card-padding` | `1rem` | Content and footer padding |
101
- | `--stuic-card-content-gap` | `0.5rem` | Gap between content elements |
102
- | `--stuic-card-image-aspect-ratio` | `16 / 9` | Image aspect ratio (vertical only) |
103
- | `--stuic-card-image-object-fit` | `cover` | Image object-fit |
104
- | `--stuic-card-image-width-horizontal` | `40%` | Image width in horizontal variant |
105
- | `--stuic-card-eyebrow-font-size` | `var(--text-xs)` | Eyebrow font size |
106
- | `--stuic-card-eyebrow-text` | `var(--stuic-color-muted-foreground)` | Eyebrow text color |
107
- | `--stuic-card-title-font-size` | `var(--text-lg)` | Title font size |
108
- | `--stuic-card-title-font-weight` | `var(--font-weight-semibold)` | Title font weight |
109
- | `--stuic-card-title-text` | `var(--stuic-color-foreground)` | Title text color |
110
- | `--stuic-card-description-font-size` | `var(--text-sm)` | Description font size |
111
- | `--stuic-card-description-text` | `var(--stuic-color-muted-foreground)` | Description text color |
112
- | `--stuic-card-ring-width` | `3px` | Focus ring width |
113
- | `--stuic-card-ring-color` | `var(--stuic-color-ring)` | Focus ring color |
114
- | `--stuic-card-opacity-disabled` | `0.5` | Disabled opacity |
85
+ | Variable | Default | Description |
86
+ | ------------------------------------- | --------------------------------------------------------- | ----------------------------------- |
87
+ | `--stuic-card-bg` | `var(--stuic-color-card, var(--stuic-color-background))` | Background color |
88
+ | `--stuic-card-bg-hover` | `var(--stuic-color-card-hover, var(--stuic-color-muted))` | Hover background (interactive only) |
89
+ | `--stuic-card-border-width` | `1px` | Border width |
90
+ | `--stuic-card-border` | `var(--stuic-color-border)` | Border color |
91
+ | `--stuic-card-border-hover` | `var(--stuic-color-border-hover)` | Hover border color |
92
+ | `--stuic-card-radius` | `var(--radius-lg)` | Border radius |
93
+ | `--stuic-card-shadow` | `var(--shadow-sm)` | Box shadow |
94
+ | `--stuic-card-shadow-hover` | `var(--shadow-md)` | Hover box shadow |
95
+ | `--stuic-card-transition` | `150ms` | Transition duration |
96
+ | `--stuic-card-padding` | `1rem` | Content and footer padding |
97
+ | `--stuic-card-content-gap` | `0.5rem` | Gap between content elements |
98
+ | `--stuic-card-image-aspect-ratio` | `16 / 9` | Image aspect ratio (vertical only) |
99
+ | `--stuic-card-image-object-fit` | `cover` | Image object-fit |
100
+ | `--stuic-card-image-width-horizontal` | `40%` | Image width in horizontal variant |
101
+ | `--stuic-card-eyebrow-font-size` | `var(--text-xs)` | Eyebrow font size |
102
+ | `--stuic-card-eyebrow-text` | `var(--stuic-color-muted-foreground)` | Eyebrow text color |
103
+ | `--stuic-card-title-font-size` | `var(--text-lg)` | Title font size |
104
+ | `--stuic-card-title-font-weight` | `var(--font-weight-semibold)` | Title font weight |
105
+ | `--stuic-card-title-text` | `var(--stuic-color-foreground)` | Title text color |
106
+ | `--stuic-card-description-font-size` | `var(--text-sm)` | Description font size |
107
+ | `--stuic-card-description-text` | `var(--stuic-color-muted-foreground)` | Description text color |
108
+ | `--stuic-card-ring-width` | `3px` | Focus ring width |
109
+ | `--stuic-card-ring-color` | `var(--stuic-color-ring)` | Focus ring color |
110
+ | `--stuic-card-opacity-disabled` | `0.5` | Disabled opacity |
115
111
 
116
112
  ## Notes
117
113