@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,74 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { tv } from "../../foundations/variants.js";
4
+ import { Children, createContext, isValidElement, useContext, } from "react";
5
+ import { composeRenderProps, Focusable, OverlayArrow, Tooltip as AriaTooltip, TooltipTrigger as AriaTooltipTrigger, } from "react-aria-components";
6
+ /**
7
+ * The name of a control that has no visible name — an icon button of a table row, of a
8
+ * toolbar — shown when the pointer rests on it or the keyboard reaches it.
9
+ *
10
+ * Context: it is not the other two ways of putting words next to a mark.
11
+ *
12
+ * - `Hint` is CONTENT: a sentence of help that does not fit on the page, behind a `?`. It
13
+ * opens on press and stays, because it is meant to be read, on a tablet too.
14
+ * - The native `title` is the browser's tooltip: it shows late, unstyled, never on keyboard
15
+ * focus and never on touch — the person who navigates with Tab never learns what the
16
+ * trash can is. That is why icon targets no longer use it.
17
+ *
18
+ * `Tooltip` is the LABEL, shown: a few words, on hover AND on focus, never interactive, never
19
+ * pinned. It is drawn on the chrome (the dark surface of the shell) because it is not part
20
+ * of the page — it is a whisper about one control, and the dark box reads as that against
21
+ * a page of white surfaces. React Aria decides when: its default delay on the first hover,
22
+ * then the next tooltip of the row opens at once (warm-up), and Escape closes it.
23
+ *
24
+ * Every `IconButton` and `IconLink` already draws one with its `label`; write a
25
+ * `TooltipTrigger` by hand only for a control that is not an icon target, or to say more
26
+ * than the label.
27
+ */
28
+ const tooltip = tv({
29
+ base: "group max-w-64 rounded-control bg-chrome px-2 py-1 text-micro text-chrome-ink shadow-float transition-[opacity,translate] duration-(--duration-quick) ease-(--ease-out) forced-colors:border forced-colors:border-[ButtonBorder] forced-colors:bg-[Canvas] forced-colors:text-[CanvasText]",
30
+ variants: {
31
+ isEntering: {
32
+ true: "opacity-0 placement-top:translate-y-1 placement-bottom:-translate-y-1 placement-left:translate-x-1 placement-right:-translate-x-1",
33
+ },
34
+ isExiting: {
35
+ true: "opacity-0",
36
+ },
37
+ },
38
+ });
39
+ export function Tooltip({ children, arrow = true, className, ...props }) {
40
+ return (_jsxs(AriaTooltip, { offset: arrow ? 8 : 4, ...props, className: composeRenderProps(className, (own, state) => tooltip({ ...state, className: own })), children: [arrow ? (_jsx(OverlayArrow, { children: _jsx("svg", { viewBox: "0 0 8 8", "aria-hidden": true, className: "block size-2 fill-chrome group-placement-bottom:rotate-180 group-placement-left:-rotate-90 group-placement-right:rotate-90 forced-colors:fill-[Canvas]", children: _jsx("path", { d: "M0 0 L4 4 L8 0" }) }) })) : null, children] }));
41
+ }
42
+ /** True inside the control slot of a `TooltipTrigger`: an `IconButton` written there leaves
43
+ * its own tooltip out, so the consumer's is the only one. */
44
+ const InTooltipTrigger = createContext(false);
45
+ export function useInTooltipTrigger() {
46
+ return useContext(InTooltipTrigger);
47
+ }
48
+ function isDisabledControl(control) {
49
+ if (!isValidElement(control))
50
+ return false;
51
+ return control.props.disabled === true || control.props.isDisabled === true;
52
+ }
53
+ /**
54
+ * Ties a control to its `Tooltip`: the control first, the tooltip second.
55
+ *
56
+ * Context: the timing is React Aria's on purpose — its default delay before the first one,
57
+ * and the warm-up that opens the next one at once while the pointer runs along a row of
58
+ * icons. A tooltip that opens on every pass of the mouse is noise; one that makes you wait
59
+ * on each icon of a row is a toll. `delay` is here for the rare control that needs it, not
60
+ * to tune each screen.
61
+ *
62
+ * ── Why the control is wrapped in `Focusable` here ──
63
+ *
64
+ * React Aria's trigger hands its pointer, focus and `aria-describedby` props to the control
65
+ * through context, and only a React Aria component reads that context. `Focusable` is the
66
+ * adapter for everything else: it clones the control with those props and a `ref`. Doing it
67
+ * here means a consumer writes `<TooltipTrigger><Button …/><Tooltip …/></TooltipTrigger>`
68
+ * with the system's own buttons — they forward `ref` and spread native props — or with a
69
+ * plain `<button>`, and never has to know the adapter exists.
70
+ */
71
+ export function TooltipTrigger({ children, delay, closeDelay, disabled = false, }) {
72
+ const [control, ...tooltip] = Children.toArray(children);
73
+ return (_jsxs(AriaTooltipTrigger, { delay: delay, closeDelay: closeDelay, isDisabled: disabled, children: [_jsx(InTooltipTrigger.Provider, { value: true, children: _jsx(Focusable, { isDisabled: isDisabledControl(control), children: control }) }), tooltip] }));
74
+ }
@@ -0,0 +1,3 @@
1
+ export { Popover, type PopoverProps } from "./Popover.js";
2
+ export { PopoverTrigger, type PopoverTriggerProps, } from "./PopoverTrigger.js";
3
+ export { Tooltip, type TooltipProps, TooltipTrigger, } from "./Tooltip.js";
@@ -0,0 +1,5 @@
1
+ /* LO QUE FLOTA anclado a algo: el calendario de un selector de fecha, la lista de un
2
+ combobox, un formulario chico abierto desde un botón, el nombre de un ícono. */
3
+ export { Popover } from "./Popover.js";
4
+ export { PopoverTrigger, } from "./PopoverTrigger.js";
5
+ export { Tooltip, TooltipTrigger, } from "./Tooltip.js";
@@ -0,0 +1,48 @@
1
+ import { type FormMode } from "../field/mode.js";
2
+ import type { LucideIcon } from "lucide-react";
3
+ import { type ReactNode } from "react";
4
+ export interface PickerItem {
5
+ id: string;
6
+ name: string;
7
+ /** The data that allows choosing well, already formatted: stock on hand, a
8
+ * price, a tax id. In tabular figures, so they line up between options, which
9
+ * is what makes them comparable at a glance. */
10
+ meta?: string;
11
+ tags?: ReactNode;
12
+ icon?: LucideIcon;
13
+ disabled?: boolean;
14
+ /** Extra text WHERE TO SEARCH that is not drawn: the internal code and the
15
+ * barcode of a product, the NIT of a customer.
16
+ *
17
+ * Context: it is the difference between being able to type the code printed
18
+ * on the box and finding the product, and having to remember what it was
19
+ * called. Drawing every code of every option is not an alternative. */
20
+ haystack?: string;
21
+ }
22
+ export type PickerProps = {
23
+ id?: string;
24
+ items: readonly PickerItem[];
25
+ value?: string;
26
+ onChange?: (id: string) => void;
27
+ placeholder?: string;
28
+ /** What to do with the ones that don't fit. Without this, the footer only
29
+ * informs. */
30
+ onBrowseAll?: () => void;
31
+ /** Open the chosen record. Drawn as a trailing affordance, the way a many2one
32
+ * offers "open" next to "pick". Absent when there is nothing chosen. */
33
+ onOpen?: (id: string) => void;
34
+ emptyText?: string;
35
+ /** Placeholder of the field while it is being typed into. It is NOT a second
36
+ * box — the field itself is the search. */
37
+ searchPlaceholder?: string;
38
+ /** The options are still coming. It gets said, not hidden: an empty picker
39
+ * while loading reads as "there is nothing", which is a different answer. */
40
+ loading?: boolean;
41
+ loadingText?: string;
42
+ describedBy?: string;
43
+ invalid?: boolean;
44
+ disabled?: boolean;
45
+ /** Forces the mode. By default it inherits the form's: in read mode it draws the name. */
46
+ mode?: FormMode;
47
+ };
48
+ export declare function Picker({ mode, ...props }: PickerProps): import("react").JSX.Element;
@@ -0,0 +1,150 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { AriaLocale } from "../../foundations/aria.js";
4
+ import { fillCopy, useUiCopy } from "../../foundations/copy.js";
5
+ import { cn, tv } from "../../foundations/variants.js";
6
+ import { IconButton } from "../button/Button.js";
7
+ import { FieldButton, FieldGroup } from "../field/aria.js";
8
+ import { useFieldIds } from "../field/context.js";
9
+ import { ReadValue } from "../field/Field.js";
10
+ import { useFormMode } from "../field/mode.js";
11
+ import { Popover } from "../overlay/Popover.js";
12
+ import { Check, ChevronDown, SquareArrowOutUpRight } from "lucide-react";
13
+ import { useContext, useMemo, useRef, useState } from "react";
14
+ import { ComboBox as AriaComboBox, ComboBoxStateContext, Input, ListBox, ListBoxItem, Text, useFilter, } from "react-aria-components";
15
+ /** The id of the «see all…» row. Not a record id: it cannot collide with one. */
16
+ const BROWSE_ALL = "\u0000browse-all";
17
+ const CAP = 8;
18
+ /* No focus ring on an option: the focus stays in the input (the list is driven from it),
19
+ and the marked row is said by its tint — a ring inside the list as well as around the
20
+ field is two marks for one place. */
21
+ const option = tv({
22
+ base: "flex w-full min-w-0 cursor-default gap-2 rounded-control px-2 text-left outline-0 select-none forced-color-adjust-none",
23
+ variants: {
24
+ hints: {
25
+ true: "items-start py-1.5",
26
+ false: "items-center py-1",
27
+ },
28
+ isFocused: {
29
+ true: "bg-primary/8 forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]",
30
+ },
31
+ isHovered: {
32
+ true: "bg-ink/4",
33
+ },
34
+ isPressed: {
35
+ true: "bg-primary/12",
36
+ },
37
+ isDisabled: {
38
+ true: "opacity-45 forced-colors:text-[GrayText]",
39
+ },
40
+ },
41
+ compoundVariants: [{ isFocused: true, isHovered: true, class: "bg-primary/8" }],
42
+ });
43
+ export function Picker({ mode, ...props }) {
44
+ if (useFormMode(mode) === "read") {
45
+ return _jsx(ReadValue, { value: props.items.find((i) => i.id === props.value)?.name ?? null });
46
+ }
47
+ /* The filter reads the locale from React Aria's context, so the box that provides it has
48
+ to be ABOVE the component that asks for it. */
49
+ return (_jsx(AriaLocale, { children: _jsx(EditablePicker, { ...props }) }));
50
+ }
51
+ function EditablePicker({ id: ownId, items, value, onChange, placeholder, onBrowseAll, onOpen, emptyText, searchPlaceholder, loading = false, loadingText, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, }) {
52
+ const { id, labelledBy, describedBy, invalid } = useFieldIds({
53
+ id: ownId,
54
+ describedBy: ownDescribedBy,
55
+ invalid: ownInvalid,
56
+ });
57
+ const copy = useUiCopy();
58
+ const { contains } = useFilter({ sensitivity: "base" });
59
+ const shownPlaceholder = placeholder ?? copy.picker.placeholder;
60
+ const shownEmpty = emptyText ?? copy.picker.empty;
61
+ const shownSearch = searchPlaceholder ?? copy.picker.search;
62
+ const shownLoading = loadingText ?? copy.picker.loading;
63
+ const chosen = items.find((i) => i.id === value);
64
+ const chosenName = chosen?.name ?? "";
65
+ /* Both the text and the choice are controlled, so React Aria never rewrites the field
66
+ from its own collection — which is capped and filtered, and may not hold the chosen
67
+ record at all. `query` is what filters: empty when the list opens (the whole list,
68
+ not just the one already chosen), the typed text after that. */
69
+ const [open, setOpen] = useState(false);
70
+ const [text, setText] = useState(chosenName);
71
+ const [query, setQuery] = useState("");
72
+ const [shownFor, setShownFor] = useState(chosenName);
73
+ if (!open && shownFor !== chosenName) {
74
+ setShownFor(chosenName);
75
+ setText(chosenName);
76
+ }
77
+ const filtered = useMemo(() => {
78
+ const q = query.trim();
79
+ if (q === "")
80
+ return items;
81
+ return items.filter((i) => contains(`${i.name} ${i.meta ?? ""} ${i.haystack ?? ""}`, q));
82
+ }, [items, query, contains]);
83
+ const visible = loading ? [] : filtered.slice(0, CAP);
84
+ const remaining = loading ? 0 : filtered.length - visible.length;
85
+ const hasMarks = items.some((i) => i.icon !== undefined);
86
+ const hasHints = visible.some((i) => i.meta !== undefined);
87
+ /* «See all…» is an OPTION at the end of the list and not a button in a footer: the
88
+ arrows reach it like any other row. As a footer button it was out of the keyboard's
89
+ path — Tab leaves the field and closes the list before getting there. */
90
+ const browsable = remaining > 0 && onBrowseAll !== undefined;
91
+ const listed = browsable
92
+ ? [...visible, { id: BROWSE_ALL, name: copy.picker.browseAll }]
93
+ : visible;
94
+ const closeList = useRef(null);
95
+ const take = (key) => {
96
+ if (key === null)
97
+ return;
98
+ const next = String(key);
99
+ if (next === BROWSE_ALL) {
100
+ /* The full list opens somewhere else (a dialog, a panel): this one closes and the
101
+ field keeps the chosen name. */
102
+ closeList.current?.();
103
+ setText(chosenName);
104
+ setQuery("");
105
+ onBrowseAll?.();
106
+ return;
107
+ }
108
+ setText(items.find((i) => i.id === next)?.name ?? "");
109
+ setQuery("");
110
+ if (next !== value)
111
+ onChange?.(next);
112
+ };
113
+ return (_jsxs(AriaComboBox, { id: id, "aria-labelledby": labelledBy, "aria-describedby": describedBy, items: listed, value: value ?? null, onChange: take, inputValue: text, onInputChange: (next) => {
114
+ setText(next);
115
+ setQuery(next);
116
+ }, onOpenChange: (isOpen, trigger) => {
117
+ setOpen(isOpen);
118
+ if (isOpen && trigger !== "input")
119
+ setQuery("");
120
+ if (!isOpen) {
121
+ setText(chosenName);
122
+ setQuery("");
123
+ }
124
+ },
125
+ /* Opening on focus is what a many2one does: you arrive at the field to choose, and
126
+ the options are the answer before the first letter. */
127
+ menuTrigger: "focus",
128
+ /* An empty list stays open to SAY it is empty (or loading) instead of vanishing. */
129
+ allowsEmptyCollection: true, isInvalid: invalid, isDisabled: disabled, className: "w-full min-w-0", children: [_jsx(CloseBridge, { into: closeList }), _jsxs(FieldGroup, { children: [_jsx(Input, { placeholder: loading ? shownLoading : open ? shownSearch : shownPlaceholder, title: shownSearch,
130
+ /* Arriving selects the name, so the first letter typed replaces it. Only when
131
+ ARRIVING: React Aria hands the focus back to the input while the list is open,
132
+ and selecting again then would swallow every second letter. */
133
+ onFocus: (event) => {
134
+ if (!open)
135
+ event.currentTarget.select();
136
+ }, className: "h-full min-w-0 flex-1 bg-transparent px-2.5 text-meta text-ink outline-0 placeholder:text-muted disabled:cursor-not-allowed" }), value !== undefined && value !== "" && onOpen !== undefined ? (_jsx(IconButton, { icon: SquareArrowOutUpRight, label: copy.picker.open, size: "sm", disabled: disabled, onClick: () => onOpen(value) })) : null, _jsx(FieldButton, { className: "mr-1", children: _jsx(ChevronDown, { "aria-hidden": true, className: "size-(--size-icon-md)", strokeWidth: 2 }) })] }), _jsxs(Popover, { placement: "bottom start", className: "flex w-(--trigger-width) flex-col overflow-hidden", children: [_jsx(ListBox, { items: listed, renderEmptyState: () => (_jsx("p", { className: "px-3 py-3 text-center text-meta text-muted", children: loading ? shownLoading : shownEmpty })), className: "min-h-0 flex-1 overflow-y-auto p-1 outline-0", children: (item) => item.id === BROWSE_ALL ? (_jsx(ListBoxItem, { id: BROWSE_ALL, textValue: item.name, className: (state) => `${option({ ...state, hints: false })} mt-1 border-t border-line text-primary`, children: _jsxs("span", { className: "flex w-full items-center justify-between gap-2", children: [_jsx("span", { className: "font-medium", children: item.name }), _jsx("span", { className: "text-micro tabular-nums text-muted", children: fillCopy(copy.picker.more, {
137
+ count: remaining.toLocaleString(copy.locale),
138
+ }) })] }) })) : (_jsx(ListBoxItem, { id: item.id, textValue: item.name, isDisabled: item.disabled, className: (state) => option({ ...state, hints: hasHints }), children: ({ isSelected }) => (_jsx(Row, { item: item, selected: isSelected, marks: hasMarks, hints: hasHints })) })) }), remaining > 0 && !browsable ? (_jsxs("div", { className: "flex shrink-0 items-center gap-2 border-t border-line bg-ground px-2.5 py-1.5", children: [_jsx("span", { className: "text-micro tabular-nums text-muted", children: fillCopy(copy.picker.more, { count: remaining.toLocaleString(copy.locale) }) }), _jsx("span", { className: "text-micro text-muted", children: copy.picker.refine })] })) : null] })] }));
139
+ }
140
+ function Row({ item, selected, marks, hints, }) {
141
+ const Icon = item.icon;
142
+ return (_jsxs(_Fragment, { children: [marks ? (_jsx("span", { className: cn(hints ? "mt-0.5" : "", "flex size-(--size-icon-sm) shrink-0 items-center justify-center"), children: selected ? (_jsx(Check, { "aria-hidden": true, className: "size-(--size-icon-sm) text-primary forced-colors:text-[HighlightText]", strokeWidth: 2.5 })) : Icon !== undefined ? (_jsx(Icon, { "aria-hidden": true, className: "size-(--size-icon-sm) text-muted", strokeWidth: 1.75 })) : null })) : null, _jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [_jsx(Text, { slot: "label", className: cn("min-w-0 truncate text-meta text-ink", selected ? "font-medium" : ""), children: item.name }), item.tags] }), item.meta === undefined ? null : (_jsx(Text, { slot: "description", className: "truncate text-micro tabular-nums text-muted", children: item.meta }))] }), !marks && selected ? (_jsx(Check, { "aria-hidden": true, className: "size-(--size-icon-sm) shrink-0 text-primary forced-colors:text-[HighlightText]", strokeWidth: 2.5 })) : null] }));
143
+ }
144
+ /** Hands the combobox's `close` to the code outside it: React Aria owns the open state and
145
+ * offers no controlled `isOpen`, and the «see all…» row has to close the list it lives in. */
146
+ function CloseBridge({ into }) {
147
+ const state = useContext(ComboBoxStateContext);
148
+ into.current = state === null ? null : () => state.close();
149
+ return null;
150
+ }
@@ -0,0 +1 @@
1
+ export { Picker, type PickerItem, type PickerProps, } from "./Picker.js";
@@ -0,0 +1,2 @@
1
+ /* The replacement for `<select>` when you have to SEE in order to choose. */
2
+ export { Picker, } from "./Picker.js";
@@ -0,0 +1,60 @@
1
+ import type { ReactNode } from "react";
2
+ export interface PeekProps {
3
+ open: boolean;
4
+ title: ReactNode;
5
+ subtitle?: ReactNode;
6
+ /** The × button and Escape (while the focus is inside the drawer). */
7
+ onClose: () => void;
8
+ /** Promoting preserves the context: it is the same view changing surface. */
9
+ onPromote?: () => void;
10
+ /** The actions of the record being previewed, next to «open as panel». */
11
+ actions?: ReactNode;
12
+ children: ReactNode;
13
+ }
14
+ /**
15
+ * The drawer that previews a record next to the list it came from.
16
+ *
17
+ * Context: the drawer comes in from the right and does NOT dim what is behind
18
+ * it — that is the property that defines it. The list it came from stays
19
+ * clickable, which is the whole point of a preview; a scrim would turn it into
20
+ * a dialog, and a dialog is for what has to be answered before moving on.
21
+ * Pressing another row replaces what the drawer shows; it never opens a second one.
22
+ *
23
+ * It is anchored to the work area and not to the window: it covers the content
24
+ * without covering the menu or the activity bar, which are still navigation.
25
+ * The left corners are the panel radius. The sheet still spans the work area;
26
+ * only the cut against the list is rounded, so the drawer reads as a panel.
27
+ *
28
+ * ── On a phone it is a bottom sheet ───────────────────────────────────
29
+ *
30
+ * Below `sm` (40rem, `--container-handheld`) there is no «beside the list»: 30rem from
31
+ * the right of a 390 px screen is the whole screen. So the same drawer anchors to the
32
+ * BOTTOM edge of the window — full width, top corners rounded, at most `--size-sheet`
33
+ * (90dvh) tall, its body scrolling inside — and comes in from below. The strip left on
34
+ * top still shows the list, and it is still not dimmed: what defines the drawer survives
35
+ * the change of edge. It is `fixed` there and not `absolute` because on a phone the work
36
+ * area is as tall as its content, and its bottom edge can be three screens down; the
37
+ * screen's bottom edge is where the thumb is. (Like any `fixed`, it is anchored to the
38
+ * window only if no ancestor has a `transform` or containment — mount it next to the
39
+ * list, not inside a `@container`.) CSS only: the focus, Escape and restore below do not
40
+ * change.
41
+ *
42
+ * ── Why it is not React Aria's `ModalOverlay` ─────────────────────────
43
+ *
44
+ * Every overlay React Aria has —`ModalOverlay`, `Sheet`— is modal: it makes the rest of the
45
+ * page `inert`, traps the focus and portals to the window. Each of those breaks a promise
46
+ * above: the list would stop answering, and the drawer would cover the menu. So the drawer
47
+ * is an `<aside>` in the work area (the «preview» landmark) holding a NON-modal React Aria
48
+ * `Dialog`:
49
+ *
50
+ * - `Dialog` — `role="dialog"` without `aria-modal`, named by its title (`Heading
51
+ * slot="title"`), and on open it takes the focus itself — else a keyboard user would have
52
+ * to tab through the whole list to reach it. Focusing the panel and not its first button
53
+ * keeps the close tooltip from popping up on every open.
54
+ * - `FocusScope restoreFocus` — on close the focus goes back to the row that opened it.
55
+ * There is no `contain`: Tab may walk out to the list, which is still live.
56
+ * - `useKeyboard` — Escape closes it while the focus is INSIDE the drawer. A listener on
57
+ * the window closed it from anywhere, competing with every other Escape on the page (a
58
+ * search field clearing, a list dropping its selection).
59
+ */
60
+ export declare function Peek({ open, title, subtitle, onClose, onPromote, actions, children, }: PeekProps): import("react").JSX.Element | null;
@@ -0,0 +1,71 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { DensityProvider } from "../../foundations/density.js";
5
+ import { cn } from "../../foundations/variants.js";
6
+ import { Button, IconButton } from "../button/Button.js";
7
+ import { Tag } from "../tag/Tag.js";
8
+ import { SquareArrowOutUpRight, X } from "lucide-react";
9
+ import { FocusScope, useKeyboard } from "react-aria";
10
+ import { Dialog, Heading } from "react-aria-components";
11
+ /**
12
+ * The drawer that previews a record next to the list it came from.
13
+ *
14
+ * Context: the drawer comes in from the right and does NOT dim what is behind
15
+ * it — that is the property that defines it. The list it came from stays
16
+ * clickable, which is the whole point of a preview; a scrim would turn it into
17
+ * a dialog, and a dialog is for what has to be answered before moving on.
18
+ * Pressing another row replaces what the drawer shows; it never opens a second one.
19
+ *
20
+ * It is anchored to the work area and not to the window: it covers the content
21
+ * without covering the menu or the activity bar, which are still navigation.
22
+ * The left corners are the panel radius. The sheet still spans the work area;
23
+ * only the cut against the list is rounded, so the drawer reads as a panel.
24
+ *
25
+ * ── On a phone it is a bottom sheet ───────────────────────────────────
26
+ *
27
+ * Below `sm` (40rem, `--container-handheld`) there is no «beside the list»: 30rem from
28
+ * the right of a 390 px screen is the whole screen. So the same drawer anchors to the
29
+ * BOTTOM edge of the window — full width, top corners rounded, at most `--size-sheet`
30
+ * (90dvh) tall, its body scrolling inside — and comes in from below. The strip left on
31
+ * top still shows the list, and it is still not dimmed: what defines the drawer survives
32
+ * the change of edge. It is `fixed` there and not `absolute` because on a phone the work
33
+ * area is as tall as its content, and its bottom edge can be three screens down; the
34
+ * screen's bottom edge is where the thumb is. (Like any `fixed`, it is anchored to the
35
+ * window only if no ancestor has a `transform` or containment — mount it next to the
36
+ * list, not inside a `@container`.) CSS only: the focus, Escape and restore below do not
37
+ * change.
38
+ *
39
+ * ── Why it is not React Aria's `ModalOverlay` ─────────────────────────
40
+ *
41
+ * Every overlay React Aria has —`ModalOverlay`, `Sheet`— is modal: it makes the rest of the
42
+ * page `inert`, traps the focus and portals to the window. Each of those breaks a promise
43
+ * above: the list would stop answering, and the drawer would cover the menu. So the drawer
44
+ * is an `<aside>` in the work area (the «preview» landmark) holding a NON-modal React Aria
45
+ * `Dialog`:
46
+ *
47
+ * - `Dialog` — `role="dialog"` without `aria-modal`, named by its title (`Heading
48
+ * slot="title"`), and on open it takes the focus itself — else a keyboard user would have
49
+ * to tab through the whole list to reach it. Focusing the panel and not its first button
50
+ * keeps the close tooltip from popping up on every open.
51
+ * - `FocusScope restoreFocus` — on close the focus goes back to the row that opened it.
52
+ * There is no `contain`: Tab may walk out to the list, which is still live.
53
+ * - `useKeyboard` — Escape closes it while the focus is INSIDE the drawer. A listener on
54
+ * the window closed it from anywhere, competing with every other Escape on the page (a
55
+ * search field clearing, a list dropping its selection).
56
+ */
57
+ export function Peek({ open, title, subtitle, onClose, onPromote, actions, children, }) {
58
+ const copy = useUiCopy();
59
+ const { keyboardProps } = useKeyboard({
60
+ onKeyDown: (e) => {
61
+ if (e.key === "Escape")
62
+ onClose();
63
+ else
64
+ e.continuePropagation();
65
+ },
66
+ });
67
+ if (!open)
68
+ return null;
69
+ const hasActions = onPromote !== undefined || actions !== undefined;
70
+ return (_jsx(FocusScope, { restoreFocus: true, children: _jsx("aside", { ...keyboardProps, "aria-label": copy.shell.preview, className: "peek-width absolute top-0 right-0 bottom-0 z-30 overflow-hidden rounded-l-panel border-l border-line-strong bg-surface shadow-window transition-[opacity,translate] duration-(--duration-panel) ease-(--ease-out) starting:translate-x-4 starting:opacity-0 max-sm:fixed max-sm:top-auto max-sm:left-0 max-sm:max-h-(--size-sheet) max-sm:w-full max-sm:rounded-tr-panel max-sm:rounded-bl-none max-sm:border-t max-sm:border-l-0 max-sm:starting:translate-x-0 max-sm:starting:translate-y-4", children: _jsxs(Dialog, { className: "flex h-full max-h-[inherit] flex-col outline-0", children: [_jsxs("header", { className: "shrink-0 border-b border-line", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2 px-3 py-2", children: [_jsx(Tag, { tone: "info", children: copy.shell.previewTag }), _jsx(Heading, { slot: "title", level: 2, className: "min-w-0 flex-1 truncate text-meta font-medium text-ink", children: title }), _jsx(IconButton, { icon: X, label: copy.shell.closePreview, onClick: onClose })] }), subtitle === undefined ? null : (_jsx("p", { className: "truncate px-3 pb-2 text-micro tabular-nums text-muted", children: subtitle }))] }), hasActions ? (_jsxs("div", { className: "flex min-w-0 shrink-0 flex-wrap items-center gap-1.5 border-b border-line px-3 py-1.5 max-sm:order-last max-sm:border-t max-sm:border-b-0 max-sm:pt-2 max-sm:safe-bottom", children: [onPromote === undefined ? null : (_jsx(Button, { role: "secondary", size: "sm", icon: SquareArrowOutUpRight, onClick: onPromote, children: copy.shell.openAsPanel })), actions] })) : null, _jsx("div", { className: cn("min-h-0 flex-1 overflow-y-auto px-3 py-3", !hasActions && "max-sm:safe-bottom"), children: _jsx(DensityProvider, { value: "compact", children: children }) })] }) }) }));
71
+ }
@@ -0,0 +1 @@
1
+ export { Peek, type PeekProps } from "./Peek.js";
@@ -0,0 +1,2 @@
1
+ /* Where a view is mounted without losing track of where it came from. */
2
+ export { Peek } from "./Peek.js";
@@ -0,0 +1,14 @@
1
+ import { type ReactNode } from "react";
2
+ /**
3
+ * One cell of a `DataTable` that edits in place: its drawing becomes a button that opens
4
+ * `editor` in a popover anchored to the cell.
5
+ *
6
+ * Internal to the table family: a column asks for it with `DataColumn.edit`.
7
+ */
8
+ export declare function CellEditor({ label, numeric, editor, children, }: {
9
+ /** The verb and the column: «Editar vencimiento». Names the button and the popover. */
10
+ label: string;
11
+ numeric: boolean;
12
+ editor: (done: () => void) => ReactNode;
13
+ children: ReactNode;
14
+ }): import("react").JSX.Element;
@@ -0,0 +1,83 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn, focusRing } from "../../foundations/variants.js";
4
+ import { Popover } from "../overlay/Popover.js";
5
+ import { Pencil } from "lucide-react";
6
+ import { useEffect, useId, useRef } from "react";
7
+ import { Button as AriaButton, Dialog, DialogTrigger } from "react-aria-components";
8
+ /* ==================================================================== *
9
+ * CELL EDITOR — change one value of a list without leaving the list.
10
+ *
11
+ * Context: the salesperson of an invoice, its due date, its stage. Opening the record to
12
+ * change one field and coming back is three screens for one value, and the person loses
13
+ * the row they were on. Odoo answers it with the editable list; this is the step before it:
14
+ * the list stays a list, and ONE cell opens a small popover with the right control —
15
+ * a `Picker`, a `Calendar`, a `Select` — anchored to the cell, the way a date picker opens
16
+ * its calendar under its field.
17
+ *
18
+ * ── The cell IS the button ──────────────────────────────────────────
19
+ *
20
+ * The whole drawing of the cell is the trigger, not a pencil on its side: the target is
21
+ * the value the eye is already on (Fitts), and a column of twelve pencils is noise. The
22
+ * pencil only appears where the pointer or the keyboard is — on hover and on visible
23
+ * focus — the same rule `RowActions` follows: an affordance without a subject yet is not
24
+ * shown. That is also why an editable column must not draw links: they would be a button
25
+ * inside a button.
26
+ *
27
+ * ── React Aria's dialog trigger ─────────────────────────────────────
28
+ *
29
+ * `DialogTrigger` + `Popover` + `Dialog`: Escape and a click outside close it, and the
30
+ * focus goes back to the cell it came from, so the keyboard continues down the column.
31
+ * What commits is the editor's own gesture (choosing a person, pressing a day), and then
32
+ * it calls `done`. Closing without that gesture changes nothing — Escape is «cancel».
33
+ *
34
+ * The button's name is the verb plus the value («Editar vencimiento 15/11/2026»): the
35
+ * value alone does not say it can be changed, the verb alone does not say what it is.
36
+ * ==================================================================== */
37
+ /** The first control of the editor gets the focus, unless the editor already placed it
38
+ * (a `Calendar` with `autoFocus`). Arriving in a popover with the focus on its frame is one
39
+ * extra Tab before the first key does anything. */
40
+ function useFocusFirst(scope) {
41
+ useEffect(() => {
42
+ const frame = requestAnimationFrame(() => {
43
+ const root = scope.current;
44
+ if (root === null)
45
+ return;
46
+ const active = document.activeElement;
47
+ if (active !== null && active !== root && root.contains(active))
48
+ return;
49
+ for (const selector of [
50
+ "input:not([disabled])",
51
+ /* A calendar: the day the keyboard is on, not the «previous month» arrow. */
52
+ '[role="grid"] [tabindex="0"]',
53
+ "button:not([disabled])",
54
+ '[tabindex="0"]',
55
+ ]) {
56
+ const target = root.querySelector(selector);
57
+ if (target !== null) {
58
+ target.focus();
59
+ return;
60
+ }
61
+ }
62
+ });
63
+ return () => cancelAnimationFrame(frame);
64
+ }, [scope]);
65
+ }
66
+ function EditorDialog({ label, editor, }) {
67
+ const ref = useRef(null);
68
+ useFocusFirst(ref);
69
+ return (_jsx(Dialog, { ref: ref, "aria-label": label, className: "max-h-[inherit] max-w-sm min-w-56 overflow-auto p-2 outline-0", children: ({ close }) => editor(close) }));
70
+ }
71
+ /**
72
+ * One cell of a `DataTable` that edits in place: its drawing becomes a button that opens
73
+ * `editor` in a popover anchored to the cell.
74
+ *
75
+ * Internal to the table family: a column asks for it with `DataColumn.edit`.
76
+ */
77
+ export function CellEditor({ label, numeric, editor, children, }) {
78
+ const verb = useId();
79
+ const shown = useId();
80
+ return (
81
+ /* The row may open its record on click; changing one cell must not also open it. */
82
+ _jsx("div", { onClick: (event) => event.stopPropagation(), className: "-mx-1 min-w-0", children: _jsxs(DialogTrigger, { children: [_jsxs(AriaButton, { "aria-labelledby": `${verb} ${shown}`, className: ({ isFocusVisible }) => cn("group/edit flex w-full min-w-0 cursor-pointer items-center gap-1.5 rounded-mark px-1 text-inherit outline-0 transition-colors duration-(--duration-instant) hover:bg-ink/6 pressed:bg-ink/10 aria-expanded:bg-primary/8", numeric ? "flex-row-reverse text-right" : "text-left", focusRing({ isFocusVisible })), children: [_jsx("span", { id: verb, hidden: true, children: label }), _jsx("span", { id: shown, className: "min-w-0 flex-1 truncate", children: children }), _jsx(Pencil, { "aria-hidden": true, strokeWidth: 2, className: "size-(--size-icon-sm) shrink-0 text-muted opacity-0 transition-opacity duration-(--duration-instant) group-hover/edit:opacity-100 group-focus-visible/edit:opacity-100 group-aria-expanded/edit:opacity-100" })] }), _jsx(Popover, { placement: numeric ? "bottom end" : "bottom start", children: _jsx(EditorDialog, { label: label, editor: editor }) })] }) }));
83
+ }