@fpkit/acss 3.2.1 → 3.3.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.
- package/libs/chunk-KAR3HDXK.js +8 -0
- package/libs/chunk-KAR3HDXK.js.map +1 -0
- package/libs/chunk-M5JARVJD.cjs +18 -0
- package/libs/chunk-M5JARVJD.cjs.map +1 -0
- package/libs/components/card.cjs +6 -6
- package/libs/components/card.js +1 -1
- package/libs/index.cjs +5 -5
- package/libs/index.js +1 -1
- package/package.json +2 -2
- package/src/components/cards/card.stories.tsx +1 -1
- package/src/components/cards/card.tsx +46 -41
- package/libs/chunk-OU52NIKA.js +0 -8
- package/libs/chunk-OU52NIKA.js.map +0 -1
- package/libs/chunk-WWPLBWCQ.cjs +0 -18
- package/libs/chunk-WWPLBWCQ.cjs.map +0 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { a } from './chunk-DDSXKOUB.js';
|
|
2
|
+
import p from 'react';
|
|
3
|
+
|
|
4
|
+
function i(...e){return e.filter(Boolean).join(" ")}var C={CARD:"card",TITLE:"card-title",CONTENT:"card-content",FOOTER:"card-footer"};function y(e,r){r&&(e.key==="Enter"||e.key===" ")&&(e.preventDefault(),r(e));}var f=({children:e,className:r,style:o,as:t="h3",...a$1})=>p.createElement(a,{as:t,classes:i(C.TITLE,r),styles:o,...a$1},e);f.displayName="Card.Title";var b=({children:e,className:r,style:o,as:t="article",...a$1})=>p.createElement(a,{as:t,classes:i(C.CONTENT,r),styles:o,...a$1},e);b.displayName="Card.Content";var m=({children:e,className:r,style:o,as:t="div",...a$1})=>p.createElement(a,{as:t,classes:i(C.FOOTER,r),styles:o,...a$1},e);m.displayName="Card.Footer";var I=({as:e="div",styles:r,children:o,classes:t="shadow-sm",id:a$1,interactive:n=!1,onClick:s,tabIndex:u,role:c,"aria-label":T,"aria-labelledby":E,"aria-describedby":P,...v})=>(p.useEffect(()=>{},[s,n]),p.createElement(a,{as:e,id:a$1,styles:r,classes:t,"aria-label":T,"aria-labelledby":E,"aria-describedby":P,"data-card":n?"interactive":!0,...n?{role:c||"button",tabIndex:u??0,onClick:s,onKeyDown:x=>{(n||s)&&y(x,s);}}:{role:c,onClick:s},...v},o)),l=I;l.displayName="Card";l.Title=f;l.Content=b;l.Footer=m;var h=l;
|
|
5
|
+
|
|
6
|
+
export { f as a, b, m as c, l as d, h as e };
|
|
7
|
+
//# sourceMappingURL=out.js.map
|
|
8
|
+
//# sourceMappingURL=chunk-KAR3HDXK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/cards/card.tsx","../src/components/cards/card.utils.ts"],"names":["React","cn","classes","CARD_CLASSES","handleCardKeyDown","event","onClick","Title","children","className","style","as","props","ui_default","Content","Footer","CardRoot","styles","id","interactive","tabIndex","role","ariaLabel","ariaLabelledBy","ariaDescribedBy","Card","card_default"],"mappings":"wCAAA,OAAOA,MAAW,QCoBX,SAASC,KAAMC,EAAwD,CAC5E,OAAOA,EAAQ,OAAO,OAAO,EAAE,KAAK,GAAG,CACzC,CAMO,IAAMC,EAAe,CAC1B,KAAM,OACN,MAAO,aACP,QAAS,eACT,OAAQ,aACV,EAcO,SAASC,EACdC,EACAC,EACM,CACDA,IAGDD,EAAM,MAAQ,SAAWA,EAAM,MAAQ,OACzCA,EAAM,eAAe,EACrBC,EAAQD,CAAK,EAEjB,CDnBO,IAAME,EAAQ,CAAC,CACpB,SAAAC,EACA,UAAAC,EACA,MAAAC,EACA,GAAAC,EAAK,KACL,GAAGC,CACL,IAEIZ,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,QAASV,EAAGE,EAAa,MAAOM,CAAS,EACzC,OAAQC,EACP,GAAGE,GAEHJ,CACH,EAIJD,EAAM,YAAc,aA+Bb,IAAMO,EAAU,CAAC,CACtB,SAAAN,EACA,UAAAC,EACA,MAAAC,EACA,GAAAC,EAAK,UACL,GAAGC,CACL,IAEIZ,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,QAASV,EAAGE,EAAa,QAASM,CAAS,EAC3C,OAAQC,EACP,GAAGE,GAEHJ,CACH,EAIJM,EAAQ,YAAc,eA4Bf,IAAMC,EAAS,CAAC,CACrB,SAAAP,EACA,UAAAC,EACA,MAAAC,EACA,GAAAC,EAAK,MACL,GAAGC,CACL,IAEIZ,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,QAASV,EAAGE,EAAa,OAAQM,CAAS,EAC1C,OAAQC,EACP,GAAGE,GAEHJ,CACH,EAIJO,EAAO,YAAc,cAuErB,IAAMC,EAAW,CAAC,CAChB,GAAAL,EAAK,MACL,OAAAM,EACA,SAAAT,EACA,QAAAN,EAAU,YACV,GAAAgB,EACA,YAAAC,EAAc,GACd,QAAAb,EACA,SAAAc,EACA,KAAAC,EACA,aAAcC,EACd,kBAAmBC,EACnB,mBAAoBC,EACpB,GAAGZ,CACL,KAEEZ,EAAM,UAAU,IAAM,CAEtB,EAAG,CAACM,EAASa,CAAW,CAAC,EAsBvBnB,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,GAAIO,EACJ,OAAQD,EACR,QAASf,EACT,aAAYoB,EACZ,kBAAiBC,EACjB,mBAAkBC,EAClB,YAAWL,EAAc,cAAgB,GACxC,GAtBoBA,EACrB,CACE,KAAME,GAAQ,SACd,SAAUD,GAAY,EACtB,QAAAd,EACA,UAXiBD,GAA+B,EAChDc,GAAeb,IACjBF,EAAkBC,EAAOC,CAAO,CAEpC,CAQI,EACA,CACE,KAAAe,EACA,QAAAf,CACF,EAaC,GAAGM,GAEHJ,CACH,GAKSiB,EAAOT,EACpBS,EAAK,YAAc,OACnBA,EAAK,MAAQlB,EACbkB,EAAK,QAAUX,EACfW,EAAK,OAASV,EAEd,IAAOW,EAAQD","sourcesContent":["import React from \"react\";\nimport UI from \"#components/ui\";\nimport type {\n CardProps,\n CardTitleProps,\n CardContentProps,\n CardFooterProps,\n CardComponent,\n} from \"./card.types\";\nimport {\n cn,\n CARD_CLASSES,\n handleCardKeyDown,\n warnInteractiveUsage,\n} from \"./card.utils\";\n\n/**\n * Card.Title - Title sub-component for Card\n *\n * Renders a heading element for the card title. Defaults to h3 for proper\n * document outline, but can be customized via the `as` prop.\n *\n * ## Accessibility\n * - Use appropriate heading level based on document structure\n * - Combine with `aria-labelledby` on parent Card for accessible names\n *\n * @example\n * ```tsx\n * <Card aria-labelledby=\"card-title-1\">\n * <Card.Title id=\"card-title-1\">Featured Product</Card.Title>\n * </Card>\n * ```\n *\n * @example\n * ```tsx\n * // Custom heading level\n * <Card.Title as=\"h2\">Section Title</Card.Title>\n * ```\n */\nexport const Title = ({\n children,\n className,\n style,\n as = \"h3\",\n ...props\n}: CardTitleProps) => {\n return (\n <UI\n as={as}\n classes={cn(CARD_CLASSES.TITLE, className)}\n styles={style}\n {...props}\n >\n {children}\n </UI>\n );\n};\n\nTitle.displayName = \"Card.Title\";\n\n/**\n * Card.Content - Content sub-component for Card\n *\n * Renders the main content area of the card. Defaults to `<article>` for\n * standalone content, but can be changed to `div` or `section` via the `as` prop.\n *\n * ## Semantic HTML Guidelines\n * - Use `article` (default) for self-contained, syndicate-able content\n * - Use `div` for generic content containers\n * - Use `section` for thematic groupings with a heading\n *\n * @example\n * ```tsx\n * <Card>\n * <Card.Title>Article Title</Card.Title>\n * <Card.Content>\n * <p>This is standalone content...</p>\n * </Card.Content>\n * </Card>\n * ```\n *\n * @example\n * ```tsx\n * // Generic container (not standalone content)\n * <Card.Content as=\"div\">\n * <p>Generic content...</p>\n * </Card.Content>\n * ```\n */\nexport const Content = ({\n children,\n className,\n style,\n as = \"article\",\n ...props\n}: CardContentProps) => {\n return (\n <UI\n as={as}\n classes={cn(CARD_CLASSES.CONTENT, className)}\n styles={style}\n {...props}\n >\n {children}\n </UI>\n );\n};\n\nContent.displayName = \"Card.Content\";\n\n/**\n * Card.Footer - Footer sub-component for Card\n *\n * Renders a footer section for the card. Use for actions, metadata, or\n * supplementary information.\n *\n * @example\n * ```tsx\n * <Card>\n * <Card.Title>Product</Card.Title>\n * <Card.Content>Description...</Card.Content>\n * <Card.Footer>\n * <button>Add to Cart</button>\n * <span>$29.99</span>\n * </Card.Footer>\n * </Card>\n * ```\n *\n * @example\n * ```tsx\n * // Semantic footer element\n * <Card.Footer as=\"footer\">\n * <p>Last updated: 2024-01-15</p>\n * </Card.Footer>\n * ```\n */\nexport const Footer = ({\n children,\n className,\n style,\n as = \"div\",\n ...props\n}: CardFooterProps) => {\n return (\n <UI\n as={as}\n classes={cn(CARD_CLASSES.FOOTER, className)}\n styles={style}\n {...props}\n >\n {children}\n </UI>\n );\n};\n\nFooter.displayName = \"Card.Footer\";\n\n/**\n * Card - A flexible, accessible card component with compound component pattern\n *\n * The Card component provides a container for grouping related content and actions.\n * It follows the compound component pattern, exposing `Card.Title`, `Card.Content`,\n * and `Card.Footer` sub-components for structured layouts.\n *\n * ## Features\n * - **Polymorphic rendering**: Render as any HTML element via `as` prop\n * - **Compound components**: Structured sub-components for consistent layouts\n * - **Interactive variant**: Built-in keyboard navigation and ARIA support\n * - **Fully accessible**: WCAG 2.1 AA compliant with proper semantic HTML\n *\n * ## Accessibility\n *\n * ### Non-Interactive Cards\n * - Use semantic HTML: `article` for standalone content, `section` for groupings\n * - Provide accessible names with `aria-labelledby` referencing the title\n *\n * ### Interactive Cards (Clickable)\n * - Set `interactive={true}` to enable keyboard navigation (Enter/Space)\n * - Provide accessible name via `aria-label` or `aria-labelledby`\n * - Ensure adequate focus indicators (handled by CSS)\n *\n * @example\n * // Basic card with compound components\n * ```tsx\n * <Card>\n * <Card.Title>Product Name</Card.Title>\n * <Card.Content>\n * <p>Product description goes here...</p>\n * </Card.Content>\n * <Card.Footer>\n * <button>Buy Now</button>\n * </Card.Footer>\n * </Card>\n * ```\n *\n * @example\n * // Accessible interactive card\n * ```tsx\n * <Card\n * interactive\n * aria-label=\"View product details\"\n * onClick={() => navigate('/product/123')}\n * >\n * <Card.Title>Product Name</Card.Title>\n * <Card.Content>Click anywhere to view details</Card.Content>\n * </Card>\n * ```\n *\n * @example\n * // Semantic article card with accessible name\n * ```tsx\n * <Card as=\"article\" aria-labelledby=\"article-title\">\n * <Card.Title id=\"article-title\">Article Headline</Card.Title>\n * <Card.Content>Article body...</Card.Content>\n * </Card>\n * ```\n *\n * @example\n * // Card as a landmark region\n * ```tsx\n * <Card role=\"region\" aria-label=\"Featured products\">\n * <Card.Title>Featured</Card.Title>\n * <Card.Content>...</Card.Content>\n * </Card>\n * ```\n */\nconst CardRoot = ({\n as = \"div\",\n styles,\n children,\n classes = \"shadow-sm\",\n id,\n interactive = false,\n onClick,\n tabIndex,\n role,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n ...props\n}: CardProps) => {\n // Development warnings for common accessibility issues\n React.useEffect(() => {\n warnInteractiveUsage(!!onClick, interactive);\n }, [onClick, interactive]);\n\n // Interactive card handling\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (interactive || onClick) {\n handleCardKeyDown(event, onClick);\n }\n };\n\n const interactiveProps = interactive\n ? {\n role: role || \"button\",\n tabIndex: tabIndex ?? 0,\n onClick,\n onKeyDown: handleKeyDown,\n }\n : {\n role,\n onClick,\n };\n\n return (\n <UI\n as={as}\n id={id}\n styles={styles}\n classes={classes}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={ariaDescribedBy}\n data-card={interactive ? \"interactive\" : true}\n {...interactiveProps}\n {...props}\n >\n {children}\n </UI>\n );\n};\n\n// Create compound component with proper TypeScript typing\nexport const Card = CardRoot as CardComponent;\nCard.displayName = \"Card\";\nCard.Title = Title;\nCard.Content = Content;\nCard.Footer = Footer;\n\nexport default Card;\n\n// Export types for external consumption\nexport type {\n CardProps,\n CardTitleProps,\n CardContentProps,\n CardFooterProps,\n CardComponent,\n} from \"./card.types\";\n","/**\n * Utility functions for the Card component.\n */\n\n/**\n * Combines multiple className strings into a single string, filtering out falsy values.\n *\n * This utility provides a cleaner alternative to template literals for className composition,\n * avoiding unnecessary string allocation on every render.\n *\n * @param classes - Array of class names (can include undefined/null/empty strings)\n * @returns Combined className string\n *\n * @example\n * ```tsx\n * cn('card-title', className) // \"card-title custom-class\"\n * cn('card-title', undefined) // \"card-title\"\n * cn('card-title', '', 'extra') // \"card-title extra\"\n * ```\n */\nexport function cn(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(' ')\n}\n\n/**\n * CSS class name constants for Card components.\n * Centralizing these prevents typos and enables easier refactoring.\n */\nexport const CARD_CLASSES = {\n CARD: 'card',\n TITLE: 'card-title',\n CONTENT: 'card-content',\n FOOTER: 'card-footer',\n} as const\n\n/**\n * Handles keyboard events for interactive cards.\n * Triggers click handler on Enter or Space key press.\n *\n * @param event - Keyboard event\n * @param onClick - Click handler to invoke\n *\n * @example\n * ```tsx\n * <div onKeyDown={(e) => handleCardKeyDown(e, handleClick)}>\n * ```\n */\nexport function handleCardKeyDown(\n event: React.KeyboardEvent,\n onClick?: (event: React.MouseEvent | React.KeyboardEvent) => void\n): void {\n if (!onClick) return\n\n // Activate on Enter or Space (standard keyboard interaction pattern)\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault() // Prevent page scroll on Space\n onClick(event)\n }\n}\n\n/**\n * Development warning for interactive card usage.\n * Warns when onClick is provided without the interactive prop.\n *\n * This function is intentionally empty to comply with no-console linting rules.\n * In the future, consider using a proper logging/warning system.\n *\n * @param hasOnClick - Whether onClick handler is provided\n * @param isInteractive - Whether interactive prop is set\n */\nexport function warnInteractiveUsage(\n hasOnClick: boolean,\n isInteractive?: boolean\n): void {\n // Development warning removed to comply with ESLint no-console rule\n // TODO: Consider using a proper warning system if needed\n void hasOnClick\n void isInteractive\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkEJ6KYBFE_cjs = require('./chunk-EJ6KYBFE.cjs');
|
|
4
|
+
var p = require('react');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var p__default = /*#__PURE__*/_interopDefault(p);
|
|
9
|
+
|
|
10
|
+
function i(...e){return e.filter(Boolean).join(" ")}var C={CARD:"card",TITLE:"card-title",CONTENT:"card-content",FOOTER:"card-footer"};function y(e,r){r&&(e.key==="Enter"||e.key===" ")&&(e.preventDefault(),r(e));}var f=({children:e,className:r,style:o,as:t="h3",...a})=>p__default.default.createElement(chunkEJ6KYBFE_cjs.a,{as:t,classes:i(C.TITLE,r),styles:o,...a},e);f.displayName="Card.Title";var b=({children:e,className:r,style:o,as:t="article",...a})=>p__default.default.createElement(chunkEJ6KYBFE_cjs.a,{as:t,classes:i(C.CONTENT,r),styles:o,...a},e);b.displayName="Card.Content";var m=({children:e,className:r,style:o,as:t="div",...a})=>p__default.default.createElement(chunkEJ6KYBFE_cjs.a,{as:t,classes:i(C.FOOTER,r),styles:o,...a},e);m.displayName="Card.Footer";var I=({as:e="div",styles:r,children:o,classes:t="shadow-sm",id:a,interactive:n=!1,onClick:s,tabIndex:u,role:c,"aria-label":T,"aria-labelledby":E,"aria-describedby":P,...v})=>(p__default.default.useEffect(()=>{},[s,n]),p__default.default.createElement(chunkEJ6KYBFE_cjs.a,{as:e,id:a,styles:r,classes:t,"aria-label":T,"aria-labelledby":E,"aria-describedby":P,"data-card":n?"interactive":!0,...n?{role:c||"button",tabIndex:u??0,onClick:s,onKeyDown:x=>{(n||s)&&y(x,s);}}:{role:c,onClick:s},...v},o)),l=I;l.displayName="Card";l.Title=f;l.Content=b;l.Footer=m;var h=l;
|
|
11
|
+
|
|
12
|
+
exports.a = f;
|
|
13
|
+
exports.b = b;
|
|
14
|
+
exports.c = m;
|
|
15
|
+
exports.d = l;
|
|
16
|
+
exports.e = h;
|
|
17
|
+
//# sourceMappingURL=out.js.map
|
|
18
|
+
//# sourceMappingURL=chunk-M5JARVJD.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/cards/card.tsx","../src/components/cards/card.utils.ts"],"names":["React","cn","classes","CARD_CLASSES","handleCardKeyDown","event","onClick","Title","children","className","style","as","props","ui_default","Content","Footer","CardRoot","styles","id","interactive","tabIndex","role","ariaLabel","ariaLabelledBy","ariaDescribedBy","Card","card_default"],"mappings":"yCAAA,OAAOA,MAAW,QCoBX,SAASC,KAAMC,EAAwD,CAC5E,OAAOA,EAAQ,OAAO,OAAO,EAAE,KAAK,GAAG,CACzC,CAMO,IAAMC,EAAe,CAC1B,KAAM,OACN,MAAO,aACP,QAAS,eACT,OAAQ,aACV,EAcO,SAASC,EACdC,EACAC,EACM,CACDA,IAGDD,EAAM,MAAQ,SAAWA,EAAM,MAAQ,OACzCA,EAAM,eAAe,EACrBC,EAAQD,CAAK,EAEjB,CDnBO,IAAME,EAAQ,CAAC,CACpB,SAAAC,EACA,UAAAC,EACA,MAAAC,EACA,GAAAC,EAAK,KACL,GAAGC,CACL,IAEIZ,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,QAASV,EAAGE,EAAa,MAAOM,CAAS,EACzC,OAAQC,EACP,GAAGE,GAEHJ,CACH,EAIJD,EAAM,YAAc,aA+Bb,IAAMO,EAAU,CAAC,CACtB,SAAAN,EACA,UAAAC,EACA,MAAAC,EACA,GAAAC,EAAK,UACL,GAAGC,CACL,IAEIZ,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,QAASV,EAAGE,EAAa,QAASM,CAAS,EAC3C,OAAQC,EACP,GAAGE,GAEHJ,CACH,EAIJM,EAAQ,YAAc,eA4Bf,IAAMC,EAAS,CAAC,CACrB,SAAAP,EACA,UAAAC,EACA,MAAAC,EACA,GAAAC,EAAK,MACL,GAAGC,CACL,IAEIZ,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,QAASV,EAAGE,EAAa,OAAQM,CAAS,EAC1C,OAAQC,EACP,GAAGE,GAEHJ,CACH,EAIJO,EAAO,YAAc,cAuErB,IAAMC,EAAW,CAAC,CAChB,GAAAL,EAAK,MACL,OAAAM,EACA,SAAAT,EACA,QAAAN,EAAU,YACV,GAAAgB,EACA,YAAAC,EAAc,GACd,QAAAb,EACA,SAAAc,EACA,KAAAC,EACA,aAAcC,EACd,kBAAmBC,EACnB,mBAAoBC,EACpB,GAAGZ,CACL,KAEEZ,EAAM,UAAU,IAAM,CAEtB,EAAG,CAACM,EAASa,CAAW,CAAC,EAsBvBnB,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,GAAIO,EACJ,OAAQD,EACR,QAASf,EACT,aAAYoB,EACZ,kBAAiBC,EACjB,mBAAkBC,EAClB,YAAWL,EAAc,cAAgB,GACxC,GAtBoBA,EACrB,CACE,KAAME,GAAQ,SACd,SAAUD,GAAY,EACtB,QAAAd,EACA,UAXiBD,GAA+B,EAChDc,GAAeb,IACjBF,EAAkBC,EAAOC,CAAO,CAEpC,CAQI,EACA,CACE,KAAAe,EACA,QAAAf,CACF,EAaC,GAAGM,GAEHJ,CACH,GAKSiB,EAAOT,EACpBS,EAAK,YAAc,OACnBA,EAAK,MAAQlB,EACbkB,EAAK,QAAUX,EACfW,EAAK,OAASV,EAEd,IAAOW,EAAQD","sourcesContent":["import React from \"react\";\nimport UI from \"#components/ui\";\nimport type {\n CardProps,\n CardTitleProps,\n CardContentProps,\n CardFooterProps,\n CardComponent,\n} from \"./card.types\";\nimport {\n cn,\n CARD_CLASSES,\n handleCardKeyDown,\n warnInteractiveUsage,\n} from \"./card.utils\";\n\n/**\n * Card.Title - Title sub-component for Card\n *\n * Renders a heading element for the card title. Defaults to h3 for proper\n * document outline, but can be customized via the `as` prop.\n *\n * ## Accessibility\n * - Use appropriate heading level based on document structure\n * - Combine with `aria-labelledby` on parent Card for accessible names\n *\n * @example\n * ```tsx\n * <Card aria-labelledby=\"card-title-1\">\n * <Card.Title id=\"card-title-1\">Featured Product</Card.Title>\n * </Card>\n * ```\n *\n * @example\n * ```tsx\n * // Custom heading level\n * <Card.Title as=\"h2\">Section Title</Card.Title>\n * ```\n */\nexport const Title = ({\n children,\n className,\n style,\n as = \"h3\",\n ...props\n}: CardTitleProps) => {\n return (\n <UI\n as={as}\n classes={cn(CARD_CLASSES.TITLE, className)}\n styles={style}\n {...props}\n >\n {children}\n </UI>\n );\n};\n\nTitle.displayName = \"Card.Title\";\n\n/**\n * Card.Content - Content sub-component for Card\n *\n * Renders the main content area of the card. Defaults to `<article>` for\n * standalone content, but can be changed to `div` or `section` via the `as` prop.\n *\n * ## Semantic HTML Guidelines\n * - Use `article` (default) for self-contained, syndicate-able content\n * - Use `div` for generic content containers\n * - Use `section` for thematic groupings with a heading\n *\n * @example\n * ```tsx\n * <Card>\n * <Card.Title>Article Title</Card.Title>\n * <Card.Content>\n * <p>This is standalone content...</p>\n * </Card.Content>\n * </Card>\n * ```\n *\n * @example\n * ```tsx\n * // Generic container (not standalone content)\n * <Card.Content as=\"div\">\n * <p>Generic content...</p>\n * </Card.Content>\n * ```\n */\nexport const Content = ({\n children,\n className,\n style,\n as = \"article\",\n ...props\n}: CardContentProps) => {\n return (\n <UI\n as={as}\n classes={cn(CARD_CLASSES.CONTENT, className)}\n styles={style}\n {...props}\n >\n {children}\n </UI>\n );\n};\n\nContent.displayName = \"Card.Content\";\n\n/**\n * Card.Footer - Footer sub-component for Card\n *\n * Renders a footer section for the card. Use for actions, metadata, or\n * supplementary information.\n *\n * @example\n * ```tsx\n * <Card>\n * <Card.Title>Product</Card.Title>\n * <Card.Content>Description...</Card.Content>\n * <Card.Footer>\n * <button>Add to Cart</button>\n * <span>$29.99</span>\n * </Card.Footer>\n * </Card>\n * ```\n *\n * @example\n * ```tsx\n * // Semantic footer element\n * <Card.Footer as=\"footer\">\n * <p>Last updated: 2024-01-15</p>\n * </Card.Footer>\n * ```\n */\nexport const Footer = ({\n children,\n className,\n style,\n as = \"div\",\n ...props\n}: CardFooterProps) => {\n return (\n <UI\n as={as}\n classes={cn(CARD_CLASSES.FOOTER, className)}\n styles={style}\n {...props}\n >\n {children}\n </UI>\n );\n};\n\nFooter.displayName = \"Card.Footer\";\n\n/**\n * Card - A flexible, accessible card component with compound component pattern\n *\n * The Card component provides a container for grouping related content and actions.\n * It follows the compound component pattern, exposing `Card.Title`, `Card.Content`,\n * and `Card.Footer` sub-components for structured layouts.\n *\n * ## Features\n * - **Polymorphic rendering**: Render as any HTML element via `as` prop\n * - **Compound components**: Structured sub-components for consistent layouts\n * - **Interactive variant**: Built-in keyboard navigation and ARIA support\n * - **Fully accessible**: WCAG 2.1 AA compliant with proper semantic HTML\n *\n * ## Accessibility\n *\n * ### Non-Interactive Cards\n * - Use semantic HTML: `article` for standalone content, `section` for groupings\n * - Provide accessible names with `aria-labelledby` referencing the title\n *\n * ### Interactive Cards (Clickable)\n * - Set `interactive={true}` to enable keyboard navigation (Enter/Space)\n * - Provide accessible name via `aria-label` or `aria-labelledby`\n * - Ensure adequate focus indicators (handled by CSS)\n *\n * @example\n * // Basic card with compound components\n * ```tsx\n * <Card>\n * <Card.Title>Product Name</Card.Title>\n * <Card.Content>\n * <p>Product description goes here...</p>\n * </Card.Content>\n * <Card.Footer>\n * <button>Buy Now</button>\n * </Card.Footer>\n * </Card>\n * ```\n *\n * @example\n * // Accessible interactive card\n * ```tsx\n * <Card\n * interactive\n * aria-label=\"View product details\"\n * onClick={() => navigate('/product/123')}\n * >\n * <Card.Title>Product Name</Card.Title>\n * <Card.Content>Click anywhere to view details</Card.Content>\n * </Card>\n * ```\n *\n * @example\n * // Semantic article card with accessible name\n * ```tsx\n * <Card as=\"article\" aria-labelledby=\"article-title\">\n * <Card.Title id=\"article-title\">Article Headline</Card.Title>\n * <Card.Content>Article body...</Card.Content>\n * </Card>\n * ```\n *\n * @example\n * // Card as a landmark region\n * ```tsx\n * <Card role=\"region\" aria-label=\"Featured products\">\n * <Card.Title>Featured</Card.Title>\n * <Card.Content>...</Card.Content>\n * </Card>\n * ```\n */\nconst CardRoot = ({\n as = \"div\",\n styles,\n children,\n classes = \"shadow-sm\",\n id,\n interactive = false,\n onClick,\n tabIndex,\n role,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n ...props\n}: CardProps) => {\n // Development warnings for common accessibility issues\n React.useEffect(() => {\n warnInteractiveUsage(!!onClick, interactive);\n }, [onClick, interactive]);\n\n // Interactive card handling\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (interactive || onClick) {\n handleCardKeyDown(event, onClick);\n }\n };\n\n const interactiveProps = interactive\n ? {\n role: role || \"button\",\n tabIndex: tabIndex ?? 0,\n onClick,\n onKeyDown: handleKeyDown,\n }\n : {\n role,\n onClick,\n };\n\n return (\n <UI\n as={as}\n id={id}\n styles={styles}\n classes={classes}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={ariaDescribedBy}\n data-card={interactive ? \"interactive\" : true}\n {...interactiveProps}\n {...props}\n >\n {children}\n </UI>\n );\n};\n\n// Create compound component with proper TypeScript typing\nexport const Card = CardRoot as CardComponent;\nCard.displayName = \"Card\";\nCard.Title = Title;\nCard.Content = Content;\nCard.Footer = Footer;\n\nexport default Card;\n\n// Export types for external consumption\nexport type {\n CardProps,\n CardTitleProps,\n CardContentProps,\n CardFooterProps,\n CardComponent,\n} from \"./card.types\";\n","/**\n * Utility functions for the Card component.\n */\n\n/**\n * Combines multiple className strings into a single string, filtering out falsy values.\n *\n * This utility provides a cleaner alternative to template literals for className composition,\n * avoiding unnecessary string allocation on every render.\n *\n * @param classes - Array of class names (can include undefined/null/empty strings)\n * @returns Combined className string\n *\n * @example\n * ```tsx\n * cn('card-title', className) // \"card-title custom-class\"\n * cn('card-title', undefined) // \"card-title\"\n * cn('card-title', '', 'extra') // \"card-title extra\"\n * ```\n */\nexport function cn(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(' ')\n}\n\n/**\n * CSS class name constants for Card components.\n * Centralizing these prevents typos and enables easier refactoring.\n */\nexport const CARD_CLASSES = {\n CARD: 'card',\n TITLE: 'card-title',\n CONTENT: 'card-content',\n FOOTER: 'card-footer',\n} as const\n\n/**\n * Handles keyboard events for interactive cards.\n * Triggers click handler on Enter or Space key press.\n *\n * @param event - Keyboard event\n * @param onClick - Click handler to invoke\n *\n * @example\n * ```tsx\n * <div onKeyDown={(e) => handleCardKeyDown(e, handleClick)}>\n * ```\n */\nexport function handleCardKeyDown(\n event: React.KeyboardEvent,\n onClick?: (event: React.MouseEvent | React.KeyboardEvent) => void\n): void {\n if (!onClick) return\n\n // Activate on Enter or Space (standard keyboard interaction pattern)\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault() // Prevent page scroll on Space\n onClick(event)\n }\n}\n\n/**\n * Development warning for interactive card usage.\n * Warns when onClick is provided without the interactive prop.\n *\n * This function is intentionally empty to comply with no-console linting rules.\n * In the future, consider using a proper logging/warning system.\n *\n * @param hasOnClick - Whether onClick handler is provided\n * @param isInteractive - Whether interactive prop is set\n */\nexport function warnInteractiveUsage(\n hasOnClick: boolean,\n isInteractive?: boolean\n): void {\n // Development warning removed to comply with ESLint no-console rule\n // TODO: Consider using a proper warning system if needed\n void hasOnClick\n void isInteractive\n}\n"]}
|
package/libs/components/card.cjs
CHANGED
|
@@ -2,30 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var chunkM5JARVJD_cjs = require('../chunk-M5JARVJD.cjs');
|
|
6
6
|
require('../chunk-EJ6KYBFE.cjs');
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
Object.defineProperty(exports, 'Card', {
|
|
11
11
|
enumerable: true,
|
|
12
|
-
get: function () { return
|
|
12
|
+
get: function () { return chunkM5JARVJD_cjs.d; }
|
|
13
13
|
});
|
|
14
14
|
Object.defineProperty(exports, 'Content', {
|
|
15
15
|
enumerable: true,
|
|
16
|
-
get: function () { return
|
|
16
|
+
get: function () { return chunkM5JARVJD_cjs.b; }
|
|
17
17
|
});
|
|
18
18
|
Object.defineProperty(exports, 'Footer', {
|
|
19
19
|
enumerable: true,
|
|
20
|
-
get: function () { return
|
|
20
|
+
get: function () { return chunkM5JARVJD_cjs.c; }
|
|
21
21
|
});
|
|
22
22
|
Object.defineProperty(exports, 'Title', {
|
|
23
23
|
enumerable: true,
|
|
24
|
-
get: function () { return
|
|
24
|
+
get: function () { return chunkM5JARVJD_cjs.a; }
|
|
25
25
|
});
|
|
26
26
|
Object.defineProperty(exports, 'default', {
|
|
27
27
|
enumerable: true,
|
|
28
|
-
get: function () { return
|
|
28
|
+
get: function () { return chunkM5JARVJD_cjs.e; }
|
|
29
29
|
});
|
|
30
30
|
//# sourceMappingURL=out.js.map
|
|
31
31
|
//# sourceMappingURL=card.cjs.map
|
package/libs/components/card.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { d as Card, b as Content, c as Footer, a as Title, e as default } from '../chunk-
|
|
1
|
+
export { d as Card, b as Content, c as Footer, a as Title, e as default } from '../chunk-KAR3HDXK.js';
|
|
2
2
|
import '../chunk-DDSXKOUB.js';
|
|
3
3
|
//# sourceMappingURL=out.js.map
|
|
4
4
|
//# sourceMappingURL=card.js.map
|
package/libs/index.cjs
CHANGED
|
@@ -12,7 +12,7 @@ var chunk2C3YLBWP_cjs = require('./chunk-2C3YLBWP.cjs');
|
|
|
12
12
|
var chunkLXODKKA3_cjs = require('./chunk-LXODKKA3.cjs');
|
|
13
13
|
require('./chunk-PDD4N5P5.cjs');
|
|
14
14
|
var chunk5CJPTDK3_cjs = require('./chunk-5CJPTDK3.cjs');
|
|
15
|
-
var
|
|
15
|
+
var chunkM5JARVJD_cjs = require('./chunk-M5JARVJD.cjs');
|
|
16
16
|
var chunkF64GE6RG_cjs = require('./chunk-F64GE6RG.cjs');
|
|
17
17
|
var chunk5QSNJQVH_cjs = require('./chunk-5QSNJQVH.cjs');
|
|
18
18
|
var chunk4BZKFPEC_cjs = require('./chunk-4BZKFPEC.cjs');
|
|
@@ -127,19 +127,19 @@ Object.defineProperty(exports, 'Icon', {
|
|
|
127
127
|
});
|
|
128
128
|
Object.defineProperty(exports, 'Card', {
|
|
129
129
|
enumerable: true,
|
|
130
|
-
get: function () { return
|
|
130
|
+
get: function () { return chunkM5JARVJD_cjs.d; }
|
|
131
131
|
});
|
|
132
132
|
Object.defineProperty(exports, 'CardContent', {
|
|
133
133
|
enumerable: true,
|
|
134
|
-
get: function () { return
|
|
134
|
+
get: function () { return chunkM5JARVJD_cjs.b; }
|
|
135
135
|
});
|
|
136
136
|
Object.defineProperty(exports, 'CardFooter', {
|
|
137
137
|
enumerable: true,
|
|
138
|
-
get: function () { return
|
|
138
|
+
get: function () { return chunkM5JARVJD_cjs.c; }
|
|
139
139
|
});
|
|
140
140
|
Object.defineProperty(exports, 'CardTitle', {
|
|
141
141
|
enumerable: true,
|
|
142
|
-
get: function () { return
|
|
142
|
+
get: function () { return chunkM5JARVJD_cjs.a; }
|
|
143
143
|
});
|
|
144
144
|
Object.defineProperty(exports, 'Modal', {
|
|
145
145
|
enumerable: true,
|
package/libs/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export { b as Breadcrumb, a as useBreadcrumbSegments } from './chunk-DIJBIOFE.js
|
|
|
12
12
|
import './chunk-GCGKYLDG.js';
|
|
13
13
|
import { b as b$1 } from './chunk-M7JLT62Q.js';
|
|
14
14
|
export { a as Icon } from './chunk-M7JLT62Q.js';
|
|
15
|
-
export { d as Card, b as CardContent, c as CardFooter, a as CardTitle } from './chunk-
|
|
15
|
+
export { d as Card, b as CardContent, c as CardFooter, a as CardTitle } from './chunk-KAR3HDXK.js';
|
|
16
16
|
export { a as Modal } from './chunk-PMWL5XZ4.js';
|
|
17
17
|
import { b } from './chunk-TF3GQKOY.js';
|
|
18
18
|
export { a as Button } from './chunk-TF3GQKOY.js';
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@fpkit/acss",
|
|
3
3
|
"description": "A lightweight React UI library for building modern and accessible components that leverage CSS custom properties for reactive Styles.",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.3.0",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=22.12.0",
|
|
8
8
|
"npm": ">=8.0.0"
|
|
@@ -126,5 +126,5 @@
|
|
|
126
126
|
"publishConfig": {
|
|
127
127
|
"access": "public"
|
|
128
128
|
},
|
|
129
|
-
"gitHead": "
|
|
129
|
+
"gitHead": "1948e0adc68133b7c44f0ba3ffda118d8b0c1391"
|
|
130
130
|
}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import UI from
|
|
1
|
+
import React from "react";
|
|
2
|
+
import UI from "#components/ui";
|
|
3
3
|
import type {
|
|
4
4
|
CardProps,
|
|
5
5
|
CardTitleProps,
|
|
6
6
|
CardContentProps,
|
|
7
7
|
CardFooterProps,
|
|
8
8
|
CardComponent,
|
|
9
|
-
} from
|
|
10
|
-
import {
|
|
9
|
+
} from "./card.types";
|
|
10
|
+
import {
|
|
11
|
+
cn,
|
|
12
|
+
CARD_CLASSES,
|
|
13
|
+
handleCardKeyDown,
|
|
14
|
+
warnInteractiveUsage,
|
|
15
|
+
} from "./card.utils";
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
18
|
* Card.Title - Title sub-component for Card
|
|
@@ -36,22 +41,22 @@ export const Title = ({
|
|
|
36
41
|
children,
|
|
37
42
|
className,
|
|
38
43
|
style,
|
|
39
|
-
as =
|
|
44
|
+
as = "h3",
|
|
40
45
|
...props
|
|
41
46
|
}: CardTitleProps) => {
|
|
42
47
|
return (
|
|
43
48
|
<UI
|
|
44
49
|
as={as}
|
|
45
|
-
|
|
50
|
+
classes={cn(CARD_CLASSES.TITLE, className)}
|
|
46
51
|
styles={style}
|
|
47
52
|
{...props}
|
|
48
53
|
>
|
|
49
54
|
{children}
|
|
50
55
|
</UI>
|
|
51
|
-
)
|
|
52
|
-
}
|
|
56
|
+
);
|
|
57
|
+
};
|
|
53
58
|
|
|
54
|
-
Title.displayName =
|
|
59
|
+
Title.displayName = "Card.Title";
|
|
55
60
|
|
|
56
61
|
/**
|
|
57
62
|
* Card.Content - Content sub-component for Card
|
|
@@ -86,22 +91,22 @@ export const Content = ({
|
|
|
86
91
|
children,
|
|
87
92
|
className,
|
|
88
93
|
style,
|
|
89
|
-
as =
|
|
94
|
+
as = "article",
|
|
90
95
|
...props
|
|
91
96
|
}: CardContentProps) => {
|
|
92
97
|
return (
|
|
93
98
|
<UI
|
|
94
99
|
as={as}
|
|
95
|
-
|
|
100
|
+
classes={cn(CARD_CLASSES.CONTENT, className)}
|
|
96
101
|
styles={style}
|
|
97
102
|
{...props}
|
|
98
103
|
>
|
|
99
104
|
{children}
|
|
100
105
|
</UI>
|
|
101
|
-
)
|
|
102
|
-
}
|
|
106
|
+
);
|
|
107
|
+
};
|
|
103
108
|
|
|
104
|
-
Content.displayName =
|
|
109
|
+
Content.displayName = "Card.Content";
|
|
105
110
|
|
|
106
111
|
/**
|
|
107
112
|
* Card.Footer - Footer sub-component for Card
|
|
@@ -133,22 +138,22 @@ export const Footer = ({
|
|
|
133
138
|
children,
|
|
134
139
|
className,
|
|
135
140
|
style,
|
|
136
|
-
as =
|
|
141
|
+
as = "div",
|
|
137
142
|
...props
|
|
138
143
|
}: CardFooterProps) => {
|
|
139
144
|
return (
|
|
140
145
|
<UI
|
|
141
146
|
as={as}
|
|
142
|
-
|
|
147
|
+
classes={cn(CARD_CLASSES.FOOTER, className)}
|
|
143
148
|
styles={style}
|
|
144
149
|
{...props}
|
|
145
150
|
>
|
|
146
151
|
{children}
|
|
147
152
|
</UI>
|
|
148
|
-
)
|
|
149
|
-
}
|
|
153
|
+
);
|
|
154
|
+
};
|
|
150
155
|
|
|
151
|
-
Footer.displayName =
|
|
156
|
+
Footer.displayName = "Card.Footer";
|
|
152
157
|
|
|
153
158
|
/**
|
|
154
159
|
* Card - A flexible, accessible card component with compound component pattern
|
|
@@ -220,35 +225,35 @@ Footer.displayName = 'Card.Footer'
|
|
|
220
225
|
* ```
|
|
221
226
|
*/
|
|
222
227
|
const CardRoot = ({
|
|
223
|
-
as =
|
|
228
|
+
as = "div",
|
|
224
229
|
styles,
|
|
225
230
|
children,
|
|
226
|
-
classes =
|
|
231
|
+
classes = "shadow-sm",
|
|
227
232
|
id,
|
|
228
233
|
interactive = false,
|
|
229
234
|
onClick,
|
|
230
235
|
tabIndex,
|
|
231
236
|
role,
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
237
|
+
"aria-label": ariaLabel,
|
|
238
|
+
"aria-labelledby": ariaLabelledBy,
|
|
239
|
+
"aria-describedby": ariaDescribedBy,
|
|
235
240
|
...props
|
|
236
241
|
}: CardProps) => {
|
|
237
242
|
// Development warnings for common accessibility issues
|
|
238
243
|
React.useEffect(() => {
|
|
239
|
-
warnInteractiveUsage(!!onClick, interactive)
|
|
240
|
-
}, [onClick, interactive])
|
|
244
|
+
warnInteractiveUsage(!!onClick, interactive);
|
|
245
|
+
}, [onClick, interactive]);
|
|
241
246
|
|
|
242
247
|
// Interactive card handling
|
|
243
248
|
const handleKeyDown = (event: React.KeyboardEvent) => {
|
|
244
249
|
if (interactive || onClick) {
|
|
245
|
-
handleCardKeyDown(event, onClick)
|
|
250
|
+
handleCardKeyDown(event, onClick);
|
|
246
251
|
}
|
|
247
|
-
}
|
|
252
|
+
};
|
|
248
253
|
|
|
249
254
|
const interactiveProps = interactive
|
|
250
255
|
? {
|
|
251
|
-
role: role ||
|
|
256
|
+
role: role || "button",
|
|
252
257
|
tabIndex: tabIndex ?? 0,
|
|
253
258
|
onClick,
|
|
254
259
|
onKeyDown: handleKeyDown,
|
|
@@ -256,34 +261,34 @@ const CardRoot = ({
|
|
|
256
261
|
: {
|
|
257
262
|
role,
|
|
258
263
|
onClick,
|
|
259
|
-
}
|
|
264
|
+
};
|
|
260
265
|
|
|
261
266
|
return (
|
|
262
267
|
<UI
|
|
263
268
|
as={as}
|
|
264
269
|
id={id}
|
|
265
270
|
styles={styles}
|
|
266
|
-
|
|
271
|
+
classes={classes}
|
|
267
272
|
aria-label={ariaLabel}
|
|
268
273
|
aria-labelledby={ariaLabelledBy}
|
|
269
274
|
aria-describedby={ariaDescribedBy}
|
|
270
|
-
data-card={interactive ?
|
|
275
|
+
data-card={interactive ? "interactive" : true}
|
|
271
276
|
{...interactiveProps}
|
|
272
277
|
{...props}
|
|
273
278
|
>
|
|
274
279
|
{children}
|
|
275
280
|
</UI>
|
|
276
|
-
)
|
|
277
|
-
}
|
|
281
|
+
);
|
|
282
|
+
};
|
|
278
283
|
|
|
279
284
|
// Create compound component with proper TypeScript typing
|
|
280
|
-
export const Card = CardRoot as CardComponent
|
|
281
|
-
Card.displayName =
|
|
282
|
-
Card.Title = Title
|
|
283
|
-
Card.Content = Content
|
|
284
|
-
Card.Footer = Footer
|
|
285
|
+
export const Card = CardRoot as CardComponent;
|
|
286
|
+
Card.displayName = "Card";
|
|
287
|
+
Card.Title = Title;
|
|
288
|
+
Card.Content = Content;
|
|
289
|
+
Card.Footer = Footer;
|
|
285
290
|
|
|
286
|
-
export default Card
|
|
291
|
+
export default Card;
|
|
287
292
|
|
|
288
293
|
// Export types for external consumption
|
|
289
294
|
export type {
|
|
@@ -292,4 +297,4 @@ export type {
|
|
|
292
297
|
CardContentProps,
|
|
293
298
|
CardFooterProps,
|
|
294
299
|
CardComponent,
|
|
295
|
-
} from
|
|
300
|
+
} from "./card.types";
|
package/libs/chunk-OU52NIKA.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { a } from './chunk-DDSXKOUB.js';
|
|
2
|
-
import p from 'react';
|
|
3
|
-
|
|
4
|
-
function i(...e){return e.filter(Boolean).join(" ")}var C={CARD:"card",TITLE:"card-title",CONTENT:"card-content",FOOTER:"card-footer"};function y(e,r){r&&(e.key==="Enter"||e.key===" ")&&(e.preventDefault(),r(e));}var m=({children:e,className:r,style:a$1,as:o="h3",...t})=>p.createElement(a,{as:o,className:i(C.TITLE,r),styles:a$1,...t},e);m.displayName="Card.Title";var f=({children:e,className:r,style:a$1,as:o="article",...t})=>p.createElement(a,{as:o,className:i(C.CONTENT,r),styles:a$1,...t},e);f.displayName="Card.Content";var b=({children:e,className:r,style:a$1,as:o="div",...t})=>p.createElement(a,{as:o,className:i(C.FOOTER,r),styles:a$1,...t},e);b.displayName="Card.Footer";var x=({as:e="div",styles:r,children:a$1,classes:o="shadow-sm",id:t,interactive:s=!1,onClick:n,tabIndex:u,role:c,"aria-label":T,"aria-labelledby":E,"aria-describedby":P,...N})=>(p.useEffect(()=>{},[n,s]),p.createElement(a,{as:e,id:t,styles:r,className:o,"aria-label":T,"aria-labelledby":E,"aria-describedby":P,"data-card":s?"interactive":!0,...s?{role:c||"button",tabIndex:u??0,onClick:n,onKeyDown:v=>{(s||n)&&y(v,n);}}:{role:c,onClick:n},...N},a$1)),l=x;l.displayName="Card";l.Title=m;l.Content=f;l.Footer=b;var h=l;
|
|
5
|
-
|
|
6
|
-
export { m as a, f as b, b as c, l as d, h as e };
|
|
7
|
-
//# sourceMappingURL=out.js.map
|
|
8
|
-
//# sourceMappingURL=chunk-OU52NIKA.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/cards/card.tsx","../src/components/cards/card.utils.ts"],"names":["React","cn","classes","CARD_CLASSES","handleCardKeyDown","event","onClick","Title","children","className","style","as","props","ui_default","Content","Footer","CardRoot","styles","id","interactive","tabIndex","role","ariaLabel","ariaLabelledBy","ariaDescribedBy","Card","card_default"],"mappings":"wCAAA,OAAOA,MAAW,QCoBX,SAASC,KAAMC,EAAwD,CAC5E,OAAOA,EAAQ,OAAO,OAAO,EAAE,KAAK,GAAG,CACzC,CAMO,IAAMC,EAAe,CAC1B,KAAM,OACN,MAAO,aACP,QAAS,eACT,OAAQ,aACV,EAcO,SAASC,EACdC,EACAC,EACM,CACDA,IAGDD,EAAM,MAAQ,SAAWA,EAAM,MAAQ,OACzCA,EAAM,eAAe,EACrBC,EAAQD,CAAK,EAEjB,CDxBO,IAAME,EAAQ,CAAC,CACpB,SAAAC,EACA,UAAAC,EACA,MAAAC,EACA,GAAAC,EAAK,KACL,GAAGC,CACL,IAEIZ,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,UAAWV,EAAGE,EAAa,MAAOM,CAAS,EAC3C,OAAQC,EACP,GAAGE,GAEHJ,CACH,EAIJD,EAAM,YAAc,aA+Bb,IAAMO,EAAU,CAAC,CACtB,SAAAN,EACA,UAAAC,EACA,MAAAC,EACA,GAAAC,EAAK,UACL,GAAGC,CACL,IAEIZ,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,UAAWV,EAAGE,EAAa,QAASM,CAAS,EAC7C,OAAQC,EACP,GAAGE,GAEHJ,CACH,EAIJM,EAAQ,YAAc,eA4Bf,IAAMC,EAAS,CAAC,CACrB,SAAAP,EACA,UAAAC,EACA,MAAAC,EACA,GAAAC,EAAK,MACL,GAAGC,CACL,IAEIZ,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,UAAWV,EAAGE,EAAa,OAAQM,CAAS,EAC5C,OAAQC,EACP,GAAGE,GAEHJ,CACH,EAIJO,EAAO,YAAc,cAuErB,IAAMC,EAAW,CAAC,CAChB,GAAAL,EAAK,MACL,OAAAM,EACA,SAAAT,EACA,QAAAN,EAAU,YACV,GAAAgB,EACA,YAAAC,EAAc,GACd,QAAAb,EACA,SAAAc,EACA,KAAAC,EACA,aAAcC,EACd,kBAAmBC,EACnB,mBAAoBC,EACpB,GAAGZ,CACL,KAEEZ,EAAM,UAAU,IAAM,CAEtB,EAAG,CAACM,EAASa,CAAW,CAAC,EAsBvBnB,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,GAAIO,EACJ,OAAQD,EACR,UAAWf,EACX,aAAYoB,EACZ,kBAAiBC,EACjB,mBAAkBC,EAClB,YAAWL,EAAc,cAAgB,GACxC,GAtBoBA,EACrB,CACE,KAAME,GAAQ,SACd,SAAUD,GAAY,EACtB,QAAAd,EACA,UAXiBD,GAA+B,EAChDc,GAAeb,IACjBF,EAAkBC,EAAOC,CAAO,CAEpC,CAQI,EACA,CACE,KAAAe,EACA,QAAAf,CACF,EAaC,GAAGM,GAEHJ,CACH,GAKSiB,EAAOT,EACpBS,EAAK,YAAc,OACnBA,EAAK,MAAQlB,EACbkB,EAAK,QAAUX,EACfW,EAAK,OAASV,EAEd,IAAOW,EAAQD","sourcesContent":["import React from 'react'\nimport UI from '#components/ui'\nimport type {\n CardProps,\n CardTitleProps,\n CardContentProps,\n CardFooterProps,\n CardComponent,\n} from './card.types'\nimport { cn, CARD_CLASSES, handleCardKeyDown, warnInteractiveUsage } from './card.utils'\n\n/**\n * Card.Title - Title sub-component for Card\n *\n * Renders a heading element for the card title. Defaults to h3 for proper\n * document outline, but can be customized via the `as` prop.\n *\n * ## Accessibility\n * - Use appropriate heading level based on document structure\n * - Combine with `aria-labelledby` on parent Card for accessible names\n *\n * @example\n * ```tsx\n * <Card aria-labelledby=\"card-title-1\">\n * <Card.Title id=\"card-title-1\">Featured Product</Card.Title>\n * </Card>\n * ```\n *\n * @example\n * ```tsx\n * // Custom heading level\n * <Card.Title as=\"h2\">Section Title</Card.Title>\n * ```\n */\nexport const Title = ({\n children,\n className,\n style,\n as = 'h3',\n ...props\n}: CardTitleProps) => {\n return (\n <UI\n as={as}\n className={cn(CARD_CLASSES.TITLE, className)}\n styles={style}\n {...props}\n >\n {children}\n </UI>\n )\n}\n\nTitle.displayName = 'Card.Title'\n\n/**\n * Card.Content - Content sub-component for Card\n *\n * Renders the main content area of the card. Defaults to `<article>` for\n * standalone content, but can be changed to `div` or `section` via the `as` prop.\n *\n * ## Semantic HTML Guidelines\n * - Use `article` (default) for self-contained, syndicate-able content\n * - Use `div` for generic content containers\n * - Use `section` for thematic groupings with a heading\n *\n * @example\n * ```tsx\n * <Card>\n * <Card.Title>Article Title</Card.Title>\n * <Card.Content>\n * <p>This is standalone content...</p>\n * </Card.Content>\n * </Card>\n * ```\n *\n * @example\n * ```tsx\n * // Generic container (not standalone content)\n * <Card.Content as=\"div\">\n * <p>Generic content...</p>\n * </Card.Content>\n * ```\n */\nexport const Content = ({\n children,\n className,\n style,\n as = 'article',\n ...props\n}: CardContentProps) => {\n return (\n <UI\n as={as}\n className={cn(CARD_CLASSES.CONTENT, className)}\n styles={style}\n {...props}\n >\n {children}\n </UI>\n )\n}\n\nContent.displayName = 'Card.Content'\n\n/**\n * Card.Footer - Footer sub-component for Card\n *\n * Renders a footer section for the card. Use for actions, metadata, or\n * supplementary information.\n *\n * @example\n * ```tsx\n * <Card>\n * <Card.Title>Product</Card.Title>\n * <Card.Content>Description...</Card.Content>\n * <Card.Footer>\n * <button>Add to Cart</button>\n * <span>$29.99</span>\n * </Card.Footer>\n * </Card>\n * ```\n *\n * @example\n * ```tsx\n * // Semantic footer element\n * <Card.Footer as=\"footer\">\n * <p>Last updated: 2024-01-15</p>\n * </Card.Footer>\n * ```\n */\nexport const Footer = ({\n children,\n className,\n style,\n as = 'div',\n ...props\n}: CardFooterProps) => {\n return (\n <UI\n as={as}\n className={cn(CARD_CLASSES.FOOTER, className)}\n styles={style}\n {...props}\n >\n {children}\n </UI>\n )\n}\n\nFooter.displayName = 'Card.Footer'\n\n/**\n * Card - A flexible, accessible card component with compound component pattern\n *\n * The Card component provides a container for grouping related content and actions.\n * It follows the compound component pattern, exposing `Card.Title`, `Card.Content`,\n * and `Card.Footer` sub-components for structured layouts.\n *\n * ## Features\n * - **Polymorphic rendering**: Render as any HTML element via `as` prop\n * - **Compound components**: Structured sub-components for consistent layouts\n * - **Interactive variant**: Built-in keyboard navigation and ARIA support\n * - **Fully accessible**: WCAG 2.1 AA compliant with proper semantic HTML\n *\n * ## Accessibility\n *\n * ### Non-Interactive Cards\n * - Use semantic HTML: `article` for standalone content, `section` for groupings\n * - Provide accessible names with `aria-labelledby` referencing the title\n *\n * ### Interactive Cards (Clickable)\n * - Set `interactive={true}` to enable keyboard navigation (Enter/Space)\n * - Provide accessible name via `aria-label` or `aria-labelledby`\n * - Ensure adequate focus indicators (handled by CSS)\n *\n * @example\n * // Basic card with compound components\n * ```tsx\n * <Card>\n * <Card.Title>Product Name</Card.Title>\n * <Card.Content>\n * <p>Product description goes here...</p>\n * </Card.Content>\n * <Card.Footer>\n * <button>Buy Now</button>\n * </Card.Footer>\n * </Card>\n * ```\n *\n * @example\n * // Accessible interactive card\n * ```tsx\n * <Card\n * interactive\n * aria-label=\"View product details\"\n * onClick={() => navigate('/product/123')}\n * >\n * <Card.Title>Product Name</Card.Title>\n * <Card.Content>Click anywhere to view details</Card.Content>\n * </Card>\n * ```\n *\n * @example\n * // Semantic article card with accessible name\n * ```tsx\n * <Card as=\"article\" aria-labelledby=\"article-title\">\n * <Card.Title id=\"article-title\">Article Headline</Card.Title>\n * <Card.Content>Article body...</Card.Content>\n * </Card>\n * ```\n *\n * @example\n * // Card as a landmark region\n * ```tsx\n * <Card role=\"region\" aria-label=\"Featured products\">\n * <Card.Title>Featured</Card.Title>\n * <Card.Content>...</Card.Content>\n * </Card>\n * ```\n */\nconst CardRoot = ({\n as = 'div',\n styles,\n children,\n classes = 'shadow-sm',\n id,\n interactive = false,\n onClick,\n tabIndex,\n role,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n ...props\n}: CardProps) => {\n // Development warnings for common accessibility issues\n React.useEffect(() => {\n warnInteractiveUsage(!!onClick, interactive)\n }, [onClick, interactive])\n\n // Interactive card handling\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (interactive || onClick) {\n handleCardKeyDown(event, onClick)\n }\n }\n\n const interactiveProps = interactive\n ? {\n role: role || 'button',\n tabIndex: tabIndex ?? 0,\n onClick,\n onKeyDown: handleKeyDown,\n }\n : {\n role,\n onClick,\n }\n\n return (\n <UI\n as={as}\n id={id}\n styles={styles}\n className={classes}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={ariaDescribedBy}\n data-card={interactive ? 'interactive' : true}\n {...interactiveProps}\n {...props}\n >\n {children}\n </UI>\n )\n}\n\n// Create compound component with proper TypeScript typing\nexport const Card = CardRoot as CardComponent\nCard.displayName = 'Card'\nCard.Title = Title\nCard.Content = Content\nCard.Footer = Footer\n\nexport default Card\n\n// Export types for external consumption\nexport type {\n CardProps,\n CardTitleProps,\n CardContentProps,\n CardFooterProps,\n CardComponent,\n} from './card.types'\n","/**\n * Utility functions for the Card component.\n */\n\n/**\n * Combines multiple className strings into a single string, filtering out falsy values.\n *\n * This utility provides a cleaner alternative to template literals for className composition,\n * avoiding unnecessary string allocation on every render.\n *\n * @param classes - Array of class names (can include undefined/null/empty strings)\n * @returns Combined className string\n *\n * @example\n * ```tsx\n * cn('card-title', className) // \"card-title custom-class\"\n * cn('card-title', undefined) // \"card-title\"\n * cn('card-title', '', 'extra') // \"card-title extra\"\n * ```\n */\nexport function cn(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(' ')\n}\n\n/**\n * CSS class name constants for Card components.\n * Centralizing these prevents typos and enables easier refactoring.\n */\nexport const CARD_CLASSES = {\n CARD: 'card',\n TITLE: 'card-title',\n CONTENT: 'card-content',\n FOOTER: 'card-footer',\n} as const\n\n/**\n * Handles keyboard events for interactive cards.\n * Triggers click handler on Enter or Space key press.\n *\n * @param event - Keyboard event\n * @param onClick - Click handler to invoke\n *\n * @example\n * ```tsx\n * <div onKeyDown={(e) => handleCardKeyDown(e, handleClick)}>\n * ```\n */\nexport function handleCardKeyDown(\n event: React.KeyboardEvent,\n onClick?: (event: React.MouseEvent | React.KeyboardEvent) => void\n): void {\n if (!onClick) return\n\n // Activate on Enter or Space (standard keyboard interaction pattern)\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault() // Prevent page scroll on Space\n onClick(event)\n }\n}\n\n/**\n * Development warning for interactive card usage.\n * Warns when onClick is provided without the interactive prop.\n *\n * This function is intentionally empty to comply with no-console linting rules.\n * In the future, consider using a proper logging/warning system.\n *\n * @param hasOnClick - Whether onClick handler is provided\n * @param isInteractive - Whether interactive prop is set\n */\nexport function warnInteractiveUsage(\n hasOnClick: boolean,\n isInteractive?: boolean\n): void {\n // Development warning removed to comply with ESLint no-console rule\n // TODO: Consider using a proper warning system if needed\n void hasOnClick\n void isInteractive\n}\n"]}
|
package/libs/chunk-WWPLBWCQ.cjs
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkEJ6KYBFE_cjs = require('./chunk-EJ6KYBFE.cjs');
|
|
4
|
-
var p = require('react');
|
|
5
|
-
|
|
6
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
-
|
|
8
|
-
var p__default = /*#__PURE__*/_interopDefault(p);
|
|
9
|
-
|
|
10
|
-
function i(...e){return e.filter(Boolean).join(" ")}var C={CARD:"card",TITLE:"card-title",CONTENT:"card-content",FOOTER:"card-footer"};function y(e,r){r&&(e.key==="Enter"||e.key===" ")&&(e.preventDefault(),r(e));}var m=({children:e,className:r,style:a,as:o="h3",...t})=>p__default.default.createElement(chunkEJ6KYBFE_cjs.a,{as:o,className:i(C.TITLE,r),styles:a,...t},e);m.displayName="Card.Title";var f=({children:e,className:r,style:a,as:o="article",...t})=>p__default.default.createElement(chunkEJ6KYBFE_cjs.a,{as:o,className:i(C.CONTENT,r),styles:a,...t},e);f.displayName="Card.Content";var b=({children:e,className:r,style:a,as:o="div",...t})=>p__default.default.createElement(chunkEJ6KYBFE_cjs.a,{as:o,className:i(C.FOOTER,r),styles:a,...t},e);b.displayName="Card.Footer";var x=({as:e="div",styles:r,children:a,classes:o="shadow-sm",id:t,interactive:s=!1,onClick:n,tabIndex:u,role:c,"aria-label":T,"aria-labelledby":E,"aria-describedby":P,...N})=>(p__default.default.useEffect(()=>{},[n,s]),p__default.default.createElement(chunkEJ6KYBFE_cjs.a,{as:e,id:t,styles:r,className:o,"aria-label":T,"aria-labelledby":E,"aria-describedby":P,"data-card":s?"interactive":!0,...s?{role:c||"button",tabIndex:u??0,onClick:n,onKeyDown:v=>{(s||n)&&y(v,n);}}:{role:c,onClick:n},...N},a)),l=x;l.displayName="Card";l.Title=m;l.Content=f;l.Footer=b;var h=l;
|
|
11
|
-
|
|
12
|
-
exports.a = m;
|
|
13
|
-
exports.b = f;
|
|
14
|
-
exports.c = b;
|
|
15
|
-
exports.d = l;
|
|
16
|
-
exports.e = h;
|
|
17
|
-
//# sourceMappingURL=out.js.map
|
|
18
|
-
//# sourceMappingURL=chunk-WWPLBWCQ.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/cards/card.tsx","../src/components/cards/card.utils.ts"],"names":["React","cn","classes","CARD_CLASSES","handleCardKeyDown","event","onClick","Title","children","className","style","as","props","ui_default","Content","Footer","CardRoot","styles","id","interactive","tabIndex","role","ariaLabel","ariaLabelledBy","ariaDescribedBy","Card","card_default"],"mappings":"yCAAA,OAAOA,MAAW,QCoBX,SAASC,KAAMC,EAAwD,CAC5E,OAAOA,EAAQ,OAAO,OAAO,EAAE,KAAK,GAAG,CACzC,CAMO,IAAMC,EAAe,CAC1B,KAAM,OACN,MAAO,aACP,QAAS,eACT,OAAQ,aACV,EAcO,SAASC,EACdC,EACAC,EACM,CACDA,IAGDD,EAAM,MAAQ,SAAWA,EAAM,MAAQ,OACzCA,EAAM,eAAe,EACrBC,EAAQD,CAAK,EAEjB,CDxBO,IAAME,EAAQ,CAAC,CACpB,SAAAC,EACA,UAAAC,EACA,MAAAC,EACA,GAAAC,EAAK,KACL,GAAGC,CACL,IAEIZ,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,UAAWV,EAAGE,EAAa,MAAOM,CAAS,EAC3C,OAAQC,EACP,GAAGE,GAEHJ,CACH,EAIJD,EAAM,YAAc,aA+Bb,IAAMO,EAAU,CAAC,CACtB,SAAAN,EACA,UAAAC,EACA,MAAAC,EACA,GAAAC,EAAK,UACL,GAAGC,CACL,IAEIZ,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,UAAWV,EAAGE,EAAa,QAASM,CAAS,EAC7C,OAAQC,EACP,GAAGE,GAEHJ,CACH,EAIJM,EAAQ,YAAc,eA4Bf,IAAMC,EAAS,CAAC,CACrB,SAAAP,EACA,UAAAC,EACA,MAAAC,EACA,GAAAC,EAAK,MACL,GAAGC,CACL,IAEIZ,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,UAAWV,EAAGE,EAAa,OAAQM,CAAS,EAC5C,OAAQC,EACP,GAAGE,GAEHJ,CACH,EAIJO,EAAO,YAAc,cAuErB,IAAMC,EAAW,CAAC,CAChB,GAAAL,EAAK,MACL,OAAAM,EACA,SAAAT,EACA,QAAAN,EAAU,YACV,GAAAgB,EACA,YAAAC,EAAc,GACd,QAAAb,EACA,SAAAc,EACA,KAAAC,EACA,aAAcC,EACd,kBAAmBC,EACnB,mBAAoBC,EACpB,GAAGZ,CACL,KAEEZ,EAAM,UAAU,IAAM,CAEtB,EAAG,CAACM,EAASa,CAAW,CAAC,EAsBvBnB,EAAA,cAACa,EAAA,CACC,GAAIF,EACJ,GAAIO,EACJ,OAAQD,EACR,UAAWf,EACX,aAAYoB,EACZ,kBAAiBC,EACjB,mBAAkBC,EAClB,YAAWL,EAAc,cAAgB,GACxC,GAtBoBA,EACrB,CACE,KAAME,GAAQ,SACd,SAAUD,GAAY,EACtB,QAAAd,EACA,UAXiBD,GAA+B,EAChDc,GAAeb,IACjBF,EAAkBC,EAAOC,CAAO,CAEpC,CAQI,EACA,CACE,KAAAe,EACA,QAAAf,CACF,EAaC,GAAGM,GAEHJ,CACH,GAKSiB,EAAOT,EACpBS,EAAK,YAAc,OACnBA,EAAK,MAAQlB,EACbkB,EAAK,QAAUX,EACfW,EAAK,OAASV,EAEd,IAAOW,EAAQD","sourcesContent":["import React from 'react'\nimport UI from '#components/ui'\nimport type {\n CardProps,\n CardTitleProps,\n CardContentProps,\n CardFooterProps,\n CardComponent,\n} from './card.types'\nimport { cn, CARD_CLASSES, handleCardKeyDown, warnInteractiveUsage } from './card.utils'\n\n/**\n * Card.Title - Title sub-component for Card\n *\n * Renders a heading element for the card title. Defaults to h3 for proper\n * document outline, but can be customized via the `as` prop.\n *\n * ## Accessibility\n * - Use appropriate heading level based on document structure\n * - Combine with `aria-labelledby` on parent Card for accessible names\n *\n * @example\n * ```tsx\n * <Card aria-labelledby=\"card-title-1\">\n * <Card.Title id=\"card-title-1\">Featured Product</Card.Title>\n * </Card>\n * ```\n *\n * @example\n * ```tsx\n * // Custom heading level\n * <Card.Title as=\"h2\">Section Title</Card.Title>\n * ```\n */\nexport const Title = ({\n children,\n className,\n style,\n as = 'h3',\n ...props\n}: CardTitleProps) => {\n return (\n <UI\n as={as}\n className={cn(CARD_CLASSES.TITLE, className)}\n styles={style}\n {...props}\n >\n {children}\n </UI>\n )\n}\n\nTitle.displayName = 'Card.Title'\n\n/**\n * Card.Content - Content sub-component for Card\n *\n * Renders the main content area of the card. Defaults to `<article>` for\n * standalone content, but can be changed to `div` or `section` via the `as` prop.\n *\n * ## Semantic HTML Guidelines\n * - Use `article` (default) for self-contained, syndicate-able content\n * - Use `div` for generic content containers\n * - Use `section` for thematic groupings with a heading\n *\n * @example\n * ```tsx\n * <Card>\n * <Card.Title>Article Title</Card.Title>\n * <Card.Content>\n * <p>This is standalone content...</p>\n * </Card.Content>\n * </Card>\n * ```\n *\n * @example\n * ```tsx\n * // Generic container (not standalone content)\n * <Card.Content as=\"div\">\n * <p>Generic content...</p>\n * </Card.Content>\n * ```\n */\nexport const Content = ({\n children,\n className,\n style,\n as = 'article',\n ...props\n}: CardContentProps) => {\n return (\n <UI\n as={as}\n className={cn(CARD_CLASSES.CONTENT, className)}\n styles={style}\n {...props}\n >\n {children}\n </UI>\n )\n}\n\nContent.displayName = 'Card.Content'\n\n/**\n * Card.Footer - Footer sub-component for Card\n *\n * Renders a footer section for the card. Use for actions, metadata, or\n * supplementary information.\n *\n * @example\n * ```tsx\n * <Card>\n * <Card.Title>Product</Card.Title>\n * <Card.Content>Description...</Card.Content>\n * <Card.Footer>\n * <button>Add to Cart</button>\n * <span>$29.99</span>\n * </Card.Footer>\n * </Card>\n * ```\n *\n * @example\n * ```tsx\n * // Semantic footer element\n * <Card.Footer as=\"footer\">\n * <p>Last updated: 2024-01-15</p>\n * </Card.Footer>\n * ```\n */\nexport const Footer = ({\n children,\n className,\n style,\n as = 'div',\n ...props\n}: CardFooterProps) => {\n return (\n <UI\n as={as}\n className={cn(CARD_CLASSES.FOOTER, className)}\n styles={style}\n {...props}\n >\n {children}\n </UI>\n )\n}\n\nFooter.displayName = 'Card.Footer'\n\n/**\n * Card - A flexible, accessible card component with compound component pattern\n *\n * The Card component provides a container for grouping related content and actions.\n * It follows the compound component pattern, exposing `Card.Title`, `Card.Content`,\n * and `Card.Footer` sub-components for structured layouts.\n *\n * ## Features\n * - **Polymorphic rendering**: Render as any HTML element via `as` prop\n * - **Compound components**: Structured sub-components for consistent layouts\n * - **Interactive variant**: Built-in keyboard navigation and ARIA support\n * - **Fully accessible**: WCAG 2.1 AA compliant with proper semantic HTML\n *\n * ## Accessibility\n *\n * ### Non-Interactive Cards\n * - Use semantic HTML: `article` for standalone content, `section` for groupings\n * - Provide accessible names with `aria-labelledby` referencing the title\n *\n * ### Interactive Cards (Clickable)\n * - Set `interactive={true}` to enable keyboard navigation (Enter/Space)\n * - Provide accessible name via `aria-label` or `aria-labelledby`\n * - Ensure adequate focus indicators (handled by CSS)\n *\n * @example\n * // Basic card with compound components\n * ```tsx\n * <Card>\n * <Card.Title>Product Name</Card.Title>\n * <Card.Content>\n * <p>Product description goes here...</p>\n * </Card.Content>\n * <Card.Footer>\n * <button>Buy Now</button>\n * </Card.Footer>\n * </Card>\n * ```\n *\n * @example\n * // Accessible interactive card\n * ```tsx\n * <Card\n * interactive\n * aria-label=\"View product details\"\n * onClick={() => navigate('/product/123')}\n * >\n * <Card.Title>Product Name</Card.Title>\n * <Card.Content>Click anywhere to view details</Card.Content>\n * </Card>\n * ```\n *\n * @example\n * // Semantic article card with accessible name\n * ```tsx\n * <Card as=\"article\" aria-labelledby=\"article-title\">\n * <Card.Title id=\"article-title\">Article Headline</Card.Title>\n * <Card.Content>Article body...</Card.Content>\n * </Card>\n * ```\n *\n * @example\n * // Card as a landmark region\n * ```tsx\n * <Card role=\"region\" aria-label=\"Featured products\">\n * <Card.Title>Featured</Card.Title>\n * <Card.Content>...</Card.Content>\n * </Card>\n * ```\n */\nconst CardRoot = ({\n as = 'div',\n styles,\n children,\n classes = 'shadow-sm',\n id,\n interactive = false,\n onClick,\n tabIndex,\n role,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n ...props\n}: CardProps) => {\n // Development warnings for common accessibility issues\n React.useEffect(() => {\n warnInteractiveUsage(!!onClick, interactive)\n }, [onClick, interactive])\n\n // Interactive card handling\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (interactive || onClick) {\n handleCardKeyDown(event, onClick)\n }\n }\n\n const interactiveProps = interactive\n ? {\n role: role || 'button',\n tabIndex: tabIndex ?? 0,\n onClick,\n onKeyDown: handleKeyDown,\n }\n : {\n role,\n onClick,\n }\n\n return (\n <UI\n as={as}\n id={id}\n styles={styles}\n className={classes}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={ariaDescribedBy}\n data-card={interactive ? 'interactive' : true}\n {...interactiveProps}\n {...props}\n >\n {children}\n </UI>\n )\n}\n\n// Create compound component with proper TypeScript typing\nexport const Card = CardRoot as CardComponent\nCard.displayName = 'Card'\nCard.Title = Title\nCard.Content = Content\nCard.Footer = Footer\n\nexport default Card\n\n// Export types for external consumption\nexport type {\n CardProps,\n CardTitleProps,\n CardContentProps,\n CardFooterProps,\n CardComponent,\n} from './card.types'\n","/**\n * Utility functions for the Card component.\n */\n\n/**\n * Combines multiple className strings into a single string, filtering out falsy values.\n *\n * This utility provides a cleaner alternative to template literals for className composition,\n * avoiding unnecessary string allocation on every render.\n *\n * @param classes - Array of class names (can include undefined/null/empty strings)\n * @returns Combined className string\n *\n * @example\n * ```tsx\n * cn('card-title', className) // \"card-title custom-class\"\n * cn('card-title', undefined) // \"card-title\"\n * cn('card-title', '', 'extra') // \"card-title extra\"\n * ```\n */\nexport function cn(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(' ')\n}\n\n/**\n * CSS class name constants for Card components.\n * Centralizing these prevents typos and enables easier refactoring.\n */\nexport const CARD_CLASSES = {\n CARD: 'card',\n TITLE: 'card-title',\n CONTENT: 'card-content',\n FOOTER: 'card-footer',\n} as const\n\n/**\n * Handles keyboard events for interactive cards.\n * Triggers click handler on Enter or Space key press.\n *\n * @param event - Keyboard event\n * @param onClick - Click handler to invoke\n *\n * @example\n * ```tsx\n * <div onKeyDown={(e) => handleCardKeyDown(e, handleClick)}>\n * ```\n */\nexport function handleCardKeyDown(\n event: React.KeyboardEvent,\n onClick?: (event: React.MouseEvent | React.KeyboardEvent) => void\n): void {\n if (!onClick) return\n\n // Activate on Enter or Space (standard keyboard interaction pattern)\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault() // Prevent page scroll on Space\n onClick(event)\n }\n}\n\n/**\n * Development warning for interactive card usage.\n * Warns when onClick is provided without the interactive prop.\n *\n * This function is intentionally empty to comply with no-console linting rules.\n * In the future, consider using a proper logging/warning system.\n *\n * @param hasOnClick - Whether onClick handler is provided\n * @param isInteractive - Whether interactive prop is set\n */\nexport function warnInteractiveUsage(\n hasOnClick: boolean,\n isInteractive?: boolean\n): void {\n // Development warning removed to comply with ESLint no-console rule\n // TODO: Consider using a proper warning system if needed\n void hasOnClick\n void isInteractive\n}\n"]}
|