@optilogic/core 1.0.0-beta.1 → 1.0.0-beta.10

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.
@@ -1,28 +1,173 @@
1
1
  import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
2
3
 
3
4
  import { cn } from "../utils/cn";
5
+ import { Checkbox } from "./checkbox";
4
6
 
5
- export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {}
7
+ // =============================================================================
8
+ // Card Variants
9
+ // =============================================================================
6
10
 
7
11
  /**
8
- * Card - Container component for grouped content
12
+ * Card variant styles using class-variance-authority.
13
+ * Provides size, hover effects, and interactive styling options.
14
+ */
15
+ const cardVariants = cva(
16
+ "rounded-xl border bg-card text-card-foreground shadow",
17
+ {
18
+ variants: {
19
+ /**
20
+ * Card width size presets
21
+ */
22
+ size: {
23
+ auto: "",
24
+ sm: "w-64",
25
+ md: "w-80",
26
+ lg: "w-96",
27
+ xl: "w-[28rem]",
28
+ full: "w-full",
29
+ },
30
+ /**
31
+ * Hover effect styles
32
+ */
33
+ hover: {
34
+ none: "",
35
+ lift: "transition-all duration-200 hover:-translate-y-1 hover:shadow-lg",
36
+ glow: "transition-shadow duration-200 hover:shadow-lg hover:shadow-accent/20",
37
+ border: "transition-colors duration-200 hover:border-accent",
38
+ "border-success": "transition-colors duration-200 hover:border-success",
39
+ "border-warning": "transition-colors duration-200 hover:border-warning",
40
+ "border-destructive": "transition-colors duration-200 hover:border-destructive",
41
+ "border-muted": "transition-colors duration-200 hover:border-muted-foreground",
42
+ scale: "transition-transform duration-200 hover:scale-[1.02]",
43
+ },
44
+ /**
45
+ * Whether the card is interactive (clickable)
46
+ */
47
+ interactive: {
48
+ true: "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
49
+ false: "",
50
+ },
51
+ /**
52
+ * Card padding density
53
+ */
54
+ padding: {
55
+ none: "",
56
+ sm: "[&>*:not(img)]:p-3 [&>*:not(img)]:pt-3",
57
+ md: "",
58
+ lg: "[&>*:not(img)]:p-8 [&>*:not(img)]:pt-8",
59
+ },
60
+ },
61
+ defaultVariants: {
62
+ size: "auto",
63
+ hover: "none",
64
+ interactive: false,
65
+ padding: "md",
66
+ },
67
+ }
68
+ );
69
+
70
+ // =============================================================================
71
+ // Base Card Component
72
+ // =============================================================================
73
+
74
+ export interface CardProps
75
+ extends React.HTMLAttributes<HTMLDivElement>,
76
+ VariantProps<typeof cardVariants> {
77
+ /**
78
+ * If true, the card acts as a button and can be clicked.
79
+ * Adds keyboard accessibility and focus states.
80
+ */
81
+ asButton?: boolean;
82
+ /**
83
+ * Custom CSS class for hover border color.
84
+ * Use Tailwind classes like "hover:border-blue-500" or custom CSS variables.
85
+ * This overrides the hover variant's border color if specified.
86
+ */
87
+ hoverBorderClass?: string;
88
+ }
89
+
90
+ /**
91
+ * Card - Versatile container component for grouped content
92
+ *
93
+ * A flexible card component supporting multiple sizes, hover effects,
94
+ * and interactive states. Use with CardHeader, CardContent, CardFooter,
95
+ * and other sub-components.
96
+ *
97
+ * @example
98
+ * <Card size="md" hover="lift">
99
+ * <CardHeader>
100
+ * <CardTitle>Title</CardTitle>
101
+ * </CardHeader>
102
+ * <CardContent>Content here</CardContent>
103
+ * </Card>
104
+ *
105
+ * @example
106
+ * <Card interactive hover="border" onClick={() => navigate('/item')}>
107
+ * <CardContent>Clickable card</CardContent>
108
+ * </Card>
9
109
  */
10
110
  const Card = React.forwardRef<HTMLDivElement, CardProps>(
11
- ({ className, ...props }, ref) => (
12
- <div
13
- ref={ref}
14
- className={cn(
15
- "rounded-xl border bg-card text-card-foreground shadow",
16
- className
17
- )}
18
- {...props}
19
- />
20
- )
111
+ (
112
+ {
113
+ className,
114
+ size,
115
+ hover,
116
+ interactive,
117
+ padding,
118
+ asButton,
119
+ hoverBorderClass,
120
+ onClick,
121
+ onKeyDown,
122
+ ...props
123
+ },
124
+ ref
125
+ ) => {
126
+ // If asButton is true, make the card interactive
127
+ const isInteractive = interactive || asButton || !!onClick;
128
+
129
+ const handleKeyDown = React.useCallback(
130
+ (e: React.KeyboardEvent<HTMLDivElement>) => {
131
+ if (isInteractive && (e.key === "Enter" || e.key === " ")) {
132
+ e.preventDefault();
133
+ onClick?.(e as unknown as React.MouseEvent<HTMLDivElement>);
134
+ }
135
+ onKeyDown?.(e);
136
+ },
137
+ [isInteractive, onClick, onKeyDown]
138
+ );
139
+
140
+ return (
141
+ <div
142
+ ref={ref}
143
+ role={isInteractive ? "button" : undefined}
144
+ tabIndex={isInteractive ? 0 : undefined}
145
+ className={cn(
146
+ cardVariants({ size, hover, interactive: isInteractive, padding }),
147
+ "group relative",
148
+ // Custom hover border color overrides variant if provided
149
+ hoverBorderClass && "transition-colors duration-200",
150
+ hoverBorderClass,
151
+ className
152
+ )}
153
+ onClick={onClick}
154
+ onKeyDown={handleKeyDown}
155
+ {...props}
156
+ />
157
+ );
158
+ }
21
159
  );
22
160
  Card.displayName = "Card";
23
161
 
162
+ // =============================================================================
163
+ // Card Sub-Components
164
+ // =============================================================================
165
+
24
166
  export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {}
25
167
 
168
+ /**
169
+ * CardHeader - Header section of a card
170
+ */
26
171
  const CardHeader = React.forwardRef<HTMLDivElement, CardHeaderProps>(
27
172
  ({ className, ...props }, ref) => (
28
173
  <div
@@ -36,6 +181,9 @@ CardHeader.displayName = "CardHeader";
36
181
 
37
182
  export interface CardTitleProps extends React.HTMLAttributes<HTMLDivElement> {}
38
183
 
184
+ /**
185
+ * CardTitle - Title text for card header
186
+ */
39
187
  const CardTitle = React.forwardRef<HTMLDivElement, CardTitleProps>(
40
188
  ({ className, ...props }, ref) => (
41
189
  <div
@@ -50,6 +198,9 @@ CardTitle.displayName = "CardTitle";
50
198
  export interface CardDescriptionProps
51
199
  extends React.HTMLAttributes<HTMLDivElement> {}
52
200
 
201
+ /**
202
+ * CardDescription - Descriptive text for card header
203
+ */
53
204
  const CardDescription = React.forwardRef<HTMLDivElement, CardDescriptionProps>(
54
205
  ({ className, ...props }, ref) => (
55
206
  <div
@@ -64,6 +215,9 @@ CardDescription.displayName = "CardDescription";
64
215
  export interface CardContentProps
65
216
  extends React.HTMLAttributes<HTMLDivElement> {}
66
217
 
218
+ /**
219
+ * CardContent - Main content area of a card
220
+ */
67
221
  const CardContent = React.forwardRef<HTMLDivElement, CardContentProps>(
68
222
  ({ className, ...props }, ref) => (
69
223
  <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
@@ -73,6 +227,9 @@ CardContent.displayName = "CardContent";
73
227
 
74
228
  export interface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> {}
75
229
 
230
+ /**
231
+ * CardFooter - Footer section of a card
232
+ */
76
233
  const CardFooter = React.forwardRef<HTMLDivElement, CardFooterProps>(
77
234
  ({ className, ...props }, ref) => (
78
235
  <div
@@ -84,11 +241,498 @@ const CardFooter = React.forwardRef<HTMLDivElement, CardFooterProps>(
84
241
  );
85
242
  CardFooter.displayName = "CardFooter";
86
243
 
244
+ // =============================================================================
245
+ // CardImage Component
246
+ // =============================================================================
247
+
248
+ const cardImageVariants = cva("w-full object-cover", {
249
+ variants: {
250
+ /**
251
+ * Image aspect ratio
252
+ */
253
+ aspectRatio: {
254
+ auto: "",
255
+ square: "aspect-square",
256
+ video: "aspect-video",
257
+ wide: "aspect-[2/1]",
258
+ portrait: "aspect-[3/4]",
259
+ },
260
+ /**
261
+ * Image position in the card
262
+ */
263
+ position: {
264
+ top: "rounded-t-xl",
265
+ bottom: "rounded-b-xl",
266
+ fill: "rounded-xl",
267
+ },
268
+ },
269
+ defaultVariants: {
270
+ aspectRatio: "video",
271
+ position: "top",
272
+ },
273
+ });
274
+
275
+ export interface CardImageProps
276
+ extends React.ImgHTMLAttributes<HTMLImageElement>,
277
+ VariantProps<typeof cardImageVariants> {
278
+ /**
279
+ * Fallback content to display if image fails to load
280
+ */
281
+ fallback?: React.ReactNode;
282
+ }
283
+
284
+ /**
285
+ * CardImage - Image/media component for cards
286
+ *
287
+ * Displays images with configurable aspect ratios and positions.
288
+ * Handles loading states and fallbacks.
289
+ *
290
+ * @example
291
+ * <Card>
292
+ * <CardImage src="/photo.jpg" alt="Photo" aspectRatio="video" />
293
+ * <CardContent>Caption</CardContent>
294
+ * </Card>
295
+ */
296
+ const CardImage = React.forwardRef<HTMLImageElement, CardImageProps>(
297
+ (
298
+ { className, aspectRatio, position, fallback, alt, src, onError, ...props },
299
+ ref
300
+ ) => {
301
+ const [hasError, setHasError] = React.useState(false);
302
+
303
+ const handleError = React.useCallback(
304
+ (e: React.SyntheticEvent<HTMLImageElement>) => {
305
+ setHasError(true);
306
+ onError?.(e);
307
+ },
308
+ [onError]
309
+ );
310
+
311
+ // Reset error state when src changes
312
+ React.useEffect(() => {
313
+ setHasError(false);
314
+ }, [src]);
315
+
316
+ if (hasError && fallback) {
317
+ return (
318
+ <div
319
+ className={cn(
320
+ "flex items-center justify-center bg-muted",
321
+ cardImageVariants({ aspectRatio, position }),
322
+ className
323
+ )}
324
+ >
325
+ {fallback}
326
+ </div>
327
+ );
328
+ }
329
+
330
+ return (
331
+ <img
332
+ ref={ref}
333
+ src={src}
334
+ alt={alt}
335
+ className={cn(cardImageVariants({ aspectRatio, position }), className)}
336
+ onError={handleError}
337
+ {...props}
338
+ />
339
+ );
340
+ }
341
+ );
342
+ CardImage.displayName = "CardImage";
343
+
344
+ // =============================================================================
345
+ // CardActions Component
346
+ // =============================================================================
347
+
348
+ const cardActionsVariants = cva(
349
+ "absolute flex items-center gap-1 transition-opacity duration-200 z-10",
350
+ {
351
+ variants: {
352
+ /**
353
+ * When to show the actions
354
+ */
355
+ showOn: {
356
+ hover: "opacity-0 group-hover:opacity-100",
357
+ always: "opacity-100",
358
+ },
359
+ /**
360
+ * Position of the actions overlay
361
+ */
362
+ position: {
363
+ "top-right": "top-2 right-2",
364
+ "top-left": "top-2 left-2",
365
+ "bottom-right": "bottom-2 right-2",
366
+ "bottom-left": "bottom-2 left-2",
367
+ },
368
+ /**
369
+ * Visual style of the actions container
370
+ */
371
+ variant: {
372
+ /** Solid background with backdrop blur */
373
+ floating: "bg-background/90 backdrop-blur-sm rounded-md shadow-sm p-1",
374
+ /** No background, just spacing */
375
+ ghost: "p-1",
376
+ /** Muted bar background */
377
+ bar: "bg-muted/80 backdrop-blur-sm rounded-md p-1.5",
378
+ /** No background on container, icons get background on hover */
379
+ "icon-hover":
380
+ "p-0 [&>button]:bg-transparent [&>button]:hover:bg-background/90 [&>button]:hover:shadow-sm [&>button]:transition-all",
381
+ },
382
+ },
383
+ defaultVariants: {
384
+ showOn: "hover",
385
+ position: "top-right",
386
+ variant: "floating",
387
+ },
388
+ }
389
+ );
390
+
391
+ export interface CardActionsProps
392
+ extends React.HTMLAttributes<HTMLDivElement>,
393
+ VariantProps<typeof cardActionsVariants> {}
394
+
395
+ /**
396
+ * CardActions - Hover-reveal action buttons for cards
397
+ *
398
+ * Place action buttons that appear on hover. Position can be customized.
399
+ * The parent Card automatically gets the 'group' class for hover detection.
400
+ *
401
+ * @example
402
+ * <Card hover="lift">
403
+ * <CardActions>
404
+ * <IconButton size="sm" variant="ghost"><Edit /></IconButton>
405
+ * <IconButton size="sm" variant="ghost"><Trash /></IconButton>
406
+ * </CardActions>
407
+ * <CardContent>Content</CardContent>
408
+ * </Card>
409
+ *
410
+ * @example
411
+ * // With floating style (default)
412
+ * <CardActions variant="floating">...</CardActions>
413
+ *
414
+ * @example
415
+ * // With bar style for image overlays
416
+ * <CardActions variant="bar" position="bottom-right">...</CardActions>
417
+ */
418
+ const CardActions = React.forwardRef<HTMLDivElement, CardActionsProps>(
419
+ ({ className, showOn, position, variant, ...props }, ref) => (
420
+ <div
421
+ ref={ref}
422
+ className={cn(cardActionsVariants({ showOn, position, variant }), className)}
423
+ // Prevent clicks from bubbling to parent card
424
+ onClick={(e) => e.stopPropagation()}
425
+ {...props}
426
+ />
427
+ )
428
+ );
429
+ CardActions.displayName = "CardActions";
430
+
431
+ // =============================================================================
432
+ // SelectableCard Component
433
+ // =============================================================================
434
+
435
+ export interface SelectableCardProps extends CardProps {
436
+ /**
437
+ * Whether the card is selected (controlled)
438
+ */
439
+ selected?: boolean;
440
+ /**
441
+ * Default selected state (uncontrolled)
442
+ */
443
+ defaultSelected?: boolean;
444
+ /**
445
+ * Callback when selection changes
446
+ */
447
+ onSelectedChange?: (selected: boolean) => void;
448
+ /**
449
+ * Whether to show a visible checkbox
450
+ */
451
+ showCheckbox?: boolean;
452
+ /**
453
+ * Position of the checkbox
454
+ */
455
+ checkboxPosition?: "top-left" | "top-right" | "inline-left";
456
+ /**
457
+ * Whether the card is disabled
458
+ */
459
+ disabled?: boolean;
460
+ }
461
+
462
+ /**
463
+ * SelectableCard - Card with selection state
464
+ *
465
+ * A card that can be selected/deselected. Supports controlled and uncontrolled modes.
466
+ * Can optionally display a checkbox indicator.
467
+ *
468
+ * @example
469
+ * <SelectableCard
470
+ * selected={isSelected}
471
+ * onSelectedChange={setIsSelected}
472
+ * showCheckbox
473
+ * >
474
+ * <CardContent>Selectable content</CardContent>
475
+ * </SelectableCard>
476
+ *
477
+ * @example
478
+ * // Multi-select list with inline checkbox
479
+ * {items.map(item => (
480
+ * <SelectableCard
481
+ * key={item.id}
482
+ * selected={selectedIds.includes(item.id)}
483
+ * onSelectedChange={(sel) => toggleSelection(item.id, sel)}
484
+ * showCheckbox
485
+ * checkboxPosition="inline-left"
486
+ * >
487
+ * <CardContent>{item.name}</CardContent>
488
+ * </SelectableCard>
489
+ * ))}
490
+ */
491
+ const SelectableCard = React.forwardRef<HTMLDivElement, SelectableCardProps>(
492
+ (
493
+ {
494
+ className,
495
+ selected: controlledSelected,
496
+ defaultSelected = false,
497
+ onSelectedChange,
498
+ showCheckbox = false,
499
+ checkboxPosition = "top-right",
500
+ disabled = false,
501
+ children,
502
+ onClick,
503
+ hover = "border",
504
+ ...props
505
+ },
506
+ ref
507
+ ) => {
508
+ // Support both controlled and uncontrolled modes
509
+ const [uncontrolledSelected, setUncontrolledSelected] =
510
+ React.useState(defaultSelected);
511
+ const isControlled = controlledSelected !== undefined;
512
+ const isSelected = isControlled ? controlledSelected : uncontrolledSelected;
513
+
514
+ const handleClick = React.useCallback(
515
+ (e: React.MouseEvent<HTMLDivElement>) => {
516
+ if (disabled) return;
517
+
518
+ const newSelected = !isSelected;
519
+ if (!isControlled) {
520
+ setUncontrolledSelected(newSelected);
521
+ }
522
+ onSelectedChange?.(newSelected);
523
+ onClick?.(e);
524
+ },
525
+ [disabled, isSelected, isControlled, onSelectedChange, onClick]
526
+ );
527
+
528
+ const handleCheckboxChange = React.useCallback(
529
+ (checked: boolean | "indeterminate") => {
530
+ if (disabled) return;
531
+
532
+ const newSelected = checked === true;
533
+ if (!isControlled) {
534
+ setUncontrolledSelected(newSelected);
535
+ }
536
+ onSelectedChange?.(newSelected);
537
+ },
538
+ [disabled, isControlled, onSelectedChange]
539
+ );
540
+
541
+ // Inline checkbox renders in a dedicated control bar
542
+ const isInline = checkboxPosition === "inline-left";
543
+
544
+ return (
545
+ <Card
546
+ ref={ref}
547
+ className={cn(
548
+ // Selection styling
549
+ isSelected && "ring-2 ring-accent border-accent",
550
+ disabled && "opacity-50 cursor-not-allowed",
551
+ className
552
+ )}
553
+ interactive={!disabled}
554
+ hover={disabled ? "none" : hover}
555
+ onClick={handleClick}
556
+ aria-selected={isSelected}
557
+ aria-disabled={disabled}
558
+ {...props}
559
+ >
560
+ {showCheckbox && isInline && (
561
+ <div
562
+ className="flex items-center gap-3 px-4 py-3 border-b border-border/50"
563
+ onClick={(e) => e.stopPropagation()}
564
+ >
565
+ <Checkbox
566
+ checked={isSelected}
567
+ onCheckedChange={handleCheckboxChange}
568
+ disabled={disabled}
569
+ />
570
+ <span className="text-xs text-muted-foreground">
571
+ {isSelected ? "Selected" : "Click to select"}
572
+ </span>
573
+ </div>
574
+ )}
575
+ {showCheckbox && !isInline && (
576
+ <div
577
+ className={cn(
578
+ "absolute z-10 p-1 bg-background/90 backdrop-blur-sm rounded-md",
579
+ checkboxPosition === "top-left" ? "top-2 left-2" : "top-2 right-2"
580
+ )}
581
+ onClick={(e) => e.stopPropagation()}
582
+ >
583
+ <Checkbox
584
+ checked={isSelected}
585
+ onCheckedChange={handleCheckboxChange}
586
+ disabled={disabled}
587
+ />
588
+ </div>
589
+ )}
590
+ {children}
591
+ </Card>
592
+ );
593
+ }
594
+ );
595
+ SelectableCard.displayName = "SelectableCard";
596
+
597
+ // =============================================================================
598
+ // Layout Components
599
+ // =============================================================================
600
+
601
+ const cardGridVariants = cva("grid", {
602
+ variants: {
603
+ /**
604
+ * Number of columns
605
+ */
606
+ columns: {
607
+ 1: "grid-cols-1",
608
+ 2: "grid-cols-1 sm:grid-cols-2",
609
+ 3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
610
+ 4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
611
+ auto: "grid-cols-[repeat(auto-fill,minmax(280px,1fr))]",
612
+ },
613
+ /**
614
+ * Gap between cards
615
+ */
616
+ gap: {
617
+ none: "gap-0",
618
+ sm: "gap-3",
619
+ md: "gap-4",
620
+ lg: "gap-6",
621
+ xl: "gap-8",
622
+ },
623
+ },
624
+ defaultVariants: {
625
+ columns: "auto",
626
+ gap: "md",
627
+ },
628
+ });
629
+
630
+ export interface CardGridProps
631
+ extends React.HTMLAttributes<HTMLDivElement>,
632
+ VariantProps<typeof cardGridVariants> {}
633
+
634
+ /**
635
+ * CardGrid - Responsive grid layout for cards
636
+ *
637
+ * Arranges cards in a responsive grid with configurable columns and gaps.
638
+ *
639
+ * @example
640
+ * <CardGrid columns={3} gap="lg">
641
+ * <Card>...</Card>
642
+ * <Card>...</Card>
643
+ * <Card>...</Card>
644
+ * </CardGrid>
645
+ *
646
+ * @example
647
+ * // Auto-fill columns based on available space
648
+ * <CardGrid columns="auto">
649
+ * {items.map(item => <Card key={item.id}>...</Card>)}
650
+ * </CardGrid>
651
+ */
652
+ const CardGrid = React.forwardRef<HTMLDivElement, CardGridProps>(
653
+ ({ className, columns, gap, ...props }, ref) => (
654
+ <div
655
+ ref={ref}
656
+ className={cn(cardGridVariants({ columns, gap }), className)}
657
+ {...props}
658
+ />
659
+ )
660
+ );
661
+ CardGrid.displayName = "CardGrid";
662
+
663
+ const cardListVariants = cva("flex flex-col", {
664
+ variants: {
665
+ /**
666
+ * Gap between cards
667
+ */
668
+ gap: {
669
+ none: "gap-0",
670
+ sm: "gap-2",
671
+ md: "gap-4",
672
+ lg: "gap-6",
673
+ },
674
+ /**
675
+ * Whether to show dividers between cards
676
+ */
677
+ divided: {
678
+ true: "[&>*:not(:last-child)]:border-b [&>*:not(:last-child)]:pb-4 [&>*:not(:last-child)]:rounded-b-none",
679
+ false: "",
680
+ },
681
+ },
682
+ defaultVariants: {
683
+ gap: "md",
684
+ divided: false,
685
+ },
686
+ });
687
+
688
+ export interface CardListProps
689
+ extends React.HTMLAttributes<HTMLDivElement>,
690
+ VariantProps<typeof cardListVariants> {}
691
+
692
+ /**
693
+ * CardList - Vertical list layout for cards
694
+ *
695
+ * Arranges cards in a vertical list with optional dividers.
696
+ *
697
+ * @example
698
+ * <CardList gap="sm" divided>
699
+ * <Card>...</Card>
700
+ * <Card>...</Card>
701
+ * </CardList>
702
+ */
703
+ const CardList = React.forwardRef<HTMLDivElement, CardListProps>(
704
+ ({ className, gap, divided, ...props }, ref) => (
705
+ <div
706
+ ref={ref}
707
+ className={cn(cardListVariants({ gap, divided }), className)}
708
+ {...props}
709
+ />
710
+ )
711
+ );
712
+ CardList.displayName = "CardList";
713
+
714
+ // =============================================================================
715
+ // Exports
716
+ // =============================================================================
717
+
87
718
  export {
719
+ // Core components
88
720
  Card,
89
721
  CardHeader,
90
722
  CardFooter,
91
723
  CardTitle,
92
724
  CardDescription,
93
725
  CardContent,
726
+ // New components
727
+ CardImage,
728
+ CardActions,
729
+ SelectableCard,
730
+ CardGrid,
731
+ CardList,
732
+ // Variants (for advanced customization)
733
+ cardVariants,
734
+ cardImageVariants,
735
+ cardActionsVariants,
736
+ cardGridVariants,
737
+ cardListVariants,
94
738
  };