zabi-components 5.0.21 → 6.0.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 (89) hide show
  1. package/README.md +42 -44
  2. package/THEME.md +473 -0
  3. package/THEMING.md +248 -0
  4. package/dist/atoms/Card.svelte +40 -67
  5. package/dist/atoms/Card.svelte.d.ts +3 -3
  6. package/dist/atoms/CardContent.svelte +1 -1
  7. package/dist/atoms/CardHeader.svelte +10 -1
  8. package/dist/atoms/CardHeader.svelte.d.ts +3 -0
  9. package/dist/atoms/Checkbox.svelte +1 -7
  10. package/dist/atoms/CodeBlock.svelte +7 -7
  11. package/dist/atoms/ColorPicker.svelte +81 -73
  12. package/dist/atoms/Input.svelte +16 -12
  13. package/dist/atoms/Input.svelte.d.ts +2 -0
  14. package/dist/atoms/List.svelte +47 -0
  15. package/dist/atoms/List.svelte.d.ts +19 -0
  16. package/dist/atoms/ListItem.svelte +148 -0
  17. package/dist/atoms/ListItem.svelte.d.ts +35 -0
  18. package/dist/atoms/Progress.svelte +4 -6
  19. package/dist/atoms/Select.svelte +65 -20
  20. package/dist/atoms/Select.svelte.d.ts +13 -1
  21. package/dist/atoms/Textarea.svelte +13 -11
  22. package/dist/atoms/Textarea.svelte.d.ts +1 -0
  23. package/dist/atoms/Toast.svelte +1 -1
  24. package/dist/atoms/Toggle.svelte +1 -7
  25. package/dist/atoms/Tooltip.svelte +8 -15
  26. package/dist/atoms/index.d.ts +2 -0
  27. package/dist/atoms/index.js +2 -0
  28. package/dist/components/atoms/index.d.ts +2 -0
  29. package/dist/components/atoms/index.d.ts.map +1 -1
  30. package/dist/components/index.d.ts +4 -1
  31. package/dist/components/index.d.ts.map +1 -1
  32. package/dist/components/molecules/index.d.ts +0 -1
  33. package/dist/components/molecules/index.d.ts.map +1 -1
  34. package/dist/components/molecules/navigation-menu-context.d.ts +7 -0
  35. package/dist/components/molecules/navigation-menu-context.d.ts.map +1 -0
  36. package/dist/components/organisms/index.d.ts +2 -1
  37. package/dist/components/organisms/index.d.ts.map +1 -1
  38. package/dist/index.d.ts +4 -1
  39. package/dist/index.js +5 -9
  40. package/dist/lib/showcase/components-catalog.d.ts +3 -0
  41. package/dist/lib/showcase/components-catalog.d.ts.map +1 -0
  42. package/dist/lib/showcase/components-showcase-constants.d.ts +63 -0
  43. package/dist/lib/showcase/components-showcase-constants.d.ts.map +1 -0
  44. package/dist/lib/showcase/docs-sidebar-helpers.d.ts +15 -0
  45. package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +1 -0
  46. package/dist/molecules/Alert.svelte +9 -4
  47. package/dist/molecules/ComponentDemo.svelte +53 -50
  48. package/dist/molecules/ContactForm.svelte +113 -76
  49. package/dist/molecules/ContactForm.svelte.d.ts +1 -4
  50. package/dist/molecules/Dropdown.svelte +4 -2
  51. package/dist/molecules/ImageUpload.svelte +27 -4
  52. package/dist/molecules/ImageUpload.svelte.d.ts +1 -0
  53. package/dist/molecules/Modal.svelte +46 -45
  54. package/dist/molecules/NavigationMenu.svelte +7 -10
  55. package/dist/molecules/NavigationMenuContent.svelte +9 -9
  56. package/dist/molecules/NavigationMenuItem.svelte +7 -6
  57. package/dist/molecules/NavigationMenuLink.svelte +8 -6
  58. package/dist/molecules/NavigationMenuList.svelte +0 -7
  59. package/dist/molecules/NavigationMenuTrigger.svelte +8 -11
  60. package/dist/molecules/Section.svelte +12 -12
  61. package/dist/molecules/SlideUp.svelte +32 -2
  62. package/dist/molecules/Tabs.svelte +74 -7
  63. package/dist/molecules/index.d.ts +0 -1
  64. package/dist/molecules/index.js +0 -1
  65. package/dist/molecules/navigation-menu-context.d.ts +6 -0
  66. package/dist/molecules/navigation-menu-context.js +1 -0
  67. package/dist/organisms/Navbar.svelte +170 -53
  68. package/dist/organisms/Navbar.svelte.d.ts +31 -4
  69. package/dist/organisms/SidebarNavigation.svelte +464 -0
  70. package/dist/organisms/SidebarNavigation.svelte.d.ts +49 -0
  71. package/dist/organisms/SidebarPanel.svelte +175 -0
  72. package/dist/organisms/SidebarPanel.svelte.d.ts +32 -0
  73. package/dist/organisms/index.d.ts +2 -1
  74. package/dist/organisms/index.js +2 -1
  75. package/dist/types/page.types.d.ts +32 -0
  76. package/dist/types/page.types.d.ts.map +1 -0
  77. package/dist/types/variants.ts +76 -0
  78. package/dist/zabi-components-colors.css +347 -324
  79. package/dist/zabi-components-theme-dark-only.css +159 -151
  80. package/dist/zabi-components-theme-dark.css +159 -151
  81. package/dist/zabi-components-theme-only.css +245 -230
  82. package/dist/zabi-components-theme.css +245 -230
  83. package/dist/zabi-components.css +722 -84
  84. package/docs/theme-imports.md +112 -0
  85. package/package.json +20 -9
  86. package/dist/molecules/Sidebar.svelte +0 -324
  87. package/dist/molecules/Sidebar.svelte.d.ts +0 -30
  88. package/dist/organisms/Navigation.svelte +0 -116
  89. package/dist/organisms/Navigation.svelte.d.ts +0 -20
package/THEMING.md ADDED
@@ -0,0 +1,248 @@
1
+ # Theming Guide
2
+
3
+ ## 🎨 Single Source of Truth
4
+
5
+ **All theming is done in one file: `src/app.css`**
6
+
7
+ This file contains:
8
+ - All color scales (brand, citron, pine, iris, base)
9
+ - Semantic color tokens (primary, secondary, success, error, etc.)
10
+ - Action colors (for buttons and interactive elements)
11
+ - Dark mode overrides
12
+ - Typography settings
13
+ - Spacing, border radius, and z-index values
14
+
15
+ ## 📍 Where to Make Changes
16
+
17
+ ### Main Theme (Light Mode)
18
+ Edit the `@theme` block in `src/app.css`.
19
+
20
+ ### Dark Mode Theme
21
+ Edit the `.dark` block in `src/app.css`.
22
+
23
+ ## ⚠️ Important: Don't Edit These Files
24
+
25
+ ### Generated Files (in `dist/` folder)
26
+ These files are **automatically generated** from `src/app.css` by the build script:
27
+ - ❌ `dist/zabi-components.css` - Full compiled CSS (generated)
28
+ - ❌ `dist/zabi-components-theme.css` - Theme with Tailwind import (generated)
29
+ - ❌ `dist/zabi-components-theme-only.css` - Theme only, no Tailwind (generated)
30
+ - ❌ `dist/zabi-components-theme-dark.css` - Dark mode theme (generated)
31
+ - ❌ `dist/zabi-components-theme-dark-only.css` - Dark mode theme only (generated)
32
+ - ❌ `dist/zabi-components-colors.css` - Standalone colors (generated from app.css)
33
+
34
+ **These files are overwritten every time you run `npm run build:css`**
35
+
36
+ ### Example Files
37
+ - ❌ `examples/theme-extensions/02-custom-brand-colors.css` - Just an example, not your actual theme
38
+ - ❌ `examples/theme-extensions/03-custom-semantic-colors.css` - Just an example, not your actual theme
39
+
40
+ ### Source Files (Edit These)
41
+ - ✅ `src/app.css` - **THIS IS THE ONLY FILE YOU NEED TO EDIT**
42
+ - ✅ Removed deprecated files (`src/styles/colors.css`, `src/styles/base.css`, `src/styles/simple.css`, `src/app-simple.css`)
43
+
44
+ ## 📦 What Are the Dist Files For?
45
+
46
+ The `dist/` files are **for consumers** of your library who want to import the theme separately.
47
+
48
+ **Full import matrix, export paths, and examples:** [docs/theme-imports.md](./docs/theme-imports.md)
49
+
50
+ ### For Library Developers (You)
51
+ - **Edit**: `src/app.css` only
52
+ - **Build**: Run `npm run build:css` to regenerate all dist files (or `npm run build:lib` before publish)
53
+ - **After token edits**: run `node scripts/verify-build.js` to confirm outputs (also runs as part of `build:lib`)
54
+ - **Base scale source**: use `tokens/base-scale.js`; run `npm run sync:tokens` (or `npm run build:css`) to render token blocks back into `src/app.css`
55
+
56
+ ### For Library Consumers
57
+
58
+ Use **package exports** (preferred):
59
+
60
+ | Need | Import |
61
+ |------|--------|
62
+ | Tailwind + full `@theme` (app has no Tailwind yet) | `zabi-components/theme` |
63
+ | Tailwind already in app — merge `@theme` only | `zabi-components/theme-only` |
64
+ | Dark overrides (+ Tailwind import in file) | `zabi-components/theme-dark` |
65
+ | Dark overrides only (Tailwind already loaded) | `zabi-components/theme-dark-only` |
66
+ | CSS variables only, no Tailwind | `zabi-components/colors` |
67
+ | Full compiled CSS (utilities + everything) | `zabi-components/css` |
68
+
69
+ Examples:
70
+
71
+ ```css
72
+ @import "zabi-components/theme-only";
73
+ @import "zabi-components/theme-dark-only";
74
+ ```
75
+
76
+ ```css
77
+ @import "zabi-components/colors";
78
+ ```
79
+
80
+ ```css
81
+ @import "zabi-components/css";
82
+ ```
83
+
84
+ Canonical recommendation for most apps remains:
85
+
86
+ ```css
87
+ @import "tailwindcss";
88
+ @import "zabi-components/theme-only";
89
+ @import "zabi-components/theme-dark-only";
90
+ ```
91
+
92
+ Dark mapping rule reference:
93
+ - Physical ramp tokens are `--zabi-base-50 ... --zabi-base-950`.
94
+ - Semantic dark mapping is generated with `S -> 1000 - S` (for example `50 <-> 950`, `75 <-> 925`, `500` unchanged).
95
+
96
+ ## 🎯 Common Customizations
97
+
98
+ ### Change Brand Colors
99
+
100
+ ```css
101
+ @theme {
102
+ /* Replace the brand color scale */
103
+ --color-brand-50: #f0f9ff;
104
+ --color-brand-100: #e0f2fe;
105
+ --color-brand-200: #bae6fd;
106
+ --color-brand-300: #7dd3fc;
107
+ --color-brand-400: #38bdf8;
108
+ --color-brand-500: #0ea5e9;
109
+ --color-brand-600: #0284c7;
110
+ --color-brand-700: #0369a1;
111
+ --color-brand-800: #075985;
112
+ --color-brand-900: #0c4a6e;
113
+ --color-brand-950: #082f49;
114
+ }
115
+ ```
116
+
117
+ ### Change Primary Button Color
118
+
119
+ The primary button uses `--color-action-primary` which defaults to `brand-800`. To change it:
120
+
121
+ ```css
122
+ @theme {
123
+ /* Use a different brand shade */
124
+ --color-action-primary: theme(colors.brand.600);
125
+
126
+ /* Or use a completely custom color */
127
+ --color-action-primary: #your-color-here;
128
+ }
129
+ ```
130
+
131
+ ### Brand Scale Usage
132
+
133
+ The brand scale is used throughout the system:
134
+
135
+ - **brand-50 to brand-100**: Subtle backgrounds, hover states
136
+ - **brand-300 to brand-400**: Light accents, disabled states
137
+ - **brand-500**: Focus rings, medium emphasis
138
+ - **brand-600**: Primary color, links
139
+ - **brand-700**: Link hover, primary hover
140
+ - **brand-800**: **Primary buttons** (`action-primary`)
141
+ - **brand-900**: Primary button hover
142
+ - **brand-950**: Darkest brand shade
143
+
144
+ ### Dark Mode
145
+
146
+ Dark mode automatically inverts the brand scale:
147
+ - `brand-50` becomes `brand-950` (darkest)
148
+ - `brand-950` becomes `brand-50` (lightest)
149
+
150
+ This happens automatically in the `.dark` block, so you typically don't need to override action colors in dark mode.
151
+
152
+ ## 🌙 Dark Mode Action Colors
153
+
154
+ Dark mode action colors are handled in `src/app.css` in the `.dark` block (lines 393-404):
155
+
156
+ ### Primary Actions
157
+ - **No explicit dark mode override needed** - Brand colors are automatically inverted
158
+ - `brand-800` (light mode) → `brand-200` (dark mode) automatically
159
+
160
+ ### Secondary Actions
161
+ - **No explicit dark mode override needed** - Base colors are automatically inverted
162
+ - `base-600` (light mode) → `base-400` (dark mode) automatically
163
+
164
+ ### Danger Actions
165
+ - **Has explicit dark mode overrides** (lines 399-404) because red colors aren't automatically inverted:
166
+ ```css
167
+ .dark {
168
+ --color-action-danger: theme(colors.red.500);
169
+ --color-action-danger-hover: theme(colors.red.600);
170
+ --color-action-danger-active: theme(colors.red.700);
171
+ /* ... */
172
+ }
173
+ ```
174
+
175
+ ### In Generated Files
176
+
177
+ - **`dist/zabi-components.css`**: Has dark mode at line 2178, danger actions at lines 2264-2269
178
+ - **`dist/zabi-components-theme-dark-only.css`**: Has dark mode action colors at lines 119-130 (only danger has explicit overrides)
179
+ - **`dist/zabi-components-theme-only.css`**: Only has light mode (@theme block), no dark mode
180
+ - **`dist/zabi-components-theme.css`**: Only has light mode (@theme block), no dark mode
181
+
182
+ ## 📁 File Structure
183
+
184
+ ```
185
+ src/
186
+ └── app.css ← 🎯 EDIT THIS FILE FOR ALL THEMING (single source of truth)
187
+
188
+ dist/ ← ⚠️ GENERATED FILES - DON'T EDIT
189
+ ├── zabi-components.css (full CSS with dark mode)
190
+ ├── zabi-components-theme.css (light mode only)
191
+ ├── zabi-components-theme-only.css (light mode only)
192
+ ├── zabi-components-theme-dark.css (dark mode only)
193
+ ├── zabi-components-theme-dark-only.css (dark mode only)
194
+ └── zabi-components-colors.css (standalone, both modes)
195
+
196
+ examples/ ← ⚠️ EXAMPLES ONLY - DON'T EDIT
197
+ └── theme-extensions/
198
+ ├── 02-custom-brand-colors.css
199
+ └── 03-custom-semantic-colors.css
200
+ ```
201
+
202
+ ## ⚠️ Important Notes
203
+
204
+ 1. **Never edit files in `dist/`** - These are automatically generated from `src/app.css` by `scripts/build-css.js`
205
+ 2. **All theming is in `src/app.css`** - This is the single source of truth
206
+ 3. **Always edit `src/app.css`** - This is your single source of truth
207
+ 4. The `examples/` folder contains examples, not your actual theme
208
+ 5. **Dist files are overwritten** every time you run `npm run build:css`
209
+
210
+ ## 🔄 Build Process
211
+
212
+ When you edit `src/app.css`:
213
+
214
+ 1. **Development**: Changes are automatically reflected (no build needed)
215
+ 2. **Production**: Run `npm run build:css` to regenerate dist files
216
+ 3. The build script (`scripts/build-css.js`) reads `src/app.css` and generates:
217
+ - `zabi-components.css` - Full compiled CSS with Tailwind processed (includes dark mode)
218
+ - `zabi-components-theme.css` - Theme block with Tailwind import (light mode only)
219
+ - `zabi-components-theme-only.css` - Theme block only (light mode only)
220
+ - `zabi-components-theme-dark.css` - Dark mode theme with Tailwind import
221
+ - `zabi-components-theme-dark-only.css` - Dark mode theme only
222
+ - `zabi-components-colors.css` - Standalone CSS custom properties (both light and dark)
223
+
224
+ ## 🔄 After Making Changes
225
+
226
+ ### For Development
227
+ After editing `src/app.css`, changes are automatically reflected in your app. No build needed!
228
+
229
+ ### For Production/Library Build
230
+ If you're building the library for distribution:
231
+ ```bash
232
+ npm run build:css # Regenerates all dist CSS files from src/app.css
233
+ ```
234
+
235
+ ## Optional perceptual midpoint generation (OKLCH)
236
+
237
+ Base midpoints are frozen hex by default (`fixed` mode).
238
+ For experimentation, you can generate midpoint steps in OKLCH interpolation and commit the resulting frozen values:
239
+
240
+ ```bash
241
+ ZABI_BASE_SCALE_MODE=oklch npm run sync:tokens
242
+ npm run build:css
243
+ ```
244
+
245
+ Notes:
246
+ - Primary anchors (`50, 100, 200 ... 950`) stay fixed.
247
+ - Midpoints (`75, 150, 250 ... 925`) are generated from adjacent anchors.
248
+ - Commit updated `src/app.css` and regenerated `dist/` outputs together.
@@ -1,70 +1,64 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
3
  import type { CardVariant, SizeVariant } from "../../types/variants.js";
4
- import CardHeader from "./CardHeader.svelte";
5
- import CardContent from "./CardContent.svelte";
6
4
 
7
5
  interface Props {
8
- title?: string;
9
- description?: string;
10
- image?: string;
11
6
  onclick?: (event: MouseEvent) => void | Promise<void>;
12
7
  size?: SizeVariant;
13
8
  variant?: CardVariant;
14
9
  fullWidth?: boolean;
10
+ className?: string;
11
+ /** Required when card is interactive (has onclick) for accessibility */
12
+ ariaLabel?: string;
15
13
  children?: Snippet;
16
14
  }
17
15
 
18
16
  let {
19
- title = "",
20
- description = "",
21
- image = "",
22
17
  onclick,
23
18
  size = "md",
24
19
  variant = "default",
25
20
  fullWidth = true,
21
+ className = "",
22
+ ariaLabel,
26
23
  children,
27
24
  ...restProps
28
25
  }: Props = $props();
29
26
 
30
- const sizeClass = $derived(() => {
31
- return size === "sm" ? "p-4" : size === "lg" ? "p-8" : "p-6";
32
- });
33
-
34
- const variantClasses = $derived(() => {
35
- switch (variant) {
36
- case "elevated":
37
- return "bg-card shadow-lg";
38
- case "outlined":
39
- return "bg-card border-2 border-base-200 shadow-none";
40
- case "flat":
41
- return "bg-card shadow-none border-none";
42
- default:
43
- return "bg-card shadow-sm";
44
- }
45
- });
46
-
47
- const useOldAPI = $derived(
48
- title !== "" || description !== "" || image !== "",
27
+ const sizeClass = $derived(
28
+ size === "sm" ? "p-4" : size === "lg" ? "p-8" : "p-6",
49
29
  );
50
30
 
51
- const cardClasses = $derived(() => {
52
- const baseClasses = `rounded-lg transition-all duration-200 ${variantClasses()}`;
53
- const interactiveClasses = onclick
54
- ? variant === "elevated"
55
- ? "cursor-pointer hover:shadow-xl hover:bg-card-hover"
56
- : variant === "outlined"
57
- ? "cursor-pointer hover:border-brand-500 hover:bg-card-hover"
58
- : variant === "flat"
59
- ? "cursor-pointer hover:bg-card-hover"
60
- : "cursor-pointer hover:shadow-lg hover:bg-card-hover"
61
- : "";
62
- const widthClasses = fullWidth ? "w-full" : "";
63
- const minWidthClass = useOldAPI ? "min-w-64" : "";
64
- const paddingClass = useOldAPI ? sizeClass() : "";
31
+ const variantClasses = $derived(
32
+ variant === "elevated"
33
+ ? "bg-card shadow-lg"
34
+ : variant === "outlined"
35
+ ? "bg-card border-2 border-base-200 shadow-none"
36
+ : variant === "flat"
37
+ ? "bg-card shadow-none border-none"
38
+ : "bg-card shadow-sm",
39
+ );
65
40
 
66
- return `${baseClasses} ${interactiveClasses} ${widthClasses} ${minWidthClass} ${paddingClass}`.trim();
67
- });
41
+ const cardClasses = $derived(
42
+ [
43
+ "rounded-lg transition-all duration-200",
44
+ variantClasses,
45
+ onclick
46
+ ? variant === "elevated"
47
+ ? "cursor-pointer hover:shadow-xl hover:bg-card-hover"
48
+ : variant === "outlined"
49
+ ? "cursor-pointer hover:border-brand-500 hover:bg-card-hover"
50
+ : variant === "flat"
51
+ ? "cursor-pointer hover:bg-card-hover"
52
+ : "cursor-pointer hover:shadow-lg hover:bg-card-hover"
53
+ : "",
54
+ fullWidth ? "w-full" : "",
55
+ sizeClass,
56
+ className,
57
+ ]
58
+ .filter(Boolean)
59
+ .join(" ")
60
+ .trim(),
61
+ );
68
62
 
69
63
  function handleKeydown(event: KeyboardEvent) {
70
64
  if (onclick && (event.key === "Enter" || event.key === " ")) {
@@ -74,37 +68,16 @@
74
68
  }
75
69
 
76
70
  const cardRole = $derived(onclick ? "button" : undefined);
77
- const cardAriaLabel = $derived(onclick && title ? title : undefined);
78
71
  </script>
79
72
 
80
73
  <div
81
- class={cardClasses()}
74
+ class={cardClasses}
82
75
  {onclick}
83
76
  role={cardRole}
84
77
  {...onclick ? { tabindex: 0 } : {}}
85
- aria-label={cardAriaLabel}
78
+ aria-label={ariaLabel}
86
79
  onkeydown={onclick ? handleKeydown : undefined}
87
80
  {...restProps}
88
81
  >
89
- {#if useOldAPI}
90
- <!-- Old API: Using title/description/image props -->
91
- {#if title || description}
92
- <CardHeader {title} {description} />
93
- {/if}
94
-
95
- {#if image || children}
96
- <CardContent {image} imageAlt={title}>
97
- {#if children}
98
- {@render children()}
99
- {/if}
100
- </CardContent>
101
- {/if}
102
- {:else}
103
- <!-- New API: Using compound components (CardHeader, CardContent, CardFooter) -->
104
- {#if children}
105
- <CardContent className="pt-6">
106
- {@render children()}
107
- </CardContent>
108
- {/if}
109
- {/if}
82
+ {@render children?.()}
110
83
  </div>
@@ -1,13 +1,13 @@
1
1
  import type { Snippet } from "svelte";
2
2
  import type { CardVariant, SizeVariant } from "../../types/variants.js";
3
3
  interface Props {
4
- title?: string;
5
- description?: string;
6
- image?: string;
7
4
  onclick?: (event: MouseEvent) => void | Promise<void>;
8
5
  size?: SizeVariant;
9
6
  variant?: CardVariant;
10
7
  fullWidth?: boolean;
8
+ className?: string;
9
+ /** Required when card is interactive (has onclick) for accessibility */
10
+ ariaLabel?: string;
11
11
  children?: Snippet;
12
12
  }
13
13
  declare const Card: import("svelte").Component<Props, {}, "">;
@@ -17,7 +17,7 @@
17
17
  }: Props = $props();
18
18
  </script>
19
19
 
20
- <div class="p-6 pt-0 {className}" {...restProps}>
20
+ <div class={className} {...restProps}>
21
21
  {#if image}
22
22
  <img
23
23
  src={image}
@@ -3,6 +3,9 @@
3
3
 
4
4
  interface Props {
5
5
  title?: string;
6
+ /** Short secondary text displayed directly under the title */
7
+ subtitle?: string;
8
+ /** Longer supporting text displayed below the subtitle */
6
9
  description?: string;
7
10
  level?: 1 | 2 | 3 | 4 | 5 | 6;
8
11
  className?: string;
@@ -11,6 +14,7 @@
11
14
 
12
15
  let {
13
16
  title = "",
17
+ subtitle = "",
14
18
  description = "",
15
19
  level = 3,
16
20
  className = "",
@@ -24,7 +28,7 @@
24
28
  });
25
29
  </script>
26
30
 
27
- <header class="flex flex-col space-y-1.5 p-6 {className}" {...restProps}>
31
+ <header class={`flex flex-col space-y-1.5 pb-4 ${className}`.trim()} {...restProps}>
28
32
  {#if title}
29
33
  <svelte:element this={headingTag} class={headingClasses()}>
30
34
  {title}
@@ -33,6 +37,11 @@
33
37
  {#if children}
34
38
  {@render children()}
35
39
  {/if}
40
+ {#if subtitle}
41
+ <p class="text-sm text-description">
42
+ {subtitle}
43
+ </p>
44
+ {/if}
36
45
  {#if description}
37
46
  <p class="text-sm text-description">
38
47
  {description}
@@ -1,6 +1,9 @@
1
1
  import type { Snippet } from "svelte";
2
2
  interface Props {
3
3
  title?: string;
4
+ /** Short secondary text displayed directly under the title */
5
+ subtitle?: string;
6
+ /** Longer supporting text displayed below the subtitle */
4
7
  description?: string;
5
8
  level?: 1 | 2 | 3 | 4 | 5 | 6;
6
9
  className?: string;
@@ -1,11 +1,5 @@
1
1
  <script lang="ts">
2
- function generateId(prefix: string = "id"): string {
3
- if (typeof window !== "undefined") {
4
- return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
5
- } else {
6
- return `${prefix}-ssr-${Date.now()}`;
7
- }
8
- }
2
+ import { generateId } from "../../routes/lib/ssr-safe.js";
9
3
 
10
4
  interface Props {
11
5
  checked?: boolean;
@@ -44,7 +44,7 @@
44
44
  {#if showCopyButton}
45
45
  <button
46
46
  onclick={copyToClipboard}
47
- class="flex items-center gap-2 px-3 py-1 text-xs text-base-300 hover:text-white hover:bg-base-700 rounded transition-colors duration-200"
47
+ class="flex cursor-pointer items-center gap-2 rounded px-3 py-1 text-xs text-base-300 transition-colors duration-200 hover:bg-base-700 hover:text-white"
48
48
  aria-label="Copy code to clipboard"
49
49
  >
50
50
  {#if copied}
@@ -92,31 +92,31 @@
92
92
 
93
93
  :global(.language-svelte .token.tag),
94
94
  :global(.language-html .token.tag) {
95
- color: #f92672;
95
+ color: var(--color-error-text);
96
96
  }
97
97
 
98
98
  :global(.language-svelte .token.attr-name),
99
99
  :global(.language-html .token.attr-name) {
100
- color: #a6e22e;
100
+ color: var(--color-success-text);
101
101
  }
102
102
 
103
103
  :global(.language-svelte .token.attr-value),
104
104
  :global(.language-html .token.attr-value) {
105
- color: #e6db74;
105
+ color: var(--color-warning-text);
106
106
  }
107
107
 
108
108
  :global(.language-javascript .token.keyword),
109
109
  :global(.language-typescript .token.keyword) {
110
- color: #66d9ef;
110
+ color: var(--color-info-text);
111
111
  }
112
112
 
113
113
  :global(.language-javascript .token.string),
114
114
  :global(.language-typescript .token.string) {
115
- color: #e6db74;
115
+ color: var(--color-warning-text);
116
116
  }
117
117
 
118
118
  :global(.language-javascript .token.function),
119
119
  :global(.language-typescript .token.function) {
120
- color: #a6e22e;
120
+ color: var(--color-success-text);
121
121
  }
122
122
  </style>