zabi-components 5.0.17 → 5.0.18

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 (65) hide show
  1. package/README.md +124 -23
  2. package/dist/SSRSafe.svelte +0 -3
  3. package/dist/atoms/Badge.svelte +19 -34
  4. package/dist/atoms/Badge.svelte.d.ts +6 -5
  5. package/dist/atoms/Button.svelte +10 -9
  6. package/dist/atoms/Button.svelte.d.ts +3 -2
  7. package/dist/atoms/Card.svelte +74 -23
  8. package/dist/atoms/Card.svelte.d.ts +3 -1
  9. package/dist/atoms/CardContent.svelte +24 -0
  10. package/dist/atoms/CardContent.svelte.d.ts +8 -0
  11. package/dist/atoms/CardDescription.svelte +24 -0
  12. package/dist/atoms/CardDescription.svelte.d.ts +8 -0
  13. package/dist/atoms/CardFooter.svelte +24 -0
  14. package/dist/atoms/CardFooter.svelte.d.ts +8 -0
  15. package/dist/atoms/CardHeader.svelte +24 -0
  16. package/dist/atoms/CardHeader.svelte.d.ts +8 -0
  17. package/dist/atoms/CardTitle.svelte +33 -0
  18. package/dist/atoms/CardTitle.svelte.d.ts +9 -0
  19. package/dist/atoms/Checkbox.svelte +0 -4
  20. package/dist/atoms/CodeBlock.svelte +0 -3
  21. package/dist/atoms/ColorPicker.svelte +33 -65
  22. package/dist/atoms/FeatureCard.svelte +0 -10
  23. package/dist/atoms/FeatureCard.svelte.d.ts +0 -8
  24. package/dist/atoms/Heading.svelte +0 -2
  25. package/dist/atoms/Input.svelte +3 -12
  26. package/dist/atoms/Input.svelte.d.ts +3 -2
  27. package/dist/atoms/Progress.svelte +0 -3
  28. package/dist/atoms/Select.svelte +0 -14
  29. package/dist/atoms/Textarea.svelte +3 -9
  30. package/dist/atoms/Textarea.svelte.d.ts +3 -1
  31. package/dist/atoms/ThemeToggle.svelte +0 -7
  32. package/dist/atoms/Toast.svelte +0 -1
  33. package/dist/atoms/Toggle.svelte +0 -6
  34. package/dist/atoms/Tooltip.svelte +0 -12
  35. package/dist/atoms/index.d.ts +5 -0
  36. package/dist/atoms/index.js +5 -1
  37. package/dist/components/atoms/index.d.ts +5 -0
  38. package/dist/components/atoms/index.d.ts.map +1 -1
  39. package/dist/components/index.d.ts.map +1 -1
  40. package/dist/components/molecules/index.d.ts.map +1 -1
  41. package/dist/components/organisms/index.d.ts.map +1 -1
  42. package/dist/index.js +0 -23
  43. package/dist/molecules/Alert.svelte +3 -13
  44. package/dist/molecules/Alert.svelte.d.ts +2 -1
  45. package/dist/molecules/ComponentDemo.svelte +0 -2
  46. package/dist/molecules/ContactForm.svelte +21 -12
  47. package/dist/molecules/Dropdown.svelte +100 -6
  48. package/dist/molecules/Dropdown.svelte.d.ts +1 -0
  49. package/dist/molecules/Form.svelte +0 -1
  50. package/dist/molecules/ImageUpload.svelte +0 -4
  51. package/dist/molecules/Modal.svelte +29 -5
  52. package/dist/molecules/SlideUp.svelte +0 -2
  53. package/dist/molecules/Tabs.svelte +0 -2
  54. package/dist/molecules/index.js +0 -1
  55. package/dist/organisms/Navbar.svelte +0 -7
  56. package/dist/organisms/Navigation.svelte +0 -6
  57. package/dist/organisms/index.js +0 -1
  58. package/dist/routes/lib/variant-utils.ts +3 -1
  59. package/dist/zabi-components-colors.css +246 -178
  60. package/dist/zabi-components-theme-dark-only.css +11 -2
  61. package/dist/zabi-components-theme-dark.css +11 -2
  62. package/dist/zabi-components-theme-only.css +32 -24
  63. package/dist/zabi-components-theme.css +32 -24
  64. package/dist/zabi-components.css +259 -83
  65. package/package.json +2 -2
package/README.md CHANGED
@@ -219,16 +219,16 @@ import type { ButtonEvents, InputEvents } from 'zabi-components/types';
219
219
 
220
220
  | Component | Category | Description | Key Features |
221
221
  |-----------|----------|-------------|--------------|
222
- | **Card** | Atom | Clean card container | Simple, interactive, image support |
222
+ | **Card** | Atom | Clean card container | Simple, interactive, image support, compound components, 4 variants |
223
223
  | **Form** | Molecule | Simple form wrapper | FormData handling, clean API |
224
224
  | **Layout** | Molecule | Page layout system | Header, main, footer slots |
225
225
  | **Navigation** | Organism | Clean navigation | Header/sidebar variants, active state |
226
- | **Button** | Atom | Action button | 3 variants (primary, secondary, danger) |
226
+ | **Button** | Atom | Action button | 6 variants (primary, secondary, danger, ghost, outline, link) |
227
227
  | **Input** | Atom | Form input | Essential props, accessibility |
228
228
  | **Badge** | Atom | Status indicator | 5 color variants, closable |
229
- | **Modal** | Molecule | Overlay dialog | Simple backdrop, close button |
229
+ | **Modal** | Molecule | Overlay dialog | Focus trap, keyboard navigation, focus return |
230
230
  | **Tabs** | Molecule | Tab navigation | Keyboard navigation, 2 variants |
231
- | **Dropdown** | Molecule | Dropdown menu | CSS-only positioning |
231
+ | **Dropdown** | Molecule | Dropdown menu | CSS-only positioning, keyboard navigation, ARIA support |
232
232
  | **ImageUpload** | Molecule | File upload | Direct selection, preview |
233
233
  | **Navbar** | Organism | Navigation bar | Mobile menu, responsive |
234
234
 
@@ -512,35 +512,67 @@ All components will automatically switch to their dark mode variants without any
512
512
 
513
513
  ### Card Component
514
514
 
515
+ Card component supports two APIs: prop-based (backward compatible) and compound components (recommended).
516
+
517
+ #### Prop-based API (Backward Compatible)
518
+
515
519
  ```svelte
516
520
  <Card
517
- variant="default" | "elevated" | "outlined"
518
- density="comfortable" | "compact"
519
- disabled={boolean}
520
- loading={boolean}
521
- className={string}
522
- on:click={(e) => console.log(e.detail.value, e.detail.event)}
521
+ title="Card Title"
522
+ description="Card description"
523
+ image="image-url.jpg"
524
+ variant="default" | "elevated" | "outlined" | "flat"
525
+ size="sm" | "md" | "lg"
526
+ fullWidth={boolean}
527
+ onclick={(e) => console.log('Card clicked', e)}
523
528
  >
524
- <div slot="header">Header Content</div>
525
- Main Content
526
- <div slot="footer">Footer Content</div>
529
+ Additional Content
530
+ </Card>
531
+ ```
532
+
533
+ #### Compound Component API (Recommended)
534
+
535
+ ```svelte
536
+ <Card variant="elevated">
537
+ <CardHeader>
538
+ <CardTitle>Card Title</CardTitle>
539
+ <CardDescription>Card description</CardDescription>
540
+ </CardHeader>
541
+ <CardContent>
542
+ Main content goes here
543
+ </CardContent>
544
+ <CardFooter>
545
+ <Button variant="primary">Action</Button>
546
+ </CardFooter>
527
547
  </Card>
528
548
  ```
529
549
 
530
550
  **Props:**
531
551
  - `variant`: Card style variant (default: "default")
532
- - `density`: Card density (default: "comfortable")
533
- - `disabled`: Disable interactions (default: false)
534
- - `loading`: Show loading state (default: false)
535
- - `className`: Additional CSS classes
536
-
537
- **Slots:**
538
- - `header`: Card header content
539
- - `default`: Main card content
540
- - `footer`: Card footer content
552
+ - `default`: Standard card with subtle shadow
553
+ - `elevated`: Card with stronger shadow (elevated appearance)
554
+ - `outlined`: Card with border, no shadow
555
+ - `flat`: Card with no shadow, no border (minimal)
556
+ - `size`: Card size (default: "md")
557
+ - `fullWidth`: Whether card takes full width (default: true)
558
+ - `title`: Card title (old API)
559
+ - `description`: Card description (old API)
560
+ - `image`: Card image URL (old API)
561
+ - `onclick`: Click handler for interactive cards
562
+
563
+ **Compound Components:**
564
+ - `CardHeader`: Header section container
565
+ - `CardContent`: Main content area
566
+ - `CardFooter`: Footer section container
567
+ - `CardTitle`: Title component (supports heading levels 1-6)
568
+ - `CardDescription`: Description text component
541
569
 
542
570
  **Events:**
543
- - `click`: Fired when card is clicked - `{ detail: { value: true, event: MouseEvent } }`
571
+ - `onclick`: Native click event - `MouseEvent`
572
+
573
+ **Keyboard Navigation (Interactive Cards):**
574
+ - **Tab**: Focus the card
575
+ - **Enter / Space**: Activate card click handler
544
576
 
545
577
  ### Alert Component
546
578
 
@@ -1198,6 +1230,75 @@ function handleFormSubmit(event: any) {
1198
1230
  - Test with screen readers
1199
1231
  - Maintain proper color contrast ratios
1200
1232
 
1233
+ ## Compound Components
1234
+
1235
+ Zabi Components supports compound component patterns for flexible composition:
1236
+
1237
+ ### Card Compound Components
1238
+
1239
+ ```svelte
1240
+ <script>
1241
+ import { Card, CardHeader, CardContent, CardFooter, CardTitle, CardDescription, Button } from 'zabi-components';
1242
+ </script>
1243
+
1244
+ <Card variant="elevated">
1245
+ <CardHeader>
1246
+ <CardTitle>Card Title</CardTitle>
1247
+ <CardDescription>
1248
+ This is a description using compound components.
1249
+ </CardDescription>
1250
+ </CardHeader>
1251
+ <CardContent>
1252
+ <p>Main content area with flexible composition.</p>
1253
+ </CardContent>
1254
+ <CardFooter>
1255
+ <Button variant="primary">Save</Button>
1256
+ <Button variant="ghost">Cancel</Button>
1257
+ </CardFooter>
1258
+ </Card>
1259
+ ```
1260
+
1261
+ ## Accessibility
1262
+
1263
+ Zabi Components is built with accessibility in mind:
1264
+
1265
+ ### WCAG 2.1 AA Compliance
1266
+ - Comprehensive ARIA attributes
1267
+ - Proper semantic HTML
1268
+ - Keyboard navigation support
1269
+ - Focus management
1270
+ - Screen reader compatibility
1271
+
1272
+ ### Keyboard Navigation
1273
+ - **Tab**: Navigate between interactive elements
1274
+ - **Enter/Space**: Activate buttons and links
1275
+ - **Arrow Keys**: Navigate within components (Tabs, Navigation, Dropdown)
1276
+ - **Escape**: Close modals and overlays
1277
+ - **Home/End**: Jump to first/last item in lists
1278
+
1279
+ ### Focus Management
1280
+ - Focus trap in modals
1281
+ - Focus return after closing modals
1282
+ - Visible focus indicators
1283
+ - Logical tab order
1284
+
1285
+ See [ACCESSIBILITY.md](./docs/ACCESSIBILITY.md) and [KEYBOARD_NAVIGATION.md](./docs/KEYBOARD_NAVIGATION.md) for detailed documentation.
1286
+
1287
+ ## Variant System
1288
+
1289
+ Zabi Components uses a consistent variant system:
1290
+
1291
+ ### Semantic Variants
1292
+ Used for state indication: `default`, `success`, `warning`, `error`, `info`
1293
+
1294
+ ### Style Variants
1295
+ Used for visual appearance: Button (`primary`, `secondary`, `outline`, `link`, etc.), Card (`elevated`, `outlined`, `flat`)
1296
+
1297
+ ### Size Variants
1298
+ Consistent across components: `sm`, `md`, `lg`
1299
+
1300
+ See [VARIANTS.md](./docs/VARIANTS.md) for complete variant documentation.
1301
+
1201
1302
  ## Support
1202
1303
 
1203
1304
  For support, please open an issue on GitHub or contact the maintainers.
@@ -1,4 +1,3 @@
1
- <!-- SSR-safe wrapper component for zabi-components -->
2
1
  <script lang="ts">
3
2
  import { onMount } from "svelte";
4
3
 
@@ -33,7 +32,6 @@
33
32
  {@render children?.()}
34
33
  </FallbackComponent>
35
34
  {:else}
36
- <!-- Fallback content for SSR -->
37
35
  <div class="zabi-ssr-fallback">
38
36
  {@render children?.()}
39
37
  </div>
@@ -41,7 +39,6 @@
41
39
 
42
40
  <style>
43
41
  .zabi-ssr-fallback {
44
- /* Minimal styling for SSR fallback */
45
42
  display: contents;
46
43
  }
47
44
  </style>
@@ -1,48 +1,35 @@
1
- <!--
2
- Badge Component - SSR-safe and robust
3
-
4
- A simple badge component with proper SSR handling and defensive programming
5
- to prevent DOM access errors during hydration and rapid state changes.
6
- -->
7
1
  <script lang="ts">
8
2
  import { Check, AlertTriangle, X, Info } from "@lucide/svelte";
9
- // Props using Svelte 5 runes
3
+ import type {
4
+ ExtendedSemanticVariant,
5
+ SizeVariant,
6
+ } from "../../types/variants.js";
7
+
8
+ interface Props {
9
+ variant?: ExtendedSemanticVariant;
10
+ size?: SizeVariant;
11
+ text: string;
12
+ showIcon?: boolean;
13
+ }
14
+
10
15
  let {
11
16
  variant = "default",
12
17
  size = "md",
13
18
  text = "",
14
- showIcon = true,
15
- }: {
16
- variant?:
17
- | "default"
18
- | "success"
19
- | "warning"
20
- | "error"
21
- | "info"
22
- | "neutral"
23
- | "energetic";
24
- size?: "sm" | "md" | "lg";
25
- text: string;
26
- showIcon?: boolean;
27
- } = $props();
19
+ showIcon = false,
20
+ }: Props = $props();
28
21
 
29
- // Class computation using conditional logic with full class names
30
22
  const classes = $derived(() => {
31
23
  const baseClasses =
32
- "inline-flex items-center font-medium border rounded-md";
24
+ "inline-flex items-center font-medium border rounded-xs";
33
25
 
34
- // Size classes - using full class names
35
26
  const sizeClass =
36
27
  size === "sm"
37
28
  ? "px-2 py-0.5 text-xs"
38
29
  : size === "lg"
39
30
  ? "px-4 py-2 text-base"
40
- : "px-3 py-1 text-sm"; // default md
31
+ : "px-3 py-1 text-sm";
41
32
 
42
- // Variant classes - using semantic color system with improved WCAG contrast
43
- // Using colored backgrounds with white text for better contrast ratios (4.5:1+)
44
- // Using darker shades for warning and energetic to ensure WCAG AA compliance
45
- // Note: Using text-white instead of text-inverse to ensure light text in both light and dark modes
46
33
  const variantClass =
47
34
  variant === "success"
48
35
  ? "bg-success border-success text-card"
@@ -56,19 +43,17 @@
56
43
  ? "bg-neutral border-neutral text-card"
57
44
  : variant === "energetic"
58
45
  ? "bg-energetic border-energetic text-card"
59
- : "bg-secondary border-secondary text-card"; // default
46
+ : "bg-secondary border-secondary text-card";
60
47
 
61
48
  return `${baseClasses} ${sizeClass} ${variantClass}`.trim();
62
49
  });
63
50
 
64
- // Icon size based on badge size
65
51
  const iconSize = $derived(() => {
66
- return size === "sm" ? 12 : size === "lg" ? 20 : 16; // default md
52
+ return size === "sm" ? 12 : size === "lg" ? 20 : 16;
67
53
  });
68
54
 
69
- // Icon spacing class
70
55
  const iconSpacingClass = $derived(() => {
71
- return size === "sm" ? "mr-1" : size === "lg" ? "mr-2" : "mr-1.5"; // default md
56
+ return size === "sm" ? "mr-1" : size === "lg" ? "mr-2" : "mr-1.5";
72
57
  });
73
58
  </script>
74
59
 
@@ -1,9 +1,10 @@
1
- type $$ComponentProps = {
2
- variant?: "default" | "success" | "warning" | "error" | "info" | "neutral" | "energetic";
3
- size?: "sm" | "md" | "lg";
1
+ import type { ExtendedSemanticVariant, SizeVariant } from "../../types/variants.js";
2
+ interface Props {
3
+ variant?: ExtendedSemanticVariant;
4
+ size?: SizeVariant;
4
5
  text: string;
5
6
  showIcon?: boolean;
6
- };
7
- declare const Badge: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ }
8
+ declare const Badge: import("svelte").Component<Props, {}, "">;
8
9
  type Badge = ReturnType<typeof Badge>;
9
10
  export default Badge;
@@ -1,9 +1,10 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
+ import type { ButtonVariant, SizeVariant } from "../../types/variants.js";
3
4
 
4
5
  interface Props {
5
- variant?: "primary" | "secondary" | "danger" | "ghost";
6
- size?: "sm" | "md" | "lg";
6
+ variant?: ButtonVariant;
7
+ size?: SizeVariant;
7
8
  disabled?: boolean;
8
9
  type?: "button" | "submit" | "reset";
9
10
  text?: string;
@@ -22,7 +23,6 @@
22
23
  ...restProps
23
24
  }: Props = $props();
24
25
 
25
- // Size classes matching M3 design specifications
26
26
  const sizeClass = $derived(() => {
27
27
  if (size === "sm") {
28
28
  return {
@@ -45,7 +45,6 @@
45
45
  gap: "gap-3",
46
46
  };
47
47
  } else {
48
- // default md
49
48
  return {
50
49
  padding: "px-5 py-3",
51
50
  text: "text-base",
@@ -58,20 +57,22 @@
58
57
  }
59
58
  });
60
59
 
61
- // Variant classes using semantic action colors
62
60
  const variantClass = $derived(() => {
63
61
  return variant === "primary"
64
- ? "bg-action-primary text-inverse hover:bg-action-primary-hover active:bg-action-primary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2"
62
+ ? "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2"
65
63
  : variant === "secondary"
66
- ? "bg-brand-100 text-brand-800 hover:bg-brand-200 active:bg-brand-300 focus:ring-2 focus:ring-brand-500 focus:ring-offset-2"
64
+ ? "bg-action-secondary text-headline hover:bg-action-secondary-hover active:bg-action-secondary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2"
67
65
  : variant === "danger"
68
66
  ? "bg-action-danger text-inverse hover:bg-action-danger-hover active:bg-action-danger-active focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
69
67
  : variant === "ghost"
70
68
  ? "bg-transparent text-headline hover:bg-base-100 active:bg-base-200 focus:ring-2 focus:ring-base-500 focus:ring-offset-2 disabled:text-disabled"
71
- : "bg-action-primary text-inverse hover:bg-action-primary-hover active:bg-action-primary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2"; // default primary
69
+ : variant === "outline"
70
+ ? "bg-transparent border-2 border-action-primary text-headline hover:bg-action-secondary hover:text-action-primary active:bg-action-primary-active active:text-action-primary focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 disabled:border-disabled disabled:text-disabled"
71
+ : variant === "link"
72
+ ? "text-brand-700 active:text-brand-700 underline-offset-4 hover:underline focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 disabled:text-disabled disabled:no-underline"
73
+ : "bg-action-primary text-action-primary hover:bg-action-primary-hover active:bg-action-primary-active focus:ring-2 focus:ring-brand-500 focus:ring-offset-2";
72
74
  });
73
75
 
74
- // Button classes matching M3 design pattern
75
76
  const buttonClasses = $derived(() => {
76
77
  const sizeStyles = sizeClass();
77
78
  const baseClasses =
@@ -1,7 +1,8 @@
1
1
  import type { Snippet } from "svelte";
2
+ import type { ButtonVariant, SizeVariant } from "../../types/variants.js";
2
3
  interface Props {
3
- variant?: "primary" | "secondary" | "danger" | "ghost";
4
- size?: "sm" | "md" | "lg";
4
+ variant?: ButtonVariant;
5
+ size?: SizeVariant;
5
6
  disabled?: boolean;
6
7
  type?: "button" | "submit" | "reset";
7
8
  text?: string;
@@ -1,12 +1,14 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
+ import type { CardVariant, SizeVariant } from "../../types/variants.js";
3
4
 
4
5
  interface Props {
5
6
  title?: string;
6
7
  description?: string;
7
8
  image?: string;
8
9
  onclick?: (event: MouseEvent) => void | Promise<void>;
9
- size?: "sm" | "md" | "lg";
10
+ size?: SizeVariant;
11
+ variant?: CardVariant;
10
12
  fullWidth?: boolean;
11
13
  children?: Snippet;
12
14
  }
@@ -17,24 +19,48 @@
17
19
  image = "",
18
20
  onclick,
19
21
  size = "md",
22
+ variant = "default",
20
23
  fullWidth = true,
21
24
  children,
22
25
  ...restProps
23
26
  }: Props = $props();
24
27
 
25
28
  const sizeClass = $derived(() => {
26
- return size === "sm" ? "p-4" : size === "lg" ? "p-8" : "p-6"; // default md
29
+ return size === "sm" ? "p-4" : size === "lg" ? "p-8" : "p-6";
27
30
  });
28
31
 
32
+ const variantClasses = $derived(() => {
33
+ switch (variant) {
34
+ case "elevated":
35
+ return "bg-card shadow-lg";
36
+ case "outlined":
37
+ return "bg-card border-2 border-base-200 shadow-none";
38
+ case "flat":
39
+ return "bg-card shadow-none border-none";
40
+ default:
41
+ return "bg-card shadow-sm";
42
+ }
43
+ });
44
+
45
+ const useOldAPI = $derived(title !== "" || description !== "" || image !== "");
46
+
29
47
  const cardClasses = $derived(() => {
30
48
  const baseClasses =
31
- "rounded-lg transition-all duration-200 min-w-64 bg-card shadow-sm";
49
+ `rounded-lg transition-all duration-200 ${variantClasses()}`;
32
50
  const interactiveClasses = onclick
33
- ? "cursor-pointer hover:shadow-lg hover:bg-card-hover"
51
+ ? variant === "elevated"
52
+ ? "cursor-pointer hover:shadow-xl hover:bg-card-hover"
53
+ : variant === "outlined"
54
+ ? "cursor-pointer hover:border-brand-500 hover:bg-card-hover"
55
+ : variant === "flat"
56
+ ? "cursor-pointer hover:bg-card-hover"
57
+ : "cursor-pointer hover:shadow-lg hover:bg-card-hover"
34
58
  : "";
35
59
  const widthClasses = fullWidth ? "w-full" : "";
60
+ const minWidthClass = useOldAPI ? "min-w-64" : "";
61
+ const paddingClass = useOldAPI ? sizeClass() : "";
36
62
 
37
- return `${baseClasses} ${interactiveClasses} ${widthClasses} ${sizeClass()}`.trim();
63
+ return `${baseClasses} ${interactiveClasses} ${widthClasses} ${minWidthClass} ${paddingClass}`.trim();
38
64
  });
39
65
 
40
66
  const titleClasses = $derived(() => {
@@ -43,7 +69,6 @@
43
69
  } else if (size === "lg") {
44
70
  return "text-2xl font-medium mb-4 text-headline";
45
71
  } else {
46
- // default md
47
72
  return "text-xl font-medium mb-3 text-headline";
48
73
  }
49
74
  });
@@ -54,30 +79,56 @@
54
79
  } else if (size === "lg") {
55
80
  return "text-base text-description mb-5";
56
81
  } else {
57
- // default md
58
82
  return "text-sm text-description mb-4";
59
83
  }
60
84
  });
85
+
86
+ function handleKeydown(event: KeyboardEvent) {
87
+ if (onclick && (event.key === "Enter" || event.key === " ")) {
88
+ event.preventDefault();
89
+ onclick(event as unknown as MouseEvent);
90
+ }
91
+ }
92
+
93
+ const cardRole = $derived(onclick ? "button" : undefined);
94
+ const cardTabIndex = $derived(onclick ? 0 : undefined);
95
+ const cardAriaLabel = $derived(onclick && title ? title : undefined);
61
96
  </script>
62
97
 
63
- <div class={cardClasses()} {onclick} {...restProps}>
64
- {#if image}
65
- <img
66
- src={image}
67
- alt={title}
68
- class="w-full h-48 object-cover rounded-lg mb-4"
69
- />
70
- {/if}
98
+ <div
99
+ class={cardClasses()}
100
+ {onclick}
101
+ role={cardRole}
102
+ tabindex={cardTabIndex}
103
+ aria-label={cardAriaLabel}
104
+ onkeydown={handleKeydown}
105
+ {...restProps}
106
+ >
107
+ {#if useOldAPI}
108
+ <!-- Old API: Using title/description/image props -->
109
+ {#if image}
110
+ <img
111
+ src={image}
112
+ alt={title}
113
+ class="w-full h-48 object-cover rounded-lg mb-4"
114
+ />
115
+ {/if}
71
116
 
72
- {#if title}
73
- <h3 class={titleClasses()}>{title}</h3>
74
- {/if}
117
+ {#if title}
118
+ <h3 class={titleClasses()}>{title}</h3>
119
+ {/if}
75
120
 
76
- {#if description}
77
- <p class={descriptionClasses()}>{description}</p>
78
- {/if}
121
+ {#if description}
122
+ <p class={descriptionClasses()}>{description}</p>
123
+ {/if}
79
124
 
80
- {#if children}
81
- {@render children()}
125
+ {#if children}
126
+ {@render children()}
127
+ {/if}
128
+ {:else}
129
+ <!-- New API: Using compound components (CardHeader, CardContent, CardFooter) -->
130
+ {#if children}
131
+ {@render children()}
132
+ {/if}
82
133
  {/if}
83
134
  </div>
@@ -1,10 +1,12 @@
1
1
  import type { Snippet } from "svelte";
2
+ import type { CardVariant, SizeVariant } from "../../types/variants.js";
2
3
  interface Props {
3
4
  title?: string;
4
5
  description?: string;
5
6
  image?: string;
6
7
  onclick?: (event: MouseEvent) => void | Promise<void>;
7
- size?: "sm" | "md" | "lg";
8
+ size?: SizeVariant;
9
+ variant?: CardVariant;
8
10
  fullWidth?: boolean;
9
11
  children?: Snippet;
10
12
  }
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+
4
+ interface Props {
5
+ className?: string;
6
+ children?: Snippet;
7
+ }
8
+
9
+ let {
10
+ className = "",
11
+ children,
12
+ ...restProps
13
+ }: Props = $props();
14
+ </script>
15
+
16
+ <div
17
+ class="p-6 pt-0 {className}"
18
+ {...restProps}
19
+ >
20
+ {#if children}
21
+ {@render children()}
22
+ {/if}
23
+ </div>
24
+
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ className?: string;
4
+ children?: Snippet;
5
+ }
6
+ declare const CardContent: import("svelte").Component<Props, {}, "">;
7
+ type CardContent = ReturnType<typeof CardContent>;
8
+ export default CardContent;
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+
4
+ interface Props {
5
+ className?: string;
6
+ children?: Snippet;
7
+ }
8
+
9
+ let {
10
+ className = "",
11
+ children,
12
+ ...restProps
13
+ }: Props = $props();
14
+ </script>
15
+
16
+ <p
17
+ class="text-sm text-description {className}"
18
+ {...restProps}
19
+ >
20
+ {#if children}
21
+ {@render children()}
22
+ {/if}
23
+ </p>
24
+
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ className?: string;
4
+ children?: Snippet;
5
+ }
6
+ declare const CardDescription: import("svelte").Component<Props, {}, "">;
7
+ type CardDescription = ReturnType<typeof CardDescription>;
8
+ export default CardDescription;
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+
4
+ interface Props {
5
+ className?: string;
6
+ children?: Snippet;
7
+ }
8
+
9
+ let {
10
+ className = "",
11
+ children,
12
+ ...restProps
13
+ }: Props = $props();
14
+ </script>
15
+
16
+ <footer
17
+ class="flex items-center p-6 pt-0 {className}"
18
+ {...restProps}
19
+ >
20
+ {#if children}
21
+ {@render children()}
22
+ {/if}
23
+ </footer>
24
+
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from "svelte";
2
+ interface Props {
3
+ className?: string;
4
+ children?: Snippet;
5
+ }
6
+ declare const CardFooter: import("svelte").Component<Props, {}, "">;
7
+ type CardFooter = ReturnType<typeof CardFooter>;
8
+ export default CardFooter;
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+
4
+ interface Props {
5
+ className?: string;
6
+ children?: Snippet;
7
+ }
8
+
9
+ let {
10
+ className = "",
11
+ children,
12
+ ...restProps
13
+ }: Props = $props();
14
+ </script>
15
+
16
+ <header
17
+ class="flex flex-col space-y-1.5 p-6 {className}"
18
+ {...restProps}
19
+ >
20
+ {#if children}
21
+ {@render children()}
22
+ {/if}
23
+ </header>
24
+