@boostdev/design-system-components 0.1.1

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.
Files changed (233) hide show
  1. package/AGENTS.md +72 -0
  2. package/README.md +396 -0
  3. package/dist/index.cjs +2273 -0
  4. package/dist/index.css +2543 -0
  5. package/dist/index.d.cts +453 -0
  6. package/dist/index.d.ts +453 -0
  7. package/dist/index.js +2221 -0
  8. package/package.json +143 -0
  9. package/src/components/interaction/Button/Button.module.css +136 -0
  10. package/src/components/interaction/Button/Button.spec.tsx +50 -0
  11. package/src/components/interaction/Button/Button.stories.tsx +43 -0
  12. package/src/components/interaction/Button/Button.tsx +68 -0
  13. package/src/components/interaction/Button/index.ts +1 -0
  14. package/src/components/interaction/Command/Command.module.css +128 -0
  15. package/src/components/interaction/Command/Command.spec.tsx +60 -0
  16. package/src/components/interaction/Command/Command.stories.tsx +35 -0
  17. package/src/components/interaction/Command/Command.tsx +161 -0
  18. package/src/components/interaction/Command/index.ts +2 -0
  19. package/src/components/interaction/Dialog/Dialog.module.css +39 -0
  20. package/src/components/interaction/Dialog/Dialog.spec.tsx +43 -0
  21. package/src/components/interaction/Dialog/Dialog.stories.tsx +36 -0
  22. package/src/components/interaction/Dialog/Dialog.tsx +42 -0
  23. package/src/components/interaction/Dialog/index.ts +1 -0
  24. package/src/components/interaction/Drawer/Drawer.module.css +98 -0
  25. package/src/components/interaction/Drawer/Drawer.spec.tsx +43 -0
  26. package/src/components/interaction/Drawer/Drawer.stories.tsx +46 -0
  27. package/src/components/interaction/Drawer/Drawer.tsx +71 -0
  28. package/src/components/interaction/Drawer/index.ts +1 -0
  29. package/src/components/interaction/DropdownMenu/DropdownMenu.module.css +68 -0
  30. package/src/components/interaction/DropdownMenu/DropdownMenu.spec.tsx +74 -0
  31. package/src/components/interaction/DropdownMenu/DropdownMenu.stories.tsx +68 -0
  32. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +137 -0
  33. package/src/components/interaction/DropdownMenu/index.ts +1 -0
  34. package/src/components/interaction/Popover/Popover.module.css +39 -0
  35. package/src/components/interaction/Popover/Popover.spec.tsx +72 -0
  36. package/src/components/interaction/Popover/Popover.stories.tsx +47 -0
  37. package/src/components/interaction/Popover/Popover.tsx +78 -0
  38. package/src/components/interaction/Popover/index.ts +1 -0
  39. package/src/components/interaction/Rating/Rating.module.css +16 -0
  40. package/src/components/interaction/Rating/Rating.spec.tsx +30 -0
  41. package/src/components/interaction/Rating/Rating.stories.tsx +29 -0
  42. package/src/components/interaction/Rating/Rating.tsx +30 -0
  43. package/src/components/interaction/Rating/index.ts +1 -0
  44. package/src/components/interaction/Toast/Toast.module.css +48 -0
  45. package/src/components/interaction/Toast/Toast.spec.tsx +41 -0
  46. package/src/components/interaction/Toast/Toast.stories.tsx +57 -0
  47. package/src/components/interaction/Toast/Toast.tsx +64 -0
  48. package/src/components/interaction/Toast/index.ts +1 -0
  49. package/src/components/interaction/form/Checkbox/Checkbox.module.css +61 -0
  50. package/src/components/interaction/form/Checkbox/Checkbox.spec.tsx +39 -0
  51. package/src/components/interaction/form/Checkbox/Checkbox.stories.tsx +17 -0
  52. package/src/components/interaction/form/Checkbox/Checkbox.tsx +39 -0
  53. package/src/components/interaction/form/Checkbox/index.ts +1 -0
  54. package/src/components/interaction/form/Combobox/Combobox.module.css +104 -0
  55. package/src/components/interaction/form/Combobox/Combobox.spec.tsx +81 -0
  56. package/src/components/interaction/form/Combobox/Combobox.stories.tsx +25 -0
  57. package/src/components/interaction/form/Combobox/Combobox.tsx +182 -0
  58. package/src/components/interaction/form/Combobox/index.ts +1 -0
  59. package/src/components/interaction/form/FileInput/FileInput.module.css +79 -0
  60. package/src/components/interaction/form/FileInput/FileInput.spec.tsx +53 -0
  61. package/src/components/interaction/form/FileInput/FileInput.stories.tsx +17 -0
  62. package/src/components/interaction/form/FileInput/FileInput.tsx +99 -0
  63. package/src/components/interaction/form/FileInput/index.ts +1 -0
  64. package/src/components/interaction/form/FormInput/FormInput.module.css +37 -0
  65. package/src/components/interaction/form/FormInput/FormInput.spec.tsx +43 -0
  66. package/src/components/interaction/form/FormInput/FormInput.stories.tsx +17 -0
  67. package/src/components/interaction/form/FormInput/FormInput.tsx +47 -0
  68. package/src/components/interaction/form/FormInput/index.ts +1 -0
  69. package/src/components/interaction/form/NumberInput/NumberInput.module.css +78 -0
  70. package/src/components/interaction/form/NumberInput/NumberInput.spec.tsx +49 -0
  71. package/src/components/interaction/form/NumberInput/NumberInput.stories.tsx +17 -0
  72. package/src/components/interaction/form/NumberInput/NumberInput.tsx +106 -0
  73. package/src/components/interaction/form/NumberInput/index.ts +1 -0
  74. package/src/components/interaction/form/Radio/Radio.module.css +62 -0
  75. package/src/components/interaction/form/Radio/Radio.spec.tsx +38 -0
  76. package/src/components/interaction/form/Radio/Radio.stories.tsx +26 -0
  77. package/src/components/interaction/form/Radio/Radio.tsx +39 -0
  78. package/src/components/interaction/form/Radio/index.ts +1 -0
  79. package/src/components/interaction/form/Select/Select.module.css +64 -0
  80. package/src/components/interaction/form/Select/Select.spec.tsx +61 -0
  81. package/src/components/interaction/form/Select/Select.stories.tsx +24 -0
  82. package/src/components/interaction/form/Select/Select.tsx +72 -0
  83. package/src/components/interaction/form/Select/index.ts +1 -0
  84. package/src/components/interaction/form/Slider/Slider.module.css +99 -0
  85. package/src/components/interaction/form/Slider/Slider.spec.tsx +53 -0
  86. package/src/components/interaction/form/Slider/Slider.stories.tsx +18 -0
  87. package/src/components/interaction/form/Slider/Slider.tsx +71 -0
  88. package/src/components/interaction/form/Slider/index.ts +1 -0
  89. package/src/components/interaction/form/Switch/Switch.module.css +114 -0
  90. package/src/components/interaction/form/Switch/Switch.spec.tsx +48 -0
  91. package/src/components/interaction/form/Switch/Switch.stories.tsx +31 -0
  92. package/src/components/interaction/form/Switch/Switch.tsx +54 -0
  93. package/src/components/interaction/form/Switch/index.ts +1 -0
  94. package/src/components/interaction/form/Textarea/Textarea.module.css +44 -0
  95. package/src/components/interaction/form/Textarea/Textarea.spec.tsx +53 -0
  96. package/src/components/interaction/form/Textarea/Textarea.stories.tsx +18 -0
  97. package/src/components/interaction/form/Textarea/Textarea.tsx +44 -0
  98. package/src/components/interaction/form/Textarea/index.ts +1 -0
  99. package/src/components/interaction/form/atoms/InputContainer.module.css +9 -0
  100. package/src/components/interaction/form/atoms/InputContainer.tsx +9 -0
  101. package/src/components/interaction/form/atoms/Label.module.css +10 -0
  102. package/src/components/interaction/form/atoms/Label.tsx +15 -0
  103. package/src/components/interaction/form/atoms/Message.module.css +11 -0
  104. package/src/components/interaction/form/atoms/Message.tsx +17 -0
  105. package/src/components/layout/ButtonGroup/ButtonGroup.module.css +59 -0
  106. package/src/components/layout/ButtonGroup/ButtonGroup.spec.tsx +20 -0
  107. package/src/components/layout/ButtonGroup/ButtonGroup.stories.tsx +28 -0
  108. package/src/components/layout/ButtonGroup/ButtonGroup.tsx +17 -0
  109. package/src/components/layout/ButtonGroup/index.ts +1 -0
  110. package/src/components/layout/Card/Card.module.css +72 -0
  111. package/src/components/layout/Card/Card.spec.tsx +33 -0
  112. package/src/components/layout/Card/Card.stories.tsx +32 -0
  113. package/src/components/layout/Card/Card.tsx +45 -0
  114. package/src/components/layout/Card/index.ts +1 -0
  115. package/src/components/layout/IconWrapper/IconWrapper.module.css +24 -0
  116. package/src/components/layout/IconWrapper/IconWrapper.spec.tsx +19 -0
  117. package/src/components/layout/IconWrapper/IconWrapper.stories.tsx +22 -0
  118. package/src/components/layout/IconWrapper/IconWrapper.tsx +14 -0
  119. package/src/components/layout/IconWrapper/index.ts +1 -0
  120. package/src/components/layout/SectionHeader/SectionHeader.module.css +75 -0
  121. package/src/components/layout/SectionHeader/SectionHeader.spec.tsx +31 -0
  122. package/src/components/layout/SectionHeader/SectionHeader.stories.tsx +21 -0
  123. package/src/components/layout/SectionHeader/SectionHeader.tsx +32 -0
  124. package/src/components/layout/SectionHeader/index.ts +1 -0
  125. package/src/components/ui/Accordion/Accordion.module.css +87 -0
  126. package/src/components/ui/Accordion/Accordion.spec.tsx +78 -0
  127. package/src/components/ui/Accordion/Accordion.stories.tsx +34 -0
  128. package/src/components/ui/Accordion/Accordion.tsx +82 -0
  129. package/src/components/ui/Accordion/index.ts +1 -0
  130. package/src/components/ui/Alert/Alert.module.css +91 -0
  131. package/src/components/ui/Alert/Alert.spec.tsx +63 -0
  132. package/src/components/ui/Alert/Alert.stories.tsx +53 -0
  133. package/src/components/ui/Alert/Alert.tsx +54 -0
  134. package/src/components/ui/Alert/index.ts +1 -0
  135. package/src/components/ui/Avatar/Avatar.module.css +42 -0
  136. package/src/components/ui/Avatar/Avatar.spec.tsx +49 -0
  137. package/src/components/ui/Avatar/Avatar.stories.tsx +44 -0
  138. package/src/components/ui/Avatar/Avatar.tsx +45 -0
  139. package/src/components/ui/Avatar/index.ts +1 -0
  140. package/src/components/ui/Badge/Badge.module.css +46 -0
  141. package/src/components/ui/Badge/Badge.spec.tsx +19 -0
  142. package/src/components/ui/Badge/Badge.stories.tsx +29 -0
  143. package/src/components/ui/Badge/Badge.tsx +13 -0
  144. package/src/components/ui/Badge/index.ts +1 -0
  145. package/src/components/ui/Breadcrumb/Breadcrumb.module.css +50 -0
  146. package/src/components/ui/Breadcrumb/Breadcrumb.spec.tsx +44 -0
  147. package/src/components/ui/Breadcrumb/Breadcrumb.stories.tsx +48 -0
  148. package/src/components/ui/Breadcrumb/Breadcrumb.tsx +41 -0
  149. package/src/components/ui/Breadcrumb/index.ts +1 -0
  150. package/src/components/ui/Calendar/Calendar.module.css +120 -0
  151. package/src/components/ui/Calendar/Calendar.spec.tsx +64 -0
  152. package/src/components/ui/Calendar/Calendar.stories.tsx +59 -0
  153. package/src/components/ui/Calendar/Calendar.tsx +184 -0
  154. package/src/components/ui/Calendar/index.ts +1 -0
  155. package/src/components/ui/Carousel/Carousel.module.css +66 -0
  156. package/src/components/ui/Carousel/Carousel.spec.tsx +29 -0
  157. package/src/components/ui/Carousel/Carousel.stories.tsx +30 -0
  158. package/src/components/ui/Carousel/Carousel.tsx +64 -0
  159. package/src/components/ui/Carousel/index.ts +1 -0
  160. package/src/components/ui/DescriptionList/DescriptionList.module.css +43 -0
  161. package/src/components/ui/DescriptionList/DescriptionList.spec.tsx +31 -0
  162. package/src/components/ui/DescriptionList/DescriptionList.stories.tsx +21 -0
  163. package/src/components/ui/DescriptionList/DescriptionList.tsx +30 -0
  164. package/src/components/ui/DescriptionList/index.ts +1 -0
  165. package/src/components/ui/Link/Link.module.css +64 -0
  166. package/src/components/ui/Link/Link.spec.tsx +43 -0
  167. package/src/components/ui/Link/Link.stories.tsx +55 -0
  168. package/src/components/ui/Link/Link.tsx +42 -0
  169. package/src/components/ui/Link/index.ts +1 -0
  170. package/src/components/ui/Loading/Loading.module.css +33 -0
  171. package/src/components/ui/Loading/Loading.spec.tsx +19 -0
  172. package/src/components/ui/Loading/Loading.stories.tsx +27 -0
  173. package/src/components/ui/Loading/Loading.tsx +15 -0
  174. package/src/components/ui/Loading/index.ts +1 -0
  175. package/src/components/ui/NotificationBanner/NotificationBanner.module.css +79 -0
  176. package/src/components/ui/NotificationBanner/NotificationBanner.spec.tsx +42 -0
  177. package/src/components/ui/NotificationBanner/NotificationBanner.stories.tsx +30 -0
  178. package/src/components/ui/NotificationBanner/NotificationBanner.tsx +45 -0
  179. package/src/components/ui/NotificationBanner/index.ts +1 -0
  180. package/src/components/ui/Pagination/Pagination.module.css +78 -0
  181. package/src/components/ui/Pagination/Pagination.spec.tsx +67 -0
  182. package/src/components/ui/Pagination/Pagination.stories.tsx +40 -0
  183. package/src/components/ui/Pagination/Pagination.tsx +87 -0
  184. package/src/components/ui/Pagination/index.ts +1 -0
  185. package/src/components/ui/Progress/Progress.module.css +51 -0
  186. package/src/components/ui/Progress/Progress.spec.tsx +55 -0
  187. package/src/components/ui/Progress/Progress.stories.tsx +30 -0
  188. package/src/components/ui/Progress/Progress.tsx +43 -0
  189. package/src/components/ui/Progress/index.ts +1 -0
  190. package/src/components/ui/ProgressCircle/ProgressCircle.module.css +40 -0
  191. package/src/components/ui/ProgressCircle/ProgressCircle.spec.tsx +34 -0
  192. package/src/components/ui/ProgressCircle/ProgressCircle.stories.tsx +18 -0
  193. package/src/components/ui/ProgressCircle/ProgressCircle.tsx +75 -0
  194. package/src/components/ui/ProgressCircle/index.ts +1 -0
  195. package/src/components/ui/Separator/Separator.module.css +23 -0
  196. package/src/components/ui/Separator/Separator.spec.tsx +30 -0
  197. package/src/components/ui/Separator/Separator.stories.tsx +40 -0
  198. package/src/components/ui/Separator/Separator.tsx +21 -0
  199. package/src/components/ui/Separator/index.ts +1 -0
  200. package/src/components/ui/Skeleton/Skeleton.module.css +24 -0
  201. package/src/components/ui/Skeleton/Skeleton.spec.tsx +19 -0
  202. package/src/components/ui/Skeleton/Skeleton.stories.tsx +25 -0
  203. package/src/components/ui/Skeleton/Skeleton.tsx +12 -0
  204. package/src/components/ui/Skeleton/index.ts +1 -0
  205. package/src/components/ui/SkipLink/SkipLink.module.css +30 -0
  206. package/src/components/ui/SkipLink/SkipLink.spec.tsx +24 -0
  207. package/src/components/ui/SkipLink/SkipLink.stories.tsx +24 -0
  208. package/src/components/ui/SkipLink/SkipLink.tsx +14 -0
  209. package/src/components/ui/SkipLink/index.ts +1 -0
  210. package/src/components/ui/Table/Table.module.css +111 -0
  211. package/src/components/ui/Table/Table.spec.tsx +69 -0
  212. package/src/components/ui/Table/Table.stories.tsx +53 -0
  213. package/src/components/ui/Table/Table.tsx +98 -0
  214. package/src/components/ui/Table/index.ts +1 -0
  215. package/src/components/ui/Tabs/Tabs.module.css +61 -0
  216. package/src/components/ui/Tabs/Tabs.spec.tsx +91 -0
  217. package/src/components/ui/Tabs/Tabs.stories.tsx +59 -0
  218. package/src/components/ui/Tabs/Tabs.tsx +100 -0
  219. package/src/components/ui/Tabs/index.ts +1 -0
  220. package/src/components/ui/Tooltip/Tooltip.module.css +69 -0
  221. package/src/components/ui/Tooltip/Tooltip.spec.tsx +46 -0
  222. package/src/components/ui/Tooltip/Tooltip.stories.tsx +69 -0
  223. package/src/components/ui/Tooltip/Tooltip.tsx +38 -0
  224. package/src/components/ui/Tooltip/index.ts +1 -0
  225. package/src/components/ui/Typography/Typography.module.css +41 -0
  226. package/src/components/ui/Typography/Typography.spec.tsx +39 -0
  227. package/src/components/ui/Typography/Typography.stories.tsx +31 -0
  228. package/src/components/ui/Typography/Typography.tsx +28 -0
  229. package/src/components/ui/Typography/index.ts +1 -0
  230. package/src/css/index.css +55 -0
  231. package/src/index.ts +54 -0
  232. package/src/test/setup.ts +1 -0
  233. package/src/typings.d.ts +4 -0
@@ -0,0 +1,59 @@
1
+ @layer component {
2
+ .buttonGroup {
3
+ container-type: inline-size;
4
+ container-name: button-group;
5
+ grid-column: var(--grid_span-100);
6
+ }
7
+
8
+ .buttonGroup .container {
9
+ position: relative;
10
+ display: flex;
11
+ flex-direction: column-reverse;
12
+ gap: var(--space_l);
13
+ align-items: center;
14
+ }
15
+
16
+ .buttonGroup.--variant__card .container {
17
+ align-items: stretch;
18
+ }
19
+
20
+ .buttonGroup.--variant__flow .container,
21
+ .buttonGroup.--variant__modal .container {
22
+ align-items: center;
23
+ }
24
+
25
+ .buttonGroup.--variant__content .container {
26
+ align-items: flex-start;
27
+ }
28
+
29
+ @supports (contain: inline-size) {
30
+ @container button-group (min-width: 30rem) {
31
+ .buttonGroup .container {
32
+ flex-direction: row;
33
+ justify-content: space-between;
34
+ }
35
+
36
+ .buttonGroup.--variant__modal .container > :first-child:last-child,
37
+ .buttonGroup.--variant__flow .container > :first-child:last-child {
38
+ margin-inline-start: auto;
39
+ }
40
+
41
+ .buttonGroup.--variant__card .container {
42
+ justify-content: center;
43
+ }
44
+
45
+ .buttonGroup.--variant__modal .container {
46
+ justify-content: flex-end;
47
+ }
48
+
49
+ .buttonGroup.--variant__content .container {
50
+ justify-content: flex-start;
51
+ align-items: center;
52
+ }
53
+
54
+ .buttonGroup.--variant__grid .container {
55
+ justify-content: center;
56
+ }
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,20 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { ButtonGroup } from './ButtonGroup';
3
+
4
+ describe('ButtonGroup', () => {
5
+ it('renders children', () => {
6
+ render(
7
+ <ButtonGroup>
8
+ <button>A</button>
9
+ <button>B</button>
10
+ </ButtonGroup>,
11
+ );
12
+ expect(screen.getByText('A')).toBeInTheDocument();
13
+ expect(screen.getByText('B')).toBeInTheDocument();
14
+ });
15
+
16
+ it('accepts a custom className', () => {
17
+ const { container } = render(<ButtonGroup className="custom"><button>X</button></ButtonGroup>);
18
+ expect(container.firstChild).toHaveClass('custom');
19
+ });
20
+ });
@@ -0,0 +1,28 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { ButtonGroup } from './ButtonGroup';
3
+ import { Button } from '../../interaction/Button/Button';
4
+
5
+ const meta = {
6
+ title: 'Layout/ButtonGroup',
7
+ component: ButtonGroup,
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ variant: { control: 'select', options: ['flow', 'card', 'modal', 'content', undefined] },
11
+ },
12
+ } satisfies Meta<typeof ButtonGroup>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ const TwoButtons = () => (
18
+ <>
19
+ <Button variant="secondary">Cancel</Button>
20
+ <Button variant="primary">Confirm</Button>
21
+ </>
22
+ );
23
+
24
+ export const Default: Story = { render: () => <ButtonGroup><TwoButtons /></ButtonGroup> };
25
+ export const Flow: Story = { render: () => <ButtonGroup variant="flow"><TwoButtons /></ButtonGroup> };
26
+ export const Card: Story = { render: () => <ButtonGroup variant="card"><TwoButtons /></ButtonGroup> };
27
+ export const Modal: Story = { render: () => <ButtonGroup variant="modal"><TwoButtons /></ButtonGroup> };
28
+ export const Content: Story = { render: () => <ButtonGroup variant="content"><TwoButtons /></ButtonGroup> };
@@ -0,0 +1,17 @@
1
+ import { ReactNode } from 'react';
2
+ import css from './ButtonGroup.module.css';
3
+ import { cn } from '@boostdev/design-system-foundation';
4
+
5
+ interface ButtonGroupProps {
6
+ children: ReactNode;
7
+ className?: string;
8
+ variant?: 'flow' | 'card' | 'modal' | 'content';
9
+ }
10
+
11
+ export function ButtonGroup({ children, className, variant }: ButtonGroupProps) {
12
+ return (
13
+ <div className={cn(css.buttonGroup, className, variant && css[`--variant__${variant}`])}>
14
+ <div className={css.container}>{children}</div>
15
+ </div>
16
+ );
17
+ }
@@ -0,0 +1 @@
1
+ export { ButtonGroup } from './ButtonGroup';
@@ -0,0 +1,72 @@
1
+ @layer component {
2
+ .card {
3
+ background-color: var(--color_bg);
4
+ border-radius: var(--border_radius--m);
5
+ transition: all 0.3s ease;
6
+ position: relative;
7
+ }
8
+
9
+ .card.--default {
10
+ box-shadow: var(--shadow_s);
11
+ }
12
+
13
+ .card.--elevated {
14
+ box-shadow: var(--shadow_m);
15
+ }
16
+
17
+ .card.--outlined {
18
+ border: 1px solid var(--color_border);
19
+ box-shadow: var(--shadow_s);
20
+ }
21
+
22
+ .card.--clickable {
23
+ cursor: pointer;
24
+ }
25
+
26
+ .card.--padding-none {
27
+ padding: 0;
28
+ }
29
+
30
+ .card.--padding-small {
31
+ padding: var(--space_s);
32
+ }
33
+
34
+ .card.--padding-medium {
35
+ padding: var(--space_m);
36
+ }
37
+
38
+ .card.--padding-large {
39
+ padding: var(--space_l);
40
+ }
41
+
42
+ .card.--text-start {
43
+ text-align: left;
44
+ }
45
+
46
+ .card.--text-center {
47
+ text-align: center;
48
+ }
49
+
50
+ .card.--text-end {
51
+ text-align: right;
52
+ }
53
+
54
+ @media (hover: hover) and (pointer: fine) {
55
+ .card.--clickable:hover {
56
+ transform: translateY(-2px);
57
+ box-shadow: var(--shadow_xl);
58
+ }
59
+
60
+ .card.--default:hover {
61
+ box-shadow: var(--shadow_m);
62
+ }
63
+
64
+ .card.--elevated:hover {
65
+ box-shadow: var(--shadow_2xl);
66
+ }
67
+
68
+ .card.--outlined:hover {
69
+ box-shadow: var(--shadow_s);
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,33 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { Card } from './Card';
4
+
5
+ describe('Card', () => {
6
+ it('renders children', () => {
7
+ render(<Card>Card content</Card>);
8
+ expect(screen.getByText('Card content')).toBeInTheDocument();
9
+ });
10
+
11
+ it('renders as a div by default', () => {
12
+ const { container } = render(<Card>Content</Card>);
13
+ expect(container.firstChild?.nodeName).toBe('DIV');
14
+ });
15
+
16
+ it('renders as a button when onClick is provided', () => {
17
+ render(<Card onClick={() => {}}>Clickable</Card>);
18
+ expect(screen.getByRole('button')).toBeInTheDocument();
19
+ });
20
+
21
+ it('calls onClick when clicked', async () => {
22
+ const user = userEvent.setup();
23
+ const onClick = vi.fn();
24
+ render(<Card onClick={onClick}>Click</Card>);
25
+ await user.click(screen.getByRole('button'));
26
+ expect(onClick).toHaveBeenCalledOnce();
27
+ });
28
+
29
+ it('accepts a custom className', () => {
30
+ const { container } = render(<Card className="custom">Content</Card>);
31
+ expect(container.firstChild).toHaveClass('custom');
32
+ });
33
+ });
@@ -0,0 +1,32 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Card } from './Card';
3
+
4
+ const meta = {
5
+ title: 'Layout/Card',
6
+ component: Card,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ variant: { control: 'select', options: ['default', 'elevated', 'outlined'] },
10
+ padding: { control: 'select', options: ['none', 'small', 'medium', 'large'] },
11
+ textAlign: { control: 'select', options: ['start', 'center', 'end'] },
12
+ },
13
+ } satisfies Meta<typeof Card>;
14
+
15
+ export default meta;
16
+ type Story = StoryObj<typeof meta>;
17
+
18
+ export const Default: Story = { args: { children: 'Card content', variant: 'default', padding: 'medium' } };
19
+ export const Elevated: Story = { args: { children: 'Elevated card', variant: 'elevated' } };
20
+ export const Outlined: Story = { args: { children: 'Outlined card', variant: 'outlined' } };
21
+ export const Clickable: Story = { args: { children: 'Click me', onClick: () => alert('clicked') } };
22
+ export const Centered: Story = { args: { children: 'Centered', textAlign: 'center' } };
23
+ export const AllVariants: Story = {
24
+ args: { children: '' },
25
+ render: () => (
26
+ <div style={{ display: 'flex', gap: '16px', flexWrap: 'wrap' }}>
27
+ {(['default', 'elevated', 'outlined'] as const).map(v => (
28
+ <Card key={v} variant={v} style={{ width: '200px' }}>{v}</Card>
29
+ ))}
30
+ </div>
31
+ ),
32
+ };
@@ -0,0 +1,45 @@
1
+ import { CSSProperties, ReactNode } from 'react';
2
+ import css from './Card.module.css';
3
+ import { cn } from '@boostdev/design-system-foundation';
4
+
5
+ interface CardProps {
6
+ children: ReactNode;
7
+ className?: string;
8
+ variant?: 'default' | 'elevated' | 'outlined';
9
+ padding?: 'none' | 'small' | 'medium' | 'large';
10
+ textAlign?: 'start' | 'center' | 'end';
11
+ style?: CSSProperties;
12
+ onClick?: () => void;
13
+ }
14
+
15
+ export function Card({
16
+ children,
17
+ className,
18
+ variant = 'default',
19
+ padding = 'medium',
20
+ textAlign = 'start',
21
+ style,
22
+ onClick,
23
+ }: CardProps) {
24
+ const classNames = cn(
25
+ css.card,
26
+ css[`--${variant}`],
27
+ css[`--padding-${padding}`],
28
+ css[`--text-${textAlign}`],
29
+ onClick && css['--clickable'],
30
+ className,
31
+ );
32
+
33
+ const Component = onClick ? 'button' : 'div';
34
+
35
+ return (
36
+ <Component
37
+ className={classNames}
38
+ onClick={onClick}
39
+ style={style}
40
+ {...(onClick && { type: 'button' as const })}
41
+ >
42
+ {children}
43
+ </Component>
44
+ );
45
+ }
@@ -0,0 +1 @@
1
+ export { Card } from './Card';
@@ -0,0 +1,24 @@
1
+ @layer component {
2
+ .wrapper {
3
+ --icon-wrapper-color: var(--color_bg);
4
+ --color_on-icon-wrapper-color: var(--color_on-bg);
5
+
6
+ width: 2em;
7
+ height: 2em;
8
+ font-size: 3em;
9
+ border-radius: 50%;
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ margin: var(--space_l) auto var(--space_m);
14
+ background: var(--icon-wrapper-color);
15
+ color: var(--color_on-icon-wrapper-color);
16
+ fill: var(--color_on-icon-wrapper-color);
17
+ position: relative;
18
+ transition: var(--animation_transition);
19
+ }
20
+
21
+ .wrapper svg {
22
+ fill: currentcolor;
23
+ }
24
+ }
@@ -0,0 +1,19 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { IconWrapper } from './IconWrapper';
3
+
4
+ describe('IconWrapper', () => {
5
+ it('renders children', () => {
6
+ render(<IconWrapper><span data-testid="icon">★</span></IconWrapper>);
7
+ expect(screen.getByTestId('icon')).toBeInTheDocument();
8
+ });
9
+
10
+ it('renders as a div', () => {
11
+ const { container } = render(<IconWrapper><span>★</span></IconWrapper>);
12
+ expect(container.firstChild?.nodeName).toBe('DIV');
13
+ });
14
+
15
+ it('accepts a custom className', () => {
16
+ const { container } = render(<IconWrapper className="custom"><span>★</span></IconWrapper>);
17
+ expect(container.firstChild).toHaveClass('custom');
18
+ });
19
+ });
@@ -0,0 +1,22 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { IconWrapper } from './IconWrapper';
3
+
4
+ const meta = {
5
+ title: 'Layout/IconWrapper',
6
+ component: IconWrapper,
7
+ tags: ['autodocs'],
8
+ } satisfies Meta<typeof IconWrapper>;
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof meta>;
12
+
13
+ export const Default: Story = {
14
+ args: {
15
+ children: (
16
+ <svg viewBox="0 0 24 24" fill="currentColor" width="1em" height="1em">
17
+ <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
18
+ </svg>
19
+ ),
20
+ },
21
+ };
22
+
@@ -0,0 +1,14 @@
1
+ import { ReactNode } from 'react';
2
+ import css from './IconWrapper.module.css';
3
+ import { cn } from '@boostdev/design-system-foundation';
4
+
5
+ interface Props {
6
+ children: ReactNode;
7
+ className?: string;
8
+ }
9
+
10
+ export function IconWrapper({ children, className }: Props) {
11
+ return (
12
+ <div className={cn(className, css.wrapper)}>{children}</div>
13
+ );
14
+ }
@@ -0,0 +1 @@
1
+ export { IconWrapper } from './IconWrapper';
@@ -0,0 +1,75 @@
1
+ @layer component {
2
+ .sectionHeader {
3
+ --sectionHeader-title-color: var(--color_on-bg);
4
+ --sectionHeader_subtitle-color: var(--color_grey--strong);
5
+
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: var(--space_m);
9
+ grid-column: var(--grid_span-100);
10
+ margin-block-end: var(--space_xl);
11
+ }
12
+
13
+ .title {
14
+ color: var(--sectionHeader-title-color);
15
+ margin: 0;
16
+ font-family: var(--font_family--body);
17
+ }
18
+
19
+ .subtitle {
20
+ color: var(--sectionHeader_subtitle-color);
21
+ margin: 0;
22
+ font-family: var(--font_family--body);
23
+ font-weight: var(--font_weight--medium);
24
+ }
25
+
26
+ .sectionHeader.--start {
27
+ align-items: flex-start;
28
+ text-align: left;
29
+ }
30
+
31
+ .sectionHeader.--center {
32
+ align-items: center;
33
+ text-align: center;
34
+ }
35
+
36
+ .sectionHeader.--end {
37
+ align-items: flex-end;
38
+ text-align: right;
39
+ }
40
+
41
+ .sectionHeader.--small .title {
42
+ font-size: var(--font_size--heading-1);
43
+ line-height: var(--font_line-height--heading);
44
+ font-weight: var(--font_weight--bold);
45
+ }
46
+
47
+ .sectionHeader.--small .subtitle {
48
+ font-size: var(--font_size--body);
49
+ line-height: 1.4;
50
+ }
51
+
52
+ .sectionHeader.--medium .title {
53
+ font-size: var(--font_size--display);
54
+ line-height: var(--font_line-height--display);
55
+ font-weight: var(--font_weight--bold);
56
+ }
57
+
58
+ .sectionHeader.--medium .subtitle {
59
+ font-size: var(--font_size--heading-3);
60
+ line-height: 1.3;
61
+ }
62
+
63
+ .sectionHeader.--large .title {
64
+ font-size: var(--font_size--display);
65
+ line-height: var(--font_line-height--display);
66
+ font-weight: var(--font_weight--bold);
67
+ letter-spacing: var(--font_tracking--tight);
68
+ }
69
+
70
+ .sectionHeader.--large .subtitle {
71
+ font-size: var(--font_size--heading-2);
72
+ line-height: 1.3;
73
+ font-weight: var(--font_weight--medium);
74
+ }
75
+ }
@@ -0,0 +1,31 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { SectionHeader } from './SectionHeader';
3
+
4
+ describe('SectionHeader', () => {
5
+ it('renders the title', () => {
6
+ render(<SectionHeader title="My Title" />);
7
+ expect(screen.getByText('My Title')).toBeInTheDocument();
8
+ });
9
+
10
+ it('renders the subtitle when provided', () => {
11
+ render(<SectionHeader title="Title" subtitle="Subtitle text" />);
12
+ expect(screen.getByText('Subtitle text')).toBeInTheDocument();
13
+ });
14
+
15
+ it('does not render subtitle when not provided', () => {
16
+ render(<SectionHeader title="Title" />);
17
+ expect(screen.queryByText('Subtitle text')).not.toBeInTheDocument();
18
+ });
19
+
20
+ it('renders inside a header element', () => {
21
+ render(<SectionHeader title="Title" />);
22
+ const heading = screen.getByRole('heading', { level: 2 });
23
+ expect(heading).toBeInTheDocument();
24
+ expect(heading.closest('header')).toBeInTheDocument();
25
+ });
26
+
27
+ it('accepts a custom className', () => {
28
+ const { container } = render(<SectionHeader title="Title" className="custom" />);
29
+ expect(container.firstChild).toHaveClass('custom');
30
+ });
31
+ });
@@ -0,0 +1,21 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { SectionHeader } from './SectionHeader';
3
+
4
+ const meta = {
5
+ title: 'Layout/SectionHeader',
6
+ component: SectionHeader,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ alignment: { control: 'select', options: ['start', 'center', 'end'] },
10
+ size: { control: 'select', options: ['small', 'medium', 'large'] },
11
+ },
12
+ } satisfies Meta<typeof SectionHeader>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Default: Story = { args: { title: 'Section Title', subtitle: 'A descriptive subtitle.' } };
18
+ export const WithoutSubtitle: Story = { args: { title: 'Title Only' } };
19
+ export const Centered: Story = { args: { title: 'Centered Title', subtitle: 'Centered subtitle.', alignment: 'center' } };
20
+ export const Small: Story = { args: { title: 'Small Header', subtitle: 'Small size.', size: 'small' } };
21
+ export const Large: Story = { args: { title: 'Large Header', subtitle: 'Large size.', size: 'large' } };
@@ -0,0 +1,32 @@
1
+ import css from './SectionHeader.module.css';
2
+ import { cn } from '@boostdev/design-system-foundation';
3
+ import {JSX} from "react/jsx-runtime";
4
+ type IntrinsicElements = JSX.IntrinsicElements;
5
+
6
+ type IntrinsicElement = keyof IntrinsicElements
7
+
8
+ type SectionHeaderProps = {
9
+ title: string;
10
+ subtitle?: string;
11
+ className?: string;
12
+ alignment?: 'start' | 'center' | 'end';
13
+ size?: 'small' | 'medium' | 'large';
14
+ titleAs?: IntrinsicElement;
15
+ };
16
+
17
+ export function SectionHeader({
18
+ title,
19
+ subtitle,
20
+ className,
21
+ alignment = 'start',
22
+ size = 'medium',
23
+ titleAs = 'h2'
24
+ }: Readonly<SectionHeaderProps>) {
25
+ const Title = titleAs;
26
+ return (
27
+ <header className={cn(css.sectionHeader, css[`--${alignment}`], css[`--${size}`], className)}>
28
+ <Title className={css.title}>{title}</Title>
29
+ {subtitle && <p className={css.subtitle}>{subtitle}</p>}
30
+ </header>
31
+ );
32
+ }
@@ -0,0 +1 @@
1
+ export { SectionHeader } from './SectionHeader';
@@ -0,0 +1,87 @@
1
+ @layer component {
2
+ .accordion {
3
+ display: flex;
4
+ flex-direction: column;
5
+ border: 1px solid var(--color_bg--subtle);
6
+ border-radius: var(--border_radius--s);
7
+ overflow: hidden;
8
+ }
9
+
10
+ .item {
11
+ border-bottom: 1px solid var(--color_bg--subtle);
12
+ }
13
+
14
+ .item:last-child {
15
+ border-bottom: none;
16
+ }
17
+
18
+ .heading {
19
+ margin: 0;
20
+ }
21
+
22
+ .trigger {
23
+ all: unset;
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: space-between;
27
+ width: 100%;
28
+ padding: var(--space_m);
29
+ font-family: var(--font_family--body);
30
+ font-size: var(--font_size--body);
31
+ font-weight: var(--font_weight--semibold);
32
+ color: var(--color_on-bg);
33
+ cursor: pointer;
34
+ transition: var(--animation_transition);
35
+ box-sizing: border-box;
36
+ }
37
+
38
+ .trigger:disabled {
39
+ opacity: 0.4;
40
+ cursor: not-allowed;
41
+ }
42
+
43
+ .trigger:focus-visible {
44
+ outline: var(--outline_default);
45
+ outline-offset: calc(var(--outline_offset) * -1);
46
+ border-radius: var(--border_radius--xs);
47
+ }
48
+
49
+ @media (hover: hover) and (pointer: fine) {
50
+ .trigger:not(:disabled):hover {
51
+ background-color: var(--color_bg--subtle);
52
+ }
53
+ }
54
+
55
+ .triggerLabel {
56
+ flex: 1;
57
+ text-align: start;
58
+ }
59
+
60
+ .chevron {
61
+ width: 1.25rem;
62
+ height: 1.25rem;
63
+ flex-shrink: 0;
64
+ transition: transform var(--animation_transition-duration) var(--animation_easing);
65
+ }
66
+
67
+ .item.--open .chevron {
68
+ transform: rotate(180deg);
69
+ }
70
+
71
+ .panel {
72
+ overflow: hidden;
73
+ }
74
+
75
+ .panelContent {
76
+ padding: 0 var(--space_m) var(--space_m);
77
+ color: var(--color_on-bg);
78
+ font-size: var(--font_size--body);
79
+ line-height: var(--font_line-height--body);
80
+ }
81
+
82
+ @media (prefers-reduced-motion: reduce) {
83
+ .chevron {
84
+ transition: none;
85
+ }
86
+ }
87
+ }