@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.
@@ -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 ButtonGroup({ children, variant, accessibilityLabel, style }) {
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");
@@ -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 ButtonGroupProps {
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 ButtonGroup({ children, variant, accessibilityLabel, style }: ButtonGroupProps): react_jsx_runtime.JSX.Element;
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 };
@@ -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 ButtonGroupProps {
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 ButtonGroup({ children, variant, accessibilityLabel, style }: ButtonGroupProps): react_jsx_runtime.JSX.Element;
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 };
@@ -2045,7 +2045,7 @@ var styles23 = StyleSheet23.create({
2045
2045
  gap: spacing.xs
2046
2046
  }
2047
2047
  });
2048
- function ButtonGroup({ children, variant, accessibilityLabel, style }) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boostdev/design-system-components",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "description": "BoostDev React component library: accessible, token-driven components built on @boostdev/design-system-foundation",
5
5
  "keywords": [
6
6
  "React",
@@ -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 `ButtonGroup` 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.
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
- # ButtonGroup
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 `ButtonGroup` when they appear inside a form. This ensures consistent spacing and alignment for form actions like submit, cancel, and navigation.
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
 
@@ -3,6 +3,7 @@
3
3
  container-type: inline-size;
4
4
  container-name: button-group;
5
5
  grid-column: var(--bds-grid_span-100);
6
+ inline-size: 100%;
6
7
  }
7
8
 
8
9
  .buttonGroup .container {
@@ -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 ButtonGroupProps {
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 ButtonGroup({ children, variant, accessibilityLabel, style }: ButtonGroupProps) {
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('ButtonGroup', () => {
4
+ describe('ButtonContainer', () => {
5
5
  it('renders children', () => {
6
6
  render(
7
- <ButtonGroup>
7
+ <ButtonContainer>
8
8
  <button>A</button>
9
9
  <button>B</button>
10
- </ButtonGroup>,
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(<ButtonGroup className="custom"><button>X</button></ButtonGroup>);
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 { ButtonGroup } from './ButtonGroup';
2
+ import { ButtonContainer } from './ButtonGroup';
3
3
  import { Button } from '../../interaction/Button';
4
4
 
5
5
  const meta = {
6
- title: 'React/Layout/ButtonGroup',
7
- component: ButtonGroup,
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 ButtonGroup>;
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="ghost">Cancel</Button>
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: () => <ButtonGroup><TwoButtons /></ButtonGroup> };
24
- export const Flow: Story = { render: () => <ButtonGroup variant="flow"><TwoButtons /></ButtonGroup> };
25
- export const Card: Story = { render: () => <ButtonGroup variant="card"><TwoButtons /></ButtonGroup> };
26
- export const Modal: Story = { render: () => <ButtonGroup variant="modal"><TwoButtons /></ButtonGroup> };
27
- export const Content: Story = { render: () => <ButtonGroup variant="content"><TwoButtons /></ButtonGroup> };
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 ButtonGroupProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
6
+ export interface ButtonContainerProps extends WithClassName, HTMLAttributes<HTMLDivElement> {
7
7
  variant?: 'flow' | 'card' | 'modal' | 'content';
8
8
  }
9
9
 
10
- export function ButtonGroup({ children, className, variant, ...rest }: ButtonGroupProps) {
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 { HTMLAttributes } from 'react';
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
- interface CardProps extends WithClassName, HTMLAttributes<HTMLElement> {
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';