@jiwambe/components 0.3.0 → 0.3.2
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/README.md +84 -0
- package/dist/components/Box/Box.d.ts +10 -32
- package/dist/components/Box/Box.d.ts.map +1 -1
- package/dist/components/Box/Box.js +9 -13
- package/dist/components/Box/Box.js.map +1 -1
- package/dist/components/Card/Card.d.ts.map +1 -1
- package/dist/components/Card/Card.js +33 -32
- package/dist/components/Card/Card.js.map +1 -1
- package/dist/components/FAQ/FAQ.d.ts.map +1 -1
- package/dist/components/FAQ/FAQ.js +7 -7
- package/dist/components/FAQ/FAQ.js.map +1 -1
- package/dist/components/List/List.d.ts +10 -9
- package/dist/components/List/List.d.ts.map +1 -1
- package/dist/components/List/List.js +20 -5
- package/dist/components/List/List.js.map +1 -1
- package/dist/components/List/index.d.ts +1 -1
- package/dist/components/List/index.d.ts.map +1 -1
- package/dist/components/Typography/Typography.d.ts +10 -293
- package/dist/components/Typography/Typography.d.ts.map +1 -1
- package/dist/components/Typography/Typography.js +6 -11
- package/dist/components/Typography/Typography.js.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/plugin/jiwambe-plugin.d.ts.map +1 -1
- package/dist/plugin/jiwambe-plugin.js +55 -2
- package/dist/plugin/jiwambe-plugin.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/types/index.d.ts +0 -6
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/list.d.ts +21 -2
- package/dist/types/list.d.ts.map +1 -1
- package/dist/types/list.js.map +1 -1
- package/package.json +18 -17
package/README.md
CHANGED
|
@@ -114,6 +114,7 @@ import { Box, Stack, Container, Grid } from '@jiwambe/components';
|
|
|
114
114
|
| space-36 | var(--space-36) | 9rem |
|
|
115
115
|
| space-48 | var(--space-48) | 12rem |
|
|
116
116
|
| space-72 | var(--space-72) | 18rem |
|
|
117
|
+
| space-list-indent | var(--space-list-indent) | 1.6875rem (27px) |
|
|
117
118
|
| fluid-1-2 | var(--space-fluid-1-2) | clamp(0.25rem, 0.1706rem + 0.3968vw, 0.5rem) |
|
|
118
119
|
| fluid-2-4 | var(--space-fluid-2-4) | clamp(0.5rem, 0.3413rem + 0.7937vw, 1rem) |
|
|
119
120
|
| fluid-4-5 | var(--space-fluid-4-5) | clamp(1rem, 0.9206rem + 0.3968vw, 1.25rem) |
|
|
@@ -260,6 +261,89 @@ Horizontal tabs with full ARIA support.
|
|
|
260
261
|
| `activeIndex` | `number` | — | Active tab index (controlled). |
|
|
261
262
|
| `onTabChange` | `(index: number) => void` | — | Called when the active tab changes (controlled). |
|
|
262
263
|
|
|
264
|
+
### List
|
|
265
|
+
|
|
266
|
+
Semantic list with correct `ul`/`ol` structure. For `marker="disc"` and `marker="decimal"` uses block layout and item margin (not flex/gap) so markers render reliably. Use `indent` and `itemPadding="none"` for legal/body-copy lists.
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
<List spacing="space-2">
|
|
270
|
+
<List.Item size="sm">Item one</List.Item>
|
|
271
|
+
<List.Item size="sm">Item two</List.Item>
|
|
272
|
+
</List>
|
|
273
|
+
|
|
274
|
+
<List marker="decimal" spacing="space-3">
|
|
275
|
+
<List.Item size="md">Step one</List.Item>
|
|
276
|
+
<List.Item size="md">Step two</List.Item>
|
|
277
|
+
</List>
|
|
278
|
+
|
|
279
|
+
{/* Legal/body-copy: 27px indent, no extra item padding */}
|
|
280
|
+
<List indent="space-list-indent" itemPadding="none" spacing="space-2">
|
|
281
|
+
<List.Item size="md">First term.</List.Item>
|
|
282
|
+
<List.Item size="md">Second term.</List.Item>
|
|
283
|
+
</List>
|
|
284
|
+
|
|
285
|
+
<List marker="none" spacing="space-1">
|
|
286
|
+
<List.Item><Link href="/about">About</Link></List.Item>
|
|
287
|
+
</List>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Props:**
|
|
291
|
+
|
|
292
|
+
| Prop | Type | Default | Description |
|
|
293
|
+
| --- | --- | --- | --- |
|
|
294
|
+
| `marker` | `"disc" \| "decimal" \| "none"` | `"disc"` | Marker style; `decimal` renders `<ol>`. |
|
|
295
|
+
| `spacing` | `SpacingToken` | `"space-2"` | Vertical spacing between items (margin for disc/decimal, gap for none). |
|
|
296
|
+
| `indent` | `SpacingToken \| string` | — | Left indent (e.g. `"space-6"`, `"space-list-indent"` for 27px, or `"27px"`). |
|
|
297
|
+
| `itemPadding` | `"default" \| "none"` | `"default"` | Item left padding; `"none"` for body-copy lists. |
|
|
298
|
+
|
|
299
|
+
### Card
|
|
300
|
+
|
|
301
|
+
Card with optional image, title, description, and action. Aligned with Figma (web · library): media cards use secondary background, fluid padding/gaps, title-md/text-md typography, and 48px action button with rounded-rad-md.
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
<Card type="text-only" title="Title" description="Description." label="Action" onLabelClick={() => {}} />
|
|
305
|
+
|
|
306
|
+
<Card
|
|
307
|
+
type="media-horizontal"
|
|
308
|
+
imageSrc="/img.jpg"
|
|
309
|
+
imageAlt="Product"
|
|
310
|
+
title="Product"
|
|
311
|
+
description="Description."
|
|
312
|
+
label="View"
|
|
313
|
+
labelHref="/product"
|
|
314
|
+
/>
|
|
315
|
+
|
|
316
|
+
<Card
|
|
317
|
+
type="media-horizontal"
|
|
318
|
+
imageSrc="/img.jpg"
|
|
319
|
+
imageAlt="Product"
|
|
320
|
+
message="Special Offer"
|
|
321
|
+
title="Product"
|
|
322
|
+
description="Description."
|
|
323
|
+
label="Get started"
|
|
324
|
+
/>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
**Props:** `type` (text-only | media-horizontal | media-vertical), `imageSrc`, `imageAlt`, `message` (overlay on image), `title`, `description`, `label`, `onLabelClick`, `labelHref`, `linkAs`.
|
|
328
|
+
|
|
329
|
+
### Breadcrumb
|
|
330
|
+
|
|
331
|
+
Breadcrumb navigation with slash or chevron separator. Optional truncation for long paths (first … second-to-last / current).
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
<Breadcrumb
|
|
335
|
+
items={[
|
|
336
|
+
{ label: 'Home', href: '/' },
|
|
337
|
+
{ label: 'Products', href: '/products' },
|
|
338
|
+
{ label: 'Detail' },
|
|
339
|
+
]}
|
|
340
|
+
/>
|
|
341
|
+
|
|
342
|
+
<Breadcrumb items={manyItems} truncate separator="slash" />
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Props:** `items` (array of `{ label, href? }`), `truncate`, `linkVariant`, `separator` (`"slash"` \| `"chevron"`).
|
|
346
|
+
|
|
263
347
|
### Grid
|
|
264
348
|
|
|
265
349
|
CSS Grid layout with responsive column counts.
|
|
@@ -6,10 +6,9 @@ type SystemPropsWithoutSpacing = Omit<SystemProps, 'p' | 'px' | 'py' | 'pt' | 'p
|
|
|
6
6
|
/** Omit display so LayoutProps (class-based) takes precedence over SystemProps (inline). */
|
|
7
7
|
type SystemPropsWithoutSpacingAndDisplay = Omit<SystemPropsWithoutSpacing, 'display'>;
|
|
8
8
|
/**
|
|
9
|
-
* Props for the Box component
|
|
10
|
-
* (dimensions, position, flex), SpacingProps, and LayoutProps.
|
|
9
|
+
* Props for the Box component using complete polymorphic typings.
|
|
11
10
|
*/
|
|
12
|
-
export
|
|
11
|
+
export type BoxProps<C extends React.ElementType> = {
|
|
13
12
|
/**
|
|
14
13
|
* The HTML element or React component to render as the root element.
|
|
15
14
|
* Accepts any valid HTML tag string ('div', 'section', 'article',
|
|
@@ -20,37 +19,16 @@ export interface BoxProps extends Omit<React.HTMLAttributes<HTMLElement>, 'displ
|
|
|
20
19
|
* design system layout control.
|
|
21
20
|
* @default 'div'
|
|
22
21
|
*/
|
|
23
|
-
as?:
|
|
24
|
-
/** Forwarded ref for the root element. @default undefined */
|
|
25
|
-
ref?: React.Ref<HTMLElement>;
|
|
22
|
+
as?: C;
|
|
26
23
|
/** Child content. @default undefined */
|
|
27
24
|
children?: React.ReactNode;
|
|
25
|
+
} & Omit<React.ComponentPropsWithoutRef<C>, keyof SystemPropsWithoutSpacingAndDisplay | keyof SpacingProps | keyof LayoutProps | 'as' | 'children'> & SystemPropsWithoutSpacingAndDisplay & SpacingProps & LayoutProps;
|
|
26
|
+
export interface BoxComponent {
|
|
27
|
+
<C extends React.ElementType = 'div'>(props: BoxProps<C> & {
|
|
28
|
+
ref?: React.ComponentPropsWithRef<C>['ref'];
|
|
29
|
+
}): React.ReactElement | null;
|
|
30
|
+
displayName?: string;
|
|
28
31
|
}
|
|
29
|
-
|
|
30
|
-
* General-purpose layout primitive. Renders any HTML element via the
|
|
31
|
-
* as prop (default: div). Use Box when you need box-model control
|
|
32
|
-
* (spacing, sizing) or display/alignment props on an arbitrary element.
|
|
33
|
-
* Prefer Stack for linear sequences, Grid for multi-column layouts,
|
|
34
|
-
* and Container for page-width constraints.
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* // Basic spacing
|
|
38
|
-
* <Box p="space-4" mt="space-8">Content</Box>
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* // Inline flex wrapper
|
|
42
|
-
* <Box as="span" display="inline-flex" alignItems="center">
|
|
43
|
-
* <Icon />
|
|
44
|
-
* Label
|
|
45
|
-
* </Box>
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* // Semantic section element
|
|
49
|
-
* <Box as="section" py="fluid-8-16">Content</Box>
|
|
50
|
-
*/
|
|
51
|
-
export declare function Box({ as: Tag, children, className, style, ref, ...allRest }: BoxProps): import("react/jsx-runtime").JSX.Element;
|
|
52
|
-
export declare namespace Box {
|
|
53
|
-
var displayName: string;
|
|
54
|
-
}
|
|
32
|
+
export declare const Box: BoxComponent;
|
|
55
33
|
export type { ResponsiveValue };
|
|
56
34
|
//# sourceMappingURL=Box.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Box.d.ts","sourceRoot":"","sources":["../../../src/components/Box/Box.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAItD,KAAK,yBAAyB,GAAG,IAAI,CACnC,WAAW,EACX,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAC9F,CAAC;AAEF,4FAA4F;AAC5F,KAAK,mCAAmC,GAAG,IAAI,CAAC,yBAAyB,EAAE,SAAS,CAAC,CAAC;AAEtF
|
|
1
|
+
{"version":3,"file":"Box.d.ts","sourceRoot":"","sources":["../../../src/components/Box/Box.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAItD,KAAK,yBAAyB,GAAG,IAAI,CACnC,WAAW,EACX,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAC9F,CAAC;AAEF,4FAA4F;AAC5F,KAAK,mCAAmC,GAAG,IAAI,CAAC,yBAAyB,EAAE,SAAS,CAAC,CAAC;AAEtF;;GAEG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,KAAK,CAAC,WAAW,IAAI;IAClD;;;;;;;;;OASG;IACH,EAAE,CAAC,EAAE,CAAC,CAAC;IACP,wCAAwC;IACxC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,GAAG,IAAI,CACN,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAC/B,MAAM,mCAAmC,GACzC,MAAM,YAAY,GAClB,MAAM,WAAW,GACjB,IAAI,GACJ,UAAU,CACb,GACC,mCAAmC,GACnC,YAAY,GACZ,WAAW,CAAC;AAEd,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,SAAS,KAAK,CAAC,WAAW,GAAG,KAAK,EAClC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG;QAAE,GAAG,CAAC,EAAE,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;KAAE,GACnE,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAiDD,eAAO,MAAM,GAAG,EAAmD,YAAY,CAAC;AAIhF,YAAY,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -3,14 +3,8 @@ import React from "react";
|
|
|
3
3
|
import { stripSystemProps, renderSystemProps } from "../../utils/responsive-props.js";
|
|
4
4
|
import { SPACING_PROP_KEYS, resolveSpacing } from "../../utils/spacing.js";
|
|
5
5
|
import { LAYOUT_PROP_KEYS, resolveLayout } from "../../utils/layoutClasses.js";
|
|
6
|
-
|
|
7
|
-
as: Tag = "div",
|
|
8
|
-
children,
|
|
9
|
-
className = "",
|
|
10
|
-
style,
|
|
11
|
-
ref,
|
|
12
|
-
...allRest
|
|
13
|
-
}) {
|
|
6
|
+
const BoxInner = (props, ref) => {
|
|
7
|
+
const { as: Tag = "div", children, className = "", style, ...allRest } = props;
|
|
14
8
|
const systemPropsOnly = { ...allRest };
|
|
15
9
|
for (const key of SPACING_PROP_KEYS) {
|
|
16
10
|
delete systemPropsOnly[key];
|
|
@@ -23,10 +17,11 @@ function Box({
|
|
|
23
17
|
for (const key of LAYOUT_PROP_KEYS) {
|
|
24
18
|
delete domRest[key];
|
|
25
19
|
}
|
|
26
|
-
const {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
const {
|
|
21
|
+
style: systemStyle,
|
|
22
|
+
styleTag,
|
|
23
|
+
dataId
|
|
24
|
+
} = renderSystemProps(systemPropsOnly, style);
|
|
30
25
|
const spacingClasses = resolveSpacing(allRest);
|
|
31
26
|
const layoutClasses = resolveLayout(allRest);
|
|
32
27
|
const mergedClassName = [layoutClasses, spacingClasses, className].filter(Boolean).join(" ");
|
|
@@ -43,7 +38,8 @@ function Box({
|
|
|
43
38
|
styleTag,
|
|
44
39
|
React.createElement(Tag, elementProps, children)
|
|
45
40
|
] });
|
|
46
|
-
}
|
|
41
|
+
};
|
|
42
|
+
const Box = React.forwardRef(BoxInner);
|
|
47
43
|
Box.displayName = "Box";
|
|
48
44
|
export {
|
|
49
45
|
Box
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Box.js","sources":["../../../src/components/Box/Box.tsx"],"sourcesContent":["import React from 'react';\nimport {\n renderSystemProps,\n stripSystemProps,\n type SystemProps,\n type ResponsiveValue,\n} from '../../utils/responsive-props';\nimport type { SpacingProps } from '../../types/spacing';\nimport type { LayoutProps } from '../../types/layout';\nimport { resolveSpacing, SPACING_PROP_KEYS } from '../../utils/spacing';\nimport { resolveLayout, LAYOUT_PROP_KEYS } from '../../utils/layoutClasses';\n\ntype SystemPropsWithoutSpacing = Omit<\n SystemProps,\n 'p' | 'px' | 'py' | 'pt' | 'pb' | 'pl' | 'pr' | 'm' | 'mx' | 'my' | 'mt' | 'mb' | 'ml' | 'mr'\n>;\n\n/** Omit display so LayoutProps (class-based) takes precedence over SystemProps (inline). */\ntype SystemPropsWithoutSpacingAndDisplay = Omit<SystemPropsWithoutSpacing, 'display'>;\n\n/**\n * Props for the Box component
|
|
1
|
+
{"version":3,"file":"Box.js","sources":["../../../src/components/Box/Box.tsx"],"sourcesContent":["import React from 'react';\nimport {\n renderSystemProps,\n stripSystemProps,\n type SystemProps,\n type ResponsiveValue,\n} from '../../utils/responsive-props';\nimport type { SpacingProps } from '../../types/spacing';\nimport type { LayoutProps } from '../../types/layout';\nimport { resolveSpacing, SPACING_PROP_KEYS } from '../../utils/spacing';\nimport { resolveLayout, LAYOUT_PROP_KEYS } from '../../utils/layoutClasses';\n\ntype SystemPropsWithoutSpacing = Omit<\n SystemProps,\n 'p' | 'px' | 'py' | 'pt' | 'pb' | 'pl' | 'pr' | 'm' | 'mx' | 'my' | 'mt' | 'mb' | 'ml' | 'mr'\n>;\n\n/** Omit display so LayoutProps (class-based) takes precedence over SystemProps (inline). */\ntype SystemPropsWithoutSpacingAndDisplay = Omit<SystemPropsWithoutSpacing, 'display'>;\n\n/**\n * Props for the Box component using complete polymorphic typings.\n */\nexport type BoxProps<C extends React.ElementType> = {\n /**\n * The HTML element or React component to render as the root element.\n * Accepts any valid HTML tag string ('div', 'section', 'article',\n * 'span', 'main', 'aside', 'header', 'footer', 'nav', 'ul', 'ol' etc.)\n * or a React component type.\n *\n * Use this to maintain correct semantic HTML without sacrificing\n * design system layout control.\n * @default 'div'\n */\n as?: C;\n /** Child content. @default undefined */\n children?: React.ReactNode;\n} & Omit<\n React.ComponentPropsWithoutRef<C>,\n | keyof SystemPropsWithoutSpacingAndDisplay\n | keyof SpacingProps\n | keyof LayoutProps\n | 'as'\n | 'children'\n> &\n SystemPropsWithoutSpacingAndDisplay &\n SpacingProps &\n LayoutProps;\n\nexport interface BoxComponent {\n <C extends React.ElementType = 'div'>(\n props: BoxProps<C> & { ref?: React.ComponentPropsWithRef<C>['ref'] },\n ): React.ReactElement | null;\n displayName?: string;\n}\n\nconst BoxInner = <C extends React.ElementType = 'div'>(\n props: BoxProps<C>,\n ref: React.ComponentPropsWithRef<C>['ref'],\n) => {\n const { as: Tag = 'div', children, className = '', style, ...allRest } = props as any;\n\n const systemPropsOnly = { ...allRest };\n for (const key of SPACING_PROP_KEYS) {\n delete (systemPropsOnly as Record<string, unknown>)[key];\n }\n for (const key of LAYOUT_PROP_KEYS) {\n delete (systemPropsOnly as Record<string, unknown>)[key];\n }\n const rest = stripSystemProps(allRest);\n const domRest = { ...rest };\n for (const key of LAYOUT_PROP_KEYS) {\n delete (domRest as Record<string, unknown>)[key];\n }\n const {\n style: systemStyle,\n styleTag,\n dataId,\n } = renderSystemProps(systemPropsOnly as SystemPropsWithoutSpacingAndDisplay, style);\n\n const spacingClasses = resolveSpacing(allRest);\n const layoutClasses = resolveLayout(allRest as LayoutProps);\n const mergedClassName = [layoutClasses, spacingClasses, className].filter(Boolean).join(' ');\n\n const elementProps: Record<string, unknown> = {\n ref,\n className: mergedClassName || undefined,\n style: systemStyle,\n ...domRest,\n };\n\n if (dataId) {\n elementProps['data-jds'] = dataId;\n }\n\n return (\n <>\n {styleTag}\n {React.createElement(Tag, elementProps, children)}\n </>\n );\n};\n\nexport const Box = React.forwardRef(BoxInner as any) as unknown as BoxComponent;\n\nBox.displayName = 'Box';\n\nexport type { ResponsiveValue };\n"],"names":[],"mappings":";;;;;AAwDA,MAAM,WAAW,CACf,OACA,QACG;AACH,QAAM,EAAE,IAAI,MAAM,OAAO,UAAU,YAAY,IAAI,OAAO,GAAG,QAAA,IAAY;AAEzE,QAAM,kBAAkB,EAAE,GAAG,QAAA;AAC7B,aAAW,OAAO,mBAAmB;AACnC,WAAQ,gBAA4C,GAAG;AAAA,EACzD;AACA,aAAW,OAAO,kBAAkB;AAClC,WAAQ,gBAA4C,GAAG;AAAA,EACzD;AACA,QAAM,OAAO,iBAAiB,OAAO;AACrC,QAAM,UAAU,EAAE,GAAG,KAAA;AACrB,aAAW,OAAO,kBAAkB;AAClC,WAAQ,QAAoC,GAAG;AAAA,EACjD;AACA,QAAM;AAAA,IACJ,OAAO;AAAA,IACP;AAAA,IACA;AAAA,EAAA,IACE,kBAAkB,iBAAwD,KAAK;AAEnF,QAAM,iBAAiB,eAAe,OAAO;AAC7C,QAAM,gBAAgB,cAAc,OAAsB;AAC1D,QAAM,kBAAkB,CAAC,eAAe,gBAAgB,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAE3F,QAAM,eAAwC;AAAA,IAC5C;AAAA,IACA,WAAW,mBAAmB;AAAA,IAC9B,OAAO;AAAA,IACP,GAAG;AAAA,EAAA;AAGL,MAAI,QAAQ;AACV,iBAAa,UAAU,IAAI;AAAA,EAC7B;AAEA,SACE,qBAAA,UAAA,EACG,UAAA;AAAA,IAAA;AAAA,IACA,MAAM,cAAc,KAAK,cAAc,QAAQ;AAAA,EAAA,GAClD;AAEJ;AAEO,MAAM,MAAM,MAAM,WAAW,QAAe;AAEnD,IAAI,cAAc;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Card.d.ts","sourceRoot":"","sources":["../../../src/components/Card/Card.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,kBAAkB,GAAG,gBAAgB,CAAC;AAE3E;;GAEG;AACH,MAAM,WAAW,SAAU,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACrE,8FAA8F;IAC9F,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,qGAAqG;IACrG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC;IAC3B,yDAAyD;IACzD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;CACjC;AAmFD;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAAC,EACnB,IAAkB,EAClB,QAAQ,EACR,QAAa,EACb,UAAgB,EAChB,WAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,OAAO,EACP,KAAK,EACL,WAAW,EACX,KAAK,EACL,YAAY,EACZ,SAAS,EACT,MAAM,EACN,SAAc,EACd,GAAG,EACH,GAAG,IAAI,EACR,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"Card.d.ts","sourceRoot":"","sources":["../../../src/components/Card/Card.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,WAAW,GAAG,kBAAkB,GAAG,gBAAgB,CAAC;AAE3E;;GAEG;AACH,MAAM,WAAW,SAAU,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACrE,8FAA8F;IAC9F,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,qGAAqG;IACrG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC;IAC3B,yDAAyD;IACzD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;CACjC;AAmFD;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAAC,EACnB,IAAkB,EAClB,QAAQ,EACR,QAAa,EACb,UAAgB,EAChB,WAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,OAAO,EACP,KAAK,EACL,WAAW,EACX,KAAK,EACL,YAAY,EACZ,SAAS,EACT,MAAM,EACN,SAAc,EACd,GAAG,EACH,GAAG,IAAI,EACR,EAAE,SAAS,2CAgDX"}
|
|
@@ -24,7 +24,7 @@ function CardImage({
|
|
|
24
24
|
className: "h-full w-full object-cover"
|
|
25
25
|
}
|
|
26
26
|
),
|
|
27
|
-
message && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-
|
|
27
|
+
message && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-fill-bg-dimmer", children: /* @__PURE__ */ jsx("span", { className: "text-title-lg text-text-primary-inverse text-center leading-[1.1] px-space-fluid-4-8", children: message }) })
|
|
28
28
|
] });
|
|
29
29
|
}
|
|
30
30
|
function CardAction({
|
|
@@ -33,7 +33,7 @@ function CardAction({
|
|
|
33
33
|
href,
|
|
34
34
|
linkAs
|
|
35
35
|
}) {
|
|
36
|
-
const classes = "inline-flex items-center justify-center rounded-rad-
|
|
36
|
+
const classes = "inline-flex items-center justify-center h-12 min-h-12 rounded-rad-md px-space-5 text-btn-reg bg-fill-action-primary text-text-action-primary hover:bg-fill-action-primary-hover active:bg-fill-action-primary-active transition-colors";
|
|
37
37
|
if (href) {
|
|
38
38
|
const LinkComponent = linkAs || "a";
|
|
39
39
|
return /* @__PURE__ */ jsx(LinkComponent, { href, className: classes, children: label });
|
|
@@ -61,36 +61,37 @@ function Card({
|
|
|
61
61
|
}) {
|
|
62
62
|
const isMedia = type !== "text-only";
|
|
63
63
|
const ratio = type === "media-vertical" ? "vertical" : "horizontal";
|
|
64
|
-
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
] })
|
|
91
|
-
]
|
|
92
|
-
|
|
93
|
-
|
|
64
|
+
const rootClasses = [
|
|
65
|
+
"flex flex-col rounded-rad-md overflow-hidden",
|
|
66
|
+
isMedia ? "bg-fill-bg-secondary pb-space-fluid-4-8 gap-space-fluid-4-6" : "bg-fill-bg-primary border border-border-light",
|
|
67
|
+
className
|
|
68
|
+
].filter(Boolean).join(" ");
|
|
69
|
+
const contentClasses = [
|
|
70
|
+
"flex flex-col text-text-primary",
|
|
71
|
+
isMedia ? "gap-space-fluid-4-6 px-space-fluid-4-8" : "gap-space-3 p-space-4"
|
|
72
|
+
].filter(Boolean).join(" ");
|
|
73
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: rootClasses, ...rest, children: [
|
|
74
|
+
isMedia && imageSrc && /* @__PURE__ */ jsx(
|
|
75
|
+
CardImage,
|
|
76
|
+
{
|
|
77
|
+
src: imageSrc,
|
|
78
|
+
alt: imageAlt,
|
|
79
|
+
ratio,
|
|
80
|
+
message,
|
|
81
|
+
width: imageWidth,
|
|
82
|
+
height: imageHeight,
|
|
83
|
+
quality: imageQuality,
|
|
84
|
+
unoptimized: imageUnoptimized
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
/* @__PURE__ */ jsxs("div", { className: contentClasses, children: [
|
|
88
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-space-1", children: [
|
|
89
|
+
/* @__PURE__ */ jsx("h3", { className: "text-title-md leading-[1.2] tracking-[0.19px]", children: title }),
|
|
90
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-text-md leading-[1.4]", children: description })
|
|
91
|
+
] }),
|
|
92
|
+
label && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(CardAction, { label, onClick: onLabelClick, href: labelHref, linkAs }) })
|
|
93
|
+
] })
|
|
94
|
+
] });
|
|
94
95
|
}
|
|
95
96
|
export {
|
|
96
97
|
Card
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Card.js","sources":["../../../src/components/Card/Card.tsx"],"sourcesContent":["import React from 'react';\nimport Image from 'next/image';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Card layout type. 'text-only' — no image; 'media-horizontal' — image above content, video aspect;\n * 'media-vertical' — image above content, 4:3 aspect.\n */\nexport type CardType = 'text-only' | 'media-horizontal' | 'media-vertical';\n\n/**\n * Props for the Card component. Title is required; image and action are optional.\n */\nexport interface CardProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Layout: text-only, or media with horizontal/vertical image aspect. @default 'text-only' */\n type?: CardType;\n /** Image source URL. Required when type is media-horizontal or media-vertical. @default undefined */\n imageSrc?: string;\n /** Image alt text for accessibility. @default '' */\n imageAlt?: string;\n /** Width for next/image. @default 600 */\n imageWidth?: number;\n /** Height for next/image. @default 400 */\n imageHeight?: number;\n /** Next.js image quality (1-100). @default undefined */\n imageQuality?: number;\n /** Bypass Next.js image optimization. Useful for small sharp PNGs. @default false */\n imageUnoptimized?: boolean;\n /** Overlay message displayed centered on the image. @default undefined */\n message?: string;\n /** Card title (required). Rendered as heading. */\n title: string;\n /** Card description text below the title. @default undefined */\n description?: string;\n /** Label for the action button or link. @default undefined */\n label?: string;\n /** Called when the action button is clicked (when labelHref is not set). @default undefined */\n onLabelClick?: () => void;\n /** When set, the action renders as a link to this href. @default undefined */\n labelHref?: string;\n /** Component to render the action link as (e.g. next/link). @default 'a' */\n linkAs?: React.ElementType;\n /** Forwarded ref for the root div. @default undefined */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nfunction CardImage({\n src,\n alt,\n ratio,\n message,\n width,\n height,\n quality,\n unoptimized,\n}: {\n src: string;\n alt: string;\n ratio: 'horizontal' | 'vertical';\n message?: string;\n width: number;\n height: number;\n quality?: number;\n unoptimized?: boolean;\n}) {\n const aspectClass = ratio === 'horizontal' ? 'aspect-video' : 'aspect-[4/3]';\n\n return (\n <div className={`relative w-full overflow-hidden rounded-t-rad-md ${aspectClass}`}>\n <Image\n src={src}\n alt={alt}\n width={width}\n height={height}\n quality={quality}\n unoptimized={unoptimized}\n className=\"h-full w-full object-cover\"\n />\n {message && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-
|
|
1
|
+
{"version":3,"file":"Card.js","sources":["../../../src/components/Card/Card.tsx"],"sourcesContent":["import React from 'react';\nimport Image from 'next/image';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Card layout type. 'text-only' — no image; 'media-horizontal' — image above content, video aspect;\n * 'media-vertical' — image above content, 4:3 aspect.\n */\nexport type CardType = 'text-only' | 'media-horizontal' | 'media-vertical';\n\n/**\n * Props for the Card component. Title is required; image and action are optional.\n */\nexport interface CardProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Layout: text-only, or media with horizontal/vertical image aspect. @default 'text-only' */\n type?: CardType;\n /** Image source URL. Required when type is media-horizontal or media-vertical. @default undefined */\n imageSrc?: string;\n /** Image alt text for accessibility. @default '' */\n imageAlt?: string;\n /** Width for next/image. @default 600 */\n imageWidth?: number;\n /** Height for next/image. @default 400 */\n imageHeight?: number;\n /** Next.js image quality (1-100). @default undefined */\n imageQuality?: number;\n /** Bypass Next.js image optimization. Useful for small sharp PNGs. @default false */\n imageUnoptimized?: boolean;\n /** Overlay message displayed centered on the image. @default undefined */\n message?: string;\n /** Card title (required). Rendered as heading. */\n title: string;\n /** Card description text below the title. @default undefined */\n description?: string;\n /** Label for the action button or link. @default undefined */\n label?: string;\n /** Called when the action button is clicked (when labelHref is not set). @default undefined */\n onLabelClick?: () => void;\n /** When set, the action renders as a link to this href. @default undefined */\n labelHref?: string;\n /** Component to render the action link as (e.g. next/link). @default 'a' */\n linkAs?: React.ElementType;\n /** Forwarded ref for the root div. @default undefined */\n ref?: React.Ref<HTMLDivElement>;\n}\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nfunction CardImage({\n src,\n alt,\n ratio,\n message,\n width,\n height,\n quality,\n unoptimized,\n}: {\n src: string;\n alt: string;\n ratio: 'horizontal' | 'vertical';\n message?: string;\n width: number;\n height: number;\n quality?: number;\n unoptimized?: boolean;\n}) {\n const aspectClass = ratio === 'horizontal' ? 'aspect-video' : 'aspect-[4/3]';\n\n return (\n <div className={`relative w-full overflow-hidden rounded-t-rad-md ${aspectClass}`}>\n <Image\n src={src}\n alt={alt}\n width={width}\n height={height}\n quality={quality}\n unoptimized={unoptimized}\n className=\"h-full w-full object-cover\"\n />\n {message && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-fill-bg-dimmer\">\n <span className=\"text-title-lg text-text-primary-inverse text-center leading-[1.1] px-space-fluid-4-8\">\n {message}\n </span>\n </div>\n )}\n </div>\n );\n}\n\nfunction CardAction({\n label,\n onClick,\n href,\n linkAs,\n}: {\n label: string;\n onClick?: () => void;\n href?: string;\n linkAs?: React.ElementType;\n}) {\n const classes =\n 'inline-flex items-center justify-center h-12 min-h-12 rounded-rad-md px-space-5 text-btn-reg bg-fill-action-primary text-text-action-primary hover:bg-fill-action-primary-hover active:bg-fill-action-primary-active transition-colors';\n\n if (href) {\n const LinkComponent = linkAs || 'a';\n return (\n <LinkComponent href={href} className={classes}>\n {label}\n </LinkComponent>\n );\n }\n\n return (\n <button type=\"button\" onClick={onClick} className={classes}>\n {label}\n </button>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Card\n// ---------------------------------------------------------------------------\n\n/**\n * Card with optional image, title, description, and action. Renders a bordered container\n * with design-token styling. Use for product cards, feature highlights, or content blocks.\n * For image-only or custom layouts use Box or a custom component.\n *\n * @example\n * <Card title=\"Feature\" description=\"Description here.\" type=\"text-only\" />\n *\n * @example\n * <Card type=\"media-horizontal\" imageSrc=\"/img.jpg\" imageAlt=\"Product\" title=\"Product\" label=\"View\" labelHref=\"/product\" />\n */\nexport function Card({\n type = 'text-only',\n imageSrc,\n imageAlt = '',\n imageWidth = 600,\n imageHeight = 400,\n imageQuality,\n imageUnoptimized,\n message,\n title,\n description,\n label,\n onLabelClick,\n labelHref,\n linkAs,\n className = '',\n ref,\n ...rest\n}: CardProps) {\n const isMedia = type !== 'text-only';\n const ratio = type === 'media-vertical' ? 'vertical' : 'horizontal';\n\n const rootClasses = [\n 'flex flex-col rounded-rad-md overflow-hidden',\n isMedia ? 'bg-fill-bg-secondary pb-space-fluid-4-8 gap-space-fluid-4-6' : 'bg-fill-bg-primary border border-border-light',\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const contentClasses = [\n 'flex flex-col text-text-primary',\n isMedia ? 'gap-space-fluid-4-6 px-space-fluid-4-8' : 'gap-space-3 p-space-4',\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <div ref={ref} className={rootClasses} {...rest}>\n {isMedia && imageSrc && (\n <CardImage\n src={imageSrc}\n alt={imageAlt}\n ratio={ratio}\n message={message}\n width={imageWidth}\n height={imageHeight}\n quality={imageQuality}\n unoptimized={imageUnoptimized}\n />\n )}\n\n <div className={contentClasses}>\n <div className=\"flex flex-col gap-space-1\">\n <h3 className=\"text-title-md leading-[1.2] tracking-[0.19px]\">{title}</h3>\n {description && <p className=\"text-text-md leading-[1.4]\">{description}</p>}\n </div>\n\n {label && (\n <div>\n <CardAction label={label} onClick={onLabelClick} href={labelHref} linkAs={linkAs} />\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":";;AAqDA,SAAS,UAAU;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASG;AACD,QAAM,cAAc,UAAU,eAAe,iBAAiB;AAE9D,SACE,qBAAC,OAAA,EAAI,WAAW,oDAAoD,WAAW,IAC7E,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,IAEX,WACC,oBAAC,OAAA,EAAI,WAAU,uEACb,8BAAC,QAAA,EAAK,WAAU,wFACb,UAAA,QAAA,CACH,EAAA,CACF;AAAA,EAAA,GAEJ;AAEJ;AAEA,SAAS,WAAW;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,UACJ;AAEF,MAAI,MAAM;AACR,UAAM,gBAAgB,UAAU;AAChC,WACE,oBAAC,eAAA,EAAc,MAAY,WAAW,SACnC,UAAA,OACH;AAAA,EAEJ;AAEA,6BACG,UAAA,EAAO,MAAK,UAAS,SAAkB,WAAW,SAChD,UAAA,OACH;AAEJ;AAiBO,SAAS,KAAK;AAAA,EACnB,OAAO;AAAA,EACP;AAAA,EACA,WAAW;AAAA,EACX,aAAa;AAAA,EACb,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,GAAG;AACL,GAAc;AACZ,QAAM,UAAU,SAAS;AACzB,QAAM,QAAQ,SAAS,mBAAmB,aAAa;AAEvD,QAAM,cAAc;AAAA,IAClB;AAAA,IACA,UAAU,gEAAgE;AAAA,IAC1E;AAAA,EAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA,UAAU,2CAA2C;AAAA,EAAA,EAEpD,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,8BACG,OAAA,EAAI,KAAU,WAAW,aAAc,GAAG,MACxC,UAAA;AAAA,IAAA,WAAW,YACV;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,aAAa;AAAA,MAAA;AAAA,IAAA;AAAA,IAIjB,qBAAC,OAAA,EAAI,WAAW,gBACd,UAAA;AAAA,MAAA,qBAAC,OAAA,EAAI,WAAU,6BACb,UAAA;AAAA,QAAA,oBAAC,MAAA,EAAG,WAAU,iDAAiD,UAAA,OAAM;AAAA,QACpE,eAAe,oBAAC,KAAA,EAAE,WAAU,8BAA8B,UAAA,YAAA,CAAY;AAAA,MAAA,GACzE;AAAA,MAEC,SACC,oBAAC,OAAA,EACC,UAAA,oBAAC,YAAA,EAAW,OAAc,SAAS,cAAc,MAAM,WAAW,OAAA,CAAgB,EAAA,CACpF;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FAQ.d.ts","sourceRoot":"","sources":["../../../src/components/FAQ/FAQ.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA0D,MAAM,OAAO,CAAC;AAC/E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAqB3C;;;GAGG;AACH,MAAM,WAAW,QAAS,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC;IACnF,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6GAA6G;IAC7G,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACrC,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACvC,uFAAuF;IACvF,IAAI,CAAC,EAAE;QACL,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,4EAA4E;IAC5E,MAAM,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC;IAC3B,wDAAwD;IACxD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,GAAG,CAAC,EAClB,OAAe,EACf,KAAK,EACL,gBAAgB,EAChB,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,SAAc,EACd,GAAG,EACH,GAAG,IAAI,EACR,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"FAQ.d.ts","sourceRoot":"","sources":["../../../src/components/FAQ/FAQ.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA0D,MAAM,OAAO,CAAC;AAC/E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAqB3C;;;GAGG;AACH,MAAM,WAAW,QAAS,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC;IACnF,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6GAA6G;IAC7G,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACrC,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACvC,uFAAuF;IACvF,IAAI,CAAC,EAAE;QACL,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,4EAA4E;IAC5E,MAAM,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC;IAC3B,wDAAwD;IACxD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,GAAG,CAAC,EAClB,OAAe,EACf,KAAK,EACL,gBAAgB,EAChB,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,SAAc,EACd,GAAG,EACH,GAAG,IAAI,EACR,EAAE,QAAQ,2CAmJV"}
|
|
@@ -103,7 +103,7 @@ function FAQ({
|
|
|
103
103
|
{
|
|
104
104
|
className: "flex flex-col gap-space-fluid-2-4 rounded-rad-md overflow-hidden bg-fill-action-inverse p-space-fluid-4-8",
|
|
105
105
|
children: [
|
|
106
|
-
/* @__PURE__ */ jsx("h3", { children: /* @__PURE__ */ jsxs(
|
|
106
|
+
/* @__PURE__ */ jsx("h3", { className: "contents", children: /* @__PURE__ */ jsxs(
|
|
107
107
|
"button",
|
|
108
108
|
{
|
|
109
109
|
ref: (el) => {
|
|
@@ -111,7 +111,7 @@ function FAQ({
|
|
|
111
111
|
},
|
|
112
112
|
id: triggerId,
|
|
113
113
|
type: "button",
|
|
114
|
-
className: `flex w-full items-
|
|
114
|
+
className: `flex w-full items-start justify-between gap-space-fluid-4-6 text-title-md text-text-primary text-left leading-[1.2] tracking-[0.19px] transition-colors outline-none min-h-px ${disabled ? "text-text-disabled cursor-not-allowed" : "hover:bg-fill-bg-secondary focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-border-focus cursor-pointer"}`,
|
|
115
115
|
"aria-expanded": isOpen,
|
|
116
116
|
"aria-controls": panelId,
|
|
117
117
|
"aria-disabled": disabled || void 0,
|
|
@@ -119,13 +119,13 @@ function FAQ({
|
|
|
119
119
|
onClick: () => toggle(index),
|
|
120
120
|
onKeyDown: (e) => handleKeyDown(e, index),
|
|
121
121
|
children: [
|
|
122
|
-
/* @__PURE__ */ jsx("span", { children: item.question }),
|
|
123
|
-
/* @__PURE__ */ jsx(
|
|
122
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1", children: item.question }),
|
|
123
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 size-6 flex items-center justify-center overflow-clip", "aria-hidden": true, children: /* @__PURE__ */ jsx(
|
|
124
124
|
ChevronIcon,
|
|
125
125
|
{
|
|
126
|
-
className: `
|
|
126
|
+
className: `size-6 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`
|
|
127
127
|
}
|
|
128
|
-
)
|
|
128
|
+
) })
|
|
129
129
|
]
|
|
130
130
|
}
|
|
131
131
|
) }),
|
|
@@ -136,7 +136,7 @@ function FAQ({
|
|
|
136
136
|
role: "region",
|
|
137
137
|
"aria-labelledby": triggerId,
|
|
138
138
|
className: `overflow-hidden transition-all duration-200 ease-in-out ${isOpen ? "max-h-[2000px] opacity-100" : "max-h-0 opacity-0"}`,
|
|
139
|
-
children: /* @__PURE__ */ jsx("div", { className: "text-text-md text-text-primary", children: item.answer })
|
|
139
|
+
children: /* @__PURE__ */ jsx("div", { className: "text-text-md text-text-primary leading-[1.4] text-left w-full", children: item.answer })
|
|
140
140
|
}
|
|
141
141
|
)
|
|
142
142
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FAQ.js","sources":["../../../src/components/FAQ/FAQ.tsx"],"sourcesContent":["'use client';\n\nimport React, { useState, useRef, useCallback, useId, useEffect } from 'react';\nimport type { FAQItem } from '../../types';\n\nfunction ChevronIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n );\n}\n\n/**\n * Props for the FAQ component. Each item uses the FAQItem shape: question, answer, disabled?.\n * @see FAQItem\n */\nexport interface FAQProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange'> {\n /** Section heading displayed above the accordion list. @default 'FAQ' */\n heading?: string;\n /** Array of question/answer pairs. Each item: question (string), answer (ReactNode), disabled? (boolean). */\n items: FAQItem[];\n /** Index(es) of the item(s) open by default (uncontrolled). @default [] */\n defaultOpenIndex?: number | number[];\n /** Currently open index(es) (controlled). @default undefined */\n value?: number | number[];\n /** Called when the open indices change. @default undefined */\n onChange?: (indices: number[]) => void;\n /** Optional link rendered below the list (e.g. \"View all FAQs\"). @default undefined */\n link?: {\n label: string;\n href: string;\n };\n /** Component to render the footer link as (e.g. next/link). @default 'a' */\n linkAs?: React.ElementType;\n /** Forwarded ref for the section. @default undefined */\n ref?: React.Ref<HTMLElement>;\n}\n\n/**\n * FAQ section: heading plus an accordion of question/answer pairs. Uses FAQItem shape\n * (question, answer, optional disabled). Supports controlled (value/onChange) or uncontrolled\n * (defaultOpenIndex) expansion. Optional footer link for \"View all FAQs\" etc.\n *\n * Layout and spacing match Figma templates across breakpoints:\n * - xs+ (320px): section padding 16px/32px, heading ~25px, item padding 16px, list gap 8px.\n * - lg+ (940px) / xl+ (1392px): section padding 32px/64px, heading 32px, item padding 32px, list gap 8px, content gap 24px.\n * Uses design tokens: bg-fill-bg-secondary, bg-fill-action-inverse, text-title-lg (heading), text-title-md (question), text-text-md (answer), text-link-md (footer link), rad-md, fluid spacing.\n *\n * @example\n * <FAQ heading=\"Questions\" items={[{ question: 'What is X?', answer: 'X is...' }]} />\n *\n * @example\n * <FAQ items={faqItems} defaultOpenIndex={0} link={{ label: 'View all', href: '/faq' }} />\n */\nexport function FAQ({\n heading = 'FAQ',\n items,\n defaultOpenIndex,\n value,\n onChange,\n link,\n linkAs,\n className = '',\n ref,\n ...rest\n}: FAQProps) {\n const baseId = useId();\n const isControlled = value !== undefined;\n\n const controlledIndices = value !== undefined ? (Array.isArray(value) ? value : [value]) : undefined;\n const defaultIndices = defaultOpenIndex !== undefined ? (Array.isArray(defaultOpenIndex) ? defaultOpenIndex : [defaultOpenIndex]) : [];\n\n const [internalOpen, setInternalOpen] = useState<number[]>(defaultIndices);\n const openIndices = isControlled ? (controlledIndices || []) : internalOpen;\n\n useEffect(() => {\n if (!isControlled && defaultOpenIndex !== undefined) {\n setInternalOpen(defaultIndices);\n }\n }, [defaultOpenIndex, isControlled]);\n\n const triggerRefs = useRef<(HTMLButtonElement | null)[]>([]);\n\n const toggle = useCallback(\n (index: number) => {\n const next = openIndices.includes(index)\n ? openIndices.filter((i) => i !== index)\n : [...openIndices, index];\n\n if (isControlled) {\n onChange?.(next);\n } else {\n setInternalOpen(next);\n }\n },\n [openIndices, isControlled, onChange],\n );\n\n const focusTrigger = (index: number) => {\n triggerRefs.current[index]?.focus();\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n const enabledIndices = items\n .map((_, i) => i)\n .filter((i) => !items[i].disabled);\n const currentPos = enabledIndices.indexOf(index);\n\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n focusTrigger(enabledIndices[(currentPos + 1) % enabledIndices.length]);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n focusTrigger(enabledIndices[(currentPos - 1 + enabledIndices.length) % enabledIndices.length]);\n break;\n }\n case 'Home': {\n e.preventDefault();\n focusTrigger(enabledIndices[0]);\n break;\n }\n case 'End': {\n e.preventDefault();\n focusTrigger(enabledIndices[enabledIndices.length - 1]);\n break;\n }\n }\n };\n\n return (\n <section\n ref={ref}\n className={`w-full bg-fill-bg-secondary px-space-fluid-4-8 py-space-fluid-8-16 ${className}`}\n {...rest}\n >\n <div className=\"mx-auto w-full max-w-[1328px]\">\n <div className=\"flex flex-col gap-space-fluid-4-5\">\n {heading && (\n <h2 className=\"text-title-lg text-text-primary\">{heading}</h2>\n )}\n\n <div className=\"flex flex-col gap-space-2\">\n {items.map((item, index) => {\n const isOpen = openIndices.includes(index);\n const triggerId = `${baseId}-faq-trigger-${index}`;\n const panelId = `${baseId}-faq-panel-${index}`;\n const disabled = !!item.disabled;\n\n return (\n <div\n key={index}\n className=\"flex flex-col gap-space-fluid-2-4 rounded-rad-md overflow-hidden bg-fill-action-inverse p-space-fluid-4-8\"\n >\n <h3>\n <button\n ref={(el) => { triggerRefs.current[index] = el; }}\n id={triggerId}\n type=\"button\"\n className={`flex w-full items-
|
|
1
|
+
{"version":3,"file":"FAQ.js","sources":["../../../src/components/FAQ/FAQ.tsx"],"sourcesContent":["'use client';\n\nimport React, { useState, useRef, useCallback, useId, useEffect } from 'react';\nimport type { FAQItem } from '../../types';\n\nfunction ChevronIcon({ className }: { className?: string }) {\n return (\n <svg\n className={className}\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n );\n}\n\n/**\n * Props for the FAQ component. Each item uses the FAQItem shape: question, answer, disabled?.\n * @see FAQItem\n */\nexport interface FAQProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange'> {\n /** Section heading displayed above the accordion list. @default 'FAQ' */\n heading?: string;\n /** Array of question/answer pairs. Each item: question (string), answer (ReactNode), disabled? (boolean). */\n items: FAQItem[];\n /** Index(es) of the item(s) open by default (uncontrolled). @default [] */\n defaultOpenIndex?: number | number[];\n /** Currently open index(es) (controlled). @default undefined */\n value?: number | number[];\n /** Called when the open indices change. @default undefined */\n onChange?: (indices: number[]) => void;\n /** Optional link rendered below the list (e.g. \"View all FAQs\"). @default undefined */\n link?: {\n label: string;\n href: string;\n };\n /** Component to render the footer link as (e.g. next/link). @default 'a' */\n linkAs?: React.ElementType;\n /** Forwarded ref for the section. @default undefined */\n ref?: React.Ref<HTMLElement>;\n}\n\n/**\n * FAQ section: heading plus an accordion of question/answer pairs. Uses FAQItem shape\n * (question, answer, optional disabled). Supports controlled (value/onChange) or uncontrolled\n * (defaultOpenIndex) expansion. Optional footer link for \"View all FAQs\" etc.\n *\n * Layout and spacing match Figma templates across breakpoints:\n * - xs+ (320px): section padding 16px/32px, heading ~25px, item padding 16px, list gap 8px.\n * - lg+ (940px) / xl+ (1392px): section padding 32px/64px, heading 32px, item padding 32px, list gap 8px, content gap 24px.\n * Uses design tokens: bg-fill-bg-secondary, bg-fill-action-inverse, text-title-lg (heading), text-title-md (question), text-text-md (answer), text-link-md (footer link), rad-md, fluid spacing.\n *\n * @example\n * <FAQ heading=\"Questions\" items={[{ question: 'What is X?', answer: 'X is...' }]} />\n *\n * @example\n * <FAQ items={faqItems} defaultOpenIndex={0} link={{ label: 'View all', href: '/faq' }} />\n */\nexport function FAQ({\n heading = 'FAQ',\n items,\n defaultOpenIndex,\n value,\n onChange,\n link,\n linkAs,\n className = '',\n ref,\n ...rest\n}: FAQProps) {\n const baseId = useId();\n const isControlled = value !== undefined;\n\n const controlledIndices = value !== undefined ? (Array.isArray(value) ? value : [value]) : undefined;\n const defaultIndices = defaultOpenIndex !== undefined ? (Array.isArray(defaultOpenIndex) ? defaultOpenIndex : [defaultOpenIndex]) : [];\n\n const [internalOpen, setInternalOpen] = useState<number[]>(defaultIndices);\n const openIndices = isControlled ? (controlledIndices || []) : internalOpen;\n\n useEffect(() => {\n if (!isControlled && defaultOpenIndex !== undefined) {\n setInternalOpen(defaultIndices);\n }\n }, [defaultOpenIndex, isControlled]);\n\n const triggerRefs = useRef<(HTMLButtonElement | null)[]>([]);\n\n const toggle = useCallback(\n (index: number) => {\n const next = openIndices.includes(index)\n ? openIndices.filter((i) => i !== index)\n : [...openIndices, index];\n\n if (isControlled) {\n onChange?.(next);\n } else {\n setInternalOpen(next);\n }\n },\n [openIndices, isControlled, onChange],\n );\n\n const focusTrigger = (index: number) => {\n triggerRefs.current[index]?.focus();\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n const enabledIndices = items\n .map((_, i) => i)\n .filter((i) => !items[i].disabled);\n const currentPos = enabledIndices.indexOf(index);\n\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n focusTrigger(enabledIndices[(currentPos + 1) % enabledIndices.length]);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n focusTrigger(enabledIndices[(currentPos - 1 + enabledIndices.length) % enabledIndices.length]);\n break;\n }\n case 'Home': {\n e.preventDefault();\n focusTrigger(enabledIndices[0]);\n break;\n }\n case 'End': {\n e.preventDefault();\n focusTrigger(enabledIndices[enabledIndices.length - 1]);\n break;\n }\n }\n };\n\n return (\n <section\n ref={ref}\n className={`w-full bg-fill-bg-secondary px-space-fluid-4-8 py-space-fluid-8-16 ${className}`}\n {...rest}\n >\n <div className=\"mx-auto w-full max-w-[1328px]\">\n <div className=\"flex flex-col gap-space-fluid-4-5\">\n {heading && (\n <h2 className=\"text-title-lg text-text-primary\">{heading}</h2>\n )}\n\n <div className=\"flex flex-col gap-space-2\">\n {items.map((item, index) => {\n const isOpen = openIndices.includes(index);\n const triggerId = `${baseId}-faq-trigger-${index}`;\n const panelId = `${baseId}-faq-panel-${index}`;\n const disabled = !!item.disabled;\n\n return (\n <div\n key={index}\n className=\"flex flex-col gap-space-fluid-2-4 rounded-rad-md overflow-hidden bg-fill-action-inverse p-space-fluid-4-8\"\n >\n <h3 className=\"contents\">\n <button\n ref={(el) => { triggerRefs.current[index] = el; }}\n id={triggerId}\n type=\"button\"\n className={`flex w-full items-start justify-between gap-space-fluid-4-6 text-title-md text-text-primary text-left leading-[1.2] tracking-[0.19px] transition-colors outline-none min-h-px ${disabled\n ? 'text-text-disabled cursor-not-allowed'\n : 'hover:bg-fill-bg-secondary focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-border-focus cursor-pointer'\n }`}\n aria-expanded={isOpen}\n aria-controls={panelId}\n aria-disabled={disabled || undefined}\n disabled={disabled}\n onClick={() => toggle(index)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n >\n <span className=\"min-w-0 flex-1\">{item.question}</span>\n <span className=\"shrink-0 size-6 flex items-center justify-center overflow-clip\" aria-hidden>\n <ChevronIcon\n className={`size-6 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}\n />\n </span>\n </button>\n </h3>\n <div\n id={panelId}\n role=\"region\"\n aria-labelledby={triggerId}\n className={`overflow-hidden transition-all duration-200 ease-in-out ${isOpen ? 'max-h-[2000px] opacity-100' : 'max-h-0 opacity-0'\n }`}\n >\n <div className=\"text-text-md text-text-primary leading-[1.4] text-left w-full\">\n {item.answer}\n </div>\n </div>\n </div>\n );\n })}\n </div>\n </div>\n\n {link && (\n <div className=\"mt-space-fluid-4-6\">\n {React.createElement(\n linkAs || 'a',\n {\n href: link.href,\n className: 'text-link-md text-link-primary underline hover:text-link-primary-hover transition-colors',\n },\n link.label,\n )}\n </div>\n )}\n </div>\n </section>\n );\n}\n"],"names":[],"mappings":";;;AAKA;AACE;AACE;AAAC;AAAA;AACC;AACM;AACC;AACC;AACH;AACE;AACK;AACE;AACC;AACH;AAEW;AAAA;AAG7B;AA4CO;AAAa;AACR;AACV;AACA;AACA;AACA;AACA;AACA;AACY;AACZ;AAEF;AACE;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACE;AACE;AAA8B;AAChC;AAGF;AAEA;AAAe;AAEX;AAIA;AACE;AAAW;AAEX;AAAoB;AACtB;AACF;AACoC;AAGtC;;AACE;AAA4B;AAG9B;AACE;AAGA;AAEA;AAAU;AAEN;AACA;AACA;AAAA;AACF;AAEE;AACA;AACA;AAAA;AACF;AAEE;AACA;AACA;AAAA;AACF;AAEE;AACA;AACA;AAAA;AACF;AAAA;AAIJ;AACE;AAAC;AAAA;AACC;AAC0F;AACtF;AAGF;AACG;AAC0D;AAKvD;AACA;AACA;AACA;AAEA;AACE;AAAC;AAAA;AAEW;AAEV;AACE;AAAC;AAAA;AACgB;AAA6B;AAAI;AAC5C;AACC;AAIH;AACa;AACA;AACY;AAC3B;AAC2B;AACa;AAExC;AAAgD;AAE9C;AAAC;AAAA;AACkF;AAAA;AAErF;AAAA;AAAA;AAEJ;AACA;AAAC;AAAA;AACK;AACC;AACY;AAEf;AAIF;AAAA;AAAA;AACF;AAAA;AArCK;AAAA;AAyCb;AACF;AAIW;AACK;AACV;AACa;AACA;AAAA;AAER;AAET;AAEJ;AAAA;AAGN;;;;"}
|
|
@@ -3,11 +3,12 @@ import { ListProps, ListItemProps } from '../../types/list';
|
|
|
3
3
|
* Semantic list component. Renders a <ul> or <ol> with consistent
|
|
4
4
|
* marker styling and item spacing using design system tokens.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* For marker="disc" and marker="decimal", the list uses block layout and
|
|
7
|
+
* margin on items (not flex/gap) so list-style-position: outside and markers
|
|
8
|
+
* render reliably. For marker="none", flex + gap is used.
|
|
9
9
|
*
|
|
10
|
-
* Use
|
|
10
|
+
* Use indent (e.g. indent="space-list-indent" for 27px) and itemPadding="none"
|
|
11
|
+
* for legal/body-copy lists.
|
|
11
12
|
*
|
|
12
13
|
* @example
|
|
13
14
|
* // Unordered feature list
|
|
@@ -17,10 +18,10 @@ import { ListProps, ListItemProps } from '../../types/list';
|
|
|
17
18
|
* </List>
|
|
18
19
|
*
|
|
19
20
|
* @example
|
|
20
|
-
* //
|
|
21
|
-
* <List
|
|
22
|
-
* <List.Item size="md">
|
|
23
|
-
* <List.Item size="md">
|
|
21
|
+
* // Legal/body-copy list (27px indent, no extra item padding)
|
|
22
|
+
* <List indent="space-list-indent" itemPadding="none" spacing="space-2">
|
|
23
|
+
* <List.Item size="md">First term.</List.Item>
|
|
24
|
+
* <List.Item size="md">Second term.</List.Item>
|
|
24
25
|
* </List>
|
|
25
26
|
*
|
|
26
27
|
* @example
|
|
@@ -29,7 +30,7 @@ import { ListProps, ListItemProps } from '../../types/list';
|
|
|
29
30
|
* <List.Item><Link href="/about">About</Link></List.Item>
|
|
30
31
|
* </List>
|
|
31
32
|
*/
|
|
32
|
-
export declare function List({ marker, spacing, className, style, children, }: ListProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
export declare function List({ marker, spacing, indent, itemPadding, className, style, children, }: ListProps): import("react/jsx-runtime").JSX.Element;
|
|
33
34
|
export declare namespace List {
|
|
34
35
|
var Item: ({ size, className, style, children, }: ListItemProps) => import("react/jsx-runtime").JSX.Element;
|
|
35
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"List.d.ts","sourceRoot":"","sources":["../../../src/components/List/List.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"List.d.ts","sourceRoot":"","sources":["../../../src/components/List/List.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAuB1B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,IAAI,CAAC,EACnB,MAAe,EACf,OAAmB,EACnB,MAAM,EACN,WAAuB,EACvB,SAAc,EACd,KAAK,EACL,QAAQ,GACT,EAAE,SAAS,2CA+BX;yBAvCe,IAAI;sDAkDjB,aAAa"}
|
|
@@ -6,25 +6,40 @@ const markerClassMap = {
|
|
|
6
6
|
decimal: "jiwambe-list--decimal",
|
|
7
7
|
none: "jiwambe-list--none"
|
|
8
8
|
};
|
|
9
|
+
function listSpacingClassSuffix(token) {
|
|
10
|
+
return token.startsWith("fluid-") ? `space-${token}` : token;
|
|
11
|
+
}
|
|
12
|
+
function resolveIndent(indent) {
|
|
13
|
+
if (indent == null) return void 0;
|
|
14
|
+
if (indent.startsWith("fluid-")) return `var(--space-${indent})`;
|
|
15
|
+
if (indent.startsWith("space-")) return `var(--${indent})`;
|
|
16
|
+
return indent;
|
|
17
|
+
}
|
|
9
18
|
function List({
|
|
10
19
|
marker = "disc",
|
|
11
20
|
spacing = "space-2",
|
|
21
|
+
indent,
|
|
22
|
+
itemPadding = "default",
|
|
12
23
|
className = "",
|
|
13
24
|
style,
|
|
14
25
|
children
|
|
15
26
|
}) {
|
|
16
27
|
const Tag = marker === "decimal" ? "ol" : "ul";
|
|
17
28
|
const markerClass = markerClassMap[marker];
|
|
18
|
-
const
|
|
29
|
+
const useBlockLayout = marker === "disc" || marker === "decimal";
|
|
30
|
+
const spacingClass = useBlockLayout ? `jiwambe-list--spacing-${listSpacingClassSuffix(spacing)}` : gapMap[spacing];
|
|
31
|
+
const itemPaddingClass = itemPadding === "none" ? "jiwambe-list--item-padding-none" : "";
|
|
19
32
|
const classes = [
|
|
20
33
|
"jiwambe-list",
|
|
21
34
|
markerClass,
|
|
22
|
-
"flex",
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
useBlockLayout ? "" : "flex flex-col",
|
|
36
|
+
spacingClass,
|
|
37
|
+
itemPaddingClass,
|
|
25
38
|
className
|
|
26
39
|
].filter(Boolean).join(" ");
|
|
27
|
-
|
|
40
|
+
const resolvedIndent = resolveIndent(indent);
|
|
41
|
+
const combinedStyle = resolvedIndent != null ? { ...style, paddingLeft: resolvedIndent } : style;
|
|
42
|
+
return /* @__PURE__ */ jsx(Tag, { className: classes, style: combinedStyle, children });
|
|
28
43
|
}
|
|
29
44
|
List.Item = function ListItem({
|
|
30
45
|
size = "sm",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"List.js","sources":["../../../src/components/List/List.tsx"],"sourcesContent":["import {\n listItemSizeClassMap,\n type ListProps,\n type ListItemProps,\n} from '../../types/list';\nimport { gapMap } from '../../utils/spacing';\n\nconst markerClassMap: Record<string, string> = {\n disc: 'jiwambe-list--disc',\n decimal: 'jiwambe-list--decimal',\n none: 'jiwambe-list--none',\n};\n\n/**\n * Semantic list component. Renders a <ul> or <ol> with consistent\n * marker styling and item spacing using design system tokens.\n *\n *
|
|
1
|
+
{"version":3,"file":"List.js","sources":["../../../src/components/List/List.tsx"],"sourcesContent":["import {\n listItemSizeClassMap,\n type ListProps,\n type ListItemProps,\n} from '../../types/list';\nimport { gapMap } from '../../utils/spacing';\nimport type { SpacingToken } from '../../types/spacing';\n\nconst markerClassMap: Record<string, string> = {\n disc: 'jiwambe-list--disc',\n decimal: 'jiwambe-list--decimal',\n none: 'jiwambe-list--none',\n};\n\n/** Maps spacing prop to plugin class suffix (jiwambe-list--spacing-{suffix}). */\nfunction listSpacingClassSuffix(token: SpacingToken): string {\n return token.startsWith('fluid-') ? `space-${token}` : token;\n}\n\n/** Resolves indent to CSS value: token → var(--space-X), custom string as-is. */\nfunction resolveIndent(indent: ListProps['indent']): string | undefined {\n if (indent == null) return undefined;\n if (indent.startsWith('fluid-')) return `var(--space-${indent})`;\n if (indent.startsWith('space-')) return `var(--${indent})`;\n return indent;\n}\n\n/**\n * Semantic list component. Renders a <ul> or <ol> with consistent\n * marker styling and item spacing using design system tokens.\n *\n * For marker=\"disc\" and marker=\"decimal\", the list uses block layout and\n * margin on items (not flex/gap) so list-style-position: outside and markers\n * render reliably. For marker=\"none\", flex + gap is used.\n *\n * Use indent (e.g. indent=\"space-list-indent\" for 27px) and itemPadding=\"none\"\n * for legal/body-copy lists.\n *\n * @example\n * // Unordered feature list\n * <List spacing=\"space-3\">\n * <List.Item size=\"sm\">Fleet Taxi financing</List.Item>\n * <List.Item size=\"sm\">Home improvement loans</List.Item>\n * </List>\n *\n * @example\n * // Legal/body-copy list (27px indent, no extra item padding)\n * <List indent=\"space-list-indent\" itemPadding=\"none\" spacing=\"space-2\">\n * <List.Item size=\"md\">First term.</List.Item>\n * <List.Item size=\"md\">Second term.</List.Item>\n * </List>\n *\n * @example\n * // Navigation links — no markers\n * <List marker=\"none\" spacing=\"space-1\">\n * <List.Item><Link href=\"/about\">About</Link></List.Item>\n * </List>\n */\nexport function List({\n marker = 'disc',\n spacing = 'space-2',\n indent,\n itemPadding = 'default',\n className = '',\n style,\n children,\n}: ListProps) {\n const Tag = marker === 'decimal' ? 'ol' : 'ul';\n const markerClass = markerClassMap[marker];\n const useBlockLayout = marker === 'disc' || marker === 'decimal';\n const spacingClass = useBlockLayout\n ? `jiwambe-list--spacing-${listSpacingClassSuffix(spacing)}`\n : gapMap[spacing];\n const itemPaddingClass = itemPadding === 'none' ? 'jiwambe-list--item-padding-none' : '';\n\n const classes = [\n 'jiwambe-list',\n markerClass,\n useBlockLayout ? '' : 'flex flex-col',\n spacingClass,\n itemPaddingClass,\n className,\n ]\n .filter(Boolean)\n .join(' ');\n\n const resolvedIndent = resolveIndent(indent);\n const combinedStyle =\n resolvedIndent != null\n ? { ...style, paddingLeft: resolvedIndent }\n : style;\n\n return (\n <Tag className={classes} style={combinedStyle}>\n {children}\n </Tag>\n );\n}\n\n/**\n * Renders a single list item (<li>) with design system typography and\n * marker styling. Use only as a direct child of List.\n */\nList.Item = function ListItem({\n size = 'sm',\n className = '',\n style,\n children,\n}: ListItemProps) {\n const typographyClass = listItemSizeClassMap[size];\n\n const classes = ['jiwambe-list-item', typographyClass, className]\n .filter(Boolean)\n .join(' ');\n\n return (\n <li className={classes} style={style}>\n {children}\n </li>\n );\n};\n"],"names":[],"mappings":";;;AAQA,MAAM,iBAAyC;AAAA,EAC7C,MAAM;AAAA,EACN,SAAS;AAAA,EACT,MAAM;AACR;AAGA,SAAS,uBAAuB,OAA6B;AAC3D,SAAO,MAAM,WAAW,QAAQ,IAAI,SAAS,KAAK,KAAK;AACzD;AAGA,SAAS,cAAc,QAAiD;AACtE,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,OAAO,WAAW,QAAQ,EAAG,QAAO,eAAe,MAAM;AAC7D,MAAI,OAAO,WAAW,QAAQ,EAAG,QAAO,SAAS,MAAM;AACvD,SAAO;AACT;AAiCO,SAAS,KAAK;AAAA,EACnB,SAAS;AAAA,EACT,UAAU;AAAA,EACV;AAAA,EACA,cAAc;AAAA,EACd,YAAY;AAAA,EACZ;AAAA,EACA;AACF,GAAc;AACZ,QAAM,MAAM,WAAW,YAAY,OAAO;AAC1C,QAAM,cAAc,eAAe,MAAM;AACzC,QAAM,iBAAiB,WAAW,UAAU,WAAW;AACvD,QAAM,eAAe,iBACjB,yBAAyB,uBAAuB,OAAO,CAAC,KACxD,OAAO,OAAO;AAClB,QAAM,mBAAmB,gBAAgB,SAAS,oCAAoC;AAEtF,QAAM,UAAU;AAAA,IACd;AAAA,IACA;AAAA,IACA,iBAAiB,KAAK;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,EAAA,EAEC,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,QAAM,iBAAiB,cAAc,MAAM;AAC3C,QAAM,gBACJ,kBAAkB,OACd,EAAE,GAAG,OAAO,aAAa,mBACzB;AAEN,6BACG,KAAA,EAAI,WAAW,SAAS,OAAO,eAC7B,UACH;AAEJ;AAMA,KAAK,OAAO,SAAS,SAAS;AAAA,EAC5B,OAAO;AAAA,EACP,YAAY;AAAA,EACZ;AAAA,EACA;AACF,GAAkB;AAChB,QAAM,kBAAkB,qBAAqB,IAAI;AAEjD,QAAM,UAAU,CAAC,qBAAqB,iBAAiB,SAAS,EAC7D,OAAO,OAAO,EACd,KAAK,GAAG;AAEX,SACE,oBAAC,MAAA,EAAG,WAAW,SAAS,OACrB,UACH;AAEJ;"}
|