@sincpro/web-ui 0.0.0-stage → 0.1.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 (354) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -2
  3. package/dist/components/assist/AssistMark.d.ts +64 -0
  4. package/dist/components/assist/AssistMark.js +108 -0
  5. package/dist/components/assist/AssistPending.d.ts +24 -0
  6. package/dist/components/assist/AssistPending.js +26 -0
  7. package/dist/components/assist/AssistSuggestion.d.ts +42 -0
  8. package/dist/components/assist/AssistSuggestion.js +55 -0
  9. package/dist/components/assist/ChangeReview.d.ts +56 -0
  10. package/dist/components/assist/ChangeReview.js +121 -0
  11. package/dist/components/assist/assist.d.ts +77 -0
  12. package/dist/components/assist/assist.js +87 -0
  13. package/dist/components/assist/index.d.ts +5 -0
  14. package/dist/components/assist/index.js +7 -0
  15. package/dist/components/chatter/Chatter.d.ts +161 -0
  16. package/dist/components/chatter/Chatter.js +232 -0
  17. package/dist/components/chatter/Messages.d.ts +66 -0
  18. package/dist/components/chatter/Messages.js +43 -0
  19. package/dist/components/chatter/Timeline.d.ts +21 -0
  20. package/dist/components/chatter/Timeline.js +30 -0
  21. package/dist/components/chatter/index.d.ts +3 -0
  22. package/dist/components/chatter/index.js +4 -0
  23. package/dist/components/form/Form.d.ts +52 -0
  24. package/dist/components/form/Form.js +49 -0
  25. package/dist/components/form/Sheet.d.ts +43 -0
  26. package/dist/components/form/Sheet.js +83 -0
  27. package/dist/components/form/index.d.ts +2 -0
  28. package/dist/components/form/index.js +4 -0
  29. package/dist/components/frame/Chart.d.ts +13 -0
  30. package/dist/components/frame/Chart.js +24 -0
  31. package/dist/components/frame/FrameView.d.ts +33 -0
  32. package/dist/components/frame/FrameView.js +125 -0
  33. package/dist/components/frame/index.d.ts +3 -0
  34. package/dist/components/frame/index.js +3 -0
  35. package/dist/components/frame/model.d.ts +43 -0
  36. package/dist/components/frame/model.js +101 -0
  37. package/dist/components/index.d.ts +10 -0
  38. package/dist/components/index.js +11 -0
  39. package/dist/components/lines/LineItems.d.ts +74 -0
  40. package/dist/components/lines/LineItems.js +229 -0
  41. package/dist/components/lines/index.d.ts +1 -0
  42. package/dist/components/lines/index.js +2 -0
  43. package/dist/components/lines/lines.d.ts +23 -0
  44. package/dist/components/lines/lines.js +26 -0
  45. package/dist/components/listing/ColumnsMenu.d.ts +20 -0
  46. package/dist/components/listing/ColumnsMenu.js +46 -0
  47. package/dist/components/listing/GroupingLevels.d.ts +35 -0
  48. package/dist/components/listing/GroupingLevels.js +45 -0
  49. package/dist/components/listing/Listing.d.ts +27 -0
  50. package/dist/components/listing/Listing.js +86 -0
  51. package/dist/components/listing/ListingBar.d.ts +73 -0
  52. package/dist/components/listing/ListingBar.js +105 -0
  53. package/dist/components/listing/Pagination.d.ts +36 -0
  54. package/dist/components/listing/Pagination.js +69 -0
  55. package/dist/components/listing/RecordCard.d.ts +33 -0
  56. package/dist/components/listing/RecordCard.js +22 -0
  57. package/dist/components/listing/RecordLink.d.ts +7 -0
  58. package/dist/components/listing/RecordLink.js +22 -0
  59. package/dist/components/listing/Row.d.ts +106 -0
  60. package/dist/components/listing/Row.js +122 -0
  61. package/dist/components/listing/SortMenu.d.ts +62 -0
  62. package/dist/components/listing/SortMenu.js +105 -0
  63. package/dist/components/listing/ViewSwitcher.d.ts +24 -0
  64. package/dist/components/listing/ViewSwitcher.js +16 -0
  65. package/dist/components/listing/columns.d.ts +47 -0
  66. package/dist/components/listing/columns.js +72 -0
  67. package/dist/components/listing/grouping.d.ts +35 -0
  68. package/dist/components/listing/grouping.js +47 -0
  69. package/dist/components/listing/index.d.ts +17 -0
  70. package/dist/components/listing/index.js +20 -0
  71. package/dist/components/listing/modelColumns.d.ts +50 -0
  72. package/dist/components/listing/modelColumns.js +103 -0
  73. package/dist/components/listing/pages.d.ts +78 -0
  74. package/dist/components/listing/pages.js +135 -0
  75. package/dist/components/listing/selection.d.ts +21 -0
  76. package/dist/components/listing/selection.js +43 -0
  77. package/dist/components/listing/set.d.ts +48 -0
  78. package/dist/components/listing/set.js +24 -0
  79. package/dist/components/listing/tree.d.ts +28 -0
  80. package/dist/components/listing/tree.js +23 -0
  81. package/dist/components/navigation/CommandPalette.d.ts +40 -0
  82. package/dist/components/navigation/CommandPalette.js +114 -0
  83. package/dist/components/navigation/NavigationTree.d.ts +31 -0
  84. package/dist/components/navigation/NavigationTree.js +79 -0
  85. package/dist/components/navigation/commands.d.ts +30 -0
  86. package/dist/components/navigation/commands.js +30 -0
  87. package/dist/components/navigation/index.d.ts +3 -0
  88. package/dist/components/navigation/index.js +4 -0
  89. package/dist/components/record/Breadcrumb.d.ts +42 -0
  90. package/dist/components/record/Breadcrumb.js +98 -0
  91. package/dist/components/record/DirtyActions.d.ts +26 -0
  92. package/dist/components/record/DirtyActions.js +22 -0
  93. package/dist/components/record/Pager.d.ts +18 -0
  94. package/dist/components/record/Pager.js +33 -0
  95. package/dist/components/record/RecordHeader.d.ts +71 -0
  96. package/dist/components/record/RecordHeader.js +72 -0
  97. package/dist/components/record/RecordSkeleton.d.ts +3 -0
  98. package/dist/components/record/RecordSkeleton.js +21 -0
  99. package/dist/components/record/SmartButtons.d.ts +35 -0
  100. package/dist/components/record/SmartButtons.js +38 -0
  101. package/dist/components/record/StatusBar.d.ts +51 -0
  102. package/dist/components/record/StatusBar.js +168 -0
  103. package/dist/components/record/ThumbBar.d.ts +36 -0
  104. package/dist/components/record/ThumbBar.js +86 -0
  105. package/dist/components/record/index.d.ts +6 -0
  106. package/dist/components/record/index.js +8 -0
  107. package/dist/components/search/CriteriaBuilder.d.ts +58 -0
  108. package/dist/components/search/CriteriaBuilder.js +278 -0
  109. package/dist/components/search/CriteriaChips.d.ts +22 -0
  110. package/dist/components/search/CriteriaChips.js +64 -0
  111. package/dist/components/search/FilterBuilder.d.ts +142 -0
  112. package/dist/components/search/FilterBuilder.js +421 -0
  113. package/dist/components/search/JoinWord.d.ts +8 -0
  114. package/dist/components/search/JoinWord.js +47 -0
  115. package/dist/components/search/JsonEditor.d.ts +19 -0
  116. package/dist/components/search/JsonEditor.js +78 -0
  117. package/dist/components/search/SearchBar.d.ts +78 -0
  118. package/dist/components/search/SearchBar.js +387 -0
  119. package/dist/components/search/criteria.d.ts +116 -0
  120. package/dist/components/search/criteria.js +159 -0
  121. package/dist/components/search/datePeriods.d.ts +122 -0
  122. package/dist/components/search/datePeriods.js +134 -0
  123. package/dist/components/search/dateRanges.d.ts +54 -0
  124. package/dist/components/search/dateRanges.js +89 -0
  125. package/dist/components/search/index.d.ts +9 -0
  126. package/dist/components/search/index.js +20 -0
  127. package/dist/components/search/model.d.ts +54 -0
  128. package/dist/components/search/model.js +37 -0
  129. package/dist/components/search/query.d.ts +166 -0
  130. package/dist/components/search/query.js +436 -0
  131. package/dist/components/shell/AppFrame.d.ts +14 -0
  132. package/dist/components/shell/AppFrame.js +80 -0
  133. package/dist/components/shell/TopBar.d.ts +73 -0
  134. package/dist/components/shell/TopBar.js +54 -0
  135. package/dist/components/shell/headerSlot.d.ts +17 -0
  136. package/dist/components/shell/headerSlot.js +21 -0
  137. package/dist/components/shell/index.d.ts +2 -0
  138. package/dist/components/shell/index.js +3 -0
  139. package/dist/foundations/aria.d.ts +14 -0
  140. package/dist/foundations/aria.js +18 -0
  141. package/dist/foundations/calendar.d.ts +28 -0
  142. package/dist/foundations/calendar.js +183 -0
  143. package/dist/foundations/copy.d.ts +619 -0
  144. package/dist/foundations/copy.es.d.ts +2 -0
  145. package/dist/foundations/copy.es.js +377 -0
  146. package/dist/foundations/copy.js +472 -0
  147. package/dist/foundations/density.d.ts +16 -0
  148. package/dist/foundations/density.js +21 -0
  149. package/dist/foundations/grouping.d.ts +281 -0
  150. package/dist/foundations/grouping.js +0 -0
  151. package/dist/foundations/index.d.ts +14 -0
  152. package/dist/foundations/index.js +30 -0
  153. package/dist/foundations/layout.d.ts +9 -0
  154. package/dist/foundations/layout.js +9 -0
  155. package/dist/foundations/pivot.d.ts +222 -0
  156. package/dist/foundations/pivot.js +198 -0
  157. package/dist/foundations/pivotSheet.d.ts +61 -0
  158. package/dist/foundations/pivotSheet.js +0 -0
  159. package/dist/foundations/slots.d.ts +67 -0
  160. package/dist/foundations/slots.js +63 -0
  161. package/dist/foundations/sticky.d.ts +53 -0
  162. package/dist/foundations/sticky.js +150 -0
  163. package/dist/foundations/surface.d.ts +9 -0
  164. package/dist/foundations/surface.js +26 -0
  165. package/dist/foundations/theme.d.ts +42 -0
  166. package/dist/foundations/theme.js +88 -0
  167. package/dist/foundations/types.d.ts +23 -0
  168. package/dist/foundations/types.js +50 -0
  169. package/dist/foundations/variants.d.ts +19 -0
  170. package/dist/foundations/variants.js +48 -0
  171. package/dist/foundations/xlsx.d.ts +86 -0
  172. package/dist/foundations/xlsx.js +0 -0
  173. package/dist/index.d.ts +19 -0
  174. package/dist/index.js +19 -0
  175. package/dist/primitives/block/Block.d.ts +66 -0
  176. package/dist/primitives/block/Block.js +34 -0
  177. package/dist/primitives/block/Line.d.ts +18 -0
  178. package/dist/primitives/block/Line.js +45 -0
  179. package/dist/primitives/block/SectionGrid.d.ts +30 -0
  180. package/dist/primitives/block/SectionGrid.js +29 -0
  181. package/dist/primitives/block/Steps.d.ts +76 -0
  182. package/dist/primitives/block/Steps.js +43 -0
  183. package/dist/primitives/block/index.d.ts +4 -0
  184. package/dist/primitives/block/index.js +7 -0
  185. package/dist/primitives/button/Button.d.ts +137 -0
  186. package/dist/primitives/button/Button.js +234 -0
  187. package/dist/primitives/button/index.d.ts +1 -0
  188. package/dist/primitives/button/index.js +2 -0
  189. package/dist/primitives/cell/CellStack.d.ts +47 -0
  190. package/dist/primitives/cell/CellStack.js +33 -0
  191. package/dist/primitives/cell/index.d.ts +1 -0
  192. package/dist/primitives/cell/index.js +3 -0
  193. package/dist/primitives/choice/ComboBox.d.ts +16 -0
  194. package/dist/primitives/choice/ComboBox.js +52 -0
  195. package/dist/primitives/choice/RadioGroup.d.ts +18 -0
  196. package/dist/primitives/choice/RadioGroup.js +50 -0
  197. package/dist/primitives/choice/SearchField.d.ts +24 -0
  198. package/dist/primitives/choice/SearchField.js +27 -0
  199. package/dist/primitives/choice/SegmentedControl.d.ts +16 -0
  200. package/dist/primitives/choice/SegmentedControl.js +69 -0
  201. package/dist/primitives/choice/Slider.d.ts +16 -0
  202. package/dist/primitives/choice/Slider.js +46 -0
  203. package/dist/primitives/choice/Switch.d.ts +14 -0
  204. package/dist/primitives/choice/Switch.js +65 -0
  205. package/dist/primitives/choice/ThemeSwitcher.d.ts +10 -0
  206. package/dist/primitives/choice/ThemeSwitcher.js +25 -0
  207. package/dist/primitives/choice/index.d.ts +8 -0
  208. package/dist/primitives/choice/index.js +11 -0
  209. package/dist/primitives/choice/options.d.ts +28 -0
  210. package/dist/primitives/choice/options.js +6 -0
  211. package/dist/primitives/date/Calendar.d.ts +31 -0
  212. package/dist/primitives/date/Calendar.js +110 -0
  213. package/dist/primitives/date/DatePicker.d.ts +34 -0
  214. package/dist/primitives/date/DatePicker.js +99 -0
  215. package/dist/primitives/date/index.d.ts +2 -0
  216. package/dist/primitives/date/index.js +4 -0
  217. package/dist/primitives/dialog/Dialog.d.ts +118 -0
  218. package/dist/primitives/dialog/Dialog.js +121 -0
  219. package/dist/primitives/dialog/index.d.ts +1 -0
  220. package/dist/primitives/dialog/index.js +2 -0
  221. package/dist/primitives/disclosure/Disclosure.d.ts +36 -0
  222. package/dist/primitives/disclosure/Disclosure.js +52 -0
  223. package/dist/primitives/disclosure/index.d.ts +1 -0
  224. package/dist/primitives/disclosure/index.js +2 -0
  225. package/dist/primitives/field/Checks.d.ts +57 -0
  226. package/dist/primitives/field/Checks.js +72 -0
  227. package/dist/primitives/field/DropZone.d.ts +42 -0
  228. package/dist/primitives/field/DropZone.js +52 -0
  229. package/dist/primitives/field/Field.d.ts +166 -0
  230. package/dist/primitives/field/Field.js +240 -0
  231. package/dist/primitives/field/Suggest.d.ts +48 -0
  232. package/dist/primitives/field/Suggest.js +43 -0
  233. package/dist/primitives/field/aria.d.ts +61 -0
  234. package/dist/primitives/field/aria.js +70 -0
  235. package/dist/primitives/field/context.d.ts +30 -0
  236. package/dist/primitives/field/context.js +13 -0
  237. package/dist/primitives/field/index.d.ts +7 -0
  238. package/dist/primitives/field/index.js +11 -0
  239. package/dist/primitives/field/layout.d.ts +24 -0
  240. package/dist/primitives/field/layout.js +10 -0
  241. package/dist/primitives/field/mode.d.ts +9 -0
  242. package/dist/primitives/field/mode.js +13 -0
  243. package/dist/primitives/hint/Hint.d.ts +31 -0
  244. package/dist/primitives/hint/Hint.js +42 -0
  245. package/dist/primitives/hint/index.d.ts +1 -0
  246. package/dist/primitives/hint/index.js +1 -0
  247. package/dist/primitives/index.d.ts +19 -0
  248. package/dist/primitives/index.js +20 -0
  249. package/dist/primitives/menu/Menu.d.ts +100 -0
  250. package/dist/primitives/menu/Menu.js +59 -0
  251. package/dist/primitives/menu/index.d.ts +1 -0
  252. package/dist/primitives/menu/index.js +2 -0
  253. package/dist/primitives/notice/Callout.d.ts +49 -0
  254. package/dist/primitives/notice/Callout.js +28 -0
  255. package/dist/primitives/notice/Notice.d.ts +45 -0
  256. package/dist/primitives/notice/Notice.js +61 -0
  257. package/dist/primitives/notice/Toast.d.ts +55 -0
  258. package/dist/primitives/notice/Toast.js +142 -0
  259. package/dist/primitives/notice/index.d.ts +3 -0
  260. package/dist/primitives/notice/index.js +5 -0
  261. package/dist/primitives/overlay/Popover.d.ts +6 -0
  262. package/dist/primitives/overlay/Popover.js +27 -0
  263. package/dist/primitives/overlay/PopoverTrigger.d.ts +55 -0
  264. package/dist/primitives/overlay/PopoverTrigger.js +38 -0
  265. package/dist/primitives/overlay/Tooltip.d.ts +38 -0
  266. package/dist/primitives/overlay/Tooltip.js +74 -0
  267. package/dist/primitives/overlay/index.d.ts +3 -0
  268. package/dist/primitives/overlay/index.js +5 -0
  269. package/dist/primitives/picker/Picker.d.ts +48 -0
  270. package/dist/primitives/picker/Picker.js +150 -0
  271. package/dist/primitives/picker/index.d.ts +1 -0
  272. package/dist/primitives/picker/index.js +2 -0
  273. package/dist/primitives/surface/Peek.d.ts +60 -0
  274. package/dist/primitives/surface/Peek.js +71 -0
  275. package/dist/primitives/surface/index.d.ts +1 -0
  276. package/dist/primitives/surface/index.js +2 -0
  277. package/dist/primitives/table/CellEditor.d.ts +14 -0
  278. package/dist/primitives/table/CellEditor.js +83 -0
  279. package/dist/primitives/table/DataTable.d.ts +135 -0
  280. package/dist/primitives/table/DataTable.js +244 -0
  281. package/dist/primitives/table/EditableTable.d.ts +96 -0
  282. package/dist/primitives/table/EditableTable.js +423 -0
  283. package/dist/primitives/table/PivotTable.d.ts +14 -0
  284. package/dist/primitives/table/PivotTable.js +0 -0
  285. package/dist/primitives/table/ReportTable.d.ts +38 -0
  286. package/dist/primitives/table/ReportTable.js +106 -0
  287. package/dist/primitives/table/RowActions.d.ts +16 -0
  288. package/dist/primitives/table/RowActions.js +28 -0
  289. package/dist/primitives/table/SheetTable.d.ts +70 -0
  290. package/dist/primitives/table/SheetTable.js +197 -0
  291. package/dist/primitives/table/Table.d.ts +106 -0
  292. package/dist/primitives/table/Table.js +116 -0
  293. package/dist/primitives/table/index.d.ts +7 -0
  294. package/dist/primitives/table/index.js +9 -0
  295. package/dist/primitives/tabs/Tabs.d.ts +91 -0
  296. package/dist/primitives/tabs/Tabs.js +56 -0
  297. package/dist/primitives/tabs/index.d.ts +1 -0
  298. package/dist/primitives/tabs/index.js +1 -0
  299. package/dist/primitives/tag/Tag.d.ts +36 -0
  300. package/dist/primitives/tag/Tag.js +68 -0
  301. package/dist/primitives/tag/Tags.d.ts +57 -0
  302. package/dist/primitives/tag/Tags.js +104 -0
  303. package/dist/primitives/tag/TagsField.d.ts +32 -0
  304. package/dist/primitives/tag/TagsField.js +182 -0
  305. package/dist/primitives/tag/choices.d.ts +38 -0
  306. package/dist/primitives/tag/choices.js +26 -0
  307. package/dist/primitives/tag/index.d.ts +4 -0
  308. package/dist/primitives/tag/index.js +8 -0
  309. package/dist/primitives/toolbar/Toolbar.d.ts +13 -0
  310. package/dist/primitives/toolbar/Toolbar.js +36 -0
  311. package/dist/primitives/toolbar/index.d.ts +1 -0
  312. package/dist/primitives/toolbar/index.js +2 -0
  313. package/dist/primitives/value/Copyable.d.ts +37 -0
  314. package/dist/primitives/value/Copyable.js +50 -0
  315. package/dist/primitives/value/Fecha.d.ts +12 -0
  316. package/dist/primitives/value/Fecha.js +80 -0
  317. package/dist/primitives/value/Meter.d.ts +29 -0
  318. package/dist/primitives/value/Meter.js +46 -0
  319. package/dist/primitives/value/Metric.d.ts +42 -0
  320. package/dist/primitives/value/Metric.js +38 -0
  321. package/dist/primitives/value/Person.d.ts +90 -0
  322. package/dist/primitives/value/Person.js +208 -0
  323. package/dist/primitives/value/Relation.d.ts +69 -0
  324. package/dist/primitives/value/Relation.js +57 -0
  325. package/dist/primitives/value/Totals.d.ts +26 -0
  326. package/dist/primitives/value/Totals.js +4 -0
  327. package/dist/primitives/value/Value.d.ts +77 -0
  328. package/dist/primitives/value/Value.js +119 -0
  329. package/dist/primitives/value/index.d.ts +8 -0
  330. package/dist/primitives/value/index.js +9 -0
  331. package/dist/tokens.css +1025 -0
  332. package/dist/views/index.d.ts +2 -0
  333. package/dist/views/index.js +3 -0
  334. package/dist/views/list/List.d.ts +113 -0
  335. package/dist/views/list/List.js +97 -0
  336. package/dist/views/list/ListView.d.ts +6 -0
  337. package/dist/views/list/ListView.js +269 -0
  338. package/dist/views/list/columns.d.ts +18 -0
  339. package/dist/views/list/columns.js +27 -0
  340. package/dist/views/list/derive.d.ts +76 -0
  341. package/dist/views/list/derive.js +181 -0
  342. package/dist/views/list/index.d.ts +2 -0
  343. package/dist/views/list/index.js +4 -0
  344. package/dist/views/list/presentations.d.ts +39 -0
  345. package/dist/views/list/presentations.js +103 -0
  346. package/dist/views/list/types.d.ts +272 -0
  347. package/dist/views/list/types.js +1 -0
  348. package/dist/views/list/useNarrowList.d.ts +17 -0
  349. package/dist/views/list/useNarrowList.js +32 -0
  350. package/dist/views/record/Record.d.ts +126 -0
  351. package/dist/views/record/Record.js +141 -0
  352. package/dist/views/record/index.d.ts +1 -0
  353. package/dist/views/record/index.js +2 -0
  354. package/package.json +109 -4
@@ -0,0 +1,59 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { buttonClasses, } from "../button/Button.js";
5
+ import { Popover } from "../overlay/Popover.js";
6
+ import { Check, ChevronDown } from "lucide-react";
7
+ import { Autocomplete, Button as AriaButton, Header, Input, Menu as AriaMenu, MenuItem, MenuSection, MenuTrigger, SearchField, Separator, useFilter, } from "react-aria-components";
8
+ const WIDTH = {
9
+ /** A menu of short verbs hanging off an icon: "duplicate", "delete". */
10
+ sm: "min-w-44 max-w-64",
11
+ md: "min-w-56 max-w-80",
12
+ lg: "min-w-80 max-w-(--container-short)",
13
+ };
14
+ export function Menu({ label, groups, onSelect, surface = "canvas", align = "start", icon: Trigger,
15
+ /** The menu as a control of a list ("filters ▾") carries the chevron; the
16
+ * actions `⋯` does not, because the icon already says there is more. */
17
+ chevron = true, active = false, iconOnly = false, variant = "button", searchPlaceholder, emptyText, width = "md", role, size = "md", title, closeOnSelect = true, }) {
18
+ const copy = useUiCopy();
19
+ const { contains } = useFilter({ sensitivity: "base" });
20
+ /* `text` is the menu of a BAR: no frame and no background, just the highlight
21
+ on hover. Five bordered menus on top of an application are five buttons
22
+ competing with the ones in the view, and the bar stops reading as an index.
23
+ `button` is the menu of a CONTROL —"filters ▾", "group ▾"—, and there the
24
+ frame is what says it can be pressed. */
25
+ const triggerClass = (open) => variant === "text"
26
+ ? surface === "chrome"
27
+ ? `border-transparent text-chrome-muted hover:bg-chrome-ink/12 hover:text-chrome-ink ${active || open ? "bg-chrome-ink/12 text-chrome-ink" : ""}`
28
+ : `border-transparent text-muted hover:bg-ink/6 hover:text-ink ${active || open ? "bg-ink/6 text-ink" : ""}`
29
+ : surface === "chrome"
30
+ ? `border-chrome-muted/35 bg-chrome-raised text-chrome-ink hover:border-chrome-accent/45 ${active ? "border-chrome-accent/60" : ""}`
31
+ : `border-line-strong bg-surface hover:border-primary/45 hover:bg-primary/5 ${active ? "border-primary/60 text-primary" : "text-ink"}`;
32
+ const hasMarks = groups.some((g) => g.options.some((o) => o.checked !== undefined || o.icon !== undefined));
33
+ const hasHints = groups.some((g) => g.options.some((o) => o.hint !== undefined));
34
+ const byId = new Map(groups.flatMap((g) => g.options.map((o) => [o.id, o])));
35
+ const menu = (_jsx(AriaMenu, { "aria-label": label, shouldCloseOnSelect: closeOnSelect, onAction: (key) => {
36
+ const option = byId.get(String(key));
37
+ option?.onSelect?.();
38
+ onSelect?.(String(key));
39
+ }, renderEmptyState: () => (_jsx("p", { className: "px-3 py-3 text-meta text-muted italic", children: emptyText ?? copy.menu.empty })), className: "max-h-[inherit] overflow-y-auto p-1 outline-0", children: groups.map((g, gi) => (_jsxs(MenuSection, { className: "[&:not(:first-child)]:mt-1 [&:not(:first-child)]:border-t [&:not(:first-child)]:border-line [&:not(:first-child)]:pt-1", children: [g.label === undefined ? null : (
40
+ /* Same skeleton as the row: the label sits on the text column. */
41
+ _jsxs(Header, { className: "flex items-center gap-2 px-2 pt-1 pb-0.5 text-micro font-medium text-muted", children: [hasMarks ? (_jsx("span", { className: "size-(--size-icon-sm) shrink-0", "aria-hidden": true })) : null, g.label] })), g.options.flatMap((o) => {
42
+ const Icon = o.icon;
43
+ const item = (_jsxs(MenuItem, { id: o.id, textValue: o.hint === undefined ? o.label : `${o.label} ${o.hint}`, isDisabled: o.disabled, className: `flex w-full cursor-default rounded-control ${hasHints ? "items-start py-1.5" : "items-center py-1"} gap-2 px-2 text-left outline-0 transition-colors duration-(--duration-instant) data-focused:bg-primary/8 disabled:opacity-45`, children: [hasMarks ? (_jsx("span", { className: `${hasHints ? "mt-0.5" : ""} flex size-(--size-icon-sm) shrink-0 items-center justify-center`, children: o.checked === true ? (_jsx(Check, { className: "size-(--size-icon-sm) text-primary", strokeWidth: 2.5, "aria-hidden": true })) : Icon !== undefined ? (_jsx(Icon, { className: "size-(--size-icon-sm) text-muted", strokeWidth: 2, "aria-hidden": true })) : null })) : null, _jsxs("span", { className: "min-w-0 flex-1", children: [_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: `truncate text-meta ${o.checked === true ? "font-medium text-ink" : "text-ink"}`, children: o.label }), o.badge === undefined ? null : typeof o.badge === "string" ? (_jsx("span", { className: "inline-flex shrink-0 items-center rounded-pill border border-warn/30 bg-warn/10 px-2 py-0.5 text-micro text-warn", children: o.badge })) : (_jsx("span", { className: "ml-auto shrink-0", children: o.badge }))] }), o.hint === undefined ? null : (_jsx("span", { className: "mt-0.5 block truncate text-micro text-muted", children: o.hint }))] })] }, o.id));
44
+ return o.divider === true
45
+ ? [
46
+ _jsx(Separator, { className: "my-1 h-px border-0 bg-line" }, `${o.id}-divider`),
47
+ item,
48
+ ]
49
+ : [item];
50
+ })] }, g.label ?? gi))) }));
51
+ return (_jsxs(MenuTrigger, { children: [_jsx(AriaButton, { "aria-label": iconOnly ? label : undefined, ...(role === undefined ? {} : { "data-emphasis": role }), className: ({ isPressed, isFocusVisible }) => role === undefined
52
+ ? `inline-flex h-(--size-control-sm) shrink-0 cursor-default items-center gap-1.5 border text-micro font-medium whitespace-nowrap outline-0 transition-colors duration-(--duration-instant) ${isFocusVisible ? "outline-2 outline-offset-2 outline-primary" : ""} ${iconOnly ? "w-(--size-control-sm) justify-center rounded-pill" : "rounded-control px-2.5"} ${triggerClass(isPressed)}`
53
+ : `${buttonClasses(role, size, surface)} gap-1.5`, children: _jsxs("span", { className: "contents", title: title ?? (iconOnly ? label : undefined), children: [Trigger === undefined ? (iconOnly ? (_jsx(ChevronDown, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true })) : null) : (_jsx(Trigger, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true })), iconOnly ? null : label, chevron && !iconOnly ? (_jsx(ChevronDown, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true })) : null] }) }), _jsx(Popover, { placement: align === "end" ? "bottom end" : "bottom start", className: WIDTH[width], children: searchPlaceholder === undefined ? (menu) : (
54
+ /* The search box stays at the top: in a menu of twenty-five entries,
55
+ scrolling loses exactly the control that explains why only three are
56
+ visible. It filters on label AND hint — half the entries of this
57
+ product are told apart only by the hint. */
58
+ _jsxs(Autocomplete, { filter: contains, children: [_jsx(SearchField, { "aria-label": searchPlaceholder, autoFocus: true, className: "border-b border-line px-2 pt-2 pb-2", children: _jsx(Input, { placeholder: searchPlaceholder, className: "h-(--size-control-sm) w-full rounded-control border border-line-strong bg-surface px-2.5 text-meta text-ink outline-0 placeholder:text-muted focus-visible:border-primary" }) }), menu] })) })] }));
59
+ }
@@ -0,0 +1 @@
1
+ export { Menu, type MenuGroup, type MenuOption } from "./Menu.js";
@@ -0,0 +1,2 @@
1
+ /* A data-driven dropdown: the actions `⋯` and "+ Create". */
2
+ export { Menu } from "./Menu.js";
@@ -0,0 +1,49 @@
1
+ import type { Tone } from "../../foundations/types.js";
2
+ import type { LucideIcon } from "lucide-react";
3
+ import type { ReactNode } from "react";
4
+ /**
5
+ * UN AVISO sobre lo que se está mirando.
6
+ *
7
+ * Context: `Empty`, `Failed` y `Missing` dicen que NO HAY contenido y ocupan el
8
+ * lugar de la vista. Esto es lo contrario: hay contenido, se ve, y hace falta
9
+ * decir algo ADEMÁS —«hay asientos sin registrar en este período», «este período
10
+ * está cerrado», «se guardó»—. Sin esta pieza, cada pantalla se lo inventaba
11
+ * con un `div` y un color a ojo, y así es como el mismo grado de urgencia
12
+ * terminaba pintado de tres maneras.
13
+ *
14
+ * ── El tono es la ÚNICA decisión ────────────────────────────────────
15
+ *
16
+ * info un dato que cambia cómo se lee lo de abajo
17
+ * warn algo que conviene mirar antes de seguir
18
+ * bad algo que está mal y ya tuvo consecuencias
19
+ * good algo salió bien y no hay nada que hacer
20
+ *
21
+ * El ícono sale del tono y no se elige: un aviso rojo con un tilde verde adentro
22
+ * es una contradicción que alguien va a escribir si se lo dejamos escribir.
23
+ *
24
+ * ── Lo que NO hace ──────────────────────────────────────────────────
25
+ *
26
+ * No se cierra solo, no tiene temporizador y no se apila. Un aviso que
27
+ * desaparece a los cuatro segundos es un aviso que alguien no llegó a leer — eso
28
+ * es un `Toast`, que es otra pieza y otra pregunta («pasó algo lejos de acá»).
29
+ * Éste habla de lo que está en pantalla, así que se queda mientras sea cierto.
30
+ */
31
+ export type CalloutProps = {
32
+ /** Qué pasa. Una oración, en la voz del producto. */
33
+ children: ReactNode;
34
+ /** Sin esto, `info`. */
35
+ tone?: Extract<Tone, "info" | "warn" | "bad" | "good">;
36
+ /** El encabezado, cuando la oración sola no alcanza. La mayoría no lo
37
+ * necesita: un aviso de dos renglones con título es un cartel. */
38
+ title?: ReactNode;
39
+ /** Lo que resuelve el aviso, si hay algo que apretar. */
40
+ action?: ReactNode;
41
+ /** Presente = se puede cerrar. Ausente = no, porque lo que dice sigue siendo
42
+ * cierto lo mire quien lo mire. */
43
+ onDismiss?: () => void;
44
+ /** El rótulo accesible de la ×. */
45
+ dismissLabel?: string;
46
+ /** Un ícono propio, cuando el del tono no dice lo suficiente. */
47
+ icon?: LucideIcon;
48
+ };
49
+ export declare function Callout({ children, tone, title, action, onDismiss, dismissLabel, icon, }: CalloutProps): import("react").JSX.Element;
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { byDensity, useDensity } from "../../foundations/density.js";
4
+ import { AlertTriangle, CheckCircle2, Info, OctagonAlert, X } from "lucide-react";
5
+ const ICON = {
6
+ info: Info,
7
+ warn: AlertTriangle,
8
+ bad: OctagonAlert,
9
+ good: CheckCircle2,
10
+ };
11
+ /* El fondo es una capa del color sobre el papel y no un color propio: así el
12
+ aviso se lee sobre el piso y sobre el papel blanco sin dos juegos de tokens, y
13
+ sigue funcionando el día que cambie la paleta. */
14
+ const SKIN = {
15
+ info: "border-info/25 bg-info/8 text-info",
16
+ warn: "border-warn/30 bg-warn/10 text-warn",
17
+ bad: "border-bad/25 bg-bad/8 text-bad",
18
+ good: "border-good/25 bg-good/8 text-good",
19
+ };
20
+ export function Callout({ children, tone = "info", title, action, onDismiss, dismissLabel, icon, }) {
21
+ const d = useDensity();
22
+ const Icon = icon ?? ICON[tone];
23
+ return (_jsxs("div", {
24
+ /* `status` y no `alert`: `alert` interrumpe al lector de pantalla y se
25
+ reserva para lo que pasó JUSTO AHORA. Un aviso que ya estaba cuando la
26
+ pantalla abrió no interrumpe nada. */
27
+ role: "status", className: `flex min-w-0 items-start gap-2.5 rounded-box border ${SKIN[tone]} ${byDensity(d, "px-3 py-2.5", "px-2.5 py-2")}`, children: [_jsx(Icon, { className: "mt-0.5 size-(--size-icon-sm) shrink-0", strokeWidth: 2, "aria-hidden": true }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [title === undefined ? null : (_jsx("p", { className: "text-meta font-medium text-ink", children: title })), _jsx("div", { className: "min-w-0 text-meta text-ink", children: children })] }), action === undefined ? null : _jsx("span", { className: "shrink-0", children: action }), onDismiss === undefined ? null : (_jsx("button", { type: "button", onClick: onDismiss, "aria-label": dismissLabel ?? "cerrar", title: dismissLabel ?? "cerrar", className: "-mr-1 shrink-0 rounded-control p-0.5 text-muted transition-colors duration-(--duration-instant) hover:bg-ink/6 hover:text-ink", children: _jsx(X, { className: "size-(--size-icon-sm)", strokeWidth: 2 }) }))] }));
28
+ }
@@ -0,0 +1,45 @@
1
+ import { type LucideIcon } from "lucide-react";
2
+ import type { ReactNode } from "react";
3
+ export declare function Skeleton({ rows, label, }: {
4
+ /** How many row-silhouettes. Six is a screen of a list, not a guess at
5
+ * the real count. */
6
+ rows?: number;
7
+ /** What a screen reader hears. The bars stay silent. */
8
+ label: string;
9
+ }): import("react").JSX.Element;
10
+ /** What is shown when there is nothing to show: a sentence, and the gesture
11
+ * that resolves it if there is one. Never a drawing. */
12
+ export declare function Empty({ sentence, action,
13
+ /** Centered for an empty list; left-aligned inside a block, where centering it
14
+ * detaches it from everything else. */
15
+ align, }: {
16
+ sentence: string;
17
+ action?: ReactNode;
18
+ align?: "center" | "start";
19
+ }): import("react").JSX.Element;
20
+ /**
21
+ * The sentence the backend wrote, whole and verbatim.
22
+ *
23
+ * Context: a hairline around a quiet red wash — it is not an alarm, it is an
24
+ * explanation. A bar on the left alone reads as a cut. Trimming the message or
25
+ * replacing it with one of our own is what forces you to go to the console to
26
+ * find out what happened.
27
+ */
28
+ export declare function Failed({ heading, sentence, action, }: {
29
+ heading: string;
30
+ sentence: string;
31
+ action?: ReactNode;
32
+ }): import("react").JSX.Element;
33
+ /**
34
+ * Nobody knows how to draw this. It carries the full identifier because that is
35
+ * the only thing that makes it fixable.
36
+ */
37
+ export declare function Missing({ heading, detail, icon: Icon, tone, }: {
38
+ heading: string;
39
+ detail?: string;
40
+ icon?: LucideIcon;
41
+ tone?: "warn" | "bad";
42
+ }): import("react").JSX.Element;
43
+ /** The icon `Missing` uses for what broke while being drawn, so that whoever
44
+ * composes does not have to choose it. */
45
+ export declare const ICONO_ROTO: LucideIcon;
@@ -0,0 +1,61 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { byDensity, useDensity } from "../../foundations/density.js";
4
+ import { AlertTriangle, PackageX } from "lucide-react";
5
+ /**
6
+ * Context: the ways a view says there is no content yet, and they are
7
+ * different because each one is fixed by a different person.
8
+ *
9
+ * Skeleton the content is coming. The silhouette stays so the eye
10
+ * knows where the rows will land.
11
+ * Empty there is nothing yet, or the filter left nothing. It is NORMAL.
12
+ * Failed something broke. It carries the backend's sentence whole and
13
+ * verbatim.
14
+ * Missing nobody knows how to resolve this. It is a wiring error.
15
+ *
16
+ * A generic «something went wrong» for all four forces you to open the console.
17
+ * A loading sentence drawn with `Empty` is the same mistake: it answers
18
+ * «there is nothing» while the data is still on its way.
19
+ */
20
+ /** Quiet stand-in for a list that has not arrived.
21
+ *
22
+ * The bars are the row: a name on the left, a figure on the right. That is
23
+ * the scan of a list, held in place so the page does not jump when the rows
24
+ * land. There is no shimmer. A pulse already means «this state is alive»
25
+ * (`StatusDot`); a second one would spend motion on a wait. The sentence is
26
+ * for the screen reader — the bars are not the message. */
27
+ const SKELETON_NAME = ["w-48", "w-36", "w-56", "w-40", "w-44", "w-32"];
28
+ export function Skeleton({ rows = 6, label, }) {
29
+ const d = useDensity();
30
+ return (_jsxs("div", { "aria-busy": "true", className: "flex flex-col", children: [_jsx("span", { className: "sr-only", children: label }), Array.from({ length: rows }, (_, i) => (_jsxs("div", { "aria-hidden": true, className: `flex items-center ${byDensity(d, "gap-3 px-2 py-2", "gap-2 px-1.5 py-1")}`, children: [_jsx("span", { className: `h-3.5 rounded-control bg-ink/8 ${SKELETON_NAME[i % SKELETON_NAME.length]}` }), _jsx("span", { className: "ml-auto h-3 w-14 rounded-control bg-ink/5" })] }, i)))] }));
31
+ }
32
+ /** What is shown when there is nothing to show: a sentence, and the gesture
33
+ * that resolves it if there is one. Never a drawing. */
34
+ export function Empty({ sentence, action,
35
+ /** Centered for an empty list; left-aligned inside a block, where centering it
36
+ * detaches it from everything else. */
37
+ align = "center", }) {
38
+ const centered = align === "center";
39
+ return (_jsxs("div", { className: `flex flex-wrap gap-3 px-4 py-10 ${centered ? "flex-col items-center text-center" : "items-center"}`, children: [_jsx("p", { className: "max-w-short text-body text-muted", children: sentence }), action] }));
40
+ }
41
+ /**
42
+ * The sentence the backend wrote, whole and verbatim.
43
+ *
44
+ * Context: a hairline around a quiet red wash — it is not an alarm, it is an
45
+ * explanation. A bar on the left alone reads as a cut. Trimming the message or
46
+ * replacing it with one of our own is what forces you to go to the console to
47
+ * find out what happened.
48
+ */
49
+ export function Failed({ heading, sentence, action, }) {
50
+ return (_jsxs("div", { className: "rounded-control border border-bad/40 bg-bad/5 px-3 py-2.5", children: [_jsx("p", { className: "text-body font-medium text-bad", children: heading }), _jsx("p", { className: "mt-1.5 max-w-prose text-lead leading-relaxed text-ink", children: sentence }), action === undefined ? null : _jsx("div", { className: "mt-2.5", children: action })] }));
51
+ }
52
+ /**
53
+ * Nobody knows how to draw this. It carries the full identifier because that is
54
+ * the only thing that makes it fixable.
55
+ */
56
+ export function Missing({ heading, detail, icon: Icon = PackageX, tone = "warn", }) {
57
+ return (_jsxs("div", { className: "flex h-full min-h-24 flex-col items-center justify-center gap-2 p-6 text-center", children: [_jsx(Icon, { className: `size-(--size-icon-lg) ${tone === "bad" ? "text-bad" : "text-warn"}`, strokeWidth: 1.75 }), _jsx("p", { className: "text-meta font-medium text-ink", children: heading }), detail === undefined ? null : (_jsx("p", { className: "max-w-short font-mono text-micro break-all text-muted", children: detail }))] }));
58
+ }
59
+ /** The icon `Missing` uses for what broke while being drawn, so that whoever
60
+ * composes does not have to choose it. */
61
+ export const ICONO_ROTO = AlertTriangle;
@@ -0,0 +1,55 @@
1
+ import type { Tone } from "../../foundations/types.js";
2
+ import { type ReactNode } from "react";
3
+ export interface ToastSpec {
4
+ /** What happened, in a sentence that stands on its own. */
5
+ message: string;
6
+ tone?: Tone;
7
+ /** The long detail: the server's reason. It goes underneath and small. */
8
+ detail?: string;
9
+ /** Where to go, or how to undo. «Deshacer», «Ver el pago». With `href` a real
10
+ * link is drawn —it can be opened in another tab, copied, shared—; with
11
+ * `onSelect`, a button. Either closes the notice. */
12
+ action?: {
13
+ label: string;
14
+ onSelect?: () => void;
15
+ href?: string;
16
+ };
17
+ /** How long it lives, in ms. `0` = until it is closed. By default: the bad
18
+ * ones stay, the rest leave after 8 s. */
19
+ duration?: number;
20
+ /** The notice's identity. Two notices with the same key are the SAME one: the
21
+ * new one replaces the old one in its place, without stacking.
22
+ *
23
+ * Context: the server sends an import's progress over a channel that can
24
+ * repeat, and three identical stacked notices are not more information —
25
+ * they are the same information, three times, taking up the screen. */
26
+ key?: string;
27
+ }
28
+ interface Toast extends ToastSpec {
29
+ id: number;
30
+ }
31
+ interface ToastApi {
32
+ show: (spec: ToastSpec) => number;
33
+ dismiss: (id: number) => void;
34
+ }
35
+ export declare function useToast(): ToastApi;
36
+ /** The same API, or `null` when no `ToastProvider` is above. For a component that OFFERS
37
+ * undo when the screen has a notice stack and still renders alone — in a story, in a test,
38
+ * in an app that has not mounted the provider yet. */
39
+ export declare function useToastIfAny(): ToastApi | null;
40
+ export declare function ToastProvider({ children }: {
41
+ children: ReactNode;
42
+ }): import("react").JSX.Element;
43
+ /** The region of a given list of notices. `ToastProvider` draws it from its own state; it is
44
+ * exported so it can be drawn without the context — a story that wants to see the tones
45
+ * together and still.
46
+ *
47
+ * Context: React Aria's queue is the behaviour (timers, pause, landmark, focus) and this
48
+ * list is the content. Each notice in the list is mirrored into the queue once; when the
49
+ * queue closes one — its timer ran out, its × or its action was pressed — `onDismiss`
50
+ * hears it, and when the list drops one the queue closes it. */
51
+ export declare function ToastLayer({ toasts, onDismiss, }: {
52
+ toasts: readonly Toast[];
53
+ onDismiss: (id: number) => void;
54
+ }): import("react").JSX.Element | null;
55
+ export {};
@@ -0,0 +1,142 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { cn, focusRing } from "../../foundations/variants.js";
5
+ import { CheckCircle2, Info, TriangleAlert, X, XCircle } from "lucide-react";
6
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
7
+ import { Button as AriaButton, Link as AriaLink, Text, UNSTABLE_Toast as AriaToast, UNSTABLE_ToastContent as ToastContent, UNSTABLE_ToastQueue as ToastQueue, UNSTABLE_ToastRegion as ToastRegion, } from "react-aria-components";
8
+ const Ctx = createContext(null);
9
+ export function useToast() {
10
+ const api = useContext(Ctx);
11
+ if (api === null) {
12
+ throw new Error("useToast needs a <ToastProvider> above it in the tree.");
13
+ }
14
+ return api;
15
+ }
16
+ /** The same API, or `null` when no `ToastProvider` is above. For a component that OFFERS
17
+ * undo when the screen has a notice stack and still renders alone — in a story, in a test,
18
+ * in an app that has not mounted the provider yet. */
19
+ export function useToastIfAny() {
20
+ return useContext(Ctx);
21
+ }
22
+ const ICON = {
23
+ neutral: Info,
24
+ info: Info,
25
+ good: CheckCircle2,
26
+ warn: TriangleAlert,
27
+ bad: XCircle,
28
+ accent: Info,
29
+ };
30
+ const TONE = {
31
+ neutral: "text-muted",
32
+ info: "text-info",
33
+ good: "text-good",
34
+ warn: "text-warn",
35
+ bad: "text-bad",
36
+ accent: "text-ink",
37
+ };
38
+ const LIFE = 8000;
39
+ /** The default: the bad ones do NOT leave on their own. See the note above. */
40
+ function lifeOf(toast) {
41
+ return toast.duration ?? (toast.tone === "bad" ? 0 : LIFE);
42
+ }
43
+ const LINK = "self-start rounded-control text-micro font-medium text-primary transition-colors duration-(--duration-instant) hover:underline hover:underline-offset-4 cursor-pointer outline-offset-1";
44
+ export function ToastProvider({ children }) {
45
+ const [toasts, setToasts] = useState([]);
46
+ const next = useRef(1);
47
+ const dismiss = useCallback((id) => {
48
+ setToasts((prev) => prev.filter((t) => t.id !== id));
49
+ }, []);
50
+ const show = useCallback((spec) => {
51
+ const id = next.current;
52
+ next.current += 1;
53
+ setToasts((prev) => {
54
+ if (spec.key === undefined)
55
+ return [...prev, { ...spec, id }];
56
+ const i = prev.findIndex((t) => t.key === spec.key);
57
+ /* Replaces IN ITS PLACE and not at the end: if the notice jumps to the
58
+ bottom of the stack every time it updates, whoever was reading it loses
59
+ it. */
60
+ if (i < 0)
61
+ return [...prev, { ...spec, id }];
62
+ const n = [...prev];
63
+ n[i] = { ...spec, id };
64
+ return n;
65
+ });
66
+ return id;
67
+ }, []);
68
+ const api = useMemo(() => ({ show, dismiss }), [show, dismiss]);
69
+ return (_jsxs(Ctx.Provider, { value: api, children: [children, _jsx(ToastLayer, { toasts: toasts, onDismiss: dismiss })] }));
70
+ }
71
+ /** The region of a given list of notices. `ToastProvider` draws it from its own state; it is
72
+ * exported so it can be drawn without the context — a story that wants to see the tones
73
+ * together and still.
74
+ *
75
+ * Context: React Aria's queue is the behaviour (timers, pause, landmark, focus) and this
76
+ * list is the content. Each notice in the list is mirrored into the queue once; when the
77
+ * queue closes one — its timer ran out, its × or its action was pressed — `onDismiss`
78
+ * hears it, and when the list drops one the queue closes it. */
79
+ export function ToastLayer({ toasts, onDismiss, }) {
80
+ const copy = useUiCopy();
81
+ const [queue] = useState(() => new ToastQueue());
82
+ const [, setVersion] = useState(0);
83
+ const latest = useRef(onDismiss);
84
+ /* The ids the layer is closing because the LIST dropped them: those must not be reported
85
+ back as if the person had closed them. */
86
+ const dropping = useRef(new Set());
87
+ useEffect(() => {
88
+ latest.current = onDismiss;
89
+ }, [onDismiss]);
90
+ useEffect(() => {
91
+ const wanted = new Set(toasts.map((t) => t.id));
92
+ const queued = new Set(queue.visibleToasts.map((t) => t.content.id));
93
+ for (const queuedToast of queue.visibleToasts) {
94
+ if (!wanted.has(queuedToast.content.id)) {
95
+ dropping.current.add(queuedToast.content.id);
96
+ queue.close(queuedToast.key);
97
+ }
98
+ }
99
+ for (const toast of toasts) {
100
+ if (queued.has(toast.id))
101
+ continue;
102
+ const life = lifeOf(toast);
103
+ queue.add(toast, {
104
+ ...(life > 0 ? { timeout: life } : {}),
105
+ onClose: () => {
106
+ if (dropping.current.delete(toast.id))
107
+ return;
108
+ latest.current(toast.id);
109
+ },
110
+ });
111
+ }
112
+ setVersion((v) => v + 1);
113
+ }, [toasts, queue]);
114
+ const byId = new Map(queue.visibleToasts.map((t) => [t.content.id, t]));
115
+ /* Drawn in the LIST's order, oldest at the bottom: the newest notice appears on top of
116
+ the stack and a replaced one stays where it was. */
117
+ const ordered = [...toasts].reverse().flatMap((t) => {
118
+ const queued = byId.get(t.id);
119
+ return queued === undefined ? [] : [{ toast: t, queued }];
120
+ });
121
+ /* Mounted only while there is something to show. React Aria registers the region as an F6
122
+ landmark when it mounts, and it skips the registration if the region's element does not
123
+ exist yet — which is the case for a region mounted empty (it draws nothing until the
124
+ first notice). Mounting it together with its first notice is what makes F6 reach it. */
125
+ if (ordered.length === 0)
126
+ return null;
127
+ return (_jsx(ToastRegion, { queue: queue, "aria-label": copy.shell.notices,
128
+ /* `inset-x-4` with a fixed width and `ml-auto`: that way the notice rests against the
129
+ right but never runs off the left edge on a phone. */
130
+ className: ({ isFocusVisible }) => cn("fixed inset-x-4 bottom-4 z-50 ml-auto flex w-(--container-toast) max-w-full flex-col gap-2 rounded-panel outline-offset-4", focusRing({ isFocusVisible })), children: ordered.map(({ toast, queued }) => (_jsx(ToastCard, { toast: toast, queued: queued, queue: queue }, queued.key))) }));
131
+ }
132
+ function ToastCard({ toast, queued, queue, }) {
133
+ const copy = useUiCopy();
134
+ const tone = toast.tone ?? "neutral";
135
+ const Icon = ICON[tone];
136
+ const close = () => queue.close(queued.key);
137
+ const action = toast.action;
138
+ return (_jsxs(AriaToast, { toast: queued, className: ({ isFocusVisible }) => cn("flex min-w-0 items-start gap-2.5 rounded-panel border border-line bg-surface px-3.5 py-3 shadow-float outline-offset-2", focusRing({ isFocusVisible })), children: [_jsx(Icon, { className: `mt-px size-(--size-icon-md) shrink-0 ${TONE[tone]}`, strokeWidth: 2, "aria-hidden": true }), _jsxs(ToastContent, { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsx(Text, { slot: "title", className: "text-meta text-ink", children: toast.message }), toast.detail === undefined ? null : (_jsx(Text, { slot: "description", className: "text-micro text-muted", children: toast.detail }))] }), action === undefined ? null : action.href !== undefined ? (_jsx(AriaLink, { href: action.href, onPress: close, className: ({ isFocusVisible }) => cn(LINK, "mt-px", focusRing({ isFocusVisible })), children: action.label })) : (_jsx(AriaButton, { onPress: () => {
139
+ action.onSelect?.();
140
+ close();
141
+ }, className: ({ isFocusVisible }) => cn(LINK, "mt-px", focusRing({ isFocusVisible })), children: action.label })), _jsx(AriaButton, { slot: "close", "aria-label": copy.shell.closeNotice, className: ({ isFocusVisible }) => cn("-mt-0.5 -mr-1 inline-flex size-6 shrink-0 cursor-default items-center justify-center rounded-pill text-muted transition-colors duration-(--duration-instant) hover:bg-ink/6 hover:text-ink pressed:bg-ink/10", focusRing({ isFocusVisible })), children: _jsx(X, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true }) })] }));
142
+ }
@@ -0,0 +1,3 @@
1
+ export { Callout, type CalloutProps } from "./Callout.js";
2
+ export { Empty, Failed, ICONO_ROTO, Missing, Skeleton, } from "./Notice.js";
3
+ export { ToastLayer, ToastProvider, type ToastSpec, useToast, useToastIfAny, } from "./Toast.js";
@@ -0,0 +1,5 @@
1
+ /* How a view says that there is nothing, that it failed, or that nobody knows
2
+ how to draw it. */
3
+ export { Callout } from "./Callout.js";
4
+ export { Empty, Failed, ICONO_ROTO, Missing, Skeleton, } from "./Notice.js";
5
+ export { ToastLayer, ToastProvider, useToast, useToastIfAny, } from "./Toast.js";
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from "react";
2
+ import { type PopoverProps as AriaPopoverProps } from "react-aria-components";
3
+ export type PopoverProps = Omit<AriaPopoverProps, "children"> & {
4
+ children: ReactNode;
5
+ };
6
+ export declare function Popover({ children, className, offset, ...props }: PopoverProps): import("react").JSX.Element;
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { tv } from "../../foundations/variants.js";
4
+ import { composeRenderProps, Popover as AriaPopover, } from "react-aria-components";
5
+ /**
6
+ * What floats over the page anchored to something: the calendar of a date picker, the list
7
+ * of a combobox, a small form opened from a button.
8
+ *
9
+ * Context: it is the same surface the system's `Menu` floats on — raised white, strong
10
+ * hairline, the `float` shadow — so everything that opens on top of the page reads as one
11
+ * kind of thing. React Aria places it, flips it when it does not fit, traps and returns the
12
+ * focus and closes it on Escape; this only dresses it.
13
+ */
14
+ const popover = tv({
15
+ base: "rounded-panel border border-line-strong bg-surface text-ink shadow-float outline-0 transition-[opacity,translate] duration-(--duration-quick) ease-(--ease-out) forced-colors:bg-[Canvas]",
16
+ variants: {
17
+ isEntering: {
18
+ true: "opacity-0 placement-bottom:-translate-y-1 placement-top:translate-y-1",
19
+ },
20
+ isExiting: {
21
+ true: "opacity-0",
22
+ },
23
+ },
24
+ });
25
+ export function Popover({ children, className, offset = 6, ...props }) {
26
+ return (_jsx(AriaPopover, { offset: offset, ...props, className: composeRenderProps(className, (own, state) => popover({ ...state, className: own })), children: children }));
27
+ }
@@ -0,0 +1,55 @@
1
+ import { type PopoverProps } from "./Popover.js";
2
+ import { type ReactElement, type ReactNode } from "react";
3
+ /** What the trigger has to accept: the system's `Button` and `IconButton` both do — they
4
+ * forward `ref`, spread native props and say what they open. */
5
+ type TriggerElement = ReactElement<{
6
+ expanded?: boolean;
7
+ haspopup?: "menu" | "dialog" | "listbox";
8
+ }>;
9
+ type Naming =
10
+ /** Drawn at the top of the popover, and its accessible name. */
11
+ {
12
+ title: string;
13
+ label?: undefined;
14
+ }
15
+ /** The accessible name when the popover has no visible title — a detail card whose
16
+ * first line already says what it is. */
17
+ | {
18
+ title?: undefined;
19
+ label: string;
20
+ };
21
+ export type PopoverTriggerProps = Naming & {
22
+ /** The button that opens it — a system `Button` or `IconButton`, written as an element. */
23
+ trigger: TriggerElement;
24
+ /** The content. As a function it receives `close`, for the «Aplicar» of a small form. */
25
+ children: ReactNode | ((close: () => void) => ReactNode);
26
+ placement?: PopoverProps["placement"];
27
+ };
28
+ /**
29
+ * A small form or a detail opened from a button, floating next to it: «filtrar por monto»,
30
+ * «ver detalle del cliente».
31
+ *
32
+ * Context: it is the step below `Dialog`. A dialog takes the screen and asks something
33
+ * before moving on; this asks two numbers WITHOUT hiding the table they filter — the
34
+ * person keeps seeing what the answer is about. It floats on the system's `Popover`, so it
35
+ * looks like the calendar of a date picker and the list of a combobox.
36
+ *
37
+ * ── Why `trigger={<Button …/>}` and not a compound `<PopoverTrigger.Button>` ──
38
+ *
39
+ * The opener is an ordinary system button, written the way every other button of the
40
+ * screen is written. A compound form would need a second button component — a React Aria
41
+ * `Button` dressed like ours — and two buttons that look the same and are different
42
+ * components drift apart by the third release. One prop, one element, one button.
43
+ *
44
+ * ── It is React Aria's `DialogTrigger`, through `Pressable` ──
45
+ *
46
+ * `DialogTrigger` hands its trigger the press handlers and a `ref` through context; React
47
+ * Aria's `Pressable` is the adapter that clones them onto any element that forwards `ref`
48
+ * and spreads native props — which the system's buttons do. So the popover is anchored to
49
+ * the button itself, focus goes into the dialog and comes back to the button, and Escape
50
+ * and a click outside close it. The open state is kept here only to tell the button
51
+ * `expanded` and `haspopup="dialog"`, so it says what it opens the same way a menu
52
+ * trigger says it.
53
+ */
54
+ export declare function PopoverTrigger({ trigger, title, label, children, placement, }: PopoverTriggerProps): import("react").JSX.Element;
55
+ export {};
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Popover } from "./Popover.js";
4
+ import { cloneElement, useState, } from "react";
5
+ import { Dialog, DialogTrigger, Heading, Pressable } from "react-aria-components";
6
+ /**
7
+ * A small form or a detail opened from a button, floating next to it: «filtrar por monto»,
8
+ * «ver detalle del cliente».
9
+ *
10
+ * Context: it is the step below `Dialog`. A dialog takes the screen and asks something
11
+ * before moving on; this asks two numbers WITHOUT hiding the table they filter — the
12
+ * person keeps seeing what the answer is about. It floats on the system's `Popover`, so it
13
+ * looks like the calendar of a date picker and the list of a combobox.
14
+ *
15
+ * ── Why `trigger={<Button …/>}` and not a compound `<PopoverTrigger.Button>` ──
16
+ *
17
+ * The opener is an ordinary system button, written the way every other button of the
18
+ * screen is written. A compound form would need a second button component — a React Aria
19
+ * `Button` dressed like ours — and two buttons that look the same and are different
20
+ * components drift apart by the third release. One prop, one element, one button.
21
+ *
22
+ * ── It is React Aria's `DialogTrigger`, through `Pressable` ──
23
+ *
24
+ * `DialogTrigger` hands its trigger the press handlers and a `ref` through context; React
25
+ * Aria's `Pressable` is the adapter that clones them onto any element that forwards `ref`
26
+ * and spreads native props — which the system's buttons do. So the popover is anchored to
27
+ * the button itself, focus goes into the dialog and comes back to the button, and Escape
28
+ * and a click outside close it. The open state is kept here only to tell the button
29
+ * `expanded` and `haspopup="dialog"`, so it says what it opens the same way a menu
30
+ * trigger says it.
31
+ */
32
+ export function PopoverTrigger({ trigger, title, label, children, placement = "bottom start", }) {
33
+ const [open, setOpen] = useState(false);
34
+ return (_jsxs(DialogTrigger, { isOpen: open, onOpenChange: setOpen, children: [_jsx(Pressable, { children:
35
+ /* React Aria types the child as an intrinsic element; a component that forwards
36
+ `ref` and spreads native props is the same thing to it at runtime. */
37
+ cloneElement(trigger, { expanded: open, haspopup: "dialog" }) }), _jsx(Popover, { placement: placement, children: _jsx(Dialog, { "aria-label": label, className: "max-h-[inherit] max-w-sm min-w-56 overflow-auto p-3 outline-0", children: ({ close }) => (_jsxs(_Fragment, { children: [title === undefined ? null : (_jsx(Heading, { slot: "title", className: "mb-2 text-meta font-semibold text-ink", children: title })), typeof children === "function" ? children(close) : children] })) }) })] }));
38
+ }
@@ -0,0 +1,38 @@
1
+ import { type ReactNode } from "react";
2
+ import { type TooltipProps as AriaTooltipProps } from "react-aria-components";
3
+ export type TooltipProps = Omit<AriaTooltipProps, "children" | "offset"> & {
4
+ /** A few words. A sentence of help is a `Hint`, not a tooltip. */
5
+ children: ReactNode;
6
+ /** The small point toward the control. On by default: in a row of five icons it says
7
+ * WHICH one is being named. */
8
+ arrow?: boolean;
9
+ };
10
+ export declare function Tooltip({ children, arrow, className, ...props }: TooltipProps): import("react").JSX.Element;
11
+ export declare function useInTooltipTrigger(): boolean;
12
+ /**
13
+ * Ties a control to its `Tooltip`: the control first, the tooltip second.
14
+ *
15
+ * Context: the timing is React Aria's on purpose — its default delay before the first one,
16
+ * and the warm-up that opens the next one at once while the pointer runs along a row of
17
+ * icons. A tooltip that opens on every pass of the mouse is noise; one that makes you wait
18
+ * on each icon of a row is a toll. `delay` is here for the rare control that needs it, not
19
+ * to tune each screen.
20
+ *
21
+ * ── Why the control is wrapped in `Focusable` here ──
22
+ *
23
+ * React Aria's trigger hands its pointer, focus and `aria-describedby` props to the control
24
+ * through context, and only a React Aria component reads that context. `Focusable` is the
25
+ * adapter for everything else: it clones the control with those props and a `ref`. Doing it
26
+ * here means a consumer writes `<TooltipTrigger><Button …/><Tooltip …/></TooltipTrigger>`
27
+ * with the system's own buttons — they forward `ref` and spread native props — or with a
28
+ * plain `<button>`, and never has to know the adapter exists.
29
+ */
30
+ export declare function TooltipTrigger({ children, delay, closeDelay, disabled, }: {
31
+ /** The control — one element that forwards `ref` and spreads native props, like every
32
+ * system button — then the `Tooltip`. */
33
+ children: ReactNode;
34
+ /** Milliseconds before the first tooltip opens. React Aria's default when absent. */
35
+ delay?: number;
36
+ closeDelay?: number;
37
+ disabled?: boolean;
38
+ }): import("react").JSX.Element;