banhaten 0.1.2 → 0.2.0

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 (365) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/CONTRIBUTING.md +44 -0
  3. package/README.md +145 -328
  4. package/SECURITY.md +15 -0
  5. package/banhaten.config.example.json +1 -3
  6. package/docs/design-system/README.md +20 -0
  7. package/docs/design-system/adoption-quality-remediation.md +87 -0
  8. package/docs/design-system/agent-usage.md +13 -0
  9. package/docs/design-system/appearance-presets.md +189 -0
  10. package/docs/design-system/appearances/default.md +94 -0
  11. package/docs/design-system/appearances/rounded.md +95 -0
  12. package/docs/design-system/appearances/sharp.md +95 -0
  13. package/docs/design-system/component-docs-pattern.md +81 -0
  14. package/docs/design-system/component-showcase-consistency-report.md +217 -0
  15. package/docs/design-system/component-token-consistency-audit.md +173 -0
  16. package/docs/design-system/components/README.md +75 -0
  17. package/docs/design-system/components/accordion.md +67 -0
  18. package/docs/design-system/components/activity-feed.md +106 -0
  19. package/docs/design-system/components/alert-dialog.md +87 -0
  20. package/docs/design-system/components/alert.md +111 -0
  21. package/docs/design-system/components/aspect-ratio.md +59 -0
  22. package/docs/design-system/components/attribute.md +103 -0
  23. package/docs/design-system/components/autocomplete.md +93 -0
  24. package/docs/design-system/components/avatar.md +70 -0
  25. package/docs/design-system/components/badge.md +72 -0
  26. package/docs/design-system/components/banner.md +99 -0
  27. package/docs/design-system/components/breadcrumbs.md +188 -0
  28. package/docs/design-system/components/button-group.md +117 -0
  29. package/docs/design-system/components/button.md +95 -0
  30. package/docs/design-system/components/card.md +95 -0
  31. package/docs/design-system/components/carousel.md +61 -0
  32. package/docs/design-system/components/catalog-components.md +59 -0
  33. package/docs/design-system/components/chart.md +58 -0
  34. package/docs/design-system/components/checkbox.md +68 -0
  35. package/docs/design-system/components/collapsible.md +63 -0
  36. package/docs/design-system/components/command.md +75 -0
  37. package/docs/design-system/components/context-menu.md +59 -0
  38. package/docs/design-system/components/date-picker.md +97 -0
  39. package/docs/design-system/components/divider.md +117 -0
  40. package/docs/design-system/components/empty-state.md +72 -0
  41. package/docs/design-system/components/field.md +86 -0
  42. package/docs/design-system/components/file-upload.md +199 -0
  43. package/docs/design-system/components/hover-card.md +61 -0
  44. package/docs/design-system/components/icons.md +48 -0
  45. package/docs/design-system/components/input-group.md +73 -0
  46. package/docs/design-system/components/input-otp.md +73 -0
  47. package/docs/design-system/components/input.md +84 -0
  48. package/docs/design-system/components/item.md +72 -0
  49. package/docs/design-system/components/kbd.md +62 -0
  50. package/docs/design-system/components/label.md +64 -0
  51. package/docs/design-system/components/menu.md +76 -0
  52. package/docs/design-system/components/menubar.md +60 -0
  53. package/docs/design-system/components/modal.md +119 -0
  54. package/docs/design-system/components/native-select.md +70 -0
  55. package/docs/design-system/components/navigation-menu.md +63 -0
  56. package/docs/design-system/components/onboarding-step-list-item.md +94 -0
  57. package/docs/design-system/components/page-header.md +98 -0
  58. package/docs/design-system/components/pagination.md +71 -0
  59. package/docs/design-system/components/popover.md +77 -0
  60. package/docs/design-system/components/progress-slider.md +66 -0
  61. package/docs/design-system/components/progress.md +92 -0
  62. package/docs/design-system/components/radio-card.md +65 -0
  63. package/docs/design-system/components/radio-group.md +72 -0
  64. package/docs/design-system/components/resizable.md +57 -0
  65. package/docs/design-system/components/scroll-area.md +60 -0
  66. package/docs/design-system/components/segmented-control.md +71 -0
  67. package/docs/design-system/components/select.md +87 -0
  68. package/docs/design-system/components/sheet.md +82 -0
  69. package/docs/design-system/components/sidebar.md +83 -0
  70. package/docs/design-system/components/skeleton.md +91 -0
  71. package/docs/design-system/components/slideout.md +77 -0
  72. package/docs/design-system/components/slider.md +81 -0
  73. package/docs/design-system/components/social-button.md +65 -0
  74. package/docs/design-system/components/spinner.md +77 -0
  75. package/docs/design-system/components/steps.md +77 -0
  76. package/docs/design-system/components/table.md +411 -0
  77. package/docs/design-system/components/tabs.md +69 -0
  78. package/docs/design-system/components/tag.md +95 -0
  79. package/docs/design-system/components/textarea.md +64 -0
  80. package/docs/design-system/components/timeline.md +95 -0
  81. package/docs/design-system/components/toast.md +73 -0
  82. package/docs/design-system/components/toggle.md +96 -0
  83. package/docs/design-system/components/toolbar.md +105 -0
  84. package/docs/design-system/components/tooltip.md +107 -0
  85. package/docs/design-system/components/typography.md +18 -0
  86. package/docs/design-system/density-and-elevation.md +44 -0
  87. package/docs/design-system/design-system-test-missing-items.md +368 -0
  88. package/docs/design-system/direction-and-localization.md +23 -0
  89. package/docs/design-system/form-recipes.md +73 -0
  90. package/docs/design-system/gap-promotion-policy.md +21 -0
  91. package/docs/design-system/gap-promotion-report.md +166 -0
  92. package/docs/design-system/icons.md +69 -0
  93. package/docs/design-system/installation.md +68 -0
  94. package/docs/design-system/launch-comparison-shadcn.md +98 -0
  95. package/docs/design-system/registry-and-cli.md +64 -0
  96. package/docs/design-system/release-policy.md +30 -0
  97. package/docs/design-system/roadmap.md +14 -0
  98. package/docs/design-system/support-matrix.md +23 -0
  99. package/docs/design-system/tabs.md +53 -0
  100. package/docs/design-system/token-governance.md +60 -0
  101. package/package.json +97 -65
  102. package/registry/component-contract.json +40 -0
  103. package/registry/components/alert-dialog.tsx +297 -0
  104. package/registry/components/alert.tsx +86 -65
  105. package/registry/components/aspect-ratio.tsx +30 -0
  106. package/registry/components/autocomplete.tsx +23 -9
  107. package/registry/components/avatar.tsx +7 -7
  108. package/registry/components/badge.tsx +1 -1
  109. package/registry/components/button-group.tsx +321 -4
  110. package/registry/components/button.tsx +38 -17
  111. package/registry/components/card.tsx +68 -59
  112. package/registry/components/carousel.tsx +282 -0
  113. package/registry/components/chart.tsx +203 -0
  114. package/registry/components/checkbox.tsx +3 -3
  115. package/registry/components/collapsible.tsx +69 -0
  116. package/registry/components/command.tsx +174 -0
  117. package/registry/components/context-menu.tsx +236 -0
  118. package/registry/components/date-picker.tsx +246 -105
  119. package/registry/components/expanded/ActivityFeed.tsx +6 -15
  120. package/registry/components/expanded/Banner.tsx +3 -3
  121. package/registry/components/expanded/Breadcrumbs.tsx +5 -5
  122. package/registry/components/expanded/CatalogComponentsShowcase.tsx +13 -13
  123. package/registry/components/expanded/CatalogDivider.tsx +1 -1
  124. package/registry/components/expanded/EmptyState.tsx +1 -1
  125. package/registry/components/expanded/FileUpload.tsx +9 -2
  126. package/registry/components/expanded/OnboardingStepListItem.tsx +2 -4
  127. package/registry/components/expanded/PageHeader.tsx +14 -14
  128. package/registry/components/expanded/Slideout.tsx +15 -415
  129. package/registry/components/expanded/Steps.tsx +4 -4
  130. package/registry/components/expanded/Table.tsx +116 -69
  131. package/registry/components/expanded/Timeline.tsx +3 -3
  132. package/registry/components/expanded/activityFeed.css +1 -1
  133. package/registry/components/expanded/breadcrumbs.css +139 -139
  134. package/registry/components/expanded/catalogComponentsShowcase.css +83 -83
  135. package/registry/components/expanded/divider.css +1 -1
  136. package/registry/components/expanded/emptyState.css +1 -1
  137. package/registry/components/expanded/fileUpload.css +6 -6
  138. package/registry/components/expanded/slideout.css +9 -685
  139. package/registry/components/expanded/steps.css +274 -274
  140. package/registry/components/expanded/table.css +2 -2
  141. package/registry/components/expanded/timeline.css +264 -264
  142. package/registry/components/field.tsx +230 -0
  143. package/registry/components/hover-card.tsx +48 -0
  144. package/registry/components/input-content.ts +1 -0
  145. package/registry/components/input-group.tsx +130 -0
  146. package/registry/components/input-otp.tsx +13 -3
  147. package/registry/components/input.tsx +70 -45
  148. package/registry/components/item.tsx +189 -0
  149. package/registry/components/kbd.tsx +151 -0
  150. package/registry/components/label.tsx +78 -0
  151. package/registry/components/menu.tsx +34 -246
  152. package/registry/components/menubar.tsx +226 -0
  153. package/registry/components/modal.tsx +216 -143
  154. package/registry/components/native-select.tsx +205 -0
  155. package/registry/components/navigation-menu.tsx +171 -0
  156. package/registry/components/pagination.tsx +80 -58
  157. package/registry/components/popover.tsx +162 -664
  158. package/registry/components/radio-card.tsx +116 -87
  159. package/registry/components/radio-group.tsx +1 -1
  160. package/registry/components/resizable.tsx +74 -0
  161. package/registry/components/scroll-area.tsx +67 -0
  162. package/registry/components/segmented-control.tsx +87 -0
  163. package/registry/components/select-content.tsx +2 -2
  164. package/registry/components/select.tsx +54 -20
  165. package/registry/components/sheet.tsx +305 -0
  166. package/registry/components/sidebar.tsx +352 -0
  167. package/registry/components/skeleton.css +0 -3
  168. package/registry/components/social-button.tsx +116 -30
  169. package/registry/components/spinner.tsx +18 -55
  170. package/registry/components/{expanded/tabs.css → tabs.css} +127 -106
  171. package/registry/components/tabs.tsx +246 -0
  172. package/registry/components/tag.tsx +2 -2
  173. package/registry/components/textarea.tsx +4 -4
  174. package/registry/components/toast.tsx +136 -0
  175. package/registry/components/toggle.tsx +2 -2
  176. package/registry/components/toolbar.tsx +89 -17
  177. package/registry/components/tooltip.tsx +1 -1
  178. package/registry/examples/alert-demo.tsx +25 -5
  179. package/registry/examples/alert-dialog-demo.tsx +42 -0
  180. package/registry/examples/aspect-ratio-demo.tsx +11 -0
  181. package/registry/examples/attribute-demo.tsx +1 -1
  182. package/registry/examples/autocomplete-demo.tsx +3 -3
  183. package/registry/examples/avatar-demo.tsx +7 -7
  184. package/registry/examples/badge-demo.tsx +1 -1
  185. package/registry/examples/button-demo.tsx +6 -6
  186. package/registry/examples/button-group-demo.tsx +23 -2
  187. package/registry/examples/card-demo.tsx +46 -8
  188. package/registry/examples/carousel-demo.tsx +43 -0
  189. package/registry/examples/chart-demo.tsx +33 -0
  190. package/registry/examples/checkbox-demo.tsx +5 -5
  191. package/registry/examples/collapsible-demo.tsx +16 -0
  192. package/registry/examples/collection-filter-bar-demo.tsx +135 -0
  193. package/registry/examples/command-demo.tsx +42 -0
  194. package/registry/examples/{expanded/command-bar-demo.tsx → command-palette-demo.tsx} +5 -15
  195. package/registry/examples/context-menu-demo.tsx +29 -0
  196. package/registry/examples/control-density-contract-demo.tsx +61 -0
  197. package/registry/examples/date-picker-demo.tsx +12 -12
  198. package/registry/examples/divider-demo.tsx +3 -3
  199. package/registry/examples/expanded/banner-demo.tsx +1 -1
  200. package/registry/examples/expanded/breadcrumbs-demo.tsx +1 -1
  201. package/registry/examples/expanded/empty-state-demo.tsx +1 -1
  202. package/registry/examples/expanded/file-upload-demo.tsx +1 -1
  203. package/registry/examples/expanded/onboarding-step-demo.tsx +1 -1
  204. package/registry/examples/expanded/slideout-demo.tsx +2 -2
  205. package/registry/examples/expanded/steps-demo.tsx +1 -1
  206. package/registry/examples/expanded/tabs-demo.tsx +2 -2
  207. package/registry/examples/expanded/timeline-demo.tsx +1 -1
  208. package/registry/examples/field-demo.tsx +51 -0
  209. package/registry/examples/hover-card-demo.tsx +23 -0
  210. package/registry/examples/input-demo.tsx +6 -6
  211. package/registry/examples/input-group-demo.tsx +16 -0
  212. package/registry/examples/input-otp-demo.tsx +4 -4
  213. package/registry/examples/item-demo.tsx +46 -0
  214. package/registry/examples/kbd-demo.tsx +11 -0
  215. package/registry/examples/label-demo.tsx +20 -0
  216. package/registry/examples/menu-demo.tsx +1 -1
  217. package/registry/examples/menubar-demo.tsx +34 -0
  218. package/registry/examples/modal-demo.tsx +67 -9
  219. package/registry/examples/native-select-demo.tsx +16 -0
  220. package/registry/examples/navigation-menu-demo.tsx +29 -0
  221. package/registry/examples/pagination-demo.tsx +6 -3
  222. package/registry/examples/popover-demo.tsx +19 -19
  223. package/registry/examples/progress-demo.tsx +1 -1
  224. package/registry/examples/progress-slider-demo.tsx +1 -1
  225. package/registry/examples/radio-card-demo.tsx +4 -4
  226. package/registry/examples/radio-group-demo.tsx +2 -2
  227. package/registry/examples/resizable-demo.tsx +22 -0
  228. package/registry/examples/results-toolbar-demo.tsx +44 -0
  229. package/registry/examples/scroll-area-demo.tsx +15 -0
  230. package/registry/examples/segmented-control-demo.tsx +39 -0
  231. package/registry/examples/select-demo.tsx +1 -1
  232. package/registry/examples/sheet-demo.tsx +47 -0
  233. package/registry/examples/sidebar-demo.tsx +55 -0
  234. package/registry/examples/slider-demo.tsx +3 -3
  235. package/registry/examples/social-button-demo.tsx +18 -8
  236. package/registry/examples/spinner-demo.tsx +13 -13
  237. package/registry/examples/tabs-demo.tsx +13 -0
  238. package/registry/examples/tag-demo.tsx +1 -1
  239. package/registry/examples/textarea-demo.tsx +4 -4
  240. package/registry/examples/toast-demo.tsx +35 -0
  241. package/registry/examples/toggle-demo.tsx +6 -6
  242. package/registry/examples/tooltip-demo.tsx +4 -4
  243. package/registry/guidance/USAGE.md +27 -0
  244. package/registry/hooks/use-direction.ts +193 -13
  245. package/registry/index.json +6010 -432
  246. package/registry/styles/globals.css +3112 -4690
  247. package/registry.json +1582 -0
  248. package/schema/config.schema.json +51 -0
  249. package/schema/registry.schema.json +190 -0
  250. package/schema/tokens.schema.json +163 -0
  251. package/src/cli/command-line.js +194 -0
  252. package/src/cli/config-editors.js +9 -0
  253. package/src/cli/doctor.js +399 -0
  254. package/src/cli/file-transaction.js +295 -0
  255. package/src/cli/global-css.js +153 -0
  256. package/src/cli/index.js +639 -1391
  257. package/src/cli/install-plan.js +592 -0
  258. package/src/cli/install-receipt.js +171 -0
  259. package/src/cli/jsonc.js +203 -0
  260. package/src/cli/package-install-state.js +254 -0
  261. package/src/cli/package-manager.js +238 -0
  262. package/src/cli/package-plan.js +185 -0
  263. package/src/cli/package-version.js +97 -0
  264. package/src/cli/registry-query.js +207 -0
  265. package/src/cli/reporting.js +55 -0
  266. package/src/cli/schema-validation.js +19 -0
  267. package/src/cli/source-scanner.js +686 -0
  268. package/src/cli/vite-config.js +478 -0
  269. package/tokens/banhaten.tokens.json +4313 -310
  270. package/registry/assets/avatars/avatar-02.jpg +0 -0
  271. package/registry/assets/avatars/avatar-03.jpg +0 -0
  272. package/registry/assets/avatars/avatar-04.jpg +0 -0
  273. package/registry/assets/avatars/avatar-05.jpg +0 -0
  274. package/registry/assets/avatars/avatar-06.jpg +0 -0
  275. package/registry/assets/avatars/avatar-07.jpg +0 -0
  276. package/registry/assets/avatars/avatar-08.jpg +0 -0
  277. package/registry/assets/avatars/avatar-09.jpg +0 -0
  278. package/registry/assets/avatars/avatar-10.jpg +0 -0
  279. package/registry/assets/avatars/avatar-11.jpg +0 -0
  280. package/registry/assets/avatars/avatar-12.jpg +0 -0
  281. package/registry/assets/avatars/avatar-13.jpg +0 -0
  282. package/registry/assets/avatars/avatar-14.jpg +0 -0
  283. package/registry/assets/avatars/avatar-15.jpg +0 -0
  284. package/registry/assets/avatars/avatar-16.jpg +0 -0
  285. package/registry/assets/avatars/avatar-17.jpg +0 -0
  286. package/registry/assets/avatars/avatar-18.jpg +0 -0
  287. package/registry/assets/avatars/avatar-19.jpg +0 -0
  288. package/registry/assets/avatars/avatar-20.jpg +0 -0
  289. package/registry/assets/avatars/avatar-21.jpg +0 -0
  290. package/registry/assets/avatars/avatar-22.jpg +0 -0
  291. package/registry/assets/avatars/avatar-23.jpg +0 -0
  292. package/registry/assets/avatars/avatar-24.jpg +0 -0
  293. package/registry/assets/avatars/avatar-25.jpg +0 -0
  294. package/registry/assets/avatars/avatar-26.jpg +0 -0
  295. package/registry/assets/avatars/avatar-27.jpg +0 -0
  296. package/registry/assets/avatars/avatar-28.jpg +0 -0
  297. package/registry/assets/avatars/avatar-29.jpg +0 -0
  298. package/registry/assets/avatars/avatar-30.jpg +0 -0
  299. package/registry/assets/avatars/avatar-31.jpg +0 -0
  300. package/registry/assets/avatars/avatar-32.jpg +0 -0
  301. package/registry/assets/avatars/avatar-33.jpg +0 -0
  302. package/registry/assets/avatars/avatar-34.jpg +0 -0
  303. package/registry/assets/avatars/avatar-35.jpg +0 -0
  304. package/registry/assets/image-assets.json +0 -744
  305. package/registry/assets/images/art-02.jpg +0 -0
  306. package/registry/assets/images/art-03.jpg +0 -0
  307. package/registry/assets/images/art-04.jpg +0 -0
  308. package/registry/assets/images/art-05.jpg +0 -0
  309. package/registry/assets/images/art-06.jpg +0 -0
  310. package/registry/assets/images/art-07.jpg +0 -0
  311. package/registry/assets/images/art-08.jpg +0 -0
  312. package/registry/assets/images/art-09.jpg +0 -0
  313. package/registry/assets/images/art-10.jpg +0 -0
  314. package/registry/assets/images/art-11.jpg +0 -0
  315. package/registry/assets/images/art-12.jpg +0 -0
  316. package/registry/assets/images/art-13.jpg +0 -0
  317. package/registry/assets/images/art-14.jpg +0 -0
  318. package/registry/assets/images/art-15.jpg +0 -0
  319. package/registry/assets/images/art-16.jpg +0 -0
  320. package/registry/assets/images/art-17.jpg +0 -0
  321. package/registry/assets/images/art-18.jpg +0 -0
  322. package/registry/assets/images/art-19.jpg +0 -0
  323. package/registry/assets/images/art-20.jpg +0 -0
  324. package/registry/assets/images/art-21.jpg +0 -0
  325. package/registry/assets/images/art-22.jpg +0 -0
  326. package/registry/assets/images/art-23.jpg +0 -0
  327. package/registry/assets/images/art-24.jpg +0 -0
  328. package/registry/assets/images/art-25.jpg +0 -0
  329. package/registry/assets/images/art-26.jpg +0 -0
  330. package/registry/assets/images/art-27.jpg +0 -0
  331. package/registry/assets/images/nature-01.jpg +0 -0
  332. package/registry/assets/images/nature-02.jpg +0 -0
  333. package/registry/assets/images/nature-03.jpg +0 -0
  334. package/registry/assets/images/nature-04.jpg +0 -0
  335. package/registry/assets/images/nature-05.jpg +0 -0
  336. package/registry/assets/images/nature-06.jpg +0 -0
  337. package/registry/assets/images/nature-07.jpg +0 -0
  338. package/registry/assets/images/nature-08.jpg +0 -0
  339. package/registry/assets/images/nature-09.jpg +0 -0
  340. package/registry/assets/images/nature-10.jpg +0 -0
  341. package/registry/assets/images/nature-11.jpg +0 -0
  342. package/registry/assets/images/nature-12.jpg +0 -0
  343. package/registry/assets/images/nature-13.jpg +0 -0
  344. package/registry/assets/images/nature-14.jpg +0 -0
  345. package/registry/assets/images/nature-15.jpg +0 -0
  346. package/registry/assets/images/nature-16.jpg +0 -0
  347. package/registry/assets/images/nature-17.jpg +0 -0
  348. package/registry/assets/images/nature-18.jpg +0 -0
  349. package/registry/assets/images/nature-19.jpg +0 -0
  350. package/registry/assets/images/nature-20.jpg +0 -0
  351. package/registry/components/expanded/BannerBoard.tsx +0 -225
  352. package/registry/components/expanded/CatalogTag.tsx +0 -85
  353. package/registry/components/expanded/CommandBar.tsx +0 -386
  354. package/registry/components/expanded/IconExplorer.tsx +0 -612
  355. package/registry/components/expanded/Tabs.tsx +0 -86
  356. package/registry/components/expanded/TimelineShowcase.tsx +0 -158
  357. package/registry/components/expanded/commandBar.css +0 -470
  358. package/registry/components/expanded/iconExplorer.css +0 -764
  359. package/registry/components/expanded/iconPacks.ts +0 -866
  360. package/registry/components/expanded/tag.css +0 -148
  361. package/registry/examples/expanded/icons-demo.tsx +0 -5
  362. package/tokens/Color mode.zip +0 -0
  363. package/tokens/Numbers.zip +0 -0
  364. package/tokens/Radius.zip +0 -0
  365. package/tokens/Theme.zip +0 -0
@@ -9,6 +9,7 @@ import {
9
9
  } from "lucide-react"
10
10
 
11
11
  import { cn } from "@/lib/utils"
12
+ import { Kbd, KbdShortcut } from "@/components/ui/kbd"
12
13
  import {
13
14
  getInputDefaultValue,
14
15
  getInputHelperText,
@@ -20,7 +21,8 @@ import {
20
21
  type InputVisualState,
21
22
  } from "./input-content"
22
23
 
23
- type InputSize = "md" | "lg"
24
+ type ControlDensity = "compact" | "default" | "comfortable"
25
+ type InputSize = "md" | "lg" | "comfortable"
24
26
  type InputVariant = "default" | "soft"
25
27
 
26
28
  type InputNativeProps = Omit<
@@ -36,6 +38,7 @@ type InputModeRenderProps = {
36
38
  leadingAddon?: React.ReactNode
37
39
  quantityLabel?: React.ReactNode
38
40
  shortcut?: React.ReactNode
41
+ shortcutKeys?: readonly string[]
39
42
  tags?: InputTag[]
40
43
  trailingAddon?: React.ReactNode
41
44
  }
@@ -62,22 +65,28 @@ type InputDynamicKindProps = { kind?: InputKind } & InputModeRenderProps
62
65
  type InputCommonProps = InputNativeProps & {
63
66
  className?: string
64
67
  controlClassName?: string
68
+ density?: ControlDensity
65
69
  helperClassName?: string
66
70
  labelClassName?: string
67
71
  surfaceClassName?: string
68
72
  errorMessage?: React.ReactNode
69
73
  hasHelperText?: boolean
74
+ /** @deprecated Use showInfo or an explicit trailingIcon. */
70
75
  hasInformationIcon?: boolean
76
+ /** @deprecated Use showAtSign or an explicit leadingIcon. */
77
+ hasLeadingIcon?: boolean
71
78
  hasLabel?: boolean
72
79
  isOptional?: boolean
73
80
  isRequired?: boolean
74
81
  label?: React.ReactNode
75
- leadingIcon?: React.ReactNode
82
+ leadingIcon?: React.ReactNode | false
76
83
  message?: React.ReactNode
77
84
  optionalText?: React.ReactNode
85
+ showAtSign?: boolean
86
+ showInfo?: boolean
78
87
  size?: InputSize
79
88
  state?: InputVisualState
80
- trailingIcon?: React.ReactNode
89
+ trailingIcon?: React.ReactNode | false
81
90
  valueText?: string
82
91
  variant?: InputVariant
83
92
  dir?: "ltr" | "rtl" | "auto"
@@ -94,8 +103,8 @@ const inputSurface = cva(
94
103
  "[--bh-input-border:var(--bh-border-input)] [--shadow-input:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-input-border,var(--bh-border-input)),var(--shadow-component-default)]",
95
104
  "[--shadow-input-surface:var(--shadow-component-default)]",
96
105
  "[--shadow-input-overlay:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-input-border,var(--bh-border-input))]",
97
- "[--shadow-input-focus-surface:0px_0px_0px_var(--bh-focus-ring-width)_color-mix(in_srgb,var(--bh-border-focus)_30%,transparent)]",
98
- "[--shadow-input-focus-overlay:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-border-focus)]",
106
+ "[--shadow-input-focus-surface:var(--shadow-input-focus-ring)]",
107
+ "[--shadow-input-focus-overlay:none]",
99
108
  "[--shadow-input-disabled-overlay:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-border-disabled)]",
100
109
  "after:pointer-events-none after:absolute after:inset-0 after:z-[var(--bh-z-raised)] after:rounded-[inherit] after:[box-shadow:var(--shadow-input-overlay)] after:content-['']",
101
110
  "focus-within:shadow-[var(--shadow-input-focus-surface)] focus-within:after:[box-shadow:var(--shadow-input-focus-overlay)]",
@@ -105,6 +114,7 @@ const inputSurface = cva(
105
114
  size: {
106
115
  md: "h-[var(--bh-input-md-height)]",
107
116
  lg: "h-[var(--bh-input-lg-height)]",
117
+ comfortable: "h-[var(--bh-input-comfortable-height)]",
108
118
  },
109
119
  variant: {
110
120
  default: "bg-[var(--bh-interactive-input-default)]",
@@ -143,6 +153,7 @@ const inputContent = cva(
143
153
  size: {
144
154
  md: "px-[var(--bh-input-md-padding-x)]",
145
155
  lg: "px-[var(--bh-input-lg-padding-x)]",
156
+ comfortable: "px-[var(--bh-input-comfortable-padding-x)]",
146
157
  },
147
158
  },
148
159
  defaultVariants: {
@@ -256,11 +267,13 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
256
267
  surfaceClassName,
257
268
  buttonLabel,
258
269
  defaultValue,
270
+ density,
259
271
  dir,
260
272
  disabled,
261
273
  errorMessage,
262
274
  hasHelperText,
263
- hasInformationIcon = true,
275
+ hasInformationIcon,
276
+ hasLeadingIcon,
264
277
  hasLabel,
265
278
  id,
266
279
  inlineLeadingAddon,
@@ -277,8 +290,11 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
277
290
  quantityLabel,
278
291
  readOnly,
279
292
  required,
280
- shortcut = inputCopy.shortcut,
281
- size = "lg",
293
+ shortcut,
294
+ shortcutKeys = inputCopy.shortcutKeys,
295
+ showAtSign = false,
296
+ showInfo = false,
297
+ size,
282
298
  state = "default",
283
299
  tags,
284
300
  trailingAddon,
@@ -291,6 +307,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
291
307
  ref
292
308
  ) => {
293
309
  const generatedId = React.useId()
310
+ const resolvedSize = resolveInputSize(density, size)
294
311
  const inputId = id || generatedId
295
312
  const helperId = `${inputId}-helper`
296
313
  const inputRef = React.useRef<HTMLInputElement | null>(null)
@@ -320,6 +337,15 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
320
337
  const shouldRenderHelperText =
321
338
  hasHelperText ?? hasRenderableContent(helperText)
322
339
  const selectedTags = getInputTags({ kind, tags })
340
+ const shouldShowLeadingIcon =
341
+ shouldRenderLeadingIcon(kind) &&
342
+ leadingIcon !== false &&
343
+ (hasRenderableContent(leadingIcon) || showAtSign || Boolean(hasLeadingIcon))
344
+ const shouldShowInformationIcon = shouldRenderInfoIcon(
345
+ kind,
346
+ trailingIcon !== false &&
347
+ (hasRenderableContent(trailingIcon) || showInfo || Boolean(hasInformationIcon))
348
+ )
323
349
 
324
350
  function focusInput() {
325
351
  if (!isDisabled) inputRef.current?.focus()
@@ -338,7 +364,8 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
338
364
  return (
339
365
  <div
340
366
  data-kind={kind}
341
- data-size={size}
367
+ data-density={density}
368
+ data-size={resolvedSize}
342
369
  data-slot="input-root"
343
370
  data-state={visualState}
344
371
  data-variant={variant}
@@ -372,7 +399,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
372
399
  data-state={visualState}
373
400
  className={cn(
374
401
  inputSurface({
375
- size,
402
+ size: resolvedSize,
376
403
  state: visualState,
377
404
  variant,
378
405
  }),
@@ -386,12 +413,15 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
386
413
  kind,
387
414
  leadingAddon,
388
415
  quantityLabel,
389
- size,
416
+ size: resolvedSize,
390
417
  trailingAddon,
391
418
  })}
392
419
 
393
- <span data-slot="input-content" className={cn(inputContent({ size }))}>
394
- {shouldRenderLeadingIcon(kind) ? (
420
+ <span
421
+ data-slot="input-content"
422
+ className={cn(inputContent({ size: resolvedSize }))}
423
+ >
424
+ {shouldShowLeadingIcon ? (
395
425
  <InputLeadingIcon disabled={isDisabled} icon={leadingIcon} />
396
426
  ) : null}
397
427
 
@@ -430,7 +460,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
430
460
  </InputInlineAddon>
431
461
  ) : null}
432
462
 
433
- {shouldRenderInfoIcon(kind, hasInformationIcon) ? (
463
+ {shouldShowInformationIcon ? (
434
464
  <InputInfoIcon
435
465
  disabled={isDisabled}
436
466
  invalid={isInvalid}
@@ -439,7 +469,11 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
439
469
  ) : null}
440
470
 
441
471
  {kind === "shortcut" ? (
442
- <InputShortcut disabled={isDisabled}>{shortcut}</InputShortcut>
472
+ hasRenderableContent(shortcut) ? (
473
+ <Kbd disabled={isDisabled}>{shortcut}</Kbd>
474
+ ) : (
475
+ <KbdShortcut disabled={isDisabled} keys={shortcutKeys} />
476
+ )
443
477
  ) : null}
444
478
  </span>
445
479
 
@@ -449,7 +483,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
449
483
  kind,
450
484
  leadingAddon,
451
485
  quantityLabel,
452
- size,
486
+ size: resolvedSize,
453
487
  trailingAddon,
454
488
  })}
455
489
  </div>
@@ -588,12 +622,12 @@ function InputLeadingIcon({
588
622
  disabled && "text-[var(--bh-content-disabled)]"
589
623
  )}
590
624
  >
591
- {icon || (
625
+ {icon ?? (
592
626
  <AtSignIcon
593
627
  aria-hidden="true"
594
628
  className="size-[var(--bh-input-icon-size)]"
595
629
  focusable="false"
596
- strokeWidth={2.25}
630
+ strokeWidth="var(--bh-icon-stroke-225)"
597
631
  />
598
632
  )}
599
633
  </span>
@@ -619,12 +653,12 @@ function InputInfoIcon({
619
653
  disabled && "text-[var(--bh-content-disabled)]"
620
654
  )}
621
655
  >
622
- {icon || (
656
+ {icon ?? (
623
657
  <InfoIcon
624
658
  aria-hidden="true"
625
659
  className="size-[var(--bh-input-info-icon-size)]"
626
660
  focusable="false"
627
- strokeWidth={2.25}
661
+ strokeWidth="var(--bh-icon-stroke-225)"
628
662
  />
629
663
  )}
630
664
  </span>
@@ -652,27 +686,6 @@ function InputInlineAddon({
652
686
  )
653
687
  }
654
688
 
655
- function InputShortcut({
656
- children,
657
- disabled,
658
- }: {
659
- children: React.ReactNode
660
- disabled: boolean
661
- }) {
662
- return (
663
- <span
664
- data-slot="input-shortcut"
665
- className={cn(
666
- "inline-flex h-[var(--bh-input-shortcut-height)] shrink-0 items-center rounded-[var(--bh-radius-md-6)] border border-[var(--bh-border-default)] bg-[var(--bh-interactive-secondary-default)] px-[var(--bh-input-shortcut-padding-x)] text-[length:var(--bh-text-body-2xs-medium-font-size)] font-[var(--bh-text-body-2xs-medium-font-weight)] leading-[var(--bh-text-body-2xs-medium-line-height)] tracking-[var(--bh-text-body-2xs-medium-letter-spacing)] text-[var(--bh-content-subtle)]",
667
- disabled &&
668
- "border-[var(--bh-border-disabled)] bg-[var(--bh-interactive-secondary-disabled)] text-[var(--bh-content-disabled)]"
669
- )}
670
- >
671
- {children}
672
- </span>
673
- )
674
- }
675
-
676
689
  function InputTagList({
677
690
  className,
678
691
  disabled,
@@ -759,7 +772,8 @@ function InputStepperButton({
759
772
  type="button"
760
773
  className={cn(
761
774
  inputStepperButton({ side }),
762
- size === "md" && "w-[var(--bh-input-md-height)]"
775
+ size === "md" && "w-[var(--bh-input-md-height)]",
776
+ size === "comfortable" && "w-[var(--bh-input-comfortable-height)]"
763
777
  )}
764
778
  onClick={(event) => {
765
779
  event.stopPropagation()
@@ -886,7 +900,7 @@ function ChevronIcon() {
886
900
  aria-hidden="true"
887
901
  className="size-[var(--bh-input-chevron-size)]"
888
902
  focusable="false"
889
- strokeWidth={2.25}
903
+ strokeWidth="var(--bh-icon-stroke-225)"
890
904
  />
891
905
  )
892
906
  }
@@ -897,7 +911,7 @@ function MinusIcon() {
897
911
  aria-hidden="true"
898
912
  className="size-[var(--bh-input-stepper-icon-size)]"
899
913
  focusable="false"
900
- strokeWidth={2.25}
914
+ strokeWidth="var(--bh-icon-stroke-225)"
901
915
  />
902
916
  )
903
917
  }
@@ -908,7 +922,7 @@ function PlusIcon() {
908
922
  aria-hidden="true"
909
923
  className="size-[var(--bh-input-stepper-icon-size)]"
910
924
  focusable="false"
911
- strokeWidth={2.25}
925
+ strokeWidth="var(--bh-icon-stroke-225)"
912
926
  />
913
927
  )
914
928
  }
@@ -936,8 +950,19 @@ function shouldRenderInfoIcon(kind: InputKind, hasInformationIcon: boolean) {
936
950
  ].includes(kind)
937
951
  }
938
952
 
953
+ function resolveInputSize(
954
+ density: ControlDensity | undefined,
955
+ size: InputSize | undefined
956
+ ): InputSize {
957
+ if (density === "compact") return "md"
958
+ if (density === "default") return "lg"
959
+ if (density === "comfortable") return "comfortable"
960
+ return size ?? "lg"
961
+ }
962
+
939
963
  export { Input, inputContent, inputControl, inputRoot, inputSurface }
940
964
  export type {
965
+ ControlDensity,
941
966
  InputKind,
942
967
  InputProps,
943
968
  InputSize,
@@ -0,0 +1,189 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const itemVariants = cva(
8
+ [
9
+ "group/item flex min-w-0 items-center gap-[var(--bh-item-gap)] rounded-[var(--bh-item-radius)]",
10
+ "px-[var(--bh-item-padding-x)] py-[var(--bh-item-padding-y)] text-start text-[var(--bh-content-default)]",
11
+ "outline-none transition-[background-color,box-shadow] focus-visible:shadow-[var(--shadow-button-focus)]",
12
+ "aria-disabled:pointer-events-none aria-disabled:text-[var(--bh-content-disabled)]",
13
+ ],
14
+ {
15
+ variants: {
16
+ size: {
17
+ sm: "min-h-[var(--bh-item-min-height-sm)]",
18
+ md: "min-h-[var(--bh-item-min-height-md)]",
19
+ lg: "min-h-[var(--bh-item-min-height-lg)]",
20
+ },
21
+ variant: {
22
+ default:
23
+ "bg-[var(--bh-bg-default)] hover:bg-[var(--bh-interactive-ghost-hover)]",
24
+ muted:
25
+ "bg-[var(--bh-bg-neutral-subtle)] hover:bg-[var(--bh-bg-neutral-soft)]",
26
+ outline:
27
+ "bg-[var(--bh-bg-raised)] shadow-[inset_0_0_0_var(--bh-border-width-default)_var(--bh-border-subtle)] hover:bg-[var(--bh-bg-raised-hover)]",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ size: "md",
32
+ variant: "default",
33
+ },
34
+ }
35
+ )
36
+
37
+ type ItemProps = React.ComponentProps<"div"> &
38
+ VariantProps<typeof itemVariants> & {
39
+ asChild?: boolean
40
+ disabled?: boolean
41
+ }
42
+
43
+ const Item = React.forwardRef<HTMLDivElement, ItemProps>(function Item(
44
+ {
45
+ asChild = false,
46
+ children,
47
+ className,
48
+ disabled = false,
49
+ size,
50
+ variant,
51
+ ...props
52
+ },
53
+ ref
54
+ ) {
55
+ const Comp = asChild ? Slot : "div"
56
+
57
+ return (
58
+ <Comp
59
+ aria-disabled={disabled || undefined}
60
+ data-size={size ?? "md"}
61
+ data-slot="item"
62
+ data-variant={variant ?? "default"}
63
+ ref={ref}
64
+ className={cn(itemVariants({ className, size, variant }))}
65
+ {...props}
66
+ >
67
+ {children}
68
+ </Comp>
69
+ )
70
+ })
71
+
72
+ type ItemGroupProps = React.ComponentProps<"div">
73
+
74
+ const ItemGroup = React.forwardRef<HTMLDivElement, ItemGroupProps>(
75
+ function ItemGroup({ className, role = "list", ...props }, ref) {
76
+ return (
77
+ <div
78
+ data-slot="item-group"
79
+ ref={ref}
80
+ role={role}
81
+ className={cn("grid min-w-0 gap-[var(--bh-item-group-gap)]", className)}
82
+ {...props}
83
+ />
84
+ )
85
+ }
86
+ )
87
+
88
+ type ItemMediaProps = React.ComponentProps<"div"> & {
89
+ variant?: "avatar" | "icon" | "image"
90
+ }
91
+
92
+ const ItemMedia = React.forwardRef<HTMLDivElement, ItemMediaProps>(
93
+ function ItemMedia({ className, variant = "icon", ...props }, ref) {
94
+ return (
95
+ <div
96
+ aria-hidden={props["aria-label"] ? undefined : true}
97
+ data-slot="item-media"
98
+ data-variant={variant}
99
+ ref={ref}
100
+ className={cn(
101
+ "flex size-[var(--bh-item-media-size)] shrink-0 items-center justify-center overflow-hidden",
102
+ "[&_svg]:size-[var(--bh-item-icon-size)] [&_img]:size-full [&_img]:object-cover",
103
+ variant === "icon" &&
104
+ "rounded-[var(--bh-item-media-radius)] bg-[var(--bh-bg-neutral-subtle)] text-[var(--bh-content-subtle)]",
105
+ variant === "avatar" &&
106
+ "rounded-[var(--bh-radius-full)] bg-[var(--bh-bg-neutral-default)] text-[var(--bh-content-on-color)]",
107
+ variant === "image" && "rounded-[var(--bh-item-media-radius)]",
108
+ className
109
+ )}
110
+ {...props}
111
+ />
112
+ )
113
+ }
114
+ )
115
+
116
+ function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
117
+ return (
118
+ <div
119
+ data-slot="item-content"
120
+ className={cn("grid min-w-0 flex-1 gap-[var(--bh-item-content-gap)]", className)}
121
+ {...props}
122
+ />
123
+ )
124
+ }
125
+
126
+ function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
127
+ return (
128
+ <div
129
+ data-slot="item-title"
130
+ dir="auto"
131
+ className={cn(
132
+ "min-w-0 break-words text-[length:var(--bh-text-body-md-medium-font-size)] font-[var(--bh-text-body-md-medium-font-weight)] leading-[var(--bh-text-body-md-medium-line-height)] tracking-[var(--bh-text-body-md-medium-letter-spacing)]",
133
+ className
134
+ )}
135
+ {...props}
136
+ />
137
+ )
138
+ }
139
+
140
+ function ItemDescription({ className, ...props }: React.ComponentProps<"div">) {
141
+ return (
142
+ <div
143
+ data-slot="item-description"
144
+ dir="auto"
145
+ className={cn(
146
+ "min-w-0 break-words text-[length:var(--bh-text-body-sm-regular-font-size)] font-[var(--bh-text-body-sm-regular-font-weight)] leading-[var(--bh-text-body-sm-regular-line-height)] tracking-[var(--bh-text-body-sm-regular-letter-spacing)] text-[var(--bh-content-subtle)]",
147
+ className
148
+ )}
149
+ {...props}
150
+ />
151
+ )
152
+ }
153
+
154
+ function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
155
+ return (
156
+ <div
157
+ data-slot="item-actions"
158
+ className={cn(
159
+ "flex shrink-0 items-center gap-[var(--bh-item-actions-gap)]",
160
+ className
161
+ )}
162
+ {...props}
163
+ />
164
+ )
165
+ }
166
+
167
+ function ItemSeparator({ className, ...props }: React.ComponentProps<"div">) {
168
+ return (
169
+ <div
170
+ aria-hidden="true"
171
+ data-slot="item-separator"
172
+ className={cn("h-[var(--bh-border-width-default)] bg-[var(--bh-border-subtle)]", className)}
173
+ {...props}
174
+ />
175
+ )
176
+ }
177
+
178
+ export {
179
+ Item,
180
+ ItemActions,
181
+ ItemContent,
182
+ ItemDescription,
183
+ ItemGroup,
184
+ ItemMedia,
185
+ ItemSeparator,
186
+ ItemTitle,
187
+ itemVariants,
188
+ }
189
+ export type { ItemGroupProps, ItemMediaProps, ItemProps }
@@ -0,0 +1,151 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const kbdVariants = cva(
9
+ [
10
+ "inline-flex min-w-[var(--bh-space-5xl-24)] items-center justify-center whitespace-nowrap rounded-[var(--bh-radius-md-6)]",
11
+ "border border-[var(--bh-border-default)] bg-[var(--bh-interactive-soft-default)] text-[var(--bh-content-subtle)]",
12
+ "font-[var(--bh-font-family)] text-[length:var(--bh-text-body-xs-medium-font-size)] font-[var(--bh-text-body-xs-medium-font-weight)]",
13
+ "leading-[var(--bh-text-body-xs-medium-line-height)] tracking-[var(--bh-text-body-xs-medium-letter-spacing)] shadow-[var(--shadow-button-soft)]",
14
+ "data-[disabled=true]:border-[var(--bh-border-disabled)] data-[disabled=true]:bg-[var(--bh-interactive-soft-disabled)] data-[disabled=true]:text-[var(--bh-content-disabled)] data-[disabled=true]:shadow-none",
15
+ ],
16
+ {
17
+ variants: {
18
+ size: {
19
+ sm: "h-[var(--bh-space-5xl-24)] gap-[var(--bh-space-xxs-2)] px-[var(--bh-space-sm-6)]",
20
+ md: "h-[var(--bh-space-6xl-32)] gap-[var(--bh-space-xs-4)] px-[var(--bh-space-md-8)]",
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ size: "sm",
25
+ },
26
+ }
27
+ )
28
+
29
+ type KbdProps = React.ComponentProps<"kbd"> &
30
+ VariantProps<typeof kbdVariants> & {
31
+ disabled?: boolean
32
+ }
33
+
34
+ const Kbd = React.forwardRef<HTMLElement, KbdProps>(function Kbd(
35
+ { className, disabled = false, size, ...props },
36
+ ref
37
+ ) {
38
+ return (
39
+ <kbd
40
+ data-disabled={disabled ? "true" : undefined}
41
+ data-slot="kbd"
42
+ ref={ref}
43
+ className={cn(kbdVariants({ size, className }))}
44
+ {...props}
45
+ />
46
+ )
47
+ })
48
+
49
+ type ShortcutPlatform = "auto" | "mac" | "windows" | "linux"
50
+
51
+ type KbdShortcutProps = Omit<React.ComponentProps<"span">, "children"> & {
52
+ disabled?: boolean
53
+ keys: readonly string[]
54
+ platform?: ShortcutPlatform
55
+ size?: KbdProps["size"]
56
+ }
57
+
58
+ const KbdShortcut = React.forwardRef<HTMLSpanElement, KbdShortcutProps>(
59
+ function KbdShortcut(
60
+ {
61
+ "aria-label": ariaLabel,
62
+ className,
63
+ disabled = false,
64
+ keys,
65
+ platform = "auto",
66
+ size = "sm",
67
+ ...props
68
+ },
69
+ ref
70
+ ) {
71
+ const [resolvedPlatform, setResolvedPlatform] = React.useState<
72
+ Exclude<ShortcutPlatform, "auto">
73
+ >(platform === "auto" ? "mac" : platform)
74
+
75
+ React.useEffect(() => {
76
+ if (platform !== "auto") {
77
+ setResolvedPlatform(platform)
78
+ return
79
+ }
80
+
81
+ setResolvedPlatform(detectShortcutPlatform())
82
+ }, [platform])
83
+
84
+ const resolvedKeys = keys.map((key) =>
85
+ resolveShortcutKey(key, resolvedPlatform)
86
+ )
87
+ const accessibleLabel =
88
+ ariaLabel ?? resolvedKeys.map(readableShortcutKey).join(" plus ")
89
+
90
+ return (
91
+ <span
92
+ aria-label={accessibleLabel}
93
+ className={cn(
94
+ "inline-flex shrink-0 items-center gap-[var(--bh-space-xs-4)]",
95
+ className
96
+ )}
97
+ data-disabled={disabled ? "true" : undefined}
98
+ data-platform={resolvedPlatform}
99
+ data-slot="kbd-shortcut"
100
+ dir="ltr"
101
+ ref={ref}
102
+ role="group"
103
+ {...props}
104
+ >
105
+ {resolvedKeys.map((key, index) => (
106
+ <Kbd
107
+ aria-hidden="true"
108
+ disabled={disabled}
109
+ key={`${key}-${index}`}
110
+ size={size}
111
+ >
112
+ {key}
113
+ </Kbd>
114
+ ))}
115
+ </span>
116
+ )
117
+ }
118
+ )
119
+
120
+ function detectShortcutPlatform(): Exclude<ShortcutPlatform, "auto"> {
121
+ if (typeof navigator === "undefined") return "mac"
122
+ const platform = navigator.platform.toLowerCase()
123
+ if (
124
+ platform.includes("mac") ||
125
+ platform.includes("iphone") ||
126
+ platform.includes("ipad")
127
+ ) {
128
+ return "mac"
129
+ }
130
+ if (platform.includes("linux")) return "linux"
131
+ return "windows"
132
+ }
133
+
134
+ function resolveShortcutKey(
135
+ key: string,
136
+ platform: Exclude<ShortcutPlatform, "auto">
137
+ ) {
138
+ if (key !== "Mod") return key
139
+ return platform === "mac" ? "⌘" : "Ctrl"
140
+ }
141
+
142
+ function readableShortcutKey(key: string) {
143
+ if (key === "⌘") return "Command"
144
+ if (key === "⌥") return "Option"
145
+ if (key === "⇧") return "Shift"
146
+ if (key === "⌃") return "Control"
147
+ return key
148
+ }
149
+
150
+ export { Kbd, KbdShortcut, kbdVariants }
151
+ export type { KbdProps, KbdShortcutProps, ShortcutPlatform }