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
@@ -1,612 +0,0 @@
1
- import { createElement, useEffect, useMemo, useState, type CSSProperties } from "react";
2
- import {
3
- iconPacks,
4
- iconSemantics,
5
- type IconPack,
6
- type IconPosture,
7
- type IconSemantic,
8
- } from "./iconPacks";
9
- import "./iconExplorer.css";
10
-
11
- type ExplorerView = "evaluate" | "catalog" | "compare";
12
- type Appearance = "default" | "rounded" | "sharp";
13
- type Density = "comfortable" | "compact";
14
-
15
- type CatalogResponse = {
16
- total?: number;
17
- title?: string;
18
- categories?: Record<string, string[]>;
19
- uncategorized?: string[];
20
- };
21
-
22
- type CatalogRow = {
23
- name: string;
24
- category: string;
25
- };
26
-
27
- type CatalogState =
28
- | { status: "idle" }
29
- | { status: "loading" }
30
- | { status: "error"; message: string }
31
- | { status: "loaded"; total: number; title: string; rows: CatalogRow[] };
32
-
33
- const postureOptions: Array<{ value: "all" | IconPosture; label: string }> = [
34
- { value: "all", label: "All candidates" },
35
- { value: "shortlist", label: "Shortlist" },
36
- { value: "broad", label: "Broad coverage" },
37
- { value: "specialized", label: "Specialized" },
38
- { value: "brand", label: "Brand logos" },
39
- ];
40
-
41
- function Icon({ name, size = 18, className }: { name: string; size?: number; className?: string }) {
42
- return createElement("iconify-icon", {
43
- icon: name,
44
- width: size,
45
- height: size,
46
- class: className,
47
- "aria-hidden": "true",
48
- });
49
- }
50
-
51
- function collectionToRows(collection: CatalogResponse) {
52
- const rows: CatalogRow[] = [];
53
- const seen = new Set<string>();
54
-
55
- Object.entries(collection.categories ?? {}).forEach(([category, names]) => {
56
- names.forEach((name) => {
57
- if (seen.has(name)) return;
58
- seen.add(name);
59
- rows.push({ name, category });
60
- });
61
- });
62
-
63
- (collection.uncategorized ?? []).forEach((name) => {
64
- if (seen.has(name)) return;
65
- seen.add(name);
66
- rows.push({ name, category: "Uncategorized" });
67
- });
68
-
69
- return rows;
70
- }
71
-
72
- export function IconExplorer() {
73
- const [activePackId, setActivePackId] = useState("lucide");
74
- const [activeVariantByPack, setActiveVariantByPack] = useState<Record<string, string>>({});
75
- const [view, setView] = useState<ExplorerView>("evaluate");
76
- const [packQuery, setPackQuery] = useState("");
77
- const [postureFilter, setPostureFilter] = useState<"all" | IconPosture>("all");
78
- const [iconSize, setIconSize] = useState(18);
79
- const [appearance, setAppearance] = useState<Appearance>("default");
80
- const [density, setDensity] = useState<Density>("comfortable");
81
- const [catalogs, setCatalogs] = useState<Record<string, CatalogState>>({});
82
- const [catalogQuery, setCatalogQuery] = useState("");
83
- const [catalogCategory, setCatalogCategory] = useState("all");
84
- const [renderLimit, setRenderLimit] = useState(180);
85
-
86
- useEffect(() => {
87
- if (document.querySelector('script[data-iconify-icon="true"]')) return;
88
-
89
- const script = document.createElement("script");
90
- script.src = "https://code.iconify.design/iconify-icon/3.0.0/iconify-icon.min.js";
91
- script.async = true;
92
- script.dataset.iconifyIcon = "true";
93
- document.head.appendChild(script);
94
- }, []);
95
-
96
- const activePack = useMemo(
97
- () => iconPacks.find((pack) => pack.id === activePackId) ?? iconPacks[0],
98
- [activePackId],
99
- );
100
-
101
- const activeVariant = useMemo(() => {
102
- const id = activeVariantByPack[activePack.id] ?? activePack.variants[0].id;
103
- return activePack.variants.find((variant) => variant.id === id) ?? activePack.variants[0];
104
- }, [activePack, activeVariantByPack]);
105
-
106
- function getIconName(pack: IconPack, semantic: IconSemantic) {
107
- const variantId = activeVariantByPack[pack.id] ?? pack.variants[0].id;
108
- const variant = pack.variants.find((item) => item.id === variantId) ?? pack.variants[0];
109
- const override = variant.overrides?.[semantic];
110
- let base = override ?? pack.icons[semantic] ?? pack.icons.home;
111
-
112
- if (!override && pack.variantMode === "prefix" && variant.prefix) {
113
- base = `${variant.prefix}${base}`;
114
- }
115
-
116
- if (!override && pack.variantMode === "suffix" && variant.suffix) {
117
- base = `${base}${variant.suffix}`;
118
- }
119
-
120
- return `${pack.prefix}:${base}`;
121
- }
122
-
123
- const filteredPacks = useMemo(() => {
124
- const query = packQuery.trim().toLowerCase();
125
- return iconPacks.filter((pack) => {
126
- const matchesQuery = `${pack.name} ${pack.license} ${pack.tags.join(" ")}`
127
- .toLowerCase()
128
- .includes(query);
129
- const matchesPosture = postureFilter === "all" || pack.posture === postureFilter;
130
- return matchesQuery && matchesPosture;
131
- });
132
- }, [packQuery, postureFilter]);
133
-
134
- const activeCatalog = catalogs[activePack.id] ?? { status: "idle" };
135
-
136
- const catalogRows = useMemo(() => {
137
- if (activeCatalog.status !== "loaded") return [];
138
-
139
- const query = catalogQuery.trim().toLowerCase();
140
- return activeCatalog.rows.filter((row) => {
141
- const matchesQuery = !query || row.name.toLowerCase().includes(query);
142
- const matchesCategory = catalogCategory === "all" || row.category === catalogCategory;
143
- return matchesQuery && matchesCategory;
144
- });
145
- }, [activeCatalog, catalogCategory, catalogQuery]);
146
-
147
- const catalogCategories = useMemo(() => {
148
- if (activeCatalog.status !== "loaded") return [];
149
- return Array.from(new Set(activeCatalog.rows.map((row) => row.category))).sort();
150
- }, [activeCatalog]);
151
-
152
- async function loadCatalog() {
153
- setCatalogs((current) => ({ ...current, [activePack.id]: { status: "loading" } }));
154
- try {
155
- const response = await fetch(`https://api.iconify.design/collection?prefix=${activePack.prefix}&info=1`);
156
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
157
-
158
- const collection = (await response.json()) as CatalogResponse;
159
- setCatalogs((current) => ({
160
- ...current,
161
- [activePack.id]: {
162
- status: "loaded",
163
- total: collection.total ?? 0,
164
- title: collection.title ?? activePack.name,
165
- rows: collectionToRows(collection),
166
- },
167
- }));
168
- setRenderLimit(180);
169
- } catch (error) {
170
- setCatalogs((current) => ({
171
- ...current,
172
- [activePack.id]: {
173
- status: "error",
174
- message: error instanceof Error ? error.message : "Unknown error",
175
- },
176
- }));
177
- }
178
- }
179
-
180
- function selectPack(packId: string) {
181
- setActivePackId(packId);
182
- setCatalogCategory("all");
183
- setCatalogQuery("");
184
- setRenderLimit(180);
185
- }
186
-
187
- function selectVariant(variantId: string) {
188
- setActiveVariantByPack((current) => ({ ...current, [activePack.id]: variantId }));
189
- }
190
-
191
- const radius =
192
- appearance === "sharp"
193
- ? "var(--bh-radius-none)"
194
- : appearance === "rounded"
195
- ? "var(--bh-radius-full)"
196
- : "var(--bh-control-default)";
197
- const panelRadius =
198
- appearance === "sharp" ? "var(--bh-radius-none)" : "var(--bh-radius-lg-8)";
199
- const rowHeight =
200
- density === "compact"
201
- ? "calc(var(--bh-space-6xl-32) + var(--bh-space-xs-4))"
202
- : "calc(var(--bh-space-7xl-40) + var(--bh-space-xxs-2))";
203
- const previewStyle = { "--preview-icon-size": `${iconSize}px` } as CSSProperties;
204
-
205
- return (
206
- <section className="icons-page" aria-label="Icon pack explorer">
207
- <aside className="icons-sidebar">
208
- <div className="icons-sidebar__header">
209
- <span className="icons-mark">
210
- <Icon name="lucide:boxes" size={20} />
211
- </span>
212
- <div>
213
- <h1>Icons</h1>
214
- <p>Open-source pack review</p>
215
- </div>
216
- </div>
217
-
218
- <div className="icons-sidebar__tools">
219
- <input
220
- aria-label="Filter icon packs"
221
- className="icons-input"
222
- onChange={(event) => setPackQuery(event.target.value)}
223
- placeholder="Filter packs"
224
- type="search"
225
- value={packQuery}
226
- />
227
- <select
228
- aria-label="Filter candidates"
229
- className="icons-select"
230
- onChange={(event) => setPostureFilter(event.target.value as "all" | IconPosture)}
231
- value={postureFilter}
232
- >
233
- {postureOptions.map((option) => (
234
- <option key={option.value} value={option.value}>
235
- {option.label}
236
- </option>
237
- ))}
238
- </select>
239
- </div>
240
-
241
- <div className="icons-pack-list">
242
- {filteredPacks.map((pack) => (
243
- <button
244
- className={pack.id === activePack.id ? "icons-pack is-active" : "icons-pack"}
245
- key={pack.id}
246
- onClick={() => selectPack(pack.id)}
247
- type="button"
248
- >
249
- <span className="icons-pack__glyph">
250
- <Icon name={getIconName(pack, "home")} size={22} />
251
- </span>
252
- <span className="icons-pack__copy">
253
- <strong>{pack.name}</strong>
254
- <span>
255
- {pack.total.toLocaleString()} icons, {pack.license}
256
- </span>
257
- </span>
258
- </button>
259
- ))}
260
- </div>
261
- </aside>
262
-
263
- <main className="icons-main">
264
- <header className="icons-topbar">
265
- <div>
266
- <h2>Icon pack explorer</h2>
267
- <p>
268
- {activePack.total.toLocaleString()} icons through Iconify prefix <code>{activePack.prefix}</code>
269
- </p>
270
- </div>
271
-
272
- <nav className="icons-tabs" aria-label="Icon explorer views">
273
- {(["evaluate", "catalog", "compare"] as ExplorerView[]).map((item) => (
274
- <button
275
- className={view === item ? "icons-tab is-active" : "icons-tab"}
276
- key={item}
277
- onClick={() => setView(item)}
278
- type="button"
279
- >
280
- {item[0].toUpperCase() + item.slice(1)}
281
- </button>
282
- ))}
283
- </nav>
284
- </header>
285
-
286
- {view === "evaluate" && (
287
- <div className="icons-content">
288
- <section className="icons-summary-grid">
289
- <div className="icons-panel icons-hero-panel">
290
- <span className="icons-hero-glyph">
291
- <Icon name={getIconName(activePack, "home")} size={46} />
292
- </span>
293
- <div className="icons-hero-copy">
294
- <h3>{activePack.name}</h3>
295
- <p>{activePack.summary}</p>
296
- <div className="icons-tags">
297
- <span className="icons-tag is-warm">{activePack.posture}</span>
298
- <span className="icons-tag">{activePack.license}</span>
299
- {activePack.tags.map((tag) => (
300
- <span className="icons-tag" key={tag}>
301
- {tag}
302
- </span>
303
- ))}
304
- </div>
305
- <div className="icons-links">
306
- <a href={activePack.officialUrl} rel="noreferrer" target="_blank">
307
- Official
308
- </a>
309
- <a href={activePack.licenseUrl} rel="noreferrer" target="_blank">
310
- License
311
- </a>
312
- <a href={`https://icon-sets.iconify.design/${activePack.prefix}/`} rel="noreferrer" target="_blank">
313
- Iconify catalog
314
- </a>
315
- </div>
316
- </div>
317
- </div>
318
-
319
- <div className="icons-panel">
320
- <div className="icons-panel__header">
321
- <h3>Fit snapshot</h3>
322
- </div>
323
- <div className="icons-score-list">
324
- {[
325
- ["System fit", activePack.scores.fit],
326
- ["Coverage", activePack.scores.coverage],
327
- ["Variant depth", activePack.scores.variants],
328
- ["Dense UI clarity", activePack.scores.density],
329
- ].map(([label, value]) => (
330
- <div className="icons-score" key={label}>
331
- <span>
332
- <strong>{label}</strong>
333
- <em>{value}</em>
334
- </span>
335
- <div>
336
- <i style={{ width: `${value}%` }} />
337
- </div>
338
- </div>
339
- ))}
340
- </div>
341
- </div>
342
- </section>
343
-
344
- <section className="icons-panel">
345
- <div className="icons-panel__header">
346
- <h3>Variants</h3>
347
- <span>{activeVariant.label}</span>
348
- </div>
349
- <div className="icons-variants">
350
- {activePack.variants.map((variant) => (
351
- <button
352
- className={variant.id === activeVariant.id ? "icons-variant is-active" : "icons-variant"}
353
- key={variant.id}
354
- onClick={() => selectVariant(variant.id)}
355
- type="button"
356
- >
357
- {variant.label}
358
- </button>
359
- ))}
360
- </div>
361
- </section>
362
-
363
- <section className="icons-panel">
364
- <div className="icons-panel__header">
365
- <h3>Icon samples</h3>
366
- <span>{activePack.prefix}</span>
367
- </div>
368
- <div className="icons-sample-grid">
369
- {iconSemantics.map(([semantic, label]) => (
370
- <button className="icons-sample" key={semantic} title={getIconName(activePack, semantic)} type="button">
371
- <Icon name={getIconName(activePack, semantic)} size={Math.max(iconSize, 22)} />
372
- <span>{label}</span>
373
- </button>
374
- ))}
375
- </div>
376
- </section>
377
-
378
- <section className="icons-component-grid">
379
- <div className="icons-panel">
380
- <div className="icons-panel__header">
381
- <h3>Component controls</h3>
382
- </div>
383
- <div className="icons-controls">
384
- <label>
385
- <span>Icon size</span>
386
- <input
387
- max={30}
388
- min={14}
389
- onChange={(event) => setIconSize(Number(event.target.value))}
390
- type="range"
391
- value={iconSize}
392
- />
393
- </label>
394
- <label>
395
- <span>Appearance</span>
396
- <select
397
- className="icons-select"
398
- onChange={(event) => setAppearance(event.target.value as Appearance)}
399
- value={appearance}
400
- >
401
- <option value="default">Default</option>
402
- <option value="rounded">Rounded</option>
403
- <option value="sharp">Sharp</option>
404
- </select>
405
- </label>
406
- <label>
407
- <span>Density</span>
408
- <select
409
- className="icons-select"
410
- onChange={(event) => setDensity(event.target.value as Density)}
411
- value={density}
412
- >
413
- <option value="comfortable">Comfortable</option>
414
- <option value="compact">Compact</option>
415
- </select>
416
- </label>
417
- </div>
418
- </div>
419
-
420
- <div className="icons-panel">
421
- <div className="icons-panel__header">
422
- <h3>Component preview</h3>
423
- </div>
424
- <div className="icons-preview" style={previewStyle}>
425
- <div className="icons-button-row">
426
- <button className="preview-button is-primary" style={{ borderRadius: radius }} type="button">
427
- <Icon name={getIconName(activePack, "add")} size={iconSize} />
428
- Create
429
- </button>
430
- <button className="preview-button" style={{ borderRadius: radius }} type="button">
431
- <Icon name={getIconName(activePack, "download")} size={iconSize} />
432
- Export
433
- </button>
434
- <button className="preview-button is-danger" style={{ borderRadius: radius }} type="button">
435
- <Icon name={getIconName(activePack, "trash")} size={iconSize} />
436
- Delete
437
- </button>
438
- <button className="preview-button is-icon-only" style={{ borderRadius: radius }} type="button">
439
- <Icon name={getIconName(activePack, "settings")} size={iconSize} />
440
- </button>
441
- </div>
442
-
443
- <div className="icons-field-row">
444
- <div className="icons-field" style={{ borderRadius: radius }}>
445
- <Icon name={getIconName(activePack, "search")} size={iconSize} />
446
- Search projects, users, invoices
447
- </div>
448
- <div className="icons-field is-select" style={{ borderRadius: radius }}>
449
- <span>
450
- <Icon name={getIconName(activePack, "filter")} size={iconSize} />
451
- Status
452
- </span>
453
- <Icon name={getIconName(activePack, "chevron")} size={16} />
454
- </div>
455
- </div>
456
-
457
- <div className="icons-menu-preview" style={{ borderRadius: panelRadius }}>
458
- {[
459
- ["home", "Dashboard", "chevron"],
460
- ["users", "Customers", "24"],
461
- ["bell", "Notifications", "Live"],
462
- ].map(([left, label, right]) => (
463
- <div className="icons-menu-row" key={label} style={{ minHeight: rowHeight }}>
464
- <Icon name={getIconName(activePack, left as IconSemantic)} size={iconSize} />
465
- <span>{label}</span>
466
- {right === "Live" ? (
467
- <em>
468
- <Icon name={getIconName(activePack, "check")} size={14} />
469
- Live
470
- </em>
471
- ) : right === "24" ? (
472
- <small>24</small>
473
- ) : (
474
- <Icon name={getIconName(activePack, "chevron")} size={16} />
475
- )}
476
- </div>
477
- ))}
478
- </div>
479
-
480
- <div className="icons-table-preview" style={{ borderRadius: panelRadius }}>
481
- {[
482
- ["chart", "Usage API", "Infrastructure", "92.8%", "more"],
483
- ["calendar", "Billing review", "Finance", "Jun 12", "edit"],
484
- ["warning", "Risk queue", "Operations", "4 open", "more"],
485
- ].map(([left, name, area, value, action]) => (
486
- <div className="icons-table-row" key={name} style={{ minHeight: rowHeight }}>
487
- <Icon name={getIconName(activePack, left as IconSemantic)} size={iconSize} />
488
- <strong>{name}</strong>
489
- <span>{area}</span>
490
- <span>{value}</span>
491
- <Icon name={getIconName(activePack, action as IconSemantic)} size={16} />
492
- </div>
493
- ))}
494
- </div>
495
-
496
- <div className="icons-empty-preview" style={{ borderRadius: panelRadius }}>
497
- <div>
498
- <span style={{ borderRadius: panelRadius }}>
499
- <Icon name={getIconName(activePack, "filter")} size={28} />
500
- </span>
501
- <h3>No matching records</h3>
502
- <p>Adjust the active filters or create a saved view for this workflow.</p>
503
- <button className="preview-button is-primary" style={{ borderRadius: radius }} type="button">
504
- <Icon name={getIconName(activePack, "add")} size={iconSize} />
505
- Saved view
506
- </button>
507
- </div>
508
- </div>
509
- </div>
510
- </div>
511
- </section>
512
- </div>
513
- )}
514
-
515
- {view === "catalog" && (
516
- <div className="icons-content">
517
- <section className="icons-panel">
518
- <div className="icons-panel__header">
519
- <div>
520
- <h3>{activePack.name} catalog</h3>
521
- <span>{catalogStatusText(activeCatalog, catalogRows.length)}</span>
522
- </div>
523
- </div>
524
- <div className="icons-catalog-tools">
525
- <input
526
- className="icons-input"
527
- onChange={(event) => {
528
- setCatalogQuery(event.target.value);
529
- setRenderLimit(180);
530
- }}
531
- placeholder="Search icons in this pack"
532
- type="search"
533
- value={catalogQuery}
534
- />
535
- <select
536
- className="icons-select"
537
- onChange={(event) => {
538
- setCatalogCategory(event.target.value);
539
- setRenderLimit(180);
540
- }}
541
- value={catalogCategory}
542
- >
543
- <option value="all">All categories</option>
544
- {catalogCategories.map((category) => (
545
- <option key={category} value={category}>
546
- {category}
547
- </option>
548
- ))}
549
- </select>
550
- <button className="preview-button" disabled={activeCatalog.status === "loading"} onClick={loadCatalog} type="button">
551
- <Icon name="lucide:download-cloud" size={18} />
552
- {activeCatalog.status === "loading" ? "Loading" : "Load catalog"}
553
- </button>
554
- </div>
555
- </section>
556
-
557
- {activeCatalog.status === "error" && <div className="icons-error">Could not load catalog: {activeCatalog.message}</div>}
558
-
559
- <section className="icons-catalog-grid">
560
- {activeCatalog.status === "loaded" &&
561
- catalogRows.slice(0, renderLimit).map((row) => (
562
- <button className="icons-catalog-card" key={`${row.category}-${row.name}`} title={`${activePack.prefix}:${row.name}`} type="button">
563
- <Icon name={`${activePack.prefix}:${row.name}`} size={30} />
564
- <span>{row.name}</span>
565
- </button>
566
- ))}
567
- </section>
568
-
569
- {activeCatalog.status === "loaded" && renderLimit < catalogRows.length && (
570
- <button className="preview-button icons-load-more" onClick={() => setRenderLimit((current) => current + 180)} type="button">
571
- <Icon name="lucide:plus" size={18} />
572
- Show more
573
- </button>
574
- )}
575
- </div>
576
- )}
577
-
578
- {view === "compare" && (
579
- <div className="icons-content">
580
- <section className="icons-compare-grid">
581
- {iconPacks.map((pack) => (
582
- <article className="icons-compare-card" key={pack.id}>
583
- <div>
584
- <strong>{pack.name}</strong>
585
- <span>{pack.license}</span>
586
- </div>
587
- <div className="icons-compare-icons">
588
- {(["home", "search", "settings", "users", "warning"] as IconSemantic[]).map((semantic) => (
589
- <span key={semantic}>
590
- <Icon name={getIconName(pack, semantic)} size={22} />
591
- </span>
592
- ))}
593
- </div>
594
- <p>
595
- {pack.total.toLocaleString()} icons, {pack.tags.join(", ")}
596
- </p>
597
- </article>
598
- ))}
599
- </section>
600
- </div>
601
- )}
602
- </main>
603
- </section>
604
- );
605
- }
606
-
607
- function catalogStatusText(catalog: CatalogState, matchCount: number) {
608
- if (catalog.status === "idle") return "Catalog names load on demand from Iconify.";
609
- if (catalog.status === "loading") return "Loading the full icon name list...";
610
- if (catalog.status === "error") return "Catalog failed to load.";
611
- return `${catalog.total.toLocaleString()} icons loaded, ${matchCount.toLocaleString()} matching.`;
612
- }