yaxa-svelte 1.12.0 → 1.13.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 (37) hide show
  1. package/README.md +3 -3
  2. package/dist/components/ai/AiChat.svelte +1 -1
  3. package/dist/components/elements/Button.svelte +1 -1
  4. package/dist/components/elements/Button.svelte.d.ts +1 -1
  5. package/dist/components/elements/CodeBlock.svelte +1 -1
  6. package/dist/components/elements/CodeDiff.svelte +1 -1
  7. package/dist/components/elements/Icon.svelte +116 -40
  8. package/dist/components/elements/Icon.svelte.d.ts +25 -0
  9. package/dist/components/elements/Icon.test.d.ts +1 -0
  10. package/dist/components/elements/Icon.test.js +91 -0
  11. package/dist/components/layout/Footer.svelte +29 -9
  12. package/dist/components/layout/Header.svelte +2 -2
  13. package/dist/components/layout/YaxaApp.svelte +4 -4
  14. package/dist/components/legal/LegalDocument.svelte +98 -13
  15. package/dist/components/overlays/Toast.svelte +283 -0
  16. package/dist/components/overlays/Toast.svelte.d.ts +62 -0
  17. package/dist/components/overlays/Toast.test.d.ts +1 -0
  18. package/dist/components/overlays/Toast.test.js +122 -0
  19. package/dist/components/overlays/Toaster.svelte +72 -0
  20. package/dist/components/overlays/Toaster.svelte.d.ts +13 -0
  21. package/dist/components/saas/PricingCard.svelte +3 -3
  22. package/dist/components/theme/ThemeStudio.svelte +154 -69
  23. package/dist/components/theme/ThemeStudio.svelte.d.ts +1 -0
  24. package/dist/composables/useFormAction.svelte.js +1 -1
  25. package/dist/composables/useToast.svelte.d.ts +64 -0
  26. package/dist/composables/useToast.svelte.js +172 -0
  27. package/dist/composables/useToast.test.js +109 -40
  28. package/dist/index.d.ts +7 -4
  29. package/dist/index.js +4 -4
  30. package/dist/styles/yaxa.css +12 -17
  31. package/dist/theme/theme.svelte.d.ts +6 -0
  32. package/dist/theme/theme.svelte.js +25 -2
  33. package/dist/vite/index.js +2 -9
  34. package/dist/vite/index.test.js +1 -6
  35. package/package.json +1 -6
  36. package/dist/composables/useToast.d.ts +0 -19
  37. package/dist/composables/useToast.js +0 -34
package/README.md CHANGED
@@ -31,8 +31,8 @@ _Nuxt UI v4 & Nuxt UI Pro Equivalent for SvelteKit 2.7+ & Svelte 5 with Built-in
31
31
  - ♿ **Accessible by Default**: Headless primitives powered by **Bits UI**.
32
32
  - 🚀 **100% DRY SEO Parity**: Automate dynamic Open Graph cards (`/api/og`), environment-aware `robots.txt`, XML sitemaps with human-readable `sitemap.xsl` stylesheets, PWA manifests, and Schema.org JSON-LD from a single `src/site.config.ts`.
33
33
  - 🔐 **Batteries-Included SaaS Suite**: Pre-integrated **Better-Auth**, multi-dialect **Drizzle ORM** (Neon PostgreSQL & Turso LibSQL/SQLite), **Polar.sh** payments & webhook sync, and **Resend** transactional emails.
34
- - 🧩 **45+ Production Components**: Elements, AI primitives, charts, advanced forms (`<RichTextEditor>`, `<PhoneInput>`, `<CreditCardInput>`), layout, overlays, and drop-in SaaS widgets (`<AuthCard>`, `<UserMenu>`, `<PricingTable>`, `<SubscriptionCard>`).
35
- - 🛠️ **Composable Runes**: `useAuth`, `useShortcuts`, `useClipboard`, `useColorMode`, `useToast`, `useMediaQuery`, `useDebounce`, `useIdle`.
34
+ - 🧩 **50+ Production Components**: Elements, AI primitives, charts, advanced forms (`<RichTextEditor>`, `<PhoneInput>`, `<CreditCardInput>`), layout, overlays, native `<Toast>` / `<Toaster>`, and drop-in SaaS widgets (`<AuthCard>`, `<UserMenu>`, `<PricingTable>`, `<SubscriptionCard>`).
35
+ - 🛠️ **Composable Runes**: `useAuth`, `useShortcuts`, `useClipboard`, `useColorMode`, `useToast`, `useMediaQuery`, `useDebounce`, `useIdle`, `useGate`.
36
36
 
37
37
  ---
38
38
 
@@ -40,7 +40,7 @@ _Nuxt UI v4 & Nuxt UI Pro Equivalent for SvelteKit 2.7+ & Svelte 5 with Built-in
40
40
 
41
41
  ### 1. Install Dependencies
42
42
 
43
- Install `yaxa-svelte` along with Tailwind CSS v4 and `@lucide/svelte`:
43
+ Install `yaxa-svelte` along with Tailwind CSS v4:
44
44
 
45
45
  ```bash
46
46
  # Core package & icons
@@ -49,7 +49,7 @@
49
49
  import PromptBar from './PromptBar.svelte';
50
50
  import AiThought from './AiThought.svelte';
51
51
  import AiToolCall from './AiToolCall.svelte';
52
- import { useToast } from '../../composables/useToast';
52
+ import { useToast } from '../../composables/useToast.svelte';
53
53
 
54
54
  let {
55
55
  messages = [],
@@ -5,7 +5,7 @@
5
5
  import type { IconSource } from './Icon.svelte';
6
6
 
7
7
  export const buttonVariants = tv({
8
- base: 'inline-flex items-center justify-center font-medium transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 select-none active:scale-[0.98]',
8
+ base: 'inline-flex items-center justify-center font-medium transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 select-none active:scale-[0.98] whitespace-nowrap max-w-full overflow-hidden text-ellipsis shrink-0 [&>svg]:shrink-0',
9
9
  variants: {
10
10
  variant: {
11
11
  solid: 'shadow-xs text-white',
@@ -30,7 +30,7 @@ export declare const buttonVariants: import("tailwind-variants").TVReturnType<{
30
30
  square: {
31
31
  true: "aspect-square p-0";
32
32
  };
33
- }, undefined, "inline-flex items-center justify-center font-medium transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 select-none active:scale-[0.98]", {
33
+ }, undefined, "inline-flex items-center justify-center font-medium transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 select-none active:scale-[0.98] whitespace-nowrap max-w-full overflow-hidden text-ellipsis shrink-0 [&>svg]:shrink-0", {
34
34
  variant: {
35
35
  solid: "shadow-xs text-white";
36
36
  outline: "border bg-transparent shadow-xs";
@@ -243,7 +243,7 @@
243
243
  <script lang="ts">
244
244
  import Button from './Button.svelte';
245
245
  import Icon from './Icon.svelte';
246
- import { useToast } from '../../composables/useToast';
246
+ import { useToast } from '../../composables/useToast.svelte';
247
247
 
248
248
  let {
249
249
  code,
@@ -156,7 +156,7 @@
156
156
  import Button from './Button.svelte';
157
157
  import Icon from './Icon.svelte';
158
158
  import Badge from './Badge.svelte';
159
- import { useToast } from '../../composables/useToast';
159
+ import { useToast } from '../../composables/useToast.svelte';
160
160
 
161
161
  let {
162
162
  original = '',
@@ -1,6 +1,23 @@
1
1
  <script module lang="ts">
2
+ import { tv } from '../../utils/cn';
2
3
  import type { Component, Snippet } from 'svelte';
3
4
 
5
+ export const iconVariants = tv({
6
+ base: 'inline-flex shrink-0 items-center justify-center',
7
+ variants: {
8
+ size: {
9
+ xs: 'w-3.5 h-3.5',
10
+ sm: 'w-4 h-4',
11
+ md: 'w-5 h-5',
12
+ lg: 'w-6 h-6',
13
+ xl: 'w-8 h-8'
14
+ }
15
+ },
16
+ defaultVariants: {
17
+ size: 'md'
18
+ }
19
+ });
20
+
4
21
  export type IconSource =
5
22
  string | Component<{ class?: string; size?: string | number; [key: string]: any }>;
6
23
 
@@ -24,22 +41,11 @@
24
41
  ...restProps
25
42
  }: IconProps = $props();
26
43
 
27
- let sizeClass = $derived.by(() => {
28
- if (typeof size === 'number') return `w-[${size}px] h-[${size}px]`;
29
- switch (size) {
30
- case 'xs':
31
- return 'w-3.5 h-3.5';
32
- case 'sm':
33
- return 'w-4 h-4';
34
- case 'lg':
35
- return 'w-6 h-6';
36
- case 'xl':
37
- return 'w-8 h-8';
38
- case 'md':
39
- default:
40
- return 'w-5 h-5';
41
- }
42
- });
44
+ let classes = $derived(
45
+ typeof size === 'number'
46
+ ? `inline-flex shrink-0 items-center justify-center w-[${size}px] h-[${size}px] ${className}`
47
+ : iconVariants({ size: size as 'xs' | 'sm' | 'md' | 'lg' | 'xl', class: className })
48
+ );
43
49
 
44
50
  let IconComponent = $derived.by(() => {
45
51
  if (icon && typeof icon !== 'string') return icon;
@@ -53,6 +59,43 @@
53
59
  return null;
54
60
  });
55
61
 
62
+ const iconAliases: Record<string, string> = {
63
+ close: 'cross',
64
+ x: 'cross',
65
+ 'x-mark': 'cross',
66
+ pencil: 'edit',
67
+ lightning: 'bolt',
68
+ zap: 'bolt',
69
+ 'arrow-right-on-rectangle': 'log-out',
70
+ logout: 'log-out',
71
+ document: 'file',
72
+ envelope: 'mail',
73
+ email: 'mail',
74
+ 'shield-check': 'shield',
75
+ security: 'shield',
76
+ paintbrush: 'paint-brush',
77
+ swatches: 'swatch',
78
+ laptop: 'computer',
79
+ desktop: 'computer',
80
+ monitor: 'computer',
81
+ menu: 'bars-3',
82
+ adjustments: 'sliders',
83
+ 'adjustments-horizontal': 'sliders',
84
+ 'adjustments-vertical': 'sliders',
85
+ 'square-3-stack-3d': 'layers',
86
+ 'chart-bar': 'chart',
87
+ 'bar-chart': 'chart',
88
+ book: 'book-open',
89
+ building: 'building-office-2',
90
+ 'cursor-arrow-rays': 'cursor',
91
+ receipt: 'receipt-percent'
92
+ };
93
+
94
+ let resolvedIconName = $derived.by(() => {
95
+ if (!stringIconName) return null;
96
+ return iconAliases[stringIconName] || stringIconName;
97
+ });
98
+
56
99
  // Common built-in SVGs for immediate zero-config use
57
100
  const builtInIcons: Record<string, string> = {
58
101
  check:
@@ -134,7 +177,7 @@
134
177
  bolt: '<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
135
178
  cube: '<path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><polyline points="3.27 6.96 12 12.01 20.73 6.96" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><line x1="12" y1="22.08" x2="12" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
136
179
  swatch:
137
- '<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10c.88 0 1.6-.72 1.6-1.6 0-.41-.17-.79-.44-1.06-.27-.28-.44-.65-.44-1.06 0-.88.72-1.6 1.6-1.6h1.88c3.15 0 5.72-2.57 5.72-5.72C21.32 6.16 17.14 2 12 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><circle cx="6.5" cy="11.5" r="1.5" fill="currentColor"/><circle cx="9.5" cy="7.5" r="1.5" fill="currentColor"/><circle cx="14.5" cy="7.5" r="1.5" fill="currentColor"/><circle cx="17.5" cy="11.5" r="1.5" fill="currentColor"/>',
180
+ '<path d="M11 17a4 4 0 0 1-8 0V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><path d="M16.7 13H19a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><circle cx="7" cy="17" r="1.5" fill="currentColor"/><path d="m11 8 2.3-2.3a2.4 2.4 0 0 1 3.4.004L18.6 7.6a2.4 2.4 0 0 1 .002 3.4L13 16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
138
181
  star: '<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
139
182
  photo:
140
183
  '<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/><circle cx="8.5" cy="8.5" r="1.5" fill="currentColor"/><polyline points="21 15 16 10 5 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
@@ -142,8 +185,14 @@
142
185
  '<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><polyline points="14 2 14 8 20 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><line x1="8" y1="13" x2="16" y2="13" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="8" y1="17" x2="13" y2="17" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
143
186
  'clipboard-document':
144
187
  '<path d="M16 4h2a2 2 0 012 2v14a2 2 0 01-2 2H6a2 2 0 01-2-2V6a2 2 0 012-2h2" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/><rect x="8" y="2" width="8" height="4" rx="1" stroke="currentColor" stroke-width="2" fill="none"/><line x1="9" y1="12" x2="15" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="9" y1="16" x2="13" y2="16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
188
+ 'rotate-ccw':
189
+ '<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><polyline points="3 3 3 8 8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
190
+ 'rotate-cw':
191
+ '<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><polyline points="21 3 21 8 16 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
145
192
  'arrow-path':
146
- '<path d="M21.5 2v6h-6M21.34 15.57a10 10 0 11-.57-8.38l5.67-5.19" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
193
+ '<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><polyline points="3 3 3 8 8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><polyline points="16 16 21 16 21 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
194
+ 'refresh-cw':
195
+ '<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><polyline points="3 3 3 8 8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><polyline points="16 16 21 16 21 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
147
196
  'code-bracket':
148
197
  '<polyline points="16 18 22 12 16 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><polyline points="8 6 2 12 8 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
149
198
  'squares-2x2':
@@ -157,38 +206,65 @@
157
206
  'layout-top':
158
207
  '<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/><line x1="3" y1="9" x2="21" y2="9" stroke="currentColor" stroke-width="2"/>',
159
208
  'panel-right':
160
- '<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/><line x1="15" y1="3" x2="15" y2="21" stroke="currentColor" stroke-width="2"/>'
209
+ '<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/><line x1="15" y1="3" x2="15" y2="21" stroke="currentColor" stroke-width="2"/>',
210
+ bell: '<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><path d="M13.73 21a2 2 0 0 1-3.46 0" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
211
+ fingerprint:
212
+ '<path d="M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/><path d="M14 13.12c0 2.38 0 6.38-1 8.88" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/><path d="M2 16h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/><path d="M21.8 16c.2-2 .131-5.354 0-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/><path d="M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/><path d="M8.65 22c.21-.66.45-1.32.57-2" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/><path d="M9 6.8a6 6 0 0 1 9 5.2v2" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/>',
213
+ key: '<path d="M21 2l-2 2m-1.5 1.5L14 9l-3-3L2 15l7 7 9-9 3.5-3.5z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><circle cx="7.5" cy="16.5" r="1.5" fill="currentColor"/>',
214
+ 'credit-card':
215
+ '<rect x="2" y="5" width="20" height="14" rx="2" stroke="currentColor" stroke-width="2" fill="none"/><line x1="2" y1="10" x2="22" y2="10" stroke="currentColor" stroke-width="2"/><line x1="6" y1="15" x2="10" y2="15" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
216
+ home: '<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><polyline points="9 22 9 12 15 12 15 22" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
217
+ settings:
218
+ '<circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" fill="none"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
219
+ 'log-out':
220
+ '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><polyline points="16 17 21 12 16 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><line x1="21" y1="12" x2="9" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
221
+ mail: '<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" stroke="currentColor" stroke-width="2" fill="none"/><polyline points="22,6 12,13 2,6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
222
+ inbox:
223
+ '<polyline points="22 12 16 12 14 15 10 15 8 12 2 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
224
+ palette:
225
+ '<circle cx="13.5" cy="6.5" r="1.5" fill="currentColor"/><circle cx="17.5" cy="10.5" r="1.5" fill="currentColor"/><circle cx="8.5" cy="7.5" r="1.5" fill="currentColor"/><circle cx="6.5" cy="12.5" r="1.5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
226
+ 'paint-brush':
227
+ '<path d="m9.53 16.122 9.72-9.72a.75.75 0 0 0-.01-1.06l-4.58-4.58a.75.75 0 0 0-1.06 0l-9.72 9.72a3.75 3.75 0 0 0-1.1 2.652v2.368a.75.75 0 0 0 .75.75h2.368a3.75 3.75 0 0 0 2.652-1.12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
228
+ layers:
229
+ '<path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
230
+ sliders:
231
+ '<line x1="4" y1="21" x2="4" y2="14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="4" y1="10" x2="4" y2="3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="12" y1="21" x2="12" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="12" y1="8" x2="12" y2="3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="20" y1="21" x2="20" y2="16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="20" y1="12" x2="20" y2="3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="1" y1="14" x2="7" y2="14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="9" y1="8" x2="15" y2="8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="17" y1="16" x2="23" y2="16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
232
+ shield:
233
+ '<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
234
+ chart:
235
+ '<line x1="12" y1="20" x2="12" y2="10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="18" y1="20" x2="18" y2="4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="6" y1="20" x2="6" y2="16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
236
+ 'building-office-2':
237
+ '<path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><path d="M6 12H4a2 2 0 0 0-2 2v8h20v-8a2 2 0 0 0-2-2h-2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><line x1="10" y1="6" x2="10" y2="6.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="14" y1="6" x2="14" y2="6.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="10" y1="10" x2="10" y2="10.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="14" y1="10" x2="14" y2="10.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="10" y1="14" x2="10" y2="14.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="14" y1="14" x2="14" y2="14.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="10" y1="18" x2="14" y2="18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
238
+ table:
239
+ '<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/><line x1="3" y1="9" x2="21" y2="9" stroke="currentColor" stroke-width="2"/><line x1="3" y1="15" x2="21" y2="15" stroke="currentColor" stroke-width="2"/><line x1="9" y1="3" x2="9" y2="21" stroke="currentColor" stroke-width="2"/><line x1="15" y1="3" x2="15" y2="21" stroke="currentColor" stroke-width="2"/>',
240
+ window:
241
+ '<rect x="2" y="3" width="20" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/><line x1="2" y1="8" x2="22" y2="8" stroke="currentColor" stroke-width="2"/><circle cx="5" cy="5.5" r="1" fill="currentColor"/><circle cx="8" cy="5.5" r="1" fill="currentColor"/><circle cx="11" cy="5.5" r="1" fill="currentColor"/>',
242
+ 'bars-3-bottom-right':
243
+ '<line x1="3" y1="6" x2="21" y2="6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="3" y1="12" x2="21" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><line x1="9" y1="18" x2="21" y2="18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',
244
+ cursor:
245
+ '<path d="m4 4 7 17 2.5-6.5L20 12Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
246
+ 'book-open':
247
+ '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>',
248
+ 'receipt-percent':
249
+ '<path d="M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><line x1="9" y1="9" x2="15" y2="15" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="9.5" cy="9.5" r=".75" fill="currentColor"/><circle cx="14.5" cy="14.5" r=".75" fill="currentColor"/>',
250
+ 'document-magnifying-glass':
251
+ '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><polyline points="14 2 14 8 20 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/><circle cx="11" cy="14" r="3" stroke="currentColor" stroke-width="2" fill="none"/><line x1="13.2" y1="16.2" x2="16.5" y2="19.5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>'
161
252
  };
162
253
  </script>
163
254
 
164
255
  {#if children}
165
- <span class="inline-flex shrink-0 items-center justify-center {sizeClass} {className}">
256
+ <span class={classes}>
166
257
  {@render children()}
167
258
  </span>
168
259
  {:else if IconComponent}
169
260
  {@const RenderIcon = IconComponent}
170
- <RenderIcon
171
- class="inline-flex shrink-0 items-center justify-center {sizeClass} {className}"
172
- {...restProps}
173
- />
174
- {:else if stringIconName && builtInIcons[stringIconName]}
175
- <svg
176
- class="inline-flex shrink-0 items-center justify-center {sizeClass} {className}"
177
- viewBox="0 0 24 24"
178
- fill="none"
179
- aria-hidden="true"
180
- {...restProps}
181
- >
182
- {@html builtInIcons[stringIconName]}
261
+ <RenderIcon class={classes} {...restProps} />
262
+ {:else if resolvedIconName && builtInIcons[resolvedIconName]}
263
+ <svg class={classes} viewBox="0 0 24 24" fill="none" aria-hidden="true" {...restProps}>
264
+ {@html builtInIcons[resolvedIconName]}
183
265
  </svg>
184
266
  {:else}
185
- <svg
186
- class="inline-flex shrink-0 items-center justify-center {sizeClass} {className}"
187
- viewBox="0 0 24 24"
188
- fill="none"
189
- aria-hidden="true"
190
- {...restProps}
191
- >
267
+ <svg class={classes} viewBox="0 0 24 24" fill="none" aria-hidden="true" {...restProps}>
192
268
  <circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2" stroke-dasharray="4 4" />
193
269
  </svg>
194
270
  {/if}
@@ -1,4 +1,29 @@
1
1
  import type { Component, Snippet } from 'svelte';
2
+ export declare const iconVariants: import("tailwind-variants").TVReturnType<{
3
+ size: {
4
+ xs: "w-3.5 h-3.5";
5
+ sm: "w-4 h-4";
6
+ md: "w-5 h-5";
7
+ lg: "w-6 h-6";
8
+ xl: "w-8 h-8";
9
+ };
10
+ }, undefined, "inline-flex shrink-0 items-center justify-center", {
11
+ size: {
12
+ xs: "w-3.5 h-3.5";
13
+ sm: "w-4 h-4";
14
+ md: "w-5 h-5";
15
+ lg: "w-6 h-6";
16
+ xl: "w-8 h-8";
17
+ };
18
+ }, undefined, import("tailwind-variants").TVReturnTypeLike<{
19
+ size: {
20
+ xs: "w-3.5 h-3.5";
21
+ sm: "w-4 h-4";
22
+ md: "w-5 h-5";
23
+ lg: "w-6 h-6";
24
+ xl: "w-8 h-8";
25
+ };
26
+ }, undefined>>;
2
27
  export type IconSource = string | Component<{
3
28
  class?: string;
4
29
  size?: string | number;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,91 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { renderComponent } from '../../testing';
3
+ import Icon from './Icon.svelte';
4
+ describe('Icon Component', () => {
5
+ it('renders built-in SVG icons by string name', () => {
6
+ const { target, cleanup } = renderComponent(Icon, {
7
+ name: 'check'
8
+ });
9
+ const svg = target.querySelector('svg');
10
+ expect(svg).toBeDefined();
11
+ expect(svg?.getAttribute('aria-hidden')).toBe('true');
12
+ expect(svg?.innerHTML).toContain('<path');
13
+ expect(svg?.getAttribute('class')).toContain('w-5 h-5');
14
+ cleanup();
15
+ });
16
+ it('resolves icon aliases correctly', () => {
17
+ const { target: t1, cleanup: c1 } = renderComponent(Icon, {
18
+ name: 'close'
19
+ });
20
+ expect(t1.querySelector('svg')?.innerHTML).toContain('18 6L6 18');
21
+ c1();
22
+ const { target: t2, cleanup: c2 } = renderComponent(Icon, {
23
+ name: 'zap'
24
+ });
25
+ expect(t2.querySelector('svg')?.innerHTML).toContain('13 2L3 14');
26
+ c2();
27
+ });
28
+ it('renders newly added common icons: bell, home, settings, credit-card, fingerprint, key, swatch, paint-brush', () => {
29
+ const names = [
30
+ 'bell',
31
+ 'home',
32
+ 'settings',
33
+ 'credit-card',
34
+ 'fingerprint',
35
+ 'key',
36
+ 'mail',
37
+ 'inbox',
38
+ 'palette',
39
+ 'shield',
40
+ 'swatch',
41
+ 'swatches',
42
+ 'paint-brush',
43
+ 'paintbrush',
44
+ 'layers'
45
+ ];
46
+ for (const name of names) {
47
+ const { target, cleanup } = renderComponent(Icon, { name });
48
+ const svg = target.querySelector('svg');
49
+ expect(svg).toBeDefined();
50
+ expect(svg?.innerHTML).not.toContain('stroke-dasharray="4 4"');
51
+ cleanup();
52
+ }
53
+ });
54
+ it('renders fallback dashed circle for unknown icon name', () => {
55
+ const { target, cleanup } = renderComponent(Icon, {
56
+ name: 'non-existent-icon-xyz'
57
+ });
58
+ const svg = target.querySelector('svg');
59
+ expect(svg).toBeDefined();
60
+ expect(svg?.innerHTML).toContain('stroke-dasharray="4 4"');
61
+ cleanup();
62
+ });
63
+ it('supports all size variants and custom numeric size', () => {
64
+ const sizeMap = {
65
+ xs: 'w-3.5 h-3.5',
66
+ sm: 'w-4 h-4',
67
+ md: 'w-5 h-5',
68
+ lg: 'w-6 h-6',
69
+ xl: 'w-8 h-8'
70
+ };
71
+ for (const [size, expectedClass] of Object.entries(sizeMap)) {
72
+ const { target, cleanup } = renderComponent(Icon, {
73
+ name: 'sparkles',
74
+ size: size
75
+ });
76
+ const svg = target.querySelector('svg');
77
+ expect(svg?.getAttribute('class')).toContain(expectedClass);
78
+ cleanup();
79
+ }
80
+ });
81
+ it('merges custom classes', () => {
82
+ const { target, cleanup } = renderComponent(Icon, {
83
+ name: 'sparkles',
84
+ class: 'text-primary-500 animate-spin'
85
+ });
86
+ const svg = target.querySelector('svg');
87
+ expect(svg?.getAttribute('class')).toContain('text-primary-500');
88
+ expect(svg?.getAttribute('class')).toContain('animate-spin');
89
+ cleanup();
90
+ });
91
+ });
@@ -31,6 +31,18 @@
31
31
  legalLinks?.privacy || legalLinks?.terms || legalLinks?.refunds || legalLinks?.impressum
32
32
  )
33
33
  );
34
+
35
+ let copiedEmail = $state(false);
36
+
37
+ function copyEmail(email: string) {
38
+ if (typeof navigator !== 'undefined' && navigator.clipboard) {
39
+ navigator.clipboard.writeText(email);
40
+ copiedEmail = true;
41
+ setTimeout(() => {
42
+ copiedEmail = false;
43
+ }, 2000);
44
+ }
45
+ }
34
46
  </script>
35
47
 
36
48
  <footer
@@ -56,20 +68,28 @@
56
68
  {currentConfig.description}
57
69
  </p>
58
70
  {#if currentConfig.email}
59
- <p class="text-xs text-neutral-500 dark:text-neutral-400">
60
- Contact:
71
+ <div class="flex items-center gap-2 text-xs text-neutral-500 dark:text-neutral-400">
72
+ <span>Contact:</span>
61
73
  <a
62
74
  href="mailto:{currentConfig.email}"
63
- class="font-medium text-neutral-700 hover:underline dark:text-neutral-300"
75
+ class="font-medium text-neutral-700 transition-colors hover:text-primary-600 hover:underline dark:text-neutral-300 dark:hover:text-primary-400"
64
76
  >
65
77
  {currentConfig.email}
66
78
  </a>
67
- </p>
68
- {/if}
69
- {#if legal?.morNotice}
70
- <p class="text-xs text-neutral-500 dark:text-neutral-500">
71
- {legal.morNotice}
72
- </p>
79
+ <button
80
+ type="button"
81
+ onclick={() => copyEmail(currentConfig.email!)}
82
+ class="inline-flex items-center justify-center rounded p-1 text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-neutral-700 dark:hover:bg-neutral-800 dark:hover:text-neutral-200"
83
+ aria-label="Copy email address"
84
+ title={copiedEmail ? 'Copied to clipboard!' : 'Copy email address'}
85
+ >
86
+ {#if copiedEmail}
87
+ <Icon name="check" size="xs" class="text-emerald-500" />
88
+ {:else}
89
+ <Icon name="clipboard" size="xs" />
90
+ {/if}
91
+ </button>
92
+ </div>
73
93
  {/if}
74
94
 
75
95
  <div class="pt-2 text-xs text-neutral-500 dark:text-neutral-400">
@@ -104,9 +104,9 @@
104
104
  aria-label="Toggle color mode"
105
105
  >
106
106
  {#if theme.resolvedTheme === 'dark'}
107
- <Icon name="sun" size="sm" class="text-amber-400" />
107
+ <Icon name="moon" size="sm" class="text-primary-400" />
108
108
  {:else}
109
- <Icon name="moon" size="sm" class="text-neutral-600" />
109
+ <Icon name="sun" size="sm" class="text-amber-500" />
110
110
  {/if}
111
111
  </Button>
112
112
 
@@ -1,9 +1,9 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { Toaster } from 'svelte-sonner';
3
+ import Toaster from '../overlays/Toaster.svelte';
4
4
  import { Tooltip } from 'bits-ui';
5
5
  import { type SiteConfig, DEFAULT_SITE_CONFIG } from '../../site/config';
6
- import { theme, themeInitScript } from '../../theme/theme.svelte';
6
+ import { themeInitScript } from '../../theme/theme.svelte';
7
7
  import Favicons from '../seo/Favicons.svelte';
8
8
  import Seo from '../seo/Seo.svelte';
9
9
  import { initYaxaState, type AuthUserContext } from '../../site/context';
@@ -53,5 +53,5 @@
53
53
  </div>
54
54
  </Tooltip.Provider>
55
55
 
56
- <!-- Global Toast Container with dynamic theme synchronization -->
57
- <Toaster richColors theme={theme.resolvedTheme} position="top-right" closeButton />
56
+ <!-- Global Toast Container -->
57
+ <Toaster richColors position="top-right" closeButton />