@donotdev/components 0.0.3 → 0.0.5

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.
@@ -59,6 +59,11 @@ interface CallToActionOwnProps {
59
59
  * @default 'center'
60
60
  */
61
61
  align?: 'start' | 'center' | 'end';
62
+ /**
63
+ * Tone system for background colors (matches Section component)
64
+ * @default 'ghost'
65
+ */
66
+ tone?: 'ghost' | 'base' | 'muted' | 'elevated' | 'contrast' | 'accent';
62
67
  /** Additional children (for custom content) */
63
68
  children?: ReactNode;
64
69
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/CallToAction/index.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAKf,OAAO,oBAAoB,CAAC;AAE5B;;GAEG;AACH,UAAU,oBAAoB;IAC5B;;;;;;OAMG;IACH,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,KAAK,CAAC;IAEjC,iBAAiB;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,qEAAqE;IACrE,aAAa,CAAC,EAAE,SAAS,CAAC;IAE1B,uEAAuE;IACvE,eAAe,CAAC,EAAE,SAAS,CAAC;IAE5B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IAEnC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,WAAW,GAAG,SAAS,IAC7D,oBAAoB,GAClB,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,MAAM,oBAAoB,CAAC,CAAC;AAE/D,QAAA,MAAM,YAAY,mIAiDjB,CAAC;AAIF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/CallToAction/index.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAKf,OAAO,oBAAoB,CAAC;AAE5B;;GAEG;AACH,UAAU,oBAAoB;IAC5B;;;;;;OAMG;IACH,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,KAAK,CAAC;IAEjC,iBAAiB;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,qEAAqE;IACrE,aAAa,CAAC,EAAE,SAAS,CAAC;IAE1B,uEAAuE;IACvE,eAAe,CAAC,EAAE,SAAS,CAAC;IAE5B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAC;IAEnC;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC;IAEvE,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,WAAW,GAAG,SAAS,IAC7D,oBAAoB,GAClB,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,MAAM,oBAAoB,CAAC,CAAC;AAE/D,QAAA,MAAM,YAAY,mIA+CjB,CAAC;AAIF,eAAe,YAAY,CAAC"}
@@ -38,14 +38,15 @@ import { createElement, forwardRef, } from 'react';
38
38
  import { cn } from '../../utils/helpers';
39
39
  import Text, { TEXT_VARIANT } from '../Text';
40
40
  import './CallToAction.css';
41
- const CallToAction = forwardRef(function CallToAction({ as = 'section', title, subtitle, primaryAction, secondaryAction, align = 'center', children, className, ...props }, ref) {
41
+ const CallToAction = forwardRef(function CallToAction({ as = 'section', title, subtitle, primaryAction, secondaryAction, align = 'center', tone = 'ghost', children, className, ...props }, ref) {
42
42
  const Component = as;
43
43
  return createElement(Component, {
44
44
  ref,
45
45
  'aria-label': props['aria-label'] || 'Call to Action',
46
46
  className: cn('dndev-cta', className),
47
+ 'data-tone': tone,
47
48
  ...props,
48
- }, _jsxs("div", { className: "dndev-cta-content", "data-text-align": align, children: [title && (_jsx("h2", { className: "dndev-text-base", "data-level": "h2", children: title })), subtitle && (_jsx(Text, { as: "p", variant: TEXT_VARIANT.MUTED, level: "h3", children: subtitle })), (primaryAction || secondaryAction) && (_jsxs("div", { className: "dndev-cta-actions", children: [primaryAction, secondaryAction] })), children] }));
49
+ }, _jsxs("div", { className: "dndev-cta-content", "data-text-align": align, children: [title && (_jsx(Text, { as: "h2", style: { fontSize: 'var(--font-size-2xl)' }, children: title })), subtitle && _jsx(Text, { as: "h4", children: subtitle }), (primaryAction || secondaryAction) && (_jsxs("div", { className: "dndev-cta-actions", children: [primaryAction, secondaryAction] })), children] }));
49
50
  });
50
51
  CallToAction.displayName = 'CallToAction';
51
52
  export default CallToAction;
@@ -10,6 +10,51 @@ import { type VariantProps } from 'class-variance-authority';
10
10
  import { surfaceVariants } from '../../utils/variants';
11
11
  import type { IconType } from '../../types';
12
12
  import type { HTMLAttributes, ReactNode, Ref } from 'react';
13
+ /**
14
+ * Unified content type for all card components.
15
+ * - `string` → renders as Text
16
+ * - `string[]` → renders as List (consumer controls icons via List directly)
17
+ * - `ReactNode` → passthrough (full control)
18
+ */
19
+ export type CardContent = string | string[] | ReactNode;
20
+ /**
21
+ * Converts CardContent to ReactNode.
22
+ * Use this when rendering CardContent outside of Card component.
23
+ *
24
+ * @param content - CardContent to convert
25
+ * @returns ReactNode ready for rendering
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * import { renderCardContent, type CardContent } from '@donotdev/components';
30
+ *
31
+ * const MyComponent = ({ content }: { content: CardContent }) => (
32
+ * <div>{renderCardContent(content)}</div>
33
+ * );
34
+ * ```
35
+ */
36
+ export declare function renderCardContent(content: CardContent | undefined): ReactNode;
37
+ /**
38
+ * Renders card header with icon, title, and subtitle.
39
+ * Groups icon+title+subtitle with no gap (trusts lineHeight).
40
+ *
41
+ * @param icon - Optional icon to display inline with title
42
+ * @param title - Optional title (string or ReactNode for Trans)
43
+ * @param subtitle - Optional subtitle (string or ReactNode for Trans)
44
+ * @returns ReactNode ready for rendering
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * import { renderCardHeader } from '@donotdev/components';
49
+ *
50
+ * // Plain string
51
+ * const header = renderCardHeader(Zap, 'Lightning Fast', 'Build in minutes');
52
+ *
53
+ * // With Trans for rich text
54
+ * const richHeader = renderCardHeader(Zap, <Trans ns="home" i18nKey="title" />);
55
+ * ```
56
+ */
57
+ export declare function renderCardHeader(icon?: IconType, title?: string | ReactNode, subtitle?: string | ReactNode): ReactNode;
13
58
  /**
14
59
  * Card variant constants - re-exported from shared SURFACE_VARIANT
15
60
  * Matches .dndev-surface CSS class
@@ -36,20 +81,31 @@ export interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'
36
81
  * @default false
37
82
  */
38
83
  asChild?: boolean;
39
- /** Optional icon to display next to the title */
84
+ /** Optional icon to display inline with title */
40
85
  icon?: IconType;
41
- /** Main title of the card */
86
+ /** Main title (string or ReactNode for Trans rich text) */
42
87
  title?: string | ReactNode;
43
- /** Subtitle displayed below the title */
88
+ /** Subtitle displayed below the title (string or ReactNode for Trans) */
44
89
  subtitle?: string | ReactNode;
45
- /** Main content of the card */
46
- content?: string | ReactNode;
90
+ /**
91
+ * Main content - string, string[], or ReactNode.
92
+ * - string → renders as Text
93
+ * - string[] → renders as List
94
+ * - ReactNode → passthrough
95
+ */
96
+ content?: CardContent;
47
97
  /** Footer content */
48
98
  footer?: ReactNode;
49
99
  /** Tooltip text */
50
100
  tooltip?: string;
51
101
  /** Click handler */
52
102
  onClick?: () => void;
103
+ /**
104
+ * Whether the card is clickable (enables hover styles).
105
+ * Set to true when wrapped in Link or when onClick is provided.
106
+ * @default false (auto-detected from onClick)
107
+ */
108
+ clickable?: boolean;
53
109
  /**
54
110
  * Whether the card has elevation/shadow.
55
111
  * @default false
@@ -77,6 +133,6 @@ export interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'
77
133
  * @param {CardProps} props - The props for the card
78
134
  * @returns {JSX.Element} The rendered card
79
135
  */
80
- declare const Card: ({ className, variant, asChild, icon, title, subtitle, content, footer, children, tooltip, onClick, elevated, style, ref, ...props }: CardProps) => import("react/jsx-runtime").JSX.Element;
136
+ declare const Card: ({ className, variant, asChild, icon, title, subtitle, content, footer, children, tooltip, onClick, clickable, elevated, style, ref, ...props }: CardProps) => import("react/jsx-runtime").JSX.Element;
81
137
  export default Card;
82
138
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/Card/index.tsx"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAKvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAE5D;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;CAAkB,CAAC;AAE5C;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC,SAAS,CAAC,CAAC;AAE1E,MAAM,WAAW,SACf,SACE,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC,EACzD,YAAY,CAAC,OAAO,eAAe,CAAC;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,qBAAqB;IACrB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,mBAAmB;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sCAAsC;IACtC,GAAG,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,QAAA,MAAM,IAAI,GAAI,qIAgBX,SAAS,4CAgEX,CAAC;AAEF,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/Card/index.tsx"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAOvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;AAExD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,CA0B7E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,CAAC,EAAE,QAAQ,EACf,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,EAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,GAC5B,SAAS,CA0BX;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;CAAkB,CAAC;AAE5C;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC,SAAS,CAAC,CAAC;AAE1E,MAAM,WAAW,SACf,SACE,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC,EACzD,YAAY,CAAC,OAAO,eAAe,CAAC;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,qBAAqB;IACrB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,mBAAmB;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sCAAsC;IACtC,GAAG,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,QAAA,MAAM,IAAI,GAAI,gJAiBX,SAAS,4CA0CX,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -12,9 +12,66 @@ import {} from 'class-variance-authority';
12
12
  import { SURFACE_VARIANT } from '../../utils/constants';
13
13
  import { cn, getVariantDataAttrs } from '../../utils/helpers';
14
14
  import { surfaceVariants } from '../../utils/variants';
15
- import IconBox from '../Icons/IconBox';
15
+ import Icon from '../Icons/Icon';
16
+ import List from '../List';
16
17
  import Slot from '../Slot';
17
18
  import Stack from '../Stack';
19
+ import Text from '../Text';
20
+ /**
21
+ * Converts CardContent to ReactNode.
22
+ * Use this when rendering CardContent outside of Card component.
23
+ *
24
+ * @param content - CardContent to convert
25
+ * @returns ReactNode ready for rendering
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * import { renderCardContent, type CardContent } from '@donotdev/components';
30
+ *
31
+ * const MyComponent = ({ content }: { content: CardContent }) => (
32
+ * <div>{renderCardContent(content)}</div>
33
+ * );
34
+ * ```
35
+ */
36
+ export function renderCardContent(content) {
37
+ if (!content)
38
+ return null;
39
+ if (typeof content === 'string') {
40
+ return (_jsx(Text, { as: "p", level: "small", children: content }));
41
+ }
42
+ if (Array.isArray(content)) {
43
+ if (content.length === 1 && typeof content[0] === 'string') {
44
+ return (_jsx(Text, { as: "p", level: "small", children: content[0] }));
45
+ }
46
+ return (_jsx(List, { items: content, gap: "tight", style: { listStyle: 'none', paddingInlineStart: 0 } }));
47
+ }
48
+ return content;
49
+ }
50
+ /**
51
+ * Renders card header with icon, title, and subtitle.
52
+ * Groups icon+title+subtitle with no gap (trusts lineHeight).
53
+ *
54
+ * @param icon - Optional icon to display inline with title
55
+ * @param title - Optional title (string or ReactNode for Trans)
56
+ * @param subtitle - Optional subtitle (string or ReactNode for Trans)
57
+ * @returns ReactNode ready for rendering
58
+ *
59
+ * @example
60
+ * ```tsx
61
+ * import { renderCardHeader } from '@donotdev/components';
62
+ *
63
+ * // Plain string
64
+ * const header = renderCardHeader(Zap, 'Lightning Fast', 'Build in minutes');
65
+ *
66
+ * // With Trans for rich text
67
+ * const richHeader = renderCardHeader(Zap, <Trans ns="home" i18nKey="title" />);
68
+ * ```
69
+ */
70
+ export function renderCardHeader(icon, title, subtitle) {
71
+ if (!icon && !title && !subtitle)
72
+ return null;
73
+ return (_jsxs("div", { className: "dndev-card-header", children: [icon && title ? (_jsxs(Stack, { direction: "row", align: "center", gap: "tight", children: [_jsx(Icon, { icon: icon, ariaHidden: true }), _jsx(Text, { as: "h3", className: "dndev-card-title", children: title })] })) : title ? (_jsx(Text, { as: "h3", className: "dndev-card-title", children: title })) : icon ? (_jsx(Icon, { icon: icon, ariaHidden: true })) : null, subtitle && (_jsx(Text, { as: "h4", className: "dndev-card-subtitle", children: subtitle }))] }));
74
+ }
18
75
  /**
19
76
  * Card variant constants - re-exported from shared SURFACE_VARIANT
20
77
  * Matches .dndev-surface CSS class
@@ -39,36 +96,25 @@ export const CARD_VARIANT = SURFACE_VARIANT;
39
96
  * @param {CardProps} props - The props for the card
40
97
  * @returns {JSX.Element} The rendered card
41
98
  */
42
- const Card = ({ className, variant, asChild = false, icon, title, subtitle, content, footer, children, tooltip, onClick, elevated, style, ref, ...props }) => {
99
+ const Card = ({ className, variant, asChild = false, icon, title, subtitle, content, footer, children, tooltip, onClick, clickable, elevated, style, ref, ...props }) => {
43
100
  const Comp = asChild ? Slot : 'div';
44
101
  const variantAttrs = getVariantDataAttrs({ variant });
102
+ const isClickable = clickable !== undefined ? clickable : !!onClick;
45
103
  const cardAttrs = {
46
104
  ...variantAttrs,
47
105
  'data-role': 'card',
48
106
  ...(elevated !== undefined && {
49
107
  'data-elevated': elevated ? 'true' : 'false',
50
108
  }),
51
- ...(onClick && { 'data-clickable': 'true' }),
52
- };
53
- const hasContent = content || children;
54
- const getGridTemplateRows = () => {
55
- const rows = [];
56
- if (title || icon)
57
- rows.push('auto');
58
- if (subtitle)
59
- rows.push('auto');
60
- if (hasContent)
61
- rows.push('1fr');
62
- if (footer)
63
- rows.push('auto');
64
- return rows.join(' ') || '1fr';
109
+ ...(isClickable && { 'data-clickable': 'true' }),
65
110
  };
66
- // Auto-merge icon + title when both provided (only for string titles)
67
- const titleContent = icon && title && typeof title === 'string' ? (_jsxs(Stack, { direction: "row", align: "center", gap: "medium", children: [_jsx(IconBox, { icon: icon }), _jsx("span", { children: title })] })) : (title);
111
+ const header = renderCardHeader(icon, title, subtitle);
112
+ const contentNode = renderCardContent(content);
113
+ const hasContent = contentNode || children;
68
114
  return (_jsxs(Comp, { ref: ref, className: cn('dndev-card', surfaceVariants({ variant }), className), style: {
69
- gridTemplateRows: getGridTemplateRows(),
70
115
  width: '100%',
116
+ height: '100%',
71
117
  ...style,
72
- }, ...cardAttrs, onClick: onClick, title: tooltip, ...props, children: [titleContent && _jsx("div", { className: "dndev-card-title", children: titleContent }), subtitle && (_jsx(Stack, { direction: "row", align: "center", className: "dndev-card-subtitle", children: subtitle })), hasContent && (_jsxs(Stack, { gap: "medium", children: [content, children] })), footer && _jsx("div", { children: footer })] }));
118
+ }, ...cardAttrs, onClick: onClick, title: tooltip, ...props, children: [header, hasContent && (_jsxs("div", { children: [contentNode, children] })), footer && _jsx("div", { children: footer })] }));
73
119
  };
74
120
  export default Card;
@@ -1,7 +1,8 @@
1
1
  import type { ReactNode, HTMLAttributes } from 'react';
2
2
  import { type VariantProps } from 'class-variance-authority';
3
3
  import { surfaceVariants } from '../../utils/variants';
4
- import { type GapVariant } from '../List';
4
+ import type { CardContent } from '../Card';
5
+ import type { IconType } from '../../types';
5
6
  import './DualCard.css';
6
7
  export type DualCardVariant = VariantProps<typeof surfaceVariants>['variant'];
7
8
  export interface DualCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'content'>, VariantProps<typeof surfaceVariants> {
@@ -12,37 +13,32 @@ export interface DualCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'tit
12
13
  elevated?: boolean;
13
14
  /** Click handler */
14
15
  onClick?: () => void;
16
+ /** Left side icon */
17
+ leftIcon?: IconType;
15
18
  /** Left side title */
16
- leftTitle?: string | ReactNode;
19
+ leftTitle?: string;
17
20
  /** Left side subtitle */
18
- leftSubtitle?: string | ReactNode;
19
- /** Left side content - same as FeatureCard: string, array, or ReactNode */
20
- leftContent?: string | (string | ReactNode)[] | ReactNode;
21
+ leftSubtitle?: string;
22
+ /**
23
+ * Left side content - string, string[], or ReactNode.
24
+ * For lists with icons, pass `<List icon={...} items={...} />` directly.
25
+ */
26
+ leftContent?: CardContent;
21
27
  /** Left side CTA (button, link, etc.) */
22
28
  leftCTA?: ReactNode;
29
+ /** Right side icon */
30
+ rightIcon?: IconType;
23
31
  /** Right side title */
24
- rightTitle?: string | ReactNode;
32
+ rightTitle?: string;
25
33
  /** Right side subtitle */
26
- rightSubtitle?: string | ReactNode;
27
- /** Right side content - same as FeatureCard: string, array, or ReactNode */
28
- rightContent?: string | (string | ReactNode)[] | ReactNode;
29
- /** Right side CTA (button, link, etc.) */
30
- rightCTA?: ReactNode;
31
- /**
32
- * List density for content rendering (page-level preset)
33
- * @default 'default'
34
- */
35
- listDensity?: 'dense' | 'narrow' | 'default' | 'expressive';
34
+ rightSubtitle?: string;
36
35
  /**
37
- * Gap between list items (component-level override)
38
- * @default 'none'
36
+ * Right side content - string, string[], or ReactNode.
37
+ * For lists with icons, pass `<List icon={...} items={...} />` directly.
39
38
  */
40
- listGap?: GapVariant;
41
- /**
42
- * Whether to show bullets in list
43
- * @default false
44
- */
45
- showBullets?: boolean;
39
+ rightContent?: CardContent;
40
+ /** Right side CTA (button, link, etc.) */
41
+ rightCTA?: ReactNode;
46
42
  }
47
43
  /**
48
44
  * DualCard - Two-column card with vertical separator
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/DualCard/index.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAIvD,OAAa,EAAE,KAAK,UAAU,EAAiB,MAAM,SAAS,CAAC;AAI/D,OAAO,gBAAgB,CAAC;AAExB,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC,SAAS,CAAC,CAAC;AAE9E,MAAM,WAAW,aACf,SACE,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC,EACzD,YAAY,CAAC,OAAO,eAAe,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,yBAAyB;IACzB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GAAG,SAAS,CAAC;IAC1D,yCAAyC;IACzC,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,0BAA0B;IAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GAAG,SAAS,CAAC;IAC3D,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAC;IAC5D;;;OAGG;IACH,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,QAAA,MAAM,QAAQ,0GAoKb,CAAC;AAIF,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/DualCard/index.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAI7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAKvD,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,gBAAgB,CAAC;AAExB,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC,OAAO,eAAe,CAAC,CAAC,SAAS,CAAC,CAAC;AAE9E,MAAM,WAAW,aACf,SACE,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC,EACzD,YAAY,CAAC,OAAO,eAAe,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,yCAAyC;IACzC,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,sBAAsB;IACtB,SAAS,CAAC,EAAE,QAAQ,CAAC;IACrB,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,QAAA,MAAM,QAAQ,0GA4Eb,CAAC;AAIF,eAAe,QAAQ,CAAC"}
@@ -15,9 +15,7 @@ import { cn, getVariantDataAttrs } from '../../utils/helpers';
15
15
  import { surfaceVariants } from '../../utils/variants';
16
16
  import Stack from '../Stack';
17
17
  import Separator, { SEPARATOR_VARIANT } from '../Separator';
18
- import Text from '../Text';
19
- import List, {} from '../List';
20
- import { Check } from 'lucide-react';
18
+ import { renderCardContent, renderCardHeader } from '../Card';
21
19
  import './DualCard.css';
22
20
  /**
23
21
  * DualCard - Two-column card with vertical separator
@@ -42,49 +40,13 @@ import './DualCard.css';
42
40
  * />
43
41
  * ```
44
42
  */
45
- const DualCard = forwardRef(({ className, variant, elevated, onClick, leftTitle, leftSubtitle, leftContent, leftCTA, rightTitle, rightSubtitle, rightContent, rightCTA, style, listDensity = 'default', listGap = 'none', showBullets = false, ...props }, ref) => {
43
+ const DualCard = forwardRef(({ className, variant, elevated, onClick, leftIcon, leftTitle, leftSubtitle, leftContent, leftCTA, rightIcon, rightTitle, rightSubtitle, rightContent, rightCTA, style, ...props }, ref) => {
46
44
  const variantAttrs = getVariantDataAttrs({ variant });
47
- // Convert content to ReactNode (same logic as FeatureCard)
48
- // Uses List component for arrays, strips manual bullet characters
49
- const convertContent = (content) => {
50
- if (!content)
51
- return null;
52
- if (typeof content === 'string') {
53
- return (_jsx(Text, { as: "p", level: "body", children: content }));
54
- }
55
- if (Array.isArray(content)) {
56
- // Strip manual bullet characters (•) if present
57
- const cleanContent = content.map((item) => {
58
- if (typeof item === 'string') {
59
- return item.replace(/^[\s•\-\*]+/, '').trim();
60
- }
61
- return item;
62
- });
63
- // If single string item, render as plain text (not a list)
64
- // If multiple items or contains ReactNodes, use List component
65
- if (cleanContent.length === 1 && typeof cleanContent[0] === 'string') {
66
- return (_jsx(Text, { as: "p", level: "body", children: cleanContent[0] }));
67
- }
68
- // Convert to ListItem objects with checkmark icons when showBullets is true
69
- const listItems = showBullets
70
- ? cleanContent.map((item) => {
71
- if (typeof item === 'string') {
72
- return {
73
- icon: _jsx(Check, { size: 16 }),
74
- content: item,
75
- };
76
- }
77
- // If already a ListItem or ReactNode, keep as is
78
- return item;
79
- })
80
- : cleanContent;
81
- return (_jsx(List, { items: listItems, density: listDensity, gap: listGap, style: { listStyle: 'none' } }));
82
- }
83
- return content;
84
- };
85
- const leftContentNode = convertContent(leftContent);
86
- const rightContentNode = convertContent(rightContent);
87
- return (_jsx("div", { ref: ref, className: cn('dndev-dual-card', surfaceVariants({ variant }), className), "data-role": "card", "data-elevated": elevated !== undefined ? (elevated ? 'true' : 'false') : undefined, "data-clickable": onClick ? 'true' : undefined, onClick: onClick, style: style, ...variantAttrs, ...props, children: _jsxs("div", { className: "dndev-dual-card-container", children: [_jsxs("div", { className: "dndev-dual-card-left", children: [leftTitle && (_jsx(Text, { as: "h3", level: "h3", className: "dndev-dual-card-title", children: leftTitle })), leftSubtitle && (_jsx(Text, { as: "p", level: "body", variant: "muted", className: "dndev-dual-card-subtitle", children: leftSubtitle })), leftContentNode && (_jsx("div", { className: "dndev-dual-card-content", children: leftContentNode })), leftCTA && _jsx("div", { className: "dndev-dual-card-cta", children: leftCTA })] }), _jsx(Separator, { orientation: "vertical", variant: SEPARATOR_VARIANT.ACCENT, className: "dndev-dual-card-separator" }), _jsxs("div", { className: "dndev-dual-card-right", children: [rightTitle && (_jsx(Text, { as: "h3", level: "h3", className: "dndev-dual-card-title", children: rightTitle })), rightSubtitle && (_jsx(Text, { as: "p", level: "body", variant: "muted", className: "dndev-dual-card-subtitle", children: rightSubtitle })), rightContentNode && (_jsx("div", { className: "dndev-dual-card-content", children: rightContentNode })), rightCTA && _jsx("div", { className: "dndev-dual-card-cta", children: rightCTA })] })] }) }));
45
+ const leftHeader = renderCardHeader(leftIcon, leftTitle, leftSubtitle);
46
+ const leftContentNode = renderCardContent(leftContent);
47
+ const rightHeader = renderCardHeader(rightIcon, rightTitle, rightSubtitle);
48
+ const rightContentNode = renderCardContent(rightContent);
49
+ return (_jsx("div", { ref: ref, className: cn('dndev-dual-card', surfaceVariants({ variant }), className), "data-role": "card", "data-elevated": elevated !== undefined ? (elevated ? 'true' : 'false') : undefined, "data-clickable": onClick ? 'true' : undefined, onClick: onClick, style: style, ...variantAttrs, ...props, children: _jsxs("div", { className: "dndev-dual-card-container", children: [_jsxs("div", { className: "dndev-dual-card-left", children: [leftHeader, leftContentNode && (_jsx("div", { className: "dndev-dual-card-content", children: leftContentNode })), leftCTA && _jsx("div", { className: "dndev-dual-card-cta", children: leftCTA })] }), _jsx(Separator, { orientation: "vertical", variant: SEPARATOR_VARIANT.ACCENT, className: "dndev-dual-card-separator" }), _jsxs("div", { className: "dndev-dual-card-right", children: [rightHeader, rightContentNode && (_jsx("div", { className: "dndev-dual-card-content", children: rightContentNode })), rightCTA && _jsx("div", { className: "dndev-dual-card-cta", children: rightCTA })] })] }) }));
88
50
  });
89
51
  DualCard.displayName = 'DualCard';
90
52
  export default DualCard;
@@ -1,37 +1,43 @@
1
1
  /**
2
2
  * @fileoverview Grid component
3
- * @description Polymorphic CSS Grid layout primitive.
4
- * Replaces redundant `div className="dndev-grid ..."` usage.
3
+ * @description Polymorphic CSS Grid layout primitive with responsive columns.
5
4
  *
6
5
  * @example
7
6
  * ```tsx
8
- * // Simple 3-column grid (default div)
7
+ * // Fixed 3-column grid
9
8
  * <Grid cols={3} gap="medium">
10
9
  * <Card />
11
10
  * <Card />
12
11
  * <Card />
13
12
  * </Grid>
14
13
  *
15
- * // Semantic section with grid layout
16
- * <Grid as="section" cols={2} aria-label="Features">
17
- * <FeatureCard />
18
- * <FeatureCard />
14
+ * // Responsive grid: [mobile, tablet, laptop, desktop]
15
+ * // 1 col on mobile/tablet, 2 on laptop, 3 on desktop
16
+ * <Grid cols={[1, 1, 2, 3]} gap="medium">
17
+ * <Card />
18
+ * <Card />
19
+ * <Card />
19
20
  * </Grid>
20
21
  *
21
22
  * // Named grid areas
22
- * <Grid as="header" areas="left center right" templateColumns="1fr auto 1fr">
23
- * <GridArea name="left">{leftContent}</GridArea>
24
- * <GridArea name="center">{centerContent}</GridArea>
25
- * <GridArea name="right">{rightContent}</GridArea>
23
+ * <Grid areas="left center right" templateColumns="1fr auto 1fr">
24
+ * <GridArea name="left">{left}</GridArea>
25
+ * <GridArea name="center">{center}</GridArea>
26
+ * <GridArea name="right">{right}</GridArea>
26
27
  * </Grid>
27
28
  * ```
28
29
  *
29
- * @version 0.0.1
30
+ * @version 0.0.4
30
31
  * @since 0.0.1
31
32
  * @author AMBROISE PARK Consulting
32
33
  */
33
34
  import { type ElementType, type ComponentPropsWithRef, type ReactNode } from 'react';
34
35
  import './Grid.css';
36
+ /**
37
+ * Responsive columns array: [mobile, tablet, laptop, desktop]
38
+ * @example [1, 1, 2, 3] - 1 col on mobile/tablet, 2 on laptop, 3 on desktop
39
+ */
40
+ export type ResponsiveCols = [number, number, number, number];
35
41
  /**
36
42
  * Grid-specific props (layout directives)
37
43
  */
@@ -43,29 +49,15 @@ interface GridOwnProps {
43
49
  */
44
50
  as?: ElementType;
45
51
  /**
46
- * Number of columns
52
+ * Number of columns - fixed or responsive
53
+ * - number: fixed columns (same on all breakpoints)
54
+ * - [mobile, tablet, laptop, desktop]: responsive columns per breakpoint
47
55
  * @default 1
56
+ * @example 3 - always 3 columns
57
+ * @example [1, 1, 2, 3] - 1 col mobile/tablet, 2 laptop, 3 desktop
58
+ * @example [1, 2, 3, 3] - 1 mobile, 2 tablet, 3 laptop/desktop
48
59
  */
49
- cols?: number;
50
- /**
51
- * Minimum column width for all layouts
52
- * Controls when columns wrap automatically - CSS Grid wraps when container < (cols × minColWidth)
53
- * @example "250px", "20rem", "280px"
54
- * @default undefined (0px - allows columns to shrink to content size)
55
- */
56
- minColWidth?: string;
57
- /**
58
- * Use auto-fit instead of fixed column count
59
- * Automatically fits as many columns as possible based on minColWidth
60
- * @default false
61
- */
62
- autoFit?: boolean;
63
- /**
64
- * Use auto-fill instead of fixed column count
65
- * Automatically fills available space with columns based on minColWidth
66
- * @default false
67
- */
68
- autoFill?: boolean;
60
+ cols?: number | ResponsiveCols;
69
61
  /**
70
62
  * Spacing between items
71
63
  * @default 'medium'
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/Grid/index.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EAEf,MAAM,OAAO,CAAC;AAIf,OAAO,YAAY,CAAC;AAEpB;;GAEG;AACH,UAAU,YAAY;IACpB;;;;OAIG;IACH,EAAE,CAAC,EAAE,WAAW,CAAC;IAEjB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IAE5C;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;IAE/C;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;IAEjD;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,cAAc;IACd,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,WAAW,GAAG,KAAK,IAAI,YAAY,GACjE,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,MAAM,YAAY,CAAC,CAAC;AAErD;;;;;GAKG;AACH,QAAA,MAAM,IAAI,uHAiET,CAAC;AAIF,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,eAAe,IAAI,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/atomic/Grid/index.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EAEf,MAAM,OAAO,CAAC;AAIf,OAAO,YAAY,CAAC;AAEpB;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAE9D;;GAEG;AACH,UAAU,YAAY;IACpB;;;;OAIG;IACH,EAAE,CAAC,EAAE,WAAW,CAAC;IAEjB;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,cAAc,CAAC;IAE/B;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IAE5C;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;IAE/C;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;IAEjD;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,cAAc;IACd,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,WAAW,GAAG,KAAK,IAAI,YAAY,GACjE,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,MAAM,YAAY,CAAC,CAAC;AAErD;;;;;GAKG;AACH,QAAA,MAAM,IAAI,uHA2DT,CAAC;AAIF,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,eAAe,IAAI,CAAC"}
@@ -1,33 +1,34 @@
1
1
  // packages/components/src/atomic/Grid/index.tsx
2
2
  /**
3
3
  * @fileoverview Grid component
4
- * @description Polymorphic CSS Grid layout primitive.
5
- * Replaces redundant `div className="dndev-grid ..."` usage.
4
+ * @description Polymorphic CSS Grid layout primitive with responsive columns.
6
5
  *
7
6
  * @example
8
7
  * ```tsx
9
- * // Simple 3-column grid (default div)
8
+ * // Fixed 3-column grid
10
9
  * <Grid cols={3} gap="medium">
11
10
  * <Card />
12
11
  * <Card />
13
12
  * <Card />
14
13
  * </Grid>
15
14
  *
16
- * // Semantic section with grid layout
17
- * <Grid as="section" cols={2} aria-label="Features">
18
- * <FeatureCard />
19
- * <FeatureCard />
15
+ * // Responsive grid: [mobile, tablet, laptop, desktop]
16
+ * // 1 col on mobile/tablet, 2 on laptop, 3 on desktop
17
+ * <Grid cols={[1, 1, 2, 3]} gap="medium">
18
+ * <Card />
19
+ * <Card />
20
+ * <Card />
20
21
  * </Grid>
21
22
  *
22
23
  * // Named grid areas
23
- * <Grid as="header" areas="left center right" templateColumns="1fr auto 1fr">
24
- * <GridArea name="left">{leftContent}</GridArea>
25
- * <GridArea name="center">{centerContent}</GridArea>
26
- * <GridArea name="right">{rightContent}</GridArea>
24
+ * <Grid areas="left center right" templateColumns="1fr auto 1fr">
25
+ * <GridArea name="left">{left}</GridArea>
26
+ * <GridArea name="center">{center}</GridArea>
27
+ * <GridArea name="right">{right}</GridArea>
27
28
  * </Grid>
28
29
  * ```
29
30
  *
30
- * @version 0.0.1
31
+ * @version 0.0.4
31
32
  * @since 0.0.1
32
33
  * @author AMBROISE PARK Consulting
33
34
  */
@@ -41,24 +42,21 @@ import './Grid.css';
41
42
  * Polymorphic CSS Grid layout primitive.
42
43
  * Can render as any HTML element while maintaining grid layout.
43
44
  */
44
- const Grid = forwardRef(({ as = 'div', cols = 1, gap = GAP_VARIANT.MEDIUM, align = 'stretch', justify = 'stretch', minColWidth, autoFit = false, autoFill = false, areas, templateColumns, className, style, children, ...props }, ref) => {
45
+ const Grid = forwardRef(({ as = 'div', cols = 1, gap = GAP_VARIANT.MEDIUM, align = 'stretch', justify = 'stretch', areas, templateColumns, className, style, children, ...props }, ref) => {
45
46
  const Component = as;
46
- const getGridTemplateColumns = () => {
47
- if (templateColumns)
48
- return templateColumns;
49
- if (areas)
50
- return undefined; // Let CSS handle it if needed
51
- if (autoFit) {
52
- return `repeat(auto-fit, minmax(min(100%, ${minColWidth || '250px'}), 1fr))`;
53
- }
54
- if (autoFill) {
55
- return `repeat(auto-fill, minmax(min(100%, ${minColWidth || '250px'}), 1fr))`;
56
- }
57
- return `repeat(${cols}, minmax(${minColWidth || '0'}, 1fr))`;
58
- };
47
+ // Parse cols into responsive values
48
+ const isResponsive = Array.isArray(cols);
49
+ const [colsMobile, colsTablet, colsLaptop, colsDesktop] = isResponsive
50
+ ? cols
51
+ : [cols, cols, cols, cols];
59
52
  const customStyle = {
60
53
  display: 'grid',
61
54
  width: '100%',
55
+ // Set CSS custom properties for responsive columns
56
+ '--grid-cols-mobile': colsMobile,
57
+ '--grid-cols-tablet': colsTablet,
58
+ '--grid-cols-laptop': colsLaptop,
59
+ '--grid-cols-desktop': colsDesktop,
62
60
  ...style,
63
61
  ...(areas
64
62
  ? {
@@ -67,9 +65,7 @@ const Grid = forwardRef(({ as = 'div', cols = 1, gap = GAP_VARIANT.MEDIUM, align
67
65
  : `"${areas}"`,
68
66
  }
69
67
  : {}),
70
- ...(getGridTemplateColumns() && {
71
- gridTemplateColumns: getGridTemplateColumns(),
72
- }),
68
+ ...(templateColumns && { gridTemplateColumns: templateColumns }),
73
69
  };
74
70
  return createElement(Component, {
75
71
  ref,
@@ -77,6 +73,7 @@ const Grid = forwardRef(({ as = 'div', cols = 1, gap = GAP_VARIANT.MEDIUM, align
77
73
  'data-gap': gap,
78
74
  'data-align': align,
79
75
  'data-justify': justify,
76
+ 'data-responsive': isResponsive ? 'true' : undefined,
80
77
  style: customStyle,
81
78
  ...props,
82
79
  }, children);