@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
package/package.json ADDED
@@ -0,0 +1,143 @@
1
+ {
2
+ "name": "@boostdev/design-system-components",
3
+ "version": "0.1.1",
4
+ "description": "BoostDev React component library: accessible, token-driven components built on @boostdev/design-system-foundation",
5
+ "keywords": [
6
+ "React",
7
+ "component",
8
+ "library:",
9
+ "accessible",
10
+ "token-driven",
11
+ "components",
12
+ "built",
13
+ "on",
14
+ "@boostdev/design-system-foundation"
15
+ ],
16
+ "homepage": "https://gitlab.com/b00stdev/boostcomponents#readme",
17
+ "bugs": {
18
+ "url": "https://gitlab.com/b00stdev/boostcomponents/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://gitlab.com/b00stdev/boostcomponents.git"
23
+ },
24
+ "license": "ISC",
25
+ "author": "Boostdev",
26
+ "type": "module",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "require": "./dist/index.cjs"
32
+ },
33
+ "./css": "./src/css/index.css",
34
+ "./css/accordion": "./src/components/ui/Accordion/Accordion.module.css",
35
+ "./css/alert": "./src/components/ui/Alert/Alert.module.css",
36
+ "./css/avatar": "./src/components/ui/Avatar/Avatar.module.css",
37
+ "./css/badge": "./src/components/ui/Badge/Badge.module.css",
38
+ "./css/breadcrumb": "./src/components/ui/Breadcrumb/Breadcrumb.module.css",
39
+ "./css/calendar": "./src/components/ui/Calendar/Calendar.module.css",
40
+ "./css/carousel": "./src/components/ui/Carousel/Carousel.module.css",
41
+ "./css/description-list": "./src/components/ui/DescriptionList/DescriptionList.module.css",
42
+ "./css/link": "./src/components/ui/Link/Link.module.css",
43
+ "./css/loading": "./src/components/ui/Loading/Loading.module.css",
44
+ "./css/notification-banner": "./src/components/ui/NotificationBanner/NotificationBanner.module.css",
45
+ "./css/pagination": "./src/components/ui/Pagination/Pagination.module.css",
46
+ "./css/progress": "./src/components/ui/Progress/Progress.module.css",
47
+ "./css/progress-circle": "./src/components/ui/ProgressCircle/ProgressCircle.module.css",
48
+ "./css/separator": "./src/components/ui/Separator/Separator.module.css",
49
+ "./css/skeleton": "./src/components/ui/Skeleton/Skeleton.module.css",
50
+ "./css/skip-link": "./src/components/ui/SkipLink/SkipLink.module.css",
51
+ "./css/table": "./src/components/ui/Table/Table.module.css",
52
+ "./css/tabs": "./src/components/ui/Tabs/Tabs.module.css",
53
+ "./css/tooltip": "./src/components/ui/Tooltip/Tooltip.module.css",
54
+ "./css/typography": "./src/components/ui/Typography/Typography.module.css",
55
+ "./css/button": "./src/components/interaction/Button/Button.module.css",
56
+ "./css/command": "./src/components/interaction/Command/Command.module.css",
57
+ "./css/dialog": "./src/components/interaction/Dialog/Dialog.module.css",
58
+ "./css/drawer": "./src/components/interaction/Drawer/Drawer.module.css",
59
+ "./css/dropdown-menu": "./src/components/interaction/DropdownMenu/DropdownMenu.module.css",
60
+ "./css/popover": "./src/components/interaction/Popover/Popover.module.css",
61
+ "./css/rating": "./src/components/interaction/Rating/Rating.module.css",
62
+ "./css/toast": "./src/components/interaction/Toast/Toast.module.css",
63
+ "./css/form/checkbox": "./src/components/interaction/form/Checkbox/Checkbox.module.css",
64
+ "./css/form/combobox": "./src/components/interaction/form/Combobox/Combobox.module.css",
65
+ "./css/form/file-input": "./src/components/interaction/form/FileInput/FileInput.module.css",
66
+ "./css/form/form-input": "./src/components/interaction/form/FormInput/FormInput.module.css",
67
+ "./css/form/number-input": "./src/components/interaction/form/NumberInput/NumberInput.module.css",
68
+ "./css/form/radio": "./src/components/interaction/form/Radio/Radio.module.css",
69
+ "./css/form/select": "./src/components/interaction/form/Select/Select.module.css",
70
+ "./css/form/slider": "./src/components/interaction/form/Slider/Slider.module.css",
71
+ "./css/form/switch": "./src/components/interaction/form/Switch/Switch.module.css",
72
+ "./css/form/textarea": "./src/components/interaction/form/Textarea/Textarea.module.css",
73
+ "./css/form/input-container": "./src/components/interaction/form/atoms/InputContainer.module.css",
74
+ "./css/form/label": "./src/components/interaction/form/atoms/Label.module.css",
75
+ "./css/form/message": "./src/components/interaction/form/atoms/Message.module.css",
76
+ "./css/button-group": "./src/components/layout/ButtonGroup/ButtonGroup.module.css",
77
+ "./css/card": "./src/components/layout/Card/Card.module.css",
78
+ "./css/icon-wrapper": "./src/components/layout/IconWrapper/IconWrapper.module.css",
79
+ "./css/section-header": "./src/components/layout/SectionHeader/SectionHeader.module.css"
80
+ },
81
+ "main": "./dist/index.js",
82
+ "types": "./dist/index.d.ts",
83
+ "files": [
84
+ "dist",
85
+ "src",
86
+ "AGENTS.md"
87
+ ],
88
+ "scripts": {
89
+ "build": "tsup",
90
+ "dev": "tsup --watch",
91
+ "typecheck": "tsc --noEmit",
92
+ "lint": "eslint src",
93
+ "lint:css": "stylelint \"src/**/*.css\"",
94
+ "storybook": "storybook dev -p 6006",
95
+ "build-storybook": "storybook build",
96
+ "test": "vitest run",
97
+ "test:watch": "vitest"
98
+ },
99
+ "dependencies": {
100
+ "eslint-scope": "^8.4.0",
101
+ "eslint-visitor-keys": "^4.2.1",
102
+ "react": "^19.2.4",
103
+ "react-dom": "^19.2.4"
104
+ },
105
+ "devDependencies": {
106
+ "@boostdev/design-system-foundation": "^0.1.10",
107
+ "@storybook/addon-essentials": "^8.6.14",
108
+ "@storybook/blocks": "^8.6.14",
109
+ "@storybook/react": "8",
110
+ "@storybook/react-vite": "^8.6.18",
111
+ "@testing-library/jest-dom": "^6.9.1",
112
+ "@testing-library/react": "^16.3.2",
113
+ "@testing-library/user-event": "^14.6.1",
114
+ "@types/node": "^25.5.0",
115
+ "@types/react": "^18",
116
+ "@types/react-dom": "^18",
117
+ "@vitejs/plugin-react": "^5.1.4",
118
+ "eslint": "^9",
119
+ "identity-obj-proxy": "^3.0.0",
120
+ "jsdom": "^28.1.0",
121
+ "storybook": "^8.6.18",
122
+ "stylelint": "^16",
123
+ "stylelint-config-standard": "^37",
124
+ "tsup": "^8",
125
+ "tsx": "^4",
126
+ "typescript": "^5.8",
127
+ "typescript-eslint": "^8",
128
+ "vite": "^5.4.21",
129
+ "vitest": "^3"
130
+ },
131
+ "peerDependencies": {
132
+ "@boostdev/design-system-foundation": ">=0.1.0",
133
+ "react": ">=18",
134
+ "react-dom": ">=18"
135
+ },
136
+ "publishConfig": {
137
+ "access": "public",
138
+ "registry": "https://registry.npmjs.org/"
139
+ },
140
+ "sideEffects": [
141
+ "src/css/**/*.css"
142
+ ]
143
+ }
@@ -0,0 +1,136 @@
1
+ @keyframes pulse {
2
+ 0% {
3
+ transform: scale(1);
4
+ box-shadow: 0 0 0 0 rgb(from var(--button_pulse-color) r g b / 50%);
5
+ }
6
+
7
+ 70% {
8
+ transform: scale(1.01);
9
+ box-shadow: 0 0 0 var(--space_m) rgb(from var(--button_pulse-color) r g b / 0%);
10
+ }
11
+
12
+ 100% {
13
+ transform: scale(1);
14
+ box-shadow: 0 0 0 0 rgb(from var(--button_pulse-color) r g b / 0%);
15
+ }
16
+ }
17
+
18
+ @layer component {
19
+ .button,
20
+ .button[href] {
21
+ /* Component tokens — override from a parent context or inline style */
22
+ --button_height: 3em;
23
+ --button_font-size: inherit;
24
+ --button_bg: var(--color_cta);
25
+ --button_text: var(--color_on-cta);
26
+ --button_border-color: var(--color_cta);
27
+ --button_pulse-color: var(--button_bg);
28
+
29
+ all: unset;
30
+ position: relative;
31
+ font: inherit;
32
+ padding-inline: var(--space_l);
33
+ display: inline-flex;
34
+ gap: var(--space_xs);
35
+ border-radius: var(--border_radius--s);
36
+ align-items: center;
37
+ cursor: pointer;
38
+ scroll-padding-block-end: var(--space_l);
39
+ justify-content: center;
40
+ height: var(--button_height);
41
+ font-size: var(--button_font-size);
42
+ white-space: nowrap;
43
+ line-height: 1;
44
+ background-color: var(--button_bg);
45
+ color: var(--button_text);
46
+ border: 2px solid var(--button_border-color);
47
+ transition: var(--animation_transition);
48
+ }
49
+
50
+ .button.--primary {
51
+ --button_bg: var(--color_cta);
52
+ --button_text: var(--color_on-cta);
53
+ --button_border-color: transparent;
54
+ }
55
+
56
+ .button.--secondary {
57
+ --button_bg: var(--color_bg);
58
+ --button_text: var(--color_on-bg);
59
+ --button_border-color: var(--button_text);
60
+ }
61
+
62
+ .button.--size_small {
63
+ --button_height: 2.25em;
64
+ --button_font-size: var(--font_size--body--s);
65
+
66
+ padding-inline: var(--space_m);
67
+ }
68
+
69
+ .button.--size_medium {
70
+ --button_height: 3em;
71
+
72
+ padding-inline: var(--space_l);
73
+ }
74
+
75
+ .button.--size_large {
76
+ --button_height: 3.75em;
77
+ --button_font-size: var(--font_size--heading-3);
78
+
79
+ padding-inline: var(--space_xl);
80
+ }
81
+
82
+ .button.--hasPulse {
83
+ animation: pulse 3s infinite;
84
+ }
85
+
86
+ .button[href] {
87
+ text-decoration: none;
88
+ }
89
+
90
+ .button svg {
91
+ --icon__stroke: currentcolor;
92
+
93
+ fill: currentcolor;
94
+ color: currentcolor;
95
+ flex-shrink: 0;
96
+ }
97
+
98
+ .prefix {
99
+ display: flex;
100
+ align-items: center;
101
+ margin-inline-start: calc(var(--space_s) * -1);
102
+ transition: var(--animation_transition);
103
+ }
104
+
105
+ .suffix {
106
+ display: flex;
107
+ align-items: center;
108
+ margin-inline-end: calc(var(--space_s) * -1);
109
+ transition: var(--animation_transition);
110
+ }
111
+
112
+ @media (hover: hover) and (pointer: fine) {
113
+ .button:hover .prefix svg {
114
+ animation: 2s infinite 'fadeZoom' ease-out;
115
+ }
116
+
117
+ .button:hover .suffix svg {
118
+ animation: 2s infinite 'fadeZoom' ease-out;
119
+ }
120
+ }
121
+
122
+ .button:focus-visible {
123
+ outline-offset: var(--outline_offset);
124
+ outline: var(--outline_default);
125
+ border-radius: var(--border_radius--s);
126
+ }
127
+
128
+ @media (hover: hover) and (pointer: fine) {
129
+ .button:hover {
130
+ --button_bg: var(--color_bg);
131
+ --button_text: var(--color_interactive);
132
+ --button_pulse-color: var(--color_interactive);
133
+ --button_border-color: currentcolor;
134
+ }
135
+ }
136
+ }
@@ -0,0 +1,50 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { Button } from './Button';
4
+
5
+ describe('Button', () => {
6
+ it('renders children', () => {
7
+ render(<Button>Click me</Button>);
8
+ expect(screen.getByText('Click me')).toBeInTheDocument();
9
+ });
10
+
11
+ it('renders as a button element by default', () => {
12
+ render(<Button>Click</Button>);
13
+ expect(screen.getByRole('button')).toBeInTheDocument();
14
+ });
15
+
16
+ it('renders as an anchor when href is provided', () => {
17
+ render(<Button href="/path">Link</Button>);
18
+ const link = screen.getByRole('link');
19
+ expect(link).toBeInTheDocument();
20
+ expect(link).toHaveAttribute('href', '/path');
21
+ });
22
+
23
+ it('calls onClick when clicked', async () => {
24
+ const user = userEvent.setup();
25
+ const onClick = vi.fn();
26
+ render(<Button onClick={onClick}>Click</Button>);
27
+ await user.click(screen.getByRole('button'));
28
+ expect(onClick).toHaveBeenCalledOnce();
29
+ });
30
+
31
+ it('is disabled when disabled prop is true', () => {
32
+ render(<Button disabled>Disabled</Button>);
33
+ expect(screen.getByRole('button')).toBeDisabled();
34
+ });
35
+
36
+ it('has type="button" by default', () => {
37
+ render(<Button>Submit</Button>);
38
+ expect(screen.getByRole('button')).toHaveAttribute('type', 'button');
39
+ });
40
+
41
+ it('renders iconStart slot', () => {
42
+ render(<Button iconStart={<span data-testid="icon">★</span>}>Label</Button>);
43
+ expect(screen.getByTestId('icon')).toBeInTheDocument();
44
+ });
45
+
46
+ it('renders iconEnd slot', () => {
47
+ render(<Button iconEnd={<span data-testid="icon">★</span>}>Label</Button>);
48
+ expect(screen.getByTestId('icon')).toBeInTheDocument();
49
+ });
50
+ });
@@ -0,0 +1,43 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Button } from './Button';
3
+
4
+ const meta = {
5
+ title: 'Interaction/Button',
6
+ component: Button,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ variant: { control: 'select', options: ['primary', 'secondary'] },
10
+ size: { control: 'select', options: ['small', 'medium', 'large'] },
11
+ onClick: { action: 'clicked' },
12
+ },
13
+ } satisfies Meta<typeof Button>;
14
+
15
+ export default meta;
16
+ type Story = StoryObj<typeof meta>;
17
+
18
+ export const Primary: Story = { args: { children: 'Primary', variant: 'primary' } };
19
+ export const Secondary: Story = { args: { children: 'Secondary', variant: 'secondary' } };
20
+ export const Small: Story = { args: { children: 'Small', size: 'small' } };
21
+ export const Medium: Story = { args: { children: 'Medium', size: 'medium' } };
22
+ export const Large: Story = { args: { children: 'Large', size: 'large' } };
23
+ export const AsLink: Story = { args: { children: 'Link Button', href: 'https://example.com' } };
24
+ export const WithPulse: Story = { args: { children: 'Pulsing', hasPulse: true } };
25
+ export const Disabled: Story = { args: { children: 'Disabled', disabled: true } };
26
+ export const WithIcons: Story = {
27
+ args: {
28
+ children: 'With Icons',
29
+ iconStart: <span>→</span>,
30
+ iconEnd: <span>←</span>,
31
+ },
32
+ };
33
+ export const AllVariants: Story = {
34
+ render: () => (
35
+ <div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap', alignItems: 'center' }}>
36
+ {(['primary', 'secondary'] as const).map(v => (
37
+ ['small', 'medium', 'large'].map(s => (
38
+ <Button key={`${v}-${s}`} variant={v} size={s as 'small' | 'medium' | 'large'}>{v} {s}</Button>
39
+ ))
40
+ ))}
41
+ </div>
42
+ ),
43
+ };
@@ -0,0 +1,68 @@
1
+ import { MouseEventHandler, ReactNode } from 'react';
2
+ import css from './Button.module.css';
3
+ import { cn } from '@boostdev/design-system-foundation';
4
+
5
+ interface ButtonProps {
6
+ href?: string;
7
+ variant?: 'primary' | 'secondary';
8
+ type?: 'button' | 'submit' | 'reset';
9
+ size?: 'small' | 'medium' | 'large';
10
+ iconStart?: ReactNode;
11
+ iconEnd?: ReactNode;
12
+ children?: ReactNode;
13
+ className?: string;
14
+ disabled?: boolean;
15
+ hasPulse?: boolean;
16
+ onClick?: MouseEventHandler<HTMLElement>;
17
+ target?: string;
18
+ rel?: string;
19
+ 'aria-label'?: string;
20
+ 'aria-describedby'?: string;
21
+ }
22
+
23
+ export function Button({
24
+ children,
25
+ className = '',
26
+ variant = 'primary',
27
+ type = 'button',
28
+ iconStart,
29
+ iconEnd,
30
+ size = 'medium',
31
+ hasPulse = false,
32
+ href,
33
+ target,
34
+ rel,
35
+ disabled,
36
+ onClick,
37
+ ...rest
38
+ }: Readonly<ButtonProps>) {
39
+ const classNames = cn(
40
+ css.button,
41
+ css[`--${variant}`],
42
+ css[`--size_${size}`],
43
+ hasPulse && css['--hasPulse'],
44
+ className,
45
+ );
46
+
47
+ const allChildren = (
48
+ <>
49
+ {!!iconStart && <span className={css.prefix}>{iconStart}</span>}
50
+ {children}
51
+ {!!iconEnd && <span className={css.suffix}>{iconEnd}</span>}
52
+ </>
53
+ );
54
+
55
+ if (href) {
56
+ return (
57
+ <a className={classNames} href={href} target={target} rel={rel} onClick={onClick as MouseEventHandler<HTMLAnchorElement>} {...rest}>
58
+ {allChildren}
59
+ </a>
60
+ );
61
+ }
62
+
63
+ return (
64
+ <button type={type} className={classNames} disabled={disabled} onClick={onClick as MouseEventHandler<HTMLButtonElement>} {...rest}>
65
+ {allChildren}
66
+ </button>
67
+ );
68
+ }
@@ -0,0 +1 @@
1
+ export { Button } from './Button';
@@ -0,0 +1,128 @@
1
+ @layer component {
2
+ .dialog {
3
+ padding: 0;
4
+ border: none;
5
+ border-radius: var(--border_radius--m);
6
+ background: transparent;
7
+ width: min(40rem, 90vw);
8
+ max-height: 70vh;
9
+ overflow: visible;
10
+ margin-block-start: 10vh;
11
+ }
12
+
13
+ .dialog::backdrop {
14
+ background: rgb(0 0 0 / 50%);
15
+ }
16
+
17
+ .palette {
18
+ display: flex;
19
+ flex-direction: column;
20
+ background-color: var(--color_bg);
21
+ border-radius: var(--border_radius--m);
22
+ box-shadow: var(--shadow_xl);
23
+ overflow: hidden;
24
+ max-height: 70vh;
25
+ border: 1px solid var(--color_bg--subtle);
26
+ }
27
+
28
+ .searchRow {
29
+ display: flex;
30
+ align-items: center;
31
+ gap: var(--space_xs);
32
+ padding: var(--space_s) var(--space_m);
33
+ border-block-end: 1px solid var(--color_bg--subtle);
34
+ flex-shrink: 0;
35
+ }
36
+
37
+ .searchIcon {
38
+ width: 1.25rem;
39
+ height: 1.25rem;
40
+ color: var(--color_on-bg--muted);
41
+ flex-shrink: 0;
42
+ }
43
+
44
+ .search {
45
+ flex: 1;
46
+ border: none;
47
+ outline: none;
48
+ font-family: var(--font_family--body);
49
+ font-size: var(--font_size--body);
50
+ color: var(--color_on-bg);
51
+ background: transparent;
52
+ min-width: 0;
53
+ }
54
+
55
+ .search::placeholder {
56
+ color: var(--color_on-bg--muted);
57
+ }
58
+
59
+ .escHint {
60
+ font-size: var(--font_size--body--s);
61
+ color: var(--color_on-bg--muted);
62
+ border: 1px solid var(--color_on-bg--muted);
63
+ border-radius: var(--border_radius--xs);
64
+ padding: 0.1em 0.4em;
65
+ flex-shrink: 0;
66
+ }
67
+
68
+ .list {
69
+ overflow-y: auto;
70
+ list-style: none;
71
+ margin: 0;
72
+ padding: var(--space_xs) 0;
73
+ }
74
+
75
+ .groupList {
76
+ list-style: none;
77
+ margin: 0;
78
+ padding: 0;
79
+ }
80
+
81
+ .group {
82
+ padding: var(--space_xs) var(--space_m);
83
+ font-size: var(--font_size--body--s);
84
+ font-weight: var(--font_weight--semibold);
85
+ color: var(--color_on-bg--muted);
86
+ text-transform: uppercase;
87
+ letter-spacing: 0.05em;
88
+ }
89
+
90
+ .item {
91
+ display: flex;
92
+ align-items: center;
93
+ gap: var(--space_s);
94
+ padding: var(--space_s) var(--space_m);
95
+ cursor: pointer;
96
+ transition: background-color var(--animation_transition-duration) var(--animation_easing);
97
+ }
98
+
99
+ .itemActive {
100
+ background-color: var(--color_bg--subtle);
101
+ }
102
+
103
+ .itemLabel {
104
+ flex: 1;
105
+ font-size: var(--font_size--body);
106
+ color: var(--color_on-bg);
107
+ }
108
+
109
+ .itemDesc {
110
+ font-size: var(--font_size--body--s);
111
+ color: var(--color_on-bg--muted);
112
+ }
113
+
114
+ .shortcut {
115
+ font-size: var(--font_size--body--s);
116
+ color: var(--color_on-bg--muted);
117
+ border: 1px solid var(--color_on-bg--muted);
118
+ border-radius: var(--border_radius--xs);
119
+ padding: 0.1em 0.4em;
120
+ }
121
+
122
+ .empty {
123
+ padding: var(--space_l) var(--space_m);
124
+ text-align: center;
125
+ color: var(--color_on-bg--muted);
126
+ font-size: var(--font_size--body);
127
+ }
128
+ }
@@ -0,0 +1,60 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { Command } from './Command';
4
+ import type { CommandItem } from './Command';
5
+
6
+ beforeEach(() => {
7
+ HTMLDialogElement.prototype.showModal = vi.fn().mockImplementation(function (this: HTMLDialogElement) {
8
+ this.setAttribute('open', '');
9
+ });
10
+ HTMLDialogElement.prototype.close = vi.fn().mockImplementation(function (this: HTMLDialogElement) {
11
+ this.removeAttribute('open');
12
+ });
13
+ });
14
+
15
+ const items: CommandItem[] = [
16
+ { id: 'new', label: 'New file', onSelect: vi.fn() },
17
+ { id: 'open', label: 'Open file', onSelect: vi.fn() },
18
+ { id: 'save', label: 'Save', description: 'Save current file', onSelect: vi.fn() },
19
+ ];
20
+
21
+ describe('Command', () => {
22
+ it('renders search input when open', () => {
23
+ render(<Command isOpen items={items} onClose={() => {}} />);
24
+ expect(screen.getByRole('combobox')).toBeInTheDocument();
25
+ });
26
+
27
+ it('shows all items initially', () => {
28
+ render(<Command isOpen items={items} onClose={() => {}} />);
29
+ expect(screen.getByText('New file')).toBeInTheDocument();
30
+ expect(screen.getByText('Open file')).toBeInTheDocument();
31
+ });
32
+
33
+ it('filters items by query', async () => {
34
+ render(<Command isOpen items={items} onClose={() => {}} />);
35
+ await userEvent.type(screen.getByRole('combobox'), 'save');
36
+ expect(screen.queryByText('New file')).not.toBeInTheDocument();
37
+ expect(screen.getByText('Save')).toBeInTheDocument();
38
+ });
39
+
40
+ it('shows empty state when no items match', async () => {
41
+ render(<Command isOpen items={items} onClose={() => {}} />);
42
+ await userEvent.type(screen.getByRole('combobox'), 'zzz');
43
+ expect(screen.getByText(/no results/i)).toBeInTheDocument();
44
+ });
45
+
46
+ it('calls onSelect and onClose when item clicked', async () => {
47
+ const onClose = vi.fn();
48
+ const onSelect = vi.fn();
49
+ const testItems: CommandItem[] = [{ id: 'x', label: 'Action', onSelect }];
50
+ render(<Command isOpen items={testItems} onClose={onClose} />);
51
+ fireEvent.click(screen.getByText('Action'));
52
+ expect(onSelect).toHaveBeenCalledOnce();
53
+ expect(onClose).toHaveBeenCalledOnce();
54
+ });
55
+
56
+ it('shows item description', () => {
57
+ render(<Command isOpen items={items} onClose={() => {}} />);
58
+ expect(screen.getByText('Save current file')).toBeInTheDocument();
59
+ });
60
+ });
@@ -0,0 +1,35 @@
1
+ import { useState } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { Command } from './Command';
4
+ import type { CommandItem } from './Command';
5
+
6
+ const meta = {
7
+ title: 'Interaction/Command',
8
+ component: Command,
9
+ tags: ['autodocs'],
10
+ } satisfies Meta<typeof Command>;
11
+
12
+ export default meta;
13
+ type Story = StoryObj<typeof meta>;
14
+
15
+ const items: CommandItem[] = [
16
+ { id: 'new-file', label: 'New file', group: 'File', shortcut: '⌘N', onSelect: () => alert('New file') },
17
+ { id: 'open-file', label: 'Open file', group: 'File', shortcut: '⌘O', onSelect: () => alert('Open file') },
18
+ { id: 'save', label: 'Save', group: 'File', shortcut: '⌘S', onSelect: () => alert('Save') },
19
+ { id: 'find', label: 'Find', description: 'Search in document', group: 'Edit', shortcut: '⌘F', onSelect: () => alert('Find') },
20
+ { id: 'preferences', label: 'Preferences', group: 'Settings', onSelect: () => alert('Preferences') },
21
+ ];
22
+
23
+ export const Default: Story = {
24
+ render: () => {
25
+ const [open, setOpen] = useState(false);
26
+ return (
27
+ <>
28
+ <button type="button" onClick={() => setOpen(true)}>
29
+ Open Command Palette <kbd>⌘K</kbd>
30
+ </button>
31
+ <Command isOpen={open} items={items} onClose={() => setOpen(false)} />
32
+ </>
33
+ );
34
+ },
35
+ };