@boostdev/design-system-components 1.2.6 → 1.2.8
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/AGENTS.md +2 -2
- package/dist/client.cjs +57 -53
- package/dist/client.css +480 -476
- package/dist/client.d.cts +18 -8
- package/dist/client.d.ts +18 -8
- package/dist/client.js +56 -53
- package/dist/index.cjs +57 -53
- package/dist/index.css +480 -476
- package/dist/index.d.cts +18 -8
- package/dist/index.d.ts +18 -8
- package/dist/index.js +56 -53
- package/dist/native/index.cjs +3 -1
- package/dist/native/index.d.cts +11 -3
- package/dist/native/index.d.ts +11 -3
- package/dist/native/index.js +3 -1
- package/package.json +1 -1
- package/src/components/interaction/Button/Button.mdx +1 -1
- package/src/components/layout/ButtonGroup/ButtonGroup.mdx +5 -3
- package/src/components/layout/ButtonGroup/ButtonGroup.module.css +1 -0
- package/src/components/layout/ButtonGroup/ButtonGroup.native.tsx +12 -2
- package/src/components/layout/ButtonGroup/ButtonGroup.spec.tsx +14 -5
- package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +10 -10
- package/src/components/layout/ButtonGroup/ButtonGroup.tsx +12 -2
- package/src/components/layout/ButtonGroup/index.ts +2 -2
- package/src/components/layout/Card/Card.mdx +9 -1
- package/src/components/layout/Card/Card.module.css +8 -4
- package/src/components/layout/Card/Card.spec.tsx +30 -0
- package/src/components/layout/Card/Card.stories.tsx +3 -1
- package/src/components/layout/Card/Card.tsx +12 -8
- package/src/index.ts +2 -2
- package/src/native.ts +2 -2
package/dist/native/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(native_exports, {
|
|
|
36
36
|
Badge: () => Badge,
|
|
37
37
|
Breadcrumb: () => Breadcrumb,
|
|
38
38
|
Button: () => Button,
|
|
39
|
+
ButtonContainer: () => ButtonContainer,
|
|
39
40
|
ButtonGroup: () => ButtonGroup,
|
|
40
41
|
Calendar: () => Calendar,
|
|
41
42
|
Card: () => Card,
|
|
@@ -2096,7 +2097,7 @@ var styles23 = import_react_native26.StyleSheet.create({
|
|
|
2096
2097
|
gap: import_native.spacing.xs
|
|
2097
2098
|
}
|
|
2098
2099
|
});
|
|
2099
|
-
function
|
|
2100
|
+
function ButtonContainer({ children, variant, accessibilityLabel, style }) {
|
|
2100
2101
|
const isColumn = variant === "card" || variant === "modal";
|
|
2101
2102
|
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
|
|
2102
2103
|
import_react_native26.View,
|
|
@@ -2107,6 +2108,7 @@ function ButtonGroup({ children, variant, accessibilityLabel, style }) {
|
|
|
2107
2108
|
}
|
|
2108
2109
|
);
|
|
2109
2110
|
}
|
|
2111
|
+
var ButtonGroup = ButtonContainer;
|
|
2110
2112
|
|
|
2111
2113
|
// src/components/interaction/Button/Button.native.tsx
|
|
2112
2114
|
var import_react_native27 = require("react-native");
|
package/dist/native/index.d.cts
CHANGED
|
@@ -268,13 +268,21 @@ interface IconWrapperProps {
|
|
|
268
268
|
}
|
|
269
269
|
declare function IconWrapper({ children, style, accessibilityElementsHidden }: IconWrapperProps): react_jsx_runtime.JSX.Element;
|
|
270
270
|
|
|
271
|
-
interface
|
|
271
|
+
interface ButtonContainerProps {
|
|
272
272
|
children: ReactNode;
|
|
273
273
|
variant?: 'flow' | 'card' | 'modal' | 'content';
|
|
274
274
|
accessibilityLabel?: string;
|
|
275
275
|
style?: StyleProp<ViewStyle>;
|
|
276
276
|
}
|
|
277
|
-
declare function
|
|
277
|
+
declare function ButtonContainer({ children, variant, accessibilityLabel, style }: ButtonContainerProps): react_jsx_runtime.JSX.Element;
|
|
278
|
+
/**
|
|
279
|
+
* @deprecated Use `ButtonContainer` instead. `ButtonGroup` will be removed in the next major version.
|
|
280
|
+
*/
|
|
281
|
+
declare const ButtonGroup: typeof ButtonContainer;
|
|
282
|
+
/**
|
|
283
|
+
* @deprecated Use `ButtonContainerProps` instead. `ButtonGroupProps` will be removed in the next major version.
|
|
284
|
+
*/
|
|
285
|
+
type ButtonGroupProps = ButtonContainerProps;
|
|
278
286
|
|
|
279
287
|
type ButtonVariant = 'default' | 'outline' | 'ghost';
|
|
280
288
|
type ButtonSize = 'small' | 'medium' | 'large';
|
|
@@ -561,4 +569,4 @@ interface MessageProps {
|
|
|
561
569
|
}
|
|
562
570
|
declare function Message({ message, type, style }: MessageProps): react_jsx_runtime.JSX.Element | null;
|
|
563
571
|
|
|
564
|
-
export { Accordion, type AccordionItem, Alert, Avatar, Badge, Breadcrumb, type BreadcrumbItem, Button, ButtonGroup, type ButtonGroupProps, type ButtonProps, Calendar, Card, Carousel, Checkbox, CheckboxGroup, Collapsible, type CollapsibleProps, Combobox, type ComboboxOption, type DescriptionItem, DescriptionList, Dialog, Drawer, DropdownMenu, type DropdownMenuItem, FileInput, FormInput, IconWrapper, type IconWrapperProps, Label, Link, Loading, Message, NotificationBanner, NumberInput, Pagination, Popover, Progress, ProgressCircle, Radio, RadioGroup, Rating, SectionHeader, SegmentedControl, Select, type SelectOption, Separator, Skeleton, Slider, Switch, type TabItem, Table, type TableColumn, Tabs, Textarea, ThemeProvider, ToastProvider, Tooltip, Typography, lineHeight, useTheme, useToast };
|
|
572
|
+
export { Accordion, type AccordionItem, Alert, Avatar, Badge, Breadcrumb, type BreadcrumbItem, Button, ButtonContainer, type ButtonContainerProps, ButtonGroup, type ButtonGroupProps, type ButtonProps, Calendar, Card, Carousel, Checkbox, CheckboxGroup, Collapsible, type CollapsibleProps, Combobox, type ComboboxOption, type DescriptionItem, DescriptionList, Dialog, Drawer, DropdownMenu, type DropdownMenuItem, FileInput, FormInput, IconWrapper, type IconWrapperProps, Label, Link, Loading, Message, NotificationBanner, NumberInput, Pagination, Popover, Progress, ProgressCircle, Radio, RadioGroup, Rating, SectionHeader, SegmentedControl, Select, type SelectOption, Separator, Skeleton, Slider, Switch, type TabItem, Table, type TableColumn, Tabs, Textarea, ThemeProvider, ToastProvider, Tooltip, Typography, lineHeight, useTheme, useToast };
|
package/dist/native/index.d.ts
CHANGED
|
@@ -268,13 +268,21 @@ interface IconWrapperProps {
|
|
|
268
268
|
}
|
|
269
269
|
declare function IconWrapper({ children, style, accessibilityElementsHidden }: IconWrapperProps): react_jsx_runtime.JSX.Element;
|
|
270
270
|
|
|
271
|
-
interface
|
|
271
|
+
interface ButtonContainerProps {
|
|
272
272
|
children: ReactNode;
|
|
273
273
|
variant?: 'flow' | 'card' | 'modal' | 'content';
|
|
274
274
|
accessibilityLabel?: string;
|
|
275
275
|
style?: StyleProp<ViewStyle>;
|
|
276
276
|
}
|
|
277
|
-
declare function
|
|
277
|
+
declare function ButtonContainer({ children, variant, accessibilityLabel, style }: ButtonContainerProps): react_jsx_runtime.JSX.Element;
|
|
278
|
+
/**
|
|
279
|
+
* @deprecated Use `ButtonContainer` instead. `ButtonGroup` will be removed in the next major version.
|
|
280
|
+
*/
|
|
281
|
+
declare const ButtonGroup: typeof ButtonContainer;
|
|
282
|
+
/**
|
|
283
|
+
* @deprecated Use `ButtonContainerProps` instead. `ButtonGroupProps` will be removed in the next major version.
|
|
284
|
+
*/
|
|
285
|
+
type ButtonGroupProps = ButtonContainerProps;
|
|
278
286
|
|
|
279
287
|
type ButtonVariant = 'default' | 'outline' | 'ghost';
|
|
280
288
|
type ButtonSize = 'small' | 'medium' | 'large';
|
|
@@ -561,4 +569,4 @@ interface MessageProps {
|
|
|
561
569
|
}
|
|
562
570
|
declare function Message({ message, type, style }: MessageProps): react_jsx_runtime.JSX.Element | null;
|
|
563
571
|
|
|
564
|
-
export { Accordion, type AccordionItem, Alert, Avatar, Badge, Breadcrumb, type BreadcrumbItem, Button, ButtonGroup, type ButtonGroupProps, type ButtonProps, Calendar, Card, Carousel, Checkbox, CheckboxGroup, Collapsible, type CollapsibleProps, Combobox, type ComboboxOption, type DescriptionItem, DescriptionList, Dialog, Drawer, DropdownMenu, type DropdownMenuItem, FileInput, FormInput, IconWrapper, type IconWrapperProps, Label, Link, Loading, Message, NotificationBanner, NumberInput, Pagination, Popover, Progress, ProgressCircle, Radio, RadioGroup, Rating, SectionHeader, SegmentedControl, Select, type SelectOption, Separator, Skeleton, Slider, Switch, type TabItem, Table, type TableColumn, Tabs, Textarea, ThemeProvider, ToastProvider, Tooltip, Typography, lineHeight, useTheme, useToast };
|
|
572
|
+
export { Accordion, type AccordionItem, Alert, Avatar, Badge, Breadcrumb, type BreadcrumbItem, Button, ButtonContainer, type ButtonContainerProps, ButtonGroup, type ButtonGroupProps, type ButtonProps, Calendar, Card, Carousel, Checkbox, CheckboxGroup, Collapsible, type CollapsibleProps, Combobox, type ComboboxOption, type DescriptionItem, DescriptionList, Dialog, Drawer, DropdownMenu, type DropdownMenuItem, FileInput, FormInput, IconWrapper, type IconWrapperProps, Label, Link, Loading, Message, NotificationBanner, NumberInput, Pagination, Popover, Progress, ProgressCircle, Radio, RadioGroup, Rating, SectionHeader, SegmentedControl, Select, type SelectOption, Separator, Skeleton, Slider, Switch, type TabItem, Table, type TableColumn, Tabs, Textarea, ThemeProvider, ToastProvider, Tooltip, Typography, lineHeight, useTheme, useToast };
|
package/dist/native/index.js
CHANGED
|
@@ -2045,7 +2045,7 @@ var styles23 = StyleSheet23.create({
|
|
|
2045
2045
|
gap: spacing.xs
|
|
2046
2046
|
}
|
|
2047
2047
|
});
|
|
2048
|
-
function
|
|
2048
|
+
function ButtonContainer({ children, variant, accessibilityLabel, style }) {
|
|
2049
2049
|
const isColumn = variant === "card" || variant === "modal";
|
|
2050
2050
|
return /* @__PURE__ */ jsx26(
|
|
2051
2051
|
View21,
|
|
@@ -2056,6 +2056,7 @@ function ButtonGroup({ children, variant, accessibilityLabel, style }) {
|
|
|
2056
2056
|
}
|
|
2057
2057
|
);
|
|
2058
2058
|
}
|
|
2059
|
+
var ButtonGroup = ButtonContainer;
|
|
2059
2060
|
|
|
2060
2061
|
// src/components/interaction/Button/Button.native.tsx
|
|
2061
2062
|
import { StyleSheet as StyleSheet24, Pressable as Pressable14, Text as Text20, View as View22 } from "react-native";
|
|
@@ -4362,6 +4363,7 @@ export {
|
|
|
4362
4363
|
Badge,
|
|
4363
4364
|
Breadcrumb,
|
|
4364
4365
|
Button,
|
|
4366
|
+
ButtonContainer,
|
|
4365
4367
|
ButtonGroup,
|
|
4366
4368
|
Calendar,
|
|
4367
4369
|
Card,
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ Primary interactive element for triggering actions. Renders as `<button>` or `<a
|
|
|
12
12
|
- Secondary or ghost actions (`ghost`)
|
|
13
13
|
- Navigation that should look like an action (`href` → renders as `<a>`)
|
|
14
14
|
|
|
15
|
-
> **Form actions:** When using buttons in a form (submit, reset, cancel), always wrap them in a `
|
|
15
|
+
> **Form actions:** When using buttons in a form (submit, reset, cancel), always wrap them in a `ButtonContainer` component. This provides consistent spacing, alignment, and responsive layout for form actions. Use `variant="flow"` for step navigation or `variant="card"` for single-form submissions.
|
|
16
16
|
|
|
17
17
|
## When not to use
|
|
18
18
|
- Navigation to another page when it reads naturally as a link — use `Link` instead
|
|
@@ -3,11 +3,13 @@ import * as Stories from './ButtonGroup.stories';
|
|
|
3
3
|
|
|
4
4
|
<Meta of={Stories} />
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# ButtonContainer
|
|
7
7
|
|
|
8
|
-
Layout wrapper that arranges `Button` children according to a contextual variant. Handles spacing, alignment, and direction.
|
|
8
|
+
Layout wrapper that arranges `Button` children according to a contextual variant. Handles spacing, alignment, and direction. Takes the full width of its parent container.
|
|
9
9
|
|
|
10
|
-
> **Recommended:** Always wrap buttons in a `
|
|
10
|
+
> **Recommended:** Always wrap buttons in a `ButtonContainer` when they appear inside a form. This ensures consistent spacing and alignment for form actions like submit, cancel, and navigation.
|
|
11
|
+
|
|
12
|
+
> **Migration:** `ButtonGroup` is deprecated in favour of `ButtonContainer`. Both names work — `ButtonGroup` is an alias that will be removed in the next major version.
|
|
11
13
|
|
|
12
14
|
## Variants
|
|
13
15
|
|
|
@@ -2,7 +2,7 @@ import { ReactNode } from 'react';
|
|
|
2
2
|
import { StyleSheet, View, StyleProp, ViewStyle } from 'react-native';
|
|
3
3
|
import { spacing } from '../../../native/tokens';
|
|
4
4
|
|
|
5
|
-
export interface
|
|
5
|
+
export interface ButtonContainerProps {
|
|
6
6
|
children: ReactNode;
|
|
7
7
|
variant?: 'flow' | 'card' | 'modal' | 'content';
|
|
8
8
|
accessibilityLabel?: string;
|
|
@@ -21,7 +21,7 @@ const styles = StyleSheet.create({
|
|
|
21
21
|
},
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
export function
|
|
24
|
+
export function ButtonContainer({ children, variant, accessibilityLabel, style }: ButtonContainerProps) {
|
|
25
25
|
const isColumn = variant === 'card' || variant === 'modal';
|
|
26
26
|
return (
|
|
27
27
|
<View
|
|
@@ -32,3 +32,13 @@ export function ButtonGroup({ children, variant, accessibilityLabel, style }: Bu
|
|
|
32
32
|
</View>
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @deprecated Use `ButtonContainer` instead. `ButtonGroup` will be removed in the next major version.
|
|
38
|
+
*/
|
|
39
|
+
export const ButtonGroup = ButtonContainer;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @deprecated Use `ButtonContainerProps` instead. `ButtonGroupProps` will be removed in the next major version.
|
|
43
|
+
*/
|
|
44
|
+
export type ButtonGroupProps = ButtonContainerProps;
|
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
|
-
import { ButtonGroup } from './ButtonGroup';
|
|
2
|
+
import { ButtonContainer, ButtonGroup } from './ButtonGroup';
|
|
3
3
|
|
|
4
|
-
describe('
|
|
4
|
+
describe('ButtonContainer', () => {
|
|
5
5
|
it('renders children', () => {
|
|
6
6
|
render(
|
|
7
|
-
<
|
|
7
|
+
<ButtonContainer>
|
|
8
8
|
<button>A</button>
|
|
9
9
|
<button>B</button>
|
|
10
|
-
</
|
|
10
|
+
</ButtonContainer>,
|
|
11
11
|
);
|
|
12
12
|
expect(screen.getByText('A')).toBeInTheDocument();
|
|
13
13
|
expect(screen.getByText('B')).toBeInTheDocument();
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
it('accepts a custom className', () => {
|
|
17
|
-
const { container } = render(<
|
|
17
|
+
const { container } = render(<ButtonContainer className="custom"><button>X</button></ButtonContainer>);
|
|
18
18
|
expect(container.firstChild).toHaveClass('custom');
|
|
19
19
|
});
|
|
20
|
+
|
|
21
|
+
it('fills the full width of its parent', () => {
|
|
22
|
+
const { container } = render(<ButtonContainer><button>X</button></ButtonContainer>);
|
|
23
|
+
expect(container.firstChild).toHaveClass('buttonGroup');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('exports ButtonGroup as a deprecated alias', () => {
|
|
27
|
+
expect(ButtonGroup).toBe(ButtonContainer);
|
|
28
|
+
});
|
|
20
29
|
});
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import {
|
|
2
|
+
import { ButtonContainer } from './ButtonGroup';
|
|
3
3
|
import { Button } from '../../interaction/Button';
|
|
4
4
|
|
|
5
5
|
const meta = {
|
|
6
|
-
title: 'React/Layout/
|
|
7
|
-
component:
|
|
6
|
+
title: 'React/Layout/ButtonContainer',
|
|
7
|
+
component: ButtonContainer,
|
|
8
8
|
argTypes: {
|
|
9
9
|
variant: { control: 'select', options: ['flow', 'card', 'modal', 'content', undefined] },
|
|
10
10
|
},
|
|
11
|
-
} satisfies Meta<typeof
|
|
11
|
+
} satisfies Meta<typeof ButtonContainer>;
|
|
12
12
|
|
|
13
13
|
export default meta;
|
|
14
14
|
type Story = StoryObj<typeof meta>;
|
|
15
15
|
|
|
16
16
|
const TwoButtons = () => (
|
|
17
17
|
<>
|
|
18
|
-
<Button variant="
|
|
18
|
+
<Button variant="outline">Cancel</Button>
|
|
19
19
|
<Button variant="default">Confirm</Button>
|
|
20
20
|
</>
|
|
21
21
|
);
|
|
22
22
|
|
|
23
|
-
export const Default: Story = { render: () => <
|
|
24
|
-
export const Flow: Story = { render: () => <
|
|
25
|
-
export const Card: Story = { render: () => <
|
|
26
|
-
export const Modal: Story = { render: () => <
|
|
27
|
-
export const Content: Story = { render: () => <
|
|
23
|
+
export const Default: Story = { render: () => <ButtonContainer><TwoButtons /></ButtonContainer> };
|
|
24
|
+
export const Flow: Story = { render: () => <ButtonContainer variant="flow"><TwoButtons /></ButtonContainer> };
|
|
25
|
+
export const Card: Story = { render: () => <ButtonContainer variant="card"><TwoButtons /></ButtonContainer> };
|
|
26
|
+
export const Modal: Story = { render: () => <ButtonContainer variant="modal"><TwoButtons /></ButtonContainer> };
|
|
27
|
+
export const Content: Story = { render: () => <ButtonContainer variant="content"><TwoButtons /></ButtonContainer> };
|
|
@@ -3,11 +3,11 @@ import css from './ButtonGroup.module.css';
|
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
4
|
import type { WithClassName } from '../../../types';
|
|
5
5
|
|
|
6
|
-
export interface
|
|
6
|
+
export interface ButtonContainerProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
|
|
7
7
|
variant?: 'flow' | 'card' | 'modal' | 'content';
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export function
|
|
10
|
+
export function ButtonContainer({ children, className, variant, ...rest }: ButtonContainerProps) {
|
|
11
11
|
return (
|
|
12
12
|
<div
|
|
13
13
|
{...rest}
|
|
@@ -18,3 +18,13 @@ export function ButtonGroup({ children, className, variant, ...rest }: ButtonGro
|
|
|
18
18
|
</div>
|
|
19
19
|
);
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @deprecated Use `ButtonContainer` instead. `ButtonGroup` will be removed in the next major version.
|
|
24
|
+
*/
|
|
25
|
+
export const ButtonGroup = ButtonContainer;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @deprecated Use `ButtonContainerProps` instead. `ButtonGroupProps` will be removed in the next major version.
|
|
29
|
+
*/
|
|
30
|
+
export type ButtonGroupProps = ButtonContainerProps;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { ButtonGroup } from './ButtonGroup';
|
|
2
|
-
export type { ButtonGroupProps } from './ButtonGroup';
|
|
1
|
+
export { ButtonContainer, ButtonGroup } from './ButtonGroup';
|
|
2
|
+
export type { ButtonContainerProps, ButtonGroupProps } from './ButtonGroup';
|
|
@@ -5,7 +5,7 @@ import * as Stories from './Card.stories';
|
|
|
5
5
|
|
|
6
6
|
# Card
|
|
7
7
|
|
|
8
|
-
Contained surface for grouping related content. Three visual variants provide different levels of elevation. When `onClick` is supplied, renders as a `<button>`.
|
|
8
|
+
Contained surface for grouping related content. Three visual variants provide different levels of elevation. When `onClick` is supplied, renders as a `<button>`. Use the `as` prop to render as any element or component (e.g. `<a>`, `<section>`, or a Next.js `Link`).
|
|
9
9
|
|
|
10
10
|
## When to use
|
|
11
11
|
- Group content that belongs together (product listing, profile summary, dashboard widget)
|
|
@@ -31,6 +31,12 @@ Contained surface for grouping related content. Three visual variants provide di
|
|
|
31
31
|
### Clickable
|
|
32
32
|
<Canvas of={Stories.Clickable} />
|
|
33
33
|
|
|
34
|
+
### As a link
|
|
35
|
+
<Canvas of={Stories.AsLink} />
|
|
36
|
+
|
|
37
|
+
### As a section
|
|
38
|
+
<Canvas of={Stories.AsSection} />
|
|
39
|
+
|
|
34
40
|
## Props
|
|
35
41
|
|
|
36
42
|
<ArgTypes of={Stories} />
|
|
@@ -45,6 +51,7 @@ Contained surface for grouping related content. Three visual variants provide di
|
|
|
45
51
|
<tbody>
|
|
46
52
|
<tr><td>`--card_color`</td><td>`var(--bds-color_bg)`</td><td>Card background colour</td></tr>
|
|
47
53
|
<tr><td>`--card_on-color`</td><td>`var(--bds-color_on-bg)`</td><td>Card text colour</td></tr>
|
|
54
|
+
<tr><td>`--card_padding`</td><td>varies by `padding` prop</td><td>Card padding (overrides the variant default)</td></tr>
|
|
48
55
|
<tr><td>`--card_border`</td><td>`none`</td><td>Border (outlined variant)</td></tr>
|
|
49
56
|
<tr><td>`--card_shadow--clickable-hover`</td><td>`var(--bds-shadow_xl)`</td><td>Box shadow on hover for clickable cards</td></tr>
|
|
50
57
|
<tr><td>`--card_shadow--default-hover`</td><td>`var(--bds-shadow_m)`</td><td>Box shadow on hover for default variant</td></tr>
|
|
@@ -58,4 +65,5 @@ Contained surface for grouping related content. Three visual variants provide di
|
|
|
58
65
|
|
|
59
66
|
- Static card renders as `<div>` — no interactive role
|
|
60
67
|
- Clickable card renders as `<button>` — keyboard and screen reader accessible
|
|
68
|
+
- When using `as="a"`, provide a valid `href`; when using a router `Link`, ensure it forwards `className` and `children`
|
|
61
69
|
- Ensure clickable cards have a meaningful accessible name (via visible text content)
|
|
@@ -27,19 +27,23 @@
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.card.--padding-none {
|
|
30
|
-
padding: 0;
|
|
30
|
+
padding: var(--card_padding, 0);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
.card.--padding-small {
|
|
34
|
-
padding: var(--bds-space_s);
|
|
34
|
+
padding: var(--card_padding, var(--bds-space_s));
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
.card.--padding-medium {
|
|
38
|
-
padding: var(--bds-space_m);
|
|
38
|
+
padding: var(--card_padding, var(--bds-space_m));
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
.card.--padding-large {
|
|
42
|
-
padding: var(--bds-space_l);
|
|
42
|
+
padding: var(--card_padding, var(--bds-space_l));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.card.--padding-extra-large {
|
|
46
|
+
padding: var(--card_padding, var(--bds-space_xl));
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
.card.--text-start {
|
|
@@ -30,4 +30,34 @@ describe('Card', () => {
|
|
|
30
30
|
const { container } = render(<Card className="custom">Content</Card>);
|
|
31
31
|
expect(container.firstChild).toHaveClass('custom');
|
|
32
32
|
});
|
|
33
|
+
|
|
34
|
+
it('renders as a custom element via the as prop', () => {
|
|
35
|
+
const { container } = render(<Card as="section">Section card</Card>);
|
|
36
|
+
expect(container.firstChild?.nodeName).toBe('SECTION');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders as an anchor when as="a"', () => {
|
|
40
|
+
render(<Card as="a" href="/details">Link card</Card>);
|
|
41
|
+
const link = screen.getByRole('link');
|
|
42
|
+
expect(link).toHaveAttribute('href', '/details');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('renders as a custom component via the as prop', () => {
|
|
46
|
+
const CustomWrapper = ({ children, ...props }: { children: React.ReactNode; className?: string }) => (
|
|
47
|
+
<article {...props} data-testid="custom">{children}</article>
|
|
48
|
+
);
|
|
49
|
+
render(<Card as={CustomWrapper}>Custom</Card>);
|
|
50
|
+
expect(screen.getByTestId('custom')).toBeInTheDocument();
|
|
51
|
+
expect(screen.getByTestId('custom').nodeName).toBe('ARTICLE');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('prefers as prop over onClick button fallback', () => {
|
|
55
|
+
const { container } = render(<Card as="div" onClick={() => {}}>Content</Card>);
|
|
56
|
+
expect(container.firstChild?.nodeName).toBe('DIV');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('renders the extra-large padding variant', () => {
|
|
60
|
+
const { container } = render(<Card padding="extra-large">Content</Card>);
|
|
61
|
+
expect(container.firstChild).toHaveClass('--padding-extra-large');
|
|
62
|
+
});
|
|
33
63
|
});
|
|
@@ -6,7 +6,7 @@ const meta = {
|
|
|
6
6
|
component: Card,
|
|
7
7
|
argTypes: {
|
|
8
8
|
variant: { control: 'select', options: ['default', 'elevated', 'outlined'] },
|
|
9
|
-
padding: { control: 'select', options: ['none', 'small', 'medium', 'large'] },
|
|
9
|
+
padding: { control: 'select', options: ['none', 'small', 'medium', 'large', 'extra-large'] },
|
|
10
10
|
textAlign: { control: 'select', options: ['start', 'center', 'end'] },
|
|
11
11
|
},
|
|
12
12
|
} satisfies Meta<typeof Card>;
|
|
@@ -18,6 +18,8 @@ export const Default: Story = { args: { children: 'Card content', variant: 'defa
|
|
|
18
18
|
export const Elevated: Story = { args: { children: 'Elevated card', variant: 'elevated' } };
|
|
19
19
|
export const Outlined: Story = { args: { children: 'Outlined card', variant: 'outlined' } };
|
|
20
20
|
export const Clickable: Story = { args: { children: 'Click me', onClick: () => alert('clicked') } };
|
|
21
|
+
export const AsLink: Story = { args: { children: 'Card as link', as: 'a', href: '#' } as Record<string, unknown> };
|
|
22
|
+
export const AsSection: Story = { args: { children: 'Card as section', as: 'section' } as Record<string, unknown> };
|
|
21
23
|
export const Centered: Story = { args: { children: 'Centered', textAlign: 'center' } };
|
|
22
24
|
export const AllVariants: Story = {
|
|
23
25
|
args: { children: '' },
|
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ElementType, ComponentPropsWithoutRef } from 'react';
|
|
2
2
|
import css from './Card.module.css';
|
|
3
3
|
import { cn } from '@boostdev/design-system-foundation';
|
|
4
4
|
import type { WithClassName } from '../../../types';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
type CardOwnProps = WithClassName & {
|
|
7
7
|
variant?: 'default' | 'elevated' | 'outlined';
|
|
8
|
-
padding?: 'none' | 'small' | 'medium' | 'large';
|
|
8
|
+
padding?: 'none' | 'small' | 'medium' | 'large' | 'extra-large';
|
|
9
9
|
textAlign?: 'start' | 'center' | 'end';
|
|
10
|
-
|
|
10
|
+
as?: ElementType;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type CardProps<C extends ElementType = 'div'> = CardOwnProps & Omit<ComponentPropsWithoutRef<C>, keyof CardOwnProps>;
|
|
11
14
|
|
|
12
|
-
export function Card({
|
|
15
|
+
export function Card<C extends ElementType = 'div'>({
|
|
13
16
|
children,
|
|
14
17
|
className,
|
|
15
18
|
variant = 'default',
|
|
16
19
|
padding = 'medium',
|
|
17
20
|
textAlign = 'start',
|
|
21
|
+
as,
|
|
18
22
|
onClick,
|
|
19
23
|
...rest
|
|
20
|
-
}: CardProps) {
|
|
24
|
+
}: CardProps<C>) {
|
|
21
25
|
const classNames = cn(
|
|
22
26
|
css.card,
|
|
23
27
|
css[`--${variant}`],
|
|
@@ -27,14 +31,14 @@ export function Card({
|
|
|
27
31
|
className,
|
|
28
32
|
);
|
|
29
33
|
|
|
30
|
-
const Component = onClick ? 'button' : 'div';
|
|
34
|
+
const Component = as ?? (onClick ? 'button' : 'div');
|
|
31
35
|
|
|
32
36
|
return (
|
|
33
37
|
<Component
|
|
34
38
|
{...rest}
|
|
35
39
|
className={classNames}
|
|
36
40
|
onClick={onClick}
|
|
37
|
-
{...(onClick && { type: 'button' as const })}
|
|
41
|
+
{...(!as && onClick && { type: 'button' as const })}
|
|
38
42
|
>
|
|
39
43
|
{children}
|
|
40
44
|
</Component>
|
package/src/index.ts
CHANGED
|
@@ -64,8 +64,8 @@ export { Switch } from './components/interaction/form/Switch';
|
|
|
64
64
|
export { Textarea } from './components/interaction/form/Textarea';
|
|
65
65
|
|
|
66
66
|
// Layout
|
|
67
|
-
export { ButtonGroup } from './components/layout/ButtonGroup';
|
|
68
|
-
export type { ButtonGroupProps } from './components/layout/ButtonGroup';
|
|
67
|
+
export { ButtonContainer, ButtonGroup } from './components/layout/ButtonGroup';
|
|
68
|
+
export type { ButtonContainerProps, ButtonGroupProps } from './components/layout/ButtonGroup';
|
|
69
69
|
export { Card } from './components/layout/Card';
|
|
70
70
|
export { SectionHeader } from './components/layout/SectionHeader';
|
|
71
71
|
export { IconWrapper } from './components/layout/IconWrapper';
|
package/src/native.ts
CHANGED
|
@@ -39,8 +39,8 @@ export { Card } from './components/layout/Card/Card.native';
|
|
|
39
39
|
export { SectionHeader } from './components/layout/SectionHeader/SectionHeader.native';
|
|
40
40
|
export { IconWrapper } from './components/layout/IconWrapper/IconWrapper.native';
|
|
41
41
|
export type { IconWrapperProps } from './components/layout/IconWrapper/IconWrapper.native';
|
|
42
|
-
export { ButtonGroup } from './components/layout/ButtonGroup/ButtonGroup.native';
|
|
43
|
-
export type { ButtonGroupProps } from './components/layout/ButtonGroup/ButtonGroup.native';
|
|
42
|
+
export { ButtonContainer, ButtonGroup } from './components/layout/ButtonGroup/ButtonGroup.native';
|
|
43
|
+
export type { ButtonContainerProps, ButtonGroupProps } from './components/layout/ButtonGroup/ButtonGroup.native';
|
|
44
44
|
|
|
45
45
|
// Interaction
|
|
46
46
|
export { Button } from './components/interaction/Button/Button.native';
|