@lar-works/design-system 0.2.1 → 0.3.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 (432) hide show
  1. package/README.md +5 -1
  2. package/dist/actions/Button.d.ts +39 -1
  3. package/dist/actions/Button.js +39 -9
  4. package/dist/actions/Button.js.map +1 -1
  5. package/dist/actions/ButtonLink.d.ts +5 -1
  6. package/dist/actions/ButtonLink.js +28 -2
  7. package/dist/actions/ButtonLink.js.map +1 -1
  8. package/dist/actions/ButtonRecipes.js +7 -0
  9. package/dist/actions/ButtonRecipes.js.map +1 -0
  10. package/dist/actions/IconButton.d.ts +2 -2
  11. package/dist/actions/IconButton.js +1 -1
  12. package/dist/actions/IconButton.js.map +1 -1
  13. package/dist/actions/ThemeSwitch.js +1 -1
  14. package/dist/board/ActivityLine.d.ts +36 -0
  15. package/dist/board/ActivityLine.js +144 -0
  16. package/dist/board/ActivityLine.js.map +1 -0
  17. package/dist/board/ActivityTrack.d.ts +11 -0
  18. package/dist/board/ActivityTrack.js +25 -0
  19. package/dist/board/ActivityTrack.js.map +1 -0
  20. package/dist/board/AddItem.d.ts +82 -0
  21. package/dist/board/AddItem.js +185 -0
  22. package/dist/board/AddItem.js.map +1 -0
  23. package/dist/board/Archive.d.ts +21 -0
  24. package/dist/board/Archive.js +63 -0
  25. package/dist/board/Archive.js.map +1 -0
  26. package/dist/board/Attention.d.ts +66 -0
  27. package/dist/board/Attention.js +122 -0
  28. package/dist/board/Attention.js.map +1 -0
  29. package/dist/board/BoardShell.d.ts +16 -0
  30. package/dist/board/BoardShell.js +35 -0
  31. package/dist/board/BoardShell.js.map +1 -0
  32. package/dist/board/BoardTitleBar.d.ts +50 -0
  33. package/dist/board/BoardTitleBar.js +125 -0
  34. package/dist/board/BoardTitleBar.js.map +1 -0
  35. package/dist/board/ButtonKinds.d.ts +24 -0
  36. package/dist/board/ButtonKinds.js +19 -0
  37. package/dist/board/ButtonKinds.js.map +1 -0
  38. package/dist/board/Cleanup.d.ts +8 -0
  39. package/dist/board/Cleanup.js +15 -0
  40. package/dist/board/Cleanup.js.map +1 -0
  41. package/dist/board/Counters.d.ts +101 -0
  42. package/dist/board/Counters.js +202 -0
  43. package/dist/board/Counters.js.map +1 -0
  44. package/dist/board/DataAttributes.d.ts +6 -0
  45. package/dist/board/Dependencies.d.ts +17 -0
  46. package/dist/board/Dependencies.js +44 -0
  47. package/dist/board/Dependencies.js.map +1 -0
  48. package/dist/board/Desk.d.ts +52 -0
  49. package/dist/board/Desk.js +130 -0
  50. package/dist/board/Desk.js.map +1 -0
  51. package/dist/board/Dossier.d.ts +100 -0
  52. package/dist/board/Dossier.js +194 -0
  53. package/dist/board/Dossier.js.map +1 -0
  54. package/dist/board/Filters.d.ts +33 -0
  55. package/dist/board/Filters.js +64 -0
  56. package/dist/board/Filters.js.map +1 -0
  57. package/dist/board/ItemDetails.d.ts +167 -0
  58. package/dist/board/ItemDetails.js +339 -0
  59. package/dist/board/ItemDetails.js.map +1 -0
  60. package/dist/board/ItemDialog.d.ts +73 -0
  61. package/dist/board/ItemDialog.js +140 -0
  62. package/dist/board/ItemDialog.js.map +1 -0
  63. package/dist/board/ItemParts.d.ts +42 -0
  64. package/dist/board/ItemParts.js +82 -0
  65. package/dist/board/ItemParts.js.map +1 -0
  66. package/dist/board/ItemProgress.d.ts +56 -0
  67. package/dist/board/ItemProgress.js +138 -0
  68. package/dist/board/ItemProgress.js.map +1 -0
  69. package/dist/board/List.d.ts +65 -0
  70. package/dist/board/List.js +118 -0
  71. package/dist/board/List.js.map +1 -0
  72. package/dist/board/LoadingScreen.d.ts +38 -0
  73. package/dist/board/LoadingScreen.js +74 -0
  74. package/dist/board/LoadingScreen.js.map +1 -0
  75. package/dist/board/NeedsYou.d.ts +13 -0
  76. package/dist/board/NeedsYou.js +31 -0
  77. package/dist/board/NeedsYou.js.map +1 -0
  78. package/dist/board/Onboarding.d.ts +53 -0
  79. package/dist/board/Onboarding.js +122 -0
  80. package/dist/board/Onboarding.js.map +1 -0
  81. package/dist/board/PartsList.d.ts +60 -0
  82. package/dist/board/PartsList.js +115 -0
  83. package/dist/board/PartsList.js.map +1 -0
  84. package/dist/board/ProjectIcon.d.ts +51 -0
  85. package/dist/board/ProjectIcon.js +120 -0
  86. package/dist/board/ProjectIcon.js.map +1 -0
  87. package/dist/board/PullRequests.d.ts +43 -0
  88. package/dist/board/PullRequests.js +83 -0
  89. package/dist/board/PullRequests.js.map +1 -0
  90. package/dist/board/RowStyles.js +14 -0
  91. package/dist/board/RowStyles.js.map +1 -0
  92. package/dist/board/Settings.d.ts +54 -0
  93. package/dist/board/Settings.js +112 -0
  94. package/dist/board/Settings.js.map +1 -0
  95. package/dist/board/Slices.d.ts +32 -0
  96. package/dist/board/Slices.js +88 -0
  97. package/dist/board/Slices.js.map +1 -0
  98. package/dist/board/SourceIcon.d.ts +12 -0
  99. package/dist/board/SourceIcon.js +24 -0
  100. package/dist/board/SourceIcon.js.map +1 -0
  101. package/dist/board/StagePane.d.ts +43 -0
  102. package/dist/board/StagePane.js +97 -0
  103. package/dist/board/StagePane.js.map +1 -0
  104. package/dist/board/StageRow.d.ts +141 -0
  105. package/dist/board/StageRow.js +287 -0
  106. package/dist/board/StageRow.js.map +1 -0
  107. package/dist/board/StageTrack.d.ts +18 -0
  108. package/dist/board/StageTrack.js +38 -0
  109. package/dist/board/StageTrack.js.map +1 -0
  110. package/dist/board/StartControls.d.ts +30 -0
  111. package/dist/board/StartControls.js +51 -0
  112. package/dist/board/StartControls.js.map +1 -0
  113. package/dist/board/StatusColors.d.ts +5 -0
  114. package/dist/board/StatusColors.js +17 -0
  115. package/dist/board/StatusColors.js.map +1 -0
  116. package/dist/board/StatusGlyph.d.ts +18 -0
  117. package/dist/board/StatusGlyph.js +51 -0
  118. package/dist/board/StatusGlyph.js.map +1 -0
  119. package/dist/board/Threads.d.ts +13 -0
  120. package/dist/board/Threads.js +35 -0
  121. package/dist/board/Threads.js.map +1 -0
  122. package/dist/board/TrackerLabels.d.ts +27 -0
  123. package/dist/board/TrackerLabels.js +56 -0
  124. package/dist/board/TrackerLabels.js.map +1 -0
  125. package/dist/board/UpdatePill.d.ts +52 -0
  126. package/dist/board/UpdatePill.js +188 -0
  127. package/dist/board/UpdatePill.js.map +1 -0
  128. package/dist/board/Vocabulary.d.ts +56 -0
  129. package/dist/board/Vocabulary.js +104 -0
  130. package/dist/board/Vocabulary.js.map +1 -0
  131. package/dist/board/Warnings.d.ts +35 -0
  132. package/dist/board/Warnings.js +85 -0
  133. package/dist/board/Warnings.js.map +1 -0
  134. package/dist/board/WorkMap.d.ts +136 -0
  135. package/dist/board/WorkMap.js +274 -0
  136. package/dist/board/WorkMap.js.map +1 -0
  137. package/dist/board/index.d.ts +42 -0
  138. package/dist/collections/CollectionState.d.ts +10 -1
  139. package/dist/collections/CollectionState.js +12 -5
  140. package/dist/collections/CollectionState.js.map +1 -1
  141. package/dist/collections/ResultList.js +1 -1
  142. package/dist/collections/ScrollRow.js +1 -1
  143. package/dist/collections/VirtualList.js +1 -1
  144. package/dist/display/Avatar.d.ts +5 -2
  145. package/dist/display/Avatar.js +2 -2
  146. package/dist/display/Avatar.js.map +1 -1
  147. package/dist/display/Badge.d.ts +6 -1
  148. package/dist/display/Badge.js +9 -4
  149. package/dist/display/Badge.js.map +1 -1
  150. package/dist/display/CheckList.js +1 -1
  151. package/dist/display/Comment.d.ts +12 -0
  152. package/dist/display/Comment.js +39 -0
  153. package/dist/display/Comment.js.map +1 -0
  154. package/dist/display/CopyButton.d.ts +10 -2
  155. package/dist/display/CopyButton.js +29 -16
  156. package/dist/display/CopyButton.js.map +1 -1
  157. package/dist/display/Diagram.d.ts +17 -0
  158. package/dist/display/Diagram.js +49 -0
  159. package/dist/display/Diagram.js.map +1 -0
  160. package/dist/display/ImagePicker.js +1 -1
  161. package/dist/display/InfoBox.js +2 -2
  162. package/dist/display/InsetPanel.d.ts +3 -1
  163. package/dist/display/InsetPanel.js +7 -4
  164. package/dist/display/InsetPanel.js.map +1 -1
  165. package/dist/display/ItemAction.js +2 -2
  166. package/dist/display/LabelChip.d.ts +26 -0
  167. package/dist/display/LabelChip.js +41 -0
  168. package/dist/display/LabelChip.js.map +1 -0
  169. package/dist/display/LabelTint.js +60 -0
  170. package/dist/display/LabelTint.js.map +1 -0
  171. package/dist/display/Person.d.ts +5 -0
  172. package/dist/display/Person.js +19 -0
  173. package/dist/display/Person.js.map +1 -0
  174. package/dist/display/PropertySheet.js +1 -1
  175. package/dist/display/RadioChoice.d.ts +9 -2
  176. package/dist/display/RadioChoice.js +5 -1
  177. package/dist/display/RadioChoice.js.map +1 -1
  178. package/dist/display/RatingLink.js +1 -1
  179. package/dist/display/RichText.d.ts +12 -0
  180. package/dist/display/RichText.js +94 -0
  181. package/dist/display/RichText.js.map +1 -0
  182. package/dist/display/RowList.js +1 -1
  183. package/dist/display/index.d.ts +6 -1
  184. package/dist/feedback/Alert.js +1 -1
  185. package/dist/feedback/AsyncState.js +1 -1
  186. package/dist/feedback/DismissButton.d.ts +12 -0
  187. package/dist/feedback/DismissButton.js +20 -0
  188. package/dist/feedback/DismissButton.js.map +1 -0
  189. package/dist/feedback/ErrorNotification.js +2 -0
  190. package/dist/feedback/ErrorNotification.js.map +1 -1
  191. package/dist/feedback/FormResult.d.ts +16 -0
  192. package/dist/feedback/FormResult.js +30 -0
  193. package/dist/feedback/FormResult.js.map +1 -0
  194. package/dist/feedback/LiveRegion.d.ts +7 -0
  195. package/dist/feedback/LiveRegion.js +15 -0
  196. package/dist/feedback/LiveRegion.js.map +1 -0
  197. package/dist/feedback/Loading.d.ts +19 -0
  198. package/dist/feedback/Loading.js +63 -0
  199. package/dist/feedback/Loading.js.map +1 -0
  200. package/dist/feedback/Notifications.d.ts +35 -1
  201. package/dist/feedback/Notifications.js +196 -4
  202. package/dist/feedback/Notifications.js.map +1 -1
  203. package/dist/feedback/Progress.d.ts +15 -0
  204. package/dist/feedback/Progress.js +38 -0
  205. package/dist/feedback/Progress.js.map +1 -0
  206. package/dist/feedback/ProgressStatusRow.js +1 -1
  207. package/dist/feedback/StatusIndicator.d.ts +6 -1
  208. package/dist/feedback/StatusIndicator.js +19 -5
  209. package/dist/feedback/StatusIndicator.js.map +1 -1
  210. package/dist/feedback/index.d.ts +8 -4
  211. package/dist/forms/Checkbox.js +1 -1
  212. package/dist/forms/ColorSwatches.d.ts +21 -0
  213. package/dist/forms/ColorSwatches.js +26 -0
  214. package/dist/forms/ColorSwatches.js.map +1 -0
  215. package/dist/forms/FormSection.d.ts +68 -6
  216. package/dist/forms/FormSection.js +139 -8
  217. package/dist/forms/FormSection.js.map +1 -1
  218. package/dist/forms/HiddenField.d.ts +6 -0
  219. package/dist/forms/HiddenField.js +13 -0
  220. package/dist/forms/HiddenField.js.map +1 -0
  221. package/dist/forms/Input.d.ts +12 -2
  222. package/dist/forms/Input.js +10 -3
  223. package/dist/forms/Input.js.map +1 -1
  224. package/dist/forms/LabelPicker.d.ts +43 -0
  225. package/dist/forms/LabelPicker.js +107 -0
  226. package/dist/forms/LabelPicker.js.map +1 -0
  227. package/dist/forms/MarkdownEditor.d.ts +21 -0
  228. package/dist/forms/MarkdownEditor.js +175 -0
  229. package/dist/forms/MarkdownEditor.js.map +1 -0
  230. package/dist/forms/MarkdownToolbar.js +204 -0
  231. package/dist/forms/MarkdownToolbar.js.map +1 -0
  232. package/dist/forms/OptionSelect.d.ts +30 -9
  233. package/dist/forms/OptionSelect.js +53 -13
  234. package/dist/forms/OptionSelect.js.map +1 -1
  235. package/dist/forms/Select.d.ts +4 -1
  236. package/dist/forms/Select.js +3 -1
  237. package/dist/forms/Select.js.map +1 -1
  238. package/dist/forms/Switch.d.ts +4 -1
  239. package/dist/forms/Switch.js +5 -2
  240. package/dist/forms/Switch.js.map +1 -1
  241. package/dist/forms/TagInput.js +1 -1
  242. package/dist/forms/index.d.ts +7 -3
  243. package/dist/foundations/AmbientMotion.d.ts +2 -0
  244. package/dist/foundations/AmbientMotion.js +39 -0
  245. package/dist/foundations/AmbientMotion.js.map +1 -0
  246. package/dist/foundations/DesignSystemProvider.d.ts +34 -0
  247. package/dist/foundations/DesignSystemProvider.js +26 -1
  248. package/dist/foundations/DesignSystemProvider.js.map +1 -1
  249. package/dist/foundations/index.d.ts +2 -1
  250. package/dist/icons/BrandIcons.d.ts +8 -2
  251. package/dist/icons/BrandIcons.js +14 -3
  252. package/dist/icons/BrandIcons.js.map +1 -1
  253. package/dist/icons/dynamic.d.ts +2 -0
  254. package/dist/icons/dynamic.js +1 -0
  255. package/dist/icons/index.d.ts +2 -2
  256. package/dist/icons/index.js +1 -1
  257. package/dist/icons.d.ts +2 -2
  258. package/dist/icons.js +1 -1
  259. package/dist/index.d.ts +71 -10
  260. package/dist/index.js +78 -21
  261. package/dist/layout/Site.js +1 -1
  262. package/dist/layout/TitleBar.js +1 -1
  263. package/dist/layout/index.d.ts +2 -2
  264. package/dist/layout/index.js +3 -3
  265. package/dist/layout/toolbar/CommandPaletteTrigger.d.ts +17 -2
  266. package/dist/layout/toolbar/CommandPaletteTrigger.js +89 -6
  267. package/dist/layout/toolbar/CommandPaletteTrigger.js.map +1 -1
  268. package/dist/layout/toolbar/TriggerFit.js +56 -0
  269. package/dist/layout/toolbar/TriggerFit.js.map +1 -0
  270. package/dist/navigation/CollectionBrowseSelector.js +1 -1
  271. package/dist/navigation/CollectionViewToggle.d.ts +26 -0
  272. package/dist/navigation/CollectionViewToggle.js +54 -23
  273. package/dist/navigation/CollectionViewToggle.js.map +1 -1
  274. package/dist/navigation/DrilldownBrowseSelector.js +1 -1
  275. package/dist/navigation/FolderSelector.js +2 -2
  276. package/dist/navigation/SortMenu.d.ts +10 -0
  277. package/dist/navigation/SortMenu.js +33 -0
  278. package/dist/navigation/SortMenu.js.map +1 -0
  279. package/dist/navigation/index.d.ts +3 -2
  280. package/dist/overlays/CommandPalette.d.ts +38 -4
  281. package/dist/overlays/CommandPalette.js +140 -54
  282. package/dist/overlays/CommandPalette.js.map +1 -1
  283. package/dist/overlays/Drawer.js +1 -1
  284. package/dist/overlays/DropdownMenu.d.ts +7 -1
  285. package/dist/overlays/DropdownMenu.js +13 -2
  286. package/dist/overlays/DropdownMenu.js.map +1 -1
  287. package/dist/overlays/HelpTip.d.ts +8 -0
  288. package/dist/overlays/HelpTip.js +29 -0
  289. package/dist/overlays/HelpTip.js.map +1 -0
  290. package/dist/overlays/ImageViewer.js +2 -2
  291. package/dist/overlays/Modal.d.ts +5 -0
  292. package/dist/overlays/Modal.js +17 -2
  293. package/dist/overlays/Modal.js.map +1 -1
  294. package/dist/overlays/ModalExpandButton.d.ts +18 -0
  295. package/dist/overlays/ModalExpandButton.js +43 -0
  296. package/dist/overlays/ModalExpandButton.js.map +1 -0
  297. package/dist/overlays/Popover.d.ts +2 -1
  298. package/dist/overlays/Popover.js +1 -0
  299. package/dist/overlays/Popover.js.map +1 -1
  300. package/dist/overlays/index.d.ts +4 -2
  301. package/dist/showcase/Cards.js +1 -1
  302. package/dist/showcase/FeatureRow.js +1 -1
  303. package/dist/showcase/PhotoSearchConcept.js +1 -1
  304. package/dist/showcase/ProductBand.js +1 -1
  305. package/dist/showcase/Status.js +1 -1
  306. package/dist/typography/Code.d.ts +18 -0
  307. package/dist/typography/Code.js +24 -0
  308. package/dist/typography/Code.js.map +1 -0
  309. package/dist/typography/Link.d.ts +7 -2
  310. package/dist/typography/Link.js +15 -5
  311. package/dist/typography/Link.js.map +1 -1
  312. package/dist/typography/Typography.d.ts +19 -1
  313. package/dist/typography/Typography.js +11 -3
  314. package/dist/typography/Typography.js.map +1 -1
  315. package/dist/typography/index.d.ts +3 -3
  316. package/package.json +26 -3
  317. package/src/actions/Button.tsx +78 -11
  318. package/src/actions/ButtonLink.tsx +34 -0
  319. package/src/actions/ButtonRecipes.ts +10 -0
  320. package/src/actions/IconButton.tsx +7 -4
  321. package/src/board/ActivityLine.tsx +176 -0
  322. package/src/board/ActivityTrack.tsx +23 -0
  323. package/src/board/AddItem.tsx +259 -0
  324. package/src/board/Archive.tsx +75 -0
  325. package/src/board/Attention.tsx +135 -0
  326. package/src/board/BoardShell.tsx +30 -0
  327. package/src/board/BoardTitleBar.tsx +167 -0
  328. package/src/board/ButtonKinds.ts +32 -0
  329. package/src/board/Cleanup.tsx +19 -0
  330. package/src/board/Counters.tsx +224 -0
  331. package/src/board/DataAttributes.ts +3 -0
  332. package/src/board/Dependencies.tsx +49 -0
  333. package/src/board/Desk.tsx +155 -0
  334. package/src/board/Dossier.tsx +262 -0
  335. package/src/board/Filters.tsx +85 -0
  336. package/src/board/ItemDetails.tsx +435 -0
  337. package/src/board/ItemDialog.tsx +179 -0
  338. package/src/board/ItemParts.tsx +128 -0
  339. package/src/board/ItemProgress.tsx +164 -0
  340. package/src/board/List.tsx +154 -0
  341. package/src/board/LoadingScreen.tsx +78 -0
  342. package/src/board/NeedsYou.tsx +30 -0
  343. package/src/board/Onboarding.tsx +124 -0
  344. package/src/board/PartsList.tsx +160 -0
  345. package/src/board/ProjectIcon.tsx +172 -0
  346. package/src/board/PullRequests.tsx +106 -0
  347. package/src/board/RowStyles.ts +17 -0
  348. package/src/board/Settings.tsx +124 -0
  349. package/src/board/Slices.tsx +103 -0
  350. package/src/board/SourceIcon.tsx +33 -0
  351. package/src/board/StagePane.tsx +128 -0
  352. package/src/board/StageRow.tsx +320 -0
  353. package/src/board/StageTrack.tsx +61 -0
  354. package/src/board/StartControls.tsx +77 -0
  355. package/src/board/StatusColors.ts +28 -0
  356. package/src/board/StatusGlyph.tsx +70 -0
  357. package/src/board/Threads.tsx +38 -0
  358. package/src/board/TrackerLabels.tsx +75 -0
  359. package/src/board/UpdatePill.tsx +222 -0
  360. package/src/board/Vocabulary.tsx +155 -0
  361. package/src/board/Warnings.tsx +83 -0
  362. package/src/board/WorkMap.tsx +436 -0
  363. package/src/board/base.css +121 -0
  364. package/src/board/button-kinds.css +8 -0
  365. package/src/board/index.ts +224 -0
  366. package/src/board/items.css +75 -0
  367. package/src/board/loading-screen.css +171 -0
  368. package/src/board/rows.css +180 -0
  369. package/src/board/title-bar.css +56 -0
  370. package/src/board/tokens.css +52 -0
  371. package/src/board.css +19 -0
  372. package/src/collections/CollectionState.tsx +20 -7
  373. package/src/display/Avatar.tsx +6 -3
  374. package/src/display/Badge.tsx +25 -3
  375. package/src/display/Comment.tsx +26 -0
  376. package/src/display/CopyButton.tsx +48 -18
  377. package/src/display/Diagram.tsx +59 -0
  378. package/src/display/InsetPanel.tsx +4 -1
  379. package/src/display/LabelChip.tsx +73 -0
  380. package/src/display/LabelTint.ts +72 -0
  381. package/src/display/Person.tsx +11 -0
  382. package/src/display/RadioChoice.tsx +11 -2
  383. package/src/display/RichText.tsx +114 -0
  384. package/src/display/index.ts +5 -0
  385. package/src/feedback/DismissButton.tsx +28 -0
  386. package/src/feedback/ErrorNotification.ts +3 -0
  387. package/src/feedback/FormResult.tsx +50 -0
  388. package/src/feedback/LiveRegion.tsx +10 -0
  389. package/src/feedback/Loading.tsx +75 -0
  390. package/src/feedback/Notifications.tsx +267 -2
  391. package/src/feedback/Progress.tsx +44 -0
  392. package/src/feedback/StatusIndicator.tsx +34 -3
  393. package/src/feedback/index.ts +12 -2
  394. package/src/forms/ColorSwatches.tsx +50 -0
  395. package/src/forms/FormSection.tsx +245 -10
  396. package/src/forms/HiddenField.tsx +4 -0
  397. package/src/forms/Input.tsx +26 -4
  398. package/src/forms/LabelPicker.tsx +140 -0
  399. package/src/forms/MarkdownEditor.tsx +283 -0
  400. package/src/forms/MarkdownToolbar.tsx +267 -0
  401. package/src/forms/OptionSelect.tsx +110 -25
  402. package/src/forms/Select.tsx +20 -13
  403. package/src/forms/Switch.tsx +16 -2
  404. package/src/forms/index.ts +16 -2
  405. package/src/foundations/AmbientMotion.ts +51 -0
  406. package/src/foundations/DesignSystemProvider.tsx +19 -0
  407. package/src/foundations/index.ts +1 -0
  408. package/src/foundations/theme.css +64 -0
  409. package/src/icons/BrandIcons.tsx +12 -3
  410. package/src/icons/dynamic.ts +4 -0
  411. package/src/icons/index.ts +1 -1
  412. package/src/index.ts +2 -1
  413. package/src/layout/index.ts +1 -1
  414. package/src/layout/toolbar/CommandPaletteTrigger.tsx +187 -31
  415. package/src/layout/toolbar/TriggerFit.ts +84 -0
  416. package/src/navigation/CollectionViewToggle.tsx +94 -24
  417. package/src/navigation/SortMenu.tsx +41 -0
  418. package/src/navigation/index.ts +2 -1
  419. package/src/overlays/CommandPalette.tsx +190 -60
  420. package/src/overlays/DropdownMenu.tsx +12 -1
  421. package/src/overlays/HelpTip.tsx +29 -0
  422. package/src/overlays/Modal.tsx +23 -0
  423. package/src/overlays/ModalExpandButton.tsx +48 -0
  424. package/src/overlays/Popover.tsx +3 -1
  425. package/src/overlays/index.ts +9 -1
  426. package/src/styles.css +3 -1
  427. package/src/typography/Code.tsx +24 -0
  428. package/src/typography/Link.tsx +19 -10
  429. package/src/typography/Typography.tsx +25 -2
  430. package/src/typography/index.ts +1 -1
  431. package/dist/feedback/index.js +0 -11
  432. package/dist/typography/index.js +0 -4
@@ -4,6 +4,11 @@ interface StatusIndicatorProps {
4
4
  className?: string;
5
5
  size?: 'sm' | 'md';
6
6
  tone: StatusTone;
7
+ /** A colour that comes with data, such as a tracker label's own, in place of the tone's; none keeps the tone. */
8
+ labelColor?: string | undefined;
9
+ /** A short word after the dot in the tone's text colour, such as a check's "Installed"; the neutral tone mutes it,
10
+ * for a check that didn't run. */
11
+ label?: string;
7
12
  }
8
13
 
9
14
  const toneClasses: Record<StatusTone, string> = {
@@ -14,15 +19,41 @@ const toneClasses: Record<StatusTone, string> = {
14
19
  info: 'bg-info',
15
20
  };
16
21
 
22
+ const labelClasses: Record<StatusTone, string> = {
23
+ neutral: 'text-base-700',
24
+ success: 'text-(--lar-success-text)',
25
+ warning: 'text-(--lar-warning-text)',
26
+ danger: 'text-(--lar-danger-text)',
27
+ info: 'text-(--lar-info-text)',
28
+ };
29
+
17
30
  const sizeClasses: Record<NonNullable<StatusIndicatorProps['size']>, string> = {
18
31
  sm: 'size-1.5',
19
32
  md: 'size-2',
20
33
  };
21
34
 
22
- export function StatusIndicator({ className, size = 'md', tone }: StatusIndicatorProps) {
23
- const classes = ['inline-block shrink-0 rounded-full', sizeClasses[size], toneClasses[tone], className]
35
+ export function StatusIndicator({ className, size = 'md', tone, labelColor, label }: StatusIndicatorProps) {
36
+ const classes = [
37
+ 'inline-block shrink-0 rounded-full',
38
+ sizeClasses[size],
39
+ toneClasses[tone],
40
+ label ? null : className,
41
+ ]
24
42
  .filter(Boolean)
25
43
  .join(' ');
44
+ const dot = (
45
+ <span aria-hidden="true" className={classes} style={labelColor ? { background: labelColor } : undefined} />
46
+ );
47
+
48
+ if (label === undefined) return dot;
26
49
 
27
- return <span aria-hidden="true" className={classes} />;
50
+ return (
51
+ <span
52
+ data-slot="status-indicator"
53
+ className={`inline-flex items-center gap-1.5 text-xs font-medium whitespace-nowrap ${labelClasses[tone]} ${className ?? ''}`}
54
+ >
55
+ {dot}
56
+ {label}
57
+ </span>
58
+ );
28
59
  }
@@ -6,10 +6,20 @@ export {
6
6
  showErrorNotification,
7
7
  showSuccessNotification,
8
8
  } from './ErrorNotification';
9
- export { Notifications } from './Notifications';
9
+ export { DismissButton } from './DismissButton';
10
+ export { FormResult } from './FormResult';
11
+ export { LiveRegion } from './LiveRegion';
12
+ export {
13
+ clearNotification,
14
+ type Notification,
15
+ Notifications,
16
+ showNotification,
17
+ useNotificationControls,
18
+ } from './Notifications';
19
+ export { Progress } from './Progress';
10
20
  export { ProgressStatusRow } from './ProgressStatusRow';
11
21
  export { SkeletonLoader } from './SkeletonLoader';
22
+ export { Loading } from './Loading';
12
23
  export { StatusIndicator, type StatusTone } from './StatusIndicator';
13
24
  export { StatusPopoverContent } from './StatusPopoverContent';
14
25
  export { StatusRow } from './StatusRow';
15
- export { Loading } from '@ninna-ui/feedback';
@@ -0,0 +1,50 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface ColorSwatch {
4
+ /** What picking it sets, such as a hex colour without its #. */
5
+ value: string;
6
+ /** The CSS colour it shows. */
7
+ color: string;
8
+ /** Its accessible name, such as "Colour #D73A4A". */
9
+ label: string;
10
+ }
11
+
12
+ /**
13
+ * A row of round colour swatches, the one matching `value` (ignoring case) pressed and ringed, then `children`, such as
14
+ * a field for any other colour, wrapping when they don't fit.
15
+ */
16
+ export function ColorSwatches({
17
+ swatches,
18
+ value,
19
+ disabled = false,
20
+ onPick,
21
+ children,
22
+ }: {
23
+ swatches: readonly ColorSwatch[];
24
+ value: string;
25
+ disabled?: boolean;
26
+ onPick: (value: string) => void;
27
+ children?: ReactNode;
28
+ }) {
29
+ return (
30
+ <span className="flex flex-wrap items-center gap-1.5">
31
+ {swatches.map((swatch) => {
32
+ const picked = swatch.value.toLowerCase() === value.toLowerCase();
33
+
34
+ return (
35
+ <button
36
+ key={swatch.value}
37
+ type="button"
38
+ aria-label={swatch.label}
39
+ aria-pressed={picked}
40
+ disabled={disabled}
41
+ className={`size-5 rounded-full border ${picked ? 'ring-2 ring-(--primary) ring-offset-1 ring-offset-(--card)' : ''}`}
42
+ style={{ background: swatch.color }}
43
+ onClick={() => onPick(swatch.value)}
44
+ />
45
+ );
46
+ })}
47
+ {children}
48
+ </span>
49
+ );
50
+ }
@@ -1,13 +1,17 @@
1
1
  import { ChevronDownIcon } from 'lucide-react';
2
- import { type ReactNode, useId, useState } from 'react';
2
+ import { type ComponentProps, type ReactNode, type SyntheticEvent, useId, useState } from 'react';
3
3
 
4
4
  import { Button, type ButtonProps } from '../actions/Button';
5
+ import { Badge } from '../display/Badge';
5
6
  import { InsetPanel } from '../display/InsetPanel';
6
7
  import { SkeletonLoader } from '../feedback/SkeletonLoader';
7
8
  import { Stack } from '../layout/Flow';
9
+ import { HelpTip } from '../overlays/HelpTip';
8
10
  import { Heading, Text } from '../typography/Typography';
9
11
  import { Field, type FieldProps } from './Field';
10
12
 
13
+ type DataAttributes = { [key: `data-${string}`]: string | undefined };
14
+
11
15
  const contentSpacing = 'px-4 py-3.5 sm:px-5';
12
16
  const rowClasses = `grid min-w-0 gap-x-6 gap-y-3 sm:items-center ${contentSpacing}`;
13
17
 
@@ -30,10 +34,68 @@ interface FormSectionProps {
30
34
  description?: string;
31
35
  loading?: boolean;
32
36
  loadingLabel?: string;
37
+ presentation?: 'default';
33
38
  title: ReactNode;
34
39
  }
35
40
 
36
- export function FormSection({
41
+ /** A dense tool's settings group: its title (with an optional help popover) and description over the content as
42
+ * given, such as one or more settings panels and their notes. */
43
+ interface DenseFormSectionProps extends DataAttributes {
44
+ presentation: 'dense';
45
+ title?: ReactNode;
46
+ /** Explains the group in a popover from a help button after the title; `helpLabel` names that button. */
47
+ help?: ReactNode;
48
+ helpLabel?: string;
49
+ description?: ReactNode;
50
+ /** Names the group when its title doesn't, or when it has none. */
51
+ 'aria-label'?: string;
52
+ children?: ReactNode;
53
+ }
54
+
55
+ export function FormSection(props: FormSectionProps | DenseFormSectionProps) {
56
+ if (props.presentation === 'dense') return <DenseFormSection {...props} />;
57
+
58
+ return <PanelFormSection {...props} />;
59
+ }
60
+
61
+ function DenseFormSection({
62
+ presentation: _presentation,
63
+ title,
64
+ help,
65
+ helpLabel,
66
+ description,
67
+ 'aria-label': label,
68
+ children,
69
+ ...data
70
+ }: DenseFormSectionProps) {
71
+ const titleId = useId();
72
+
73
+ return (
74
+ <section
75
+ {...data}
76
+ aria-label={label}
77
+ aria-labelledby={label || !title ? undefined : titleId}
78
+ className="grid min-w-0 gap-3"
79
+ >
80
+ {(title || description) && (
81
+ <div>
82
+ {title && (
83
+ <h3 id={titleId} className="m-0 flex items-center gap-1.5 text-[13px] font-semibold">
84
+ {title}
85
+ {help && (
86
+ <HelpTip label={helpLabel ?? (typeof title === 'string' ? title : '')}>{help}</HelpTip>
87
+ )}
88
+ </h3>
89
+ )}
90
+ {description && <p className="text-base-700 m-0 mt-0.5 text-xs">{description}</p>}
91
+ </div>
92
+ )}
93
+ {children}
94
+ </section>
95
+ );
96
+ }
97
+
98
+ function PanelFormSection({
37
99
  action,
38
100
  badges,
39
101
  children,
@@ -76,16 +138,40 @@ export function FormSection({
76
138
  );
77
139
  }
78
140
 
79
- interface FormRowProps {
141
+ interface FormRowColumnsProps {
80
142
  badges?: ReactNode;
81
143
  children: ReactNode;
82
144
  description: string;
83
145
  /** The row's element id, for linking or scrolling to it; a row with one can take focus when it is opened. */
84
146
  id?: string;
85
147
  label: ReactNode;
148
+ layout?: 'columns';
149
+ }
150
+
151
+ /** A dense tool's settings row: the label (with an optional help popover) and description, then its controls flowing
152
+ * beside them and wrapping under them when they don't fit, then content below and an error. */
153
+ interface FormRowFlowProps extends DataAttributes {
154
+ layout: 'flow';
155
+ label: ReactNode;
156
+ /** Explains the setting in a popover from a help button after the label; `helpLabel` names that button. */
157
+ help?: ReactNode;
158
+ helpLabel?: string;
159
+ description?: ReactNode;
160
+ children?: ReactNode;
161
+ /** Full-width content under the label and controls, such as a list the setting governs. */
162
+ below?: ReactNode;
163
+ error?: string | undefined;
164
+ /** The error's element id, for a control's aria-describedby. */
165
+ errorId?: string;
86
166
  }
87
167
 
88
- export function FormRow({ badges, children, description, id, label }: FormRowProps) {
168
+ type FormRowProps = FormRowColumnsProps | FormRowFlowProps;
169
+
170
+ export function FormRow(props: FormRowProps) {
171
+ if (props.layout === 'flow') return <FormRowFlow {...props} />;
172
+
173
+ const { badges, children, description, id, label } = props;
174
+
89
175
  return (
90
176
  <div
91
177
  id={id}
@@ -108,6 +194,58 @@ export function FormRow({ badges, children, description, id, label }: FormRowPro
108
194
  );
109
195
  }
110
196
 
197
+ function FormRowFlow({
198
+ layout: _layout,
199
+ label,
200
+ help,
201
+ helpLabel,
202
+ description,
203
+ children,
204
+ below,
205
+ error,
206
+ errorId,
207
+ ...data
208
+ }: FormRowFlowProps) {
209
+ const fallbackErrorId = useId();
210
+
211
+ return (
212
+ <div {...data} className="settings-row grid min-w-0 gap-2 px-4 py-3">
213
+ <div className="flex min-w-0 flex-wrap items-center gap-x-6 gap-y-2">
214
+ <div className="min-w-32 flex-1">
215
+ <div className="settings-row-title flex min-w-0 items-center gap-1.5 text-[13px] font-medium wrap-anywhere">
216
+ {label}
217
+ {help && (
218
+ <HelpTip label={helpLabel ?? (typeof label === 'string' ? label : '')}>{help}</HelpTip>
219
+ )}
220
+ </div>
221
+ {description && <div className="text-base-700 text-xs wrap-anywhere">{description}</div>}
222
+ </div>
223
+ {children && (
224
+ <div className="settings-row-control flex max-w-full min-w-0 flex-wrap items-center gap-2 sm:max-w-[60%] [&_button]:max-w-full [&>span]:max-w-full">
225
+ {children}
226
+ </div>
227
+ )}
228
+ </div>
229
+ {below}
230
+ {error && (
231
+ <p id={errorId ?? fallbackErrorId} role="alert" className="m-0 text-xs text-(--lar-danger-text)">
232
+ {error}
233
+ </p>
234
+ )}
235
+ </div>
236
+ );
237
+ }
238
+
239
+ /** A value and its unit beside it in a row's controls, such as a number field and "days". */
240
+ export function FormUnit({ children, unit }: { children: ReactNode; unit: ReactNode }) {
241
+ return (
242
+ <span className="flex items-center gap-2 text-sm [&_input]:w-20">
243
+ {children}
244
+ {unit}
245
+ </span>
246
+ );
247
+ }
248
+
111
249
  type FormFieldProps = Omit<FieldProps, 'children' | 'className'> & { action?: ReactNode } & (
112
250
  | { children: ReactNode; loading?: false; loadingLabel?: never }
113
251
  | { children?: never; loading: true; loadingLabel: string }
@@ -156,20 +294,117 @@ export function FormAction(props: Omit<ButtonProps, 'type' | 'size' | 'variant'
156
294
  return <Button {...props} type="button" variant="subtle" />;
157
295
  }
158
296
 
159
- export function Disclosure({ children, label, lazy = false }: { children: ReactNode; label: string; lazy?: boolean }) {
160
- const [open, setOpen] = useState(false);
297
+ export function Disclosure({
298
+ children,
299
+ label,
300
+ lazy = false,
301
+ presentation = 'note',
302
+ defaultOpen = false,
303
+ badge,
304
+ onOpenChange,
305
+ ...data
306
+ }: {
307
+ children: ReactNode;
308
+ label: string;
309
+ lazy?: boolean;
310
+ /** `note` folds small muted text behind a chevron; `section` folds a section's own content, such as an earlier
311
+ * version of a text, behind a muted label in the body size; `group` folds a form's optional group behind its
312
+ * heading, such as optional settings; `framed` folds a short list in a frame, such as entries that can't be
313
+ * picked. */
314
+ presentation?: 'note' | 'section' | 'group' | 'framed';
315
+ /** Starts open, such as a group whose kept answers may need a look. */
316
+ defaultOpen?: boolean;
317
+ /** A `group`'s badge after its heading, such as "Optional". */
318
+ badge?: string | undefined;
319
+ /** Hears each time it opens or closes, such as to read what it shows once it opens. */
320
+ onOpenChange?: (open: boolean) => void;
321
+ } & DataAttributes) {
322
+ const [open, setOpen] = useState(defaultOpen);
323
+ const shown = !lazy || open ? children : null;
324
+ const toggle = (event: SyntheticEvent<HTMLDetailsElement>) => {
325
+ setOpen(event.currentTarget.open);
326
+ onOpenChange?.(event.currentTarget.open);
327
+ };
328
+
329
+ if (presentation === 'group')
330
+ return (
331
+ <details {...data} open={open} onToggle={toggle} className="min-w-0">
332
+ <summary className="cursor-pointer text-[13px] font-semibold">
333
+ {label}
334
+ {badge && (
335
+ <>
336
+ {' '}
337
+ <Badge className="ms-1">{badge}</Badge>
338
+ </>
339
+ )}
340
+ </summary>
341
+ {shown && <div className="mt-3 grid min-w-0 gap-3">{shown}</div>}
342
+ </details>
343
+ );
344
+
345
+ if (presentation === 'framed')
346
+ return (
347
+ <details {...data} open={open} onToggle={toggle} className="min-w-0 rounded-lg border px-3 py-2">
348
+ <summary className="cursor-pointer text-sm font-medium">{label}</summary>
349
+ <div className="mt-2">{shown}</div>
350
+ </details>
351
+ );
352
+
353
+ if (presentation === 'section')
354
+ return (
355
+ <details {...data} className="grid min-w-0 gap-2" onToggle={toggle}>
356
+ <summary className="text-base-700 cursor-pointer text-sm">{label}</summary>
357
+ <div className="pt-2">{shown}</div>
358
+ </details>
359
+ );
161
360
 
162
361
  return (
163
- <details className="group px-1" onToggle={(event) => setOpen(event.currentTarget.open)}>
362
+ <details {...data} className="group px-1" onToggle={toggle}>
164
363
  <summary className="text-base-700 hover:text-base-content focus-visible:outline-primary flex w-fit cursor-pointer list-none items-center gap-1.5 rounded py-1 text-xs focus-visible:outline-2 focus-visible:outline-offset-2 [&::-webkit-details-marker]:hidden">
165
364
  <span aria-hidden="true" className="-rotate-90 transition-transform group-open:rotate-0 [&>svg]:size-3">
166
365
  <ChevronDownIcon />
167
366
  </span>
168
367
  {label}
169
368
  </summary>
170
- <div className="text-base-700 mt-2 space-y-2 text-xs leading-relaxed">
171
- {!lazy || open ? children : null}
172
- </div>
369
+ <div className="text-base-700 mt-2 space-y-2 text-xs leading-relaxed">{shown}</div>
173
370
  </details>
174
371
  );
175
372
  }
373
+
374
+ type FormFieldsetProps = Omit<ComponentProps<'fieldset'>, 'style'> & {
375
+ /** The legend over the group. */
376
+ legend?: ReactNode;
377
+ /** Names the group for assistive technology only, where what's around it already says what it is. */
378
+ hiddenLegend?: boolean;
379
+ /** `group` only groups and disables its fields, with no frame. */
380
+ presentation?: 'bordered' | 'group';
381
+ };
382
+
383
+ /** The form section's fieldset: a bordered group of controls under a legend, on the native fieldset, which also
384
+ * disables every control inside it. As a `group`, it only groups and disables its fields. */
385
+ export function FormFieldset({
386
+ legend,
387
+ hiddenLegend = false,
388
+ presentation = 'bordered',
389
+ className = '',
390
+ children,
391
+ ...props
392
+ }: FormFieldsetProps) {
393
+ return (
394
+ <fieldset
395
+ className={`m-0 min-w-0 ${presentation === 'group' ? 'border-0 p-0' : 'border-base-300 mb-3 rounded-lg border px-3 pt-2 pb-0 [&>legend]:mb-2 [&>legend]:font-semibold'} ${className}`}
396
+ data-slot="fieldset"
397
+ {...props}
398
+ >
399
+ {legend ? (
400
+ <legend
401
+ className={hiddenLegend ? 'sr-only' : 'text-base-content p-0 text-sm leading-5 font-medium'}
402
+ data-slot="fieldset-legend"
403
+ >
404
+ {legend}
405
+ </legend>
406
+ ) : null}
407
+ {children}
408
+ </fieldset>
409
+ );
410
+ }
@@ -0,0 +1,4 @@
1
+ /** A value a form submits that nobody edits in place, such as a pick made in a popover: a hidden field. */
2
+ export function HiddenField({ name, value }: { name: string; value: string }) {
3
+ return <input type="hidden" name={name} value={value} />;
4
+ }
@@ -3,18 +3,40 @@ import { forwardRef } from 'react';
3
3
 
4
4
  import { formControlClasses, formControlDefaults } from './FormControlStyles';
5
5
 
6
- export type InputProps = Omit<NinnaInputProps, 'fullWidth'>;
6
+ export type InputProps = Omit<NinnaInputProps, 'fullWidth'> & {
7
+ /** A `field` fills its container; `setting` is a settings row's 16rem text field, narrower where the row is; `short`
8
+ * holds a few characters, such as a hex colour; `count` a small whole number; `title` is a document's title typed
9
+ * in place, borderless in large semibold type, such as a new item's. */
10
+ presentation?: 'field' | 'setting' | 'short' | 'count' | 'title';
11
+ };
12
+
13
+ const presentations = {
14
+ field: '',
15
+ setting: 'w-64 max-w-full',
16
+ short: 'w-24',
17
+ count: 'w-20',
18
+ title: 'text-base-content w-full shrink-0 border-0 bg-transparent p-0 text-xl font-semibold tracking-tight outline-none placeholder:font-medium placeholder:text-(--muted-foreground)',
19
+ } as const;
7
20
 
8
21
  export const Input = forwardRef<HTMLInputElement, InputProps>(
9
- ({ size = formControlDefaults.size, variant = formControlDefaults.variant, className, ...props }, ref) => (
22
+ (
23
+ {
24
+ size = formControlDefaults.size,
25
+ variant = formControlDefaults.variant,
26
+ presentation = 'field',
27
+ className,
28
+ ...props
29
+ },
30
+ ref,
31
+ ) => (
10
32
  <NinnaInput
11
33
  ref={ref}
12
34
  color={formControlDefaults.color}
13
35
  {...props}
14
36
  fullWidth
15
37
  size={size}
16
- variant={variant}
17
- className={`${variant === 'filled' ? formControlClasses : ''} ${className ?? ''}`}
38
+ variant={presentation === 'title' ? 'unstyled' : variant}
39
+ className={`${variant === 'filled' && presentation !== 'title' ? formControlClasses : ''} ${presentations[presentation]} ${className ?? ''}`}
18
40
  />
19
41
  ),
20
42
  );
@@ -0,0 +1,140 @@
1
+ import { PlusIcon } from 'lucide-react';
2
+ import { useState } from 'react';
3
+
4
+ import { Button } from '../actions/Button';
5
+ import { StatusIndicator } from '../feedback/StatusIndicator';
6
+ import { Checkbox } from './Checkbox';
7
+ import { Input } from './Input';
8
+
9
+ export interface LabelChoice {
10
+ name: string;
11
+ /** A CSS colour; a tracker with no label colours gives none. */
12
+ color?: string | undefined;
13
+ description?: string | undefined;
14
+ }
15
+
16
+ /** Lets a typed name no choice has become a new label. */
17
+ export interface LabelCreation {
18
+ /** Why this person can't add new labels at all, or null when they can. */
19
+ refused: string | null;
20
+ /** Why this name can't be a label, or null when it can. */
21
+ problem: (name: string) => string | null;
22
+ /** What adding one does, under the list. */
23
+ note: string;
24
+ /** The button that adds `name`, such as `Create "name"`. */
25
+ createLabel: (name: string) => string;
26
+ onCreate: (name: string) => void;
27
+ }
28
+
29
+ interface LabelPickerProps {
30
+ choices: readonly LabelChoice[];
31
+ picked: readonly string[];
32
+ onToggle: (name: string) => void;
33
+ /** The filter field's accessible name and its placeholder. */
34
+ filterLabel: string;
35
+ placeholder: string;
36
+ /** Shown when the filter matches nothing. */
37
+ noMatchLabel: string;
38
+ /** Choices shown ticked or not but not changeable here, each marked with `lockedNote`. */
39
+ locked?: readonly string[];
40
+ lockedNote?: string;
41
+ busy?: boolean;
42
+ create?: LabelCreation;
43
+ /** Whether two names are the same label; trackers compare them ignoring case. */
44
+ same?: (a: string, b: string) => boolean;
45
+ }
46
+
47
+ const ignoringCase = (a: string, b: string) => a.toLowerCase() === b.toLowerCase();
48
+
49
+ /**
50
+ * A searchable checklist of labels, with each label's colour and description. It holds only its search text: the
51
+ * caller gives the choices, what is picked and what a toggle does, so it reads and writes whichever way its surface
52
+ * needs. With `create`, Enter or the create button lets a typed name no choice has in.
53
+ */
54
+ export function LabelPicker({
55
+ choices,
56
+ picked,
57
+ onToggle,
58
+ filterLabel,
59
+ placeholder,
60
+ noMatchLabel,
61
+ locked = [],
62
+ lockedNote,
63
+ busy = false,
64
+ create,
65
+ same = ignoringCase,
66
+ }: LabelPickerProps) {
67
+ const [query, setQuery] = useState('');
68
+ const typed = query.trim();
69
+ const hits = choices.filter(({ name }) => name.toLowerCase().includes(typed.toLowerCase()));
70
+ const fresh = !!create && !!typed && !choices.some(({ name }) => same(name, typed));
71
+ const refusal = fresh ? (create.refused ?? create.problem(typed)) : null;
72
+ const add = () => {
73
+ create?.onCreate(typed);
74
+ setQuery('');
75
+ };
76
+
77
+ return (
78
+ <div className="grid w-72 gap-2" data-label-picker aria-busy={busy}>
79
+ <Input
80
+ autoFocus
81
+ placeholder={placeholder}
82
+ aria-label={filterLabel}
83
+ value={query}
84
+ onChange={(e) => setQuery(e.target.value)}
85
+ onKeyDown={(e) => {
86
+ if (e.key !== 'Enter') return;
87
+
88
+ e.preventDefault();
89
+
90
+ if (fresh && !refusal) add();
91
+ }}
92
+ />
93
+ <div className="grid max-h-56 gap-0.5 overflow-y-auto">
94
+ {hits.map(({ name, color, description }) => {
95
+ const fixed = locked.some((label) => same(label, name));
96
+
97
+ return (
98
+ <label
99
+ key={name}
100
+ data-label-choice
101
+ className="hover:bg-base-200 flex cursor-pointer items-start gap-2 rounded-md px-1.5 py-1 text-xs"
102
+ >
103
+ <Checkbox
104
+ checked={picked.some((label) => same(label, name))}
105
+ disabled={fixed}
106
+ onChange={() => onToggle(name)}
107
+ />
108
+ {color && (
109
+ <span data-label-dot className="mt-1 flex">
110
+ <StatusIndicator tone="neutral" labelColor={color} />
111
+ </span>
112
+ )}
113
+ <span className="min-w-0 flex-1">
114
+ <span className="block wrap-anywhere">
115
+ {name}
116
+ {fixed && lockedNote && <span className="text-base-700"> · {lockedNote}</span>}
117
+ </span>
118
+ {description && <span className="text-base-700 block text-[11px]">{description}</span>}
119
+ </span>
120
+ </label>
121
+ );
122
+ })}
123
+ {!hits.length && !fresh && <p className="text-base-700 m-0 p-1.5 text-[11px]">{noMatchLabel}</p>}
124
+ </div>
125
+ {fresh && !refusal && (
126
+ <span className="justify-self-start">
127
+ <Button variant="secondary" compact leftIcon={<PlusIcon />} onClick={add}>
128
+ {create.createLabel(typed)}
129
+ </Button>
130
+ </span>
131
+ )}
132
+ {refusal && (
133
+ <p role="alert" className="text-base-700 m-0 text-[11px]">
134
+ {refusal}
135
+ </p>
136
+ )}
137
+ {create && !create.refused && <p className="text-base-700 m-0 text-[11px]">{create.note}</p>}
138
+ </div>
139
+ );
140
+ }