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
@@ -0,0 +1,35 @@
1
+ import { Button } from "@/components/ui/button"
2
+ import { Toaster, toast } from "@/components/ui/toast"
3
+
4
+ export function ToastDemo() {
5
+ return (
6
+ <div className="grid gap-[var(--bh-space-3xl-16)]">
7
+ <div className="flex flex-wrap gap-[var(--bh-space-md-8)]">
8
+ <Button size="sm" onClick={() => toast.success("Project saved")}>
9
+ Success
10
+ </Button>
11
+ <Button size="sm" variant="warning" onClick={() => toast.warning("Review required")}>
12
+ Warning
13
+ </Button>
14
+ <Button size="sm" variant="danger" onClick={() => toast.danger("Unable to publish")}>
15
+ Danger
16
+ </Button>
17
+ <Button
18
+ size="sm"
19
+ variant="outline"
20
+ onClick={() =>
21
+ toast.info("Invite sent", {
22
+ action: {
23
+ label: "Undo",
24
+ onClick: () => toast("Invite cancelled"),
25
+ },
26
+ })
27
+ }
28
+ >
29
+ Action
30
+ </Button>
31
+ </div>
32
+ <Toaster />
33
+ </div>
34
+ )
35
+ }
@@ -6,8 +6,8 @@ export function ToggleDemo() {
6
6
  const [checked, setChecked] = React.useState(true)
7
7
 
8
8
  return (
9
- <div className="grid gap-6">
10
- <div className="flex flex-wrap items-center gap-4">
9
+ <div className="grid gap-[var(--bh-space-5xl-24)]">
10
+ <div className="flex flex-wrap items-center gap-[var(--bh-space-3xl-16)]">
11
11
  <Toggle
12
12
  checked={checked}
13
13
  onCheckedChange={setChecked}
@@ -18,14 +18,14 @@ export function ToggleDemo() {
18
18
  <Toggle aria-label="Disabled toggle" disabled />
19
19
  </div>
20
20
 
21
- <div className="grid gap-4 sm:grid-cols-2">
21
+ <div className="grid gap-[var(--bh-space-3xl-16)] sm:grid-cols-2">
22
22
  <ToggleField
23
23
  defaultChecked
24
24
  label="Label"
25
25
  description="Supporting text"
26
26
  />
27
27
  <ToggleField
28
- className="w-56"
28
+ className="w-[var(--bh-space-15xl-224)]"
29
29
  controlPosition="end"
30
30
  label="Label"
31
31
  />
@@ -42,14 +42,14 @@ export function ToggleDemo() {
42
42
  />
43
43
  </div>
44
44
 
45
- <div dir="rtl" className="grid gap-4 sm:grid-cols-2">
45
+ <div dir="rtl" className="grid gap-[var(--bh-space-3xl-16)] sm:grid-cols-2">
46
46
  <ToggleField
47
47
  defaultChecked
48
48
  label={"\u0627\u0644\u062a\u0633\u0645\u064a\u0629"}
49
49
  description={"\u0646\u0635 \u0645\u0633\u0627\u0639\u062f"}
50
50
  />
51
51
  <ToggleField
52
- className="w-56"
52
+ className="w-[var(--bh-space-15xl-224)]"
53
53
  controlPosition="end"
54
54
  label={"\u0627\u0644\u062a\u0633\u0645\u064a\u0629"}
55
55
  />
@@ -17,8 +17,8 @@ const rtlSupportText =
17
17
  export function TooltipDemo() {
18
18
  return (
19
19
  <TooltipProvider delayDuration={0}>
20
- <div className="grid gap-8">
21
- <div className="flex flex-wrap items-center gap-8">
20
+ <div className="grid gap-[var(--bh-space-6xl-32)]">
21
+ <div className="flex flex-wrap items-center gap-[var(--bh-space-6xl-32)]">
22
22
  <Tooltip defaultOpen>
23
23
  <TooltipTrigger asChild>
24
24
  <Button size="icon" variant="secondary" aria-label="More information">
@@ -53,7 +53,7 @@ export function TooltipDemo() {
53
53
  </Tooltip>
54
54
  </div>
55
55
 
56
- <div className="flex flex-wrap items-center gap-8">
56
+ <div className="flex flex-wrap items-center gap-[var(--bh-space-6xl-32)]">
57
57
  <Tooltip defaultOpen>
58
58
  <TooltipTrigger asChild>
59
59
  <Button variant="secondary">Dark large</Button>
@@ -82,7 +82,7 @@ export function TooltipDemo() {
82
82
  </Tooltip>
83
83
  </div>
84
84
 
85
- <div dir="rtl" className="flex flex-wrap items-center gap-8">
85
+ <div dir="rtl" className="flex flex-wrap items-center gap-[var(--bh-space-6xl-32)]">
86
86
  <Tooltip defaultOpen>
87
87
  <TooltipTrigger asChild>
88
88
  <Button size="icon" variant="secondary" aria-label={rtlLabel}>
@@ -0,0 +1,27 @@
1
+ # Banhaten implementation rules
2
+
3
+ This project uses the Banhaten design system. Treat the installed component API and registry metadata as the source of truth; do not redraw controls from memory or infer defaults from the first documented variant.
4
+
5
+ ## Before changing UI
6
+
7
+ 1. Run `npx banhaten search <need>` to find the intended component.
8
+ 2. Run `npx banhaten docs <component>` to inspect explicit defaults, recommended uses, avoid rules, and recipes.
9
+ 3. Run `npx banhaten view <component>` before wrapping or extending its internals.
10
+ 4. Prefer the documented golden recipe when the screen resembles a collection filter bar or results toolbar.
11
+
12
+ ## Composition rules
13
+
14
+ - Use one semantic `density` across controls in the same row: `compact` is 32px, `default` is 36px, and `comfortable` is 40px.
15
+ - Do not align Banhaten controls with raw height, padding, radius, or font-size utilities. Width and responsive layout utilities are allowed.
16
+ - Use `DatePicker` or `DateRangePicker` for dates. Do not simulate calendar fields with `Input` or native date inputs.
17
+ - Pass explicit `leadingIcon` and `trailingIcon` nodes. Use `showAtSign` or `showInfo` only for those exact semantic affordances. The legacy `hasLeadingIcon` and `hasInformationIcon` flags are deprecated.
18
+ - Use `shortcutKeys={["Mod", "K"]}` or `KbdShortcut` for shortcuts. Never hard-code a single Command glyph for every platform.
19
+ - Use `SegmentedControl` for small mutually exclusive view, density, period, or numeric choices. Do not rebuild joined numeric buttons locally.
20
+ - Use functional `Select` for option menus. `ToolbarSelect` is only a trigger-shaped toolbar primitive.
21
+ - Preserve inherited `dir`; use logical spacing and alignment utilities for RTL-safe composition.
22
+
23
+ ## Before finishing
24
+
25
+ Run `npx banhaten doctor` and inspect every diagnostic. Run `npx banhaten diff` before updating installed components, then execute the product's type, behavior, accessibility, and visual tests.
26
+
27
+ Canonical recipes live in `docs/design-system/form-recipes.md` in the Banhaten package.
@@ -4,24 +4,204 @@ import * as React from "react"
4
4
 
5
5
  export type Direction = "ltr" | "rtl"
6
6
 
7
- export function useDirection(defaultDirection: Direction = "ltr") {
8
- const [direction, setDirection] = React.useState<Direction>(defaultDirection)
7
+ type DirectionStore = {
8
+ listeners: Set<() => void>
9
+ observer?: MutationObserver
10
+ }
11
+
12
+ const DirectionContext = React.createContext<Direction | undefined>(undefined)
13
+ const directionStores = new WeakMap<Document, DirectionStore>()
14
+ const noop = () => undefined
15
+
16
+ export type DirectionProviderProps = {
17
+ children: React.ReactNode
18
+ dir: Direction
19
+ }
20
+
21
+ export function DirectionProvider({
22
+ children,
23
+ dir,
24
+ }: DirectionProviderProps) {
25
+ return React.createElement(DirectionContext.Provider, { value: dir }, children)
26
+ }
27
+
28
+ export function resolveDirection(
29
+ explicitDirection?: Direction,
30
+ element?: Element | null
31
+ ): Direction {
32
+ if (explicitDirection) return explicitDirection
33
+
34
+ let ancestor = element?.parentElement
35
+ while (ancestor) {
36
+ const inheritedDirection = normalizeDirection(ancestor.getAttribute("dir"))
37
+ if (inheritedDirection) return inheritedDirection
38
+ ancestor = ancestor.parentElement
39
+ }
40
+
41
+ const ownerDocument = element?.ownerDocument ?? getBrowserDocument()
42
+ const documentDirection = normalizeDirection(
43
+ ownerDocument?.documentElement.getAttribute("dir")
44
+ )
45
+
46
+ return documentDirection ?? "ltr"
47
+ }
48
+
49
+ export function useResolvedDirection<T extends Element>(
50
+ explicitDirection: Direction | undefined,
51
+ elementRef: React.RefObject<T | null>
52
+ ) {
53
+ const contextDirection = React.useContext(DirectionContext)
54
+ const preferredDirection = explicitDirection ?? contextDirection
55
+
56
+ const subscribe = React.useCallback(
57
+ (listener: () => void) => {
58
+ if (preferredDirection) return noop
59
+
60
+ const ownerDocument =
61
+ elementRef.current?.ownerDocument ?? getBrowserDocument()
62
+
63
+ return ownerDocument
64
+ ? subscribeToDirectionChanges(ownerDocument, listener)
65
+ : noop
66
+ },
67
+ [elementRef, preferredDirection]
68
+ )
69
+ const getSnapshot = React.useCallback(
70
+ () => resolveDirection(preferredDirection, elementRef.current),
71
+ [elementRef, preferredDirection]
72
+ )
73
+ const getServerSnapshot = React.useCallback(
74
+ () => preferredDirection ?? "ltr",
75
+ [preferredDirection]
76
+ )
77
+
78
+ return React.useSyncExternalStore(
79
+ subscribe,
80
+ getSnapshot,
81
+ getServerSnapshot
82
+ )
83
+ }
84
+
85
+ export function useDirection(defaultDirection?: Direction) {
86
+ const contextDirection = React.useContext(DirectionContext)
87
+ const preferredDirection = defaultDirection ?? contextDirection
88
+ const observedDirection = useDocumentDirection(preferredDirection)
89
+ const observedDirectionRef = React.useRef(observedDirection)
90
+ const [directionOverride, setDirectionOverride] = React.useState<
91
+ Direction | undefined
92
+ >(undefined)
93
+
94
+ observedDirectionRef.current = observedDirection
95
+
96
+ const setDirection = React.useCallback<
97
+ React.Dispatch<React.SetStateAction<Direction>>
98
+ >((nextDirection) => {
99
+ setDirectionOverride((currentDirection) => {
100
+ const selectedDirection =
101
+ currentDirection ?? observedDirectionRef.current
102
+
103
+ return typeof nextDirection === "function"
104
+ ? nextDirection(selectedDirection)
105
+ : nextDirection
106
+ })
107
+ }, [])
9
108
 
10
109
  React.useEffect(() => {
11
- const htmlDirection = document.documentElement.getAttribute("dir")
110
+ if (defaultDirection === undefined && contextDirection) {
111
+ setDirectionOverride(contextDirection)
112
+ }
113
+ }, [contextDirection, defaultDirection])
114
+
115
+ return [directionOverride ?? observedDirection, setDirection] as const
116
+ }
117
+
118
+ function useDocumentDirection(preferredDirection?: Direction) {
119
+ const subscribe = React.useCallback(
120
+ (listener: () => void) => {
121
+ if (preferredDirection) return noop
122
+
123
+ const ownerDocument = getBrowserDocument()
124
+ return ownerDocument
125
+ ? subscribeToDirectionChanges(ownerDocument, listener)
126
+ : noop
127
+ },
128
+ [preferredDirection]
129
+ )
130
+ const getSnapshot = React.useCallback(
131
+ () => resolveDirection(preferredDirection),
132
+ [preferredDirection]
133
+ )
134
+ const getServerSnapshot = React.useCallback(
135
+ () => preferredDirection ?? "ltr",
136
+ [preferredDirection]
137
+ )
138
+
139
+ return React.useSyncExternalStore(
140
+ subscribe,
141
+ getSnapshot,
142
+ getServerSnapshot
143
+ )
144
+ }
145
+
146
+ function subscribeToDirectionChanges(
147
+ ownerDocument: Document,
148
+ listener: () => void
149
+ ) {
150
+ const store = getDirectionStore(ownerDocument)
151
+ store.listeners.add(listener)
152
+
153
+ if (store.listeners.size === 1) {
154
+ startDirectionObserver(ownerDocument, store)
155
+ }
156
+
157
+ return () => {
158
+ store.listeners.delete(listener)
12
159
 
13
- if (htmlDirection === "ltr" || htmlDirection === "rtl") {
14
- setDirection(htmlDirection)
15
- return
160
+ if (store.listeners.size === 0) {
161
+ store.observer?.disconnect()
162
+ store.observer = undefined
16
163
  }
164
+ }
165
+ }
17
166
 
18
- document.documentElement.setAttribute("dir", defaultDirection)
19
- }, [defaultDirection])
167
+ function getDirectionStore(ownerDocument: Document) {
168
+ const existingStore = directionStores.get(ownerDocument)
169
+ if (existingStore) return existingStore
20
170
 
21
- const updateDirection = React.useCallback((nextDirection: Direction) => {
22
- document.documentElement.setAttribute("dir", nextDirection)
23
- setDirection(nextDirection)
24
- }, [])
171
+ const store: DirectionStore = { listeners: new Set() }
172
+ directionStores.set(ownerDocument, store)
173
+ return store
174
+ }
175
+
176
+ function startDirectionObserver(
177
+ ownerDocument: Document,
178
+ store: DirectionStore
179
+ ) {
180
+ const MutationObserverConstructor =
181
+ ownerDocument.defaultView?.MutationObserver
182
+ const directionRoot = ownerDocument.documentElement
183
+
184
+ if (!MutationObserverConstructor || !directionRoot) return
185
+
186
+ store.observer = new MutationObserverConstructor(() => {
187
+ for (const listener of [...store.listeners]) {
188
+ listener()
189
+ }
190
+ })
191
+ store.observer.observe(directionRoot, {
192
+ attributeFilter: ["dir"],
193
+ attributes: true,
194
+ subtree: true,
195
+ })
196
+ }
197
+
198
+ function getBrowserDocument() {
199
+ return typeof document === "undefined" ? undefined : document
200
+ }
25
201
 
26
- return [direction, updateDirection] as const
202
+ function normalizeDirection(value?: string | null): Direction | undefined {
203
+ const normalizedValue = value?.trim().toLowerCase()
204
+ return normalizedValue === "ltr" || normalizedValue === "rtl"
205
+ ? normalizedValue
206
+ : undefined
27
207
  }