@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,208 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useUiCopy } from "../../foundations/copy.js";
4
+ import { useSurface } from "../../foundations/surface.js";
5
+ import { Button } from "../button/Button.js";
6
+ import { PopoverTrigger } from "../overlay/PopoverTrigger.js";
7
+ import { ArrowRight, Mail, MessageCircle, Phone } from "lucide-react";
8
+ /* ==================================================================== *
9
+ * WHO — the person as a datum, not as a string of text.
10
+ *
11
+ * «andres.g@sincpro.com.bo» in a cell is a correct and unreadable datum: twelve
12
+ * rows of emails get read letter by letter. An initial in a circle is
13
+ * recognized without reading, which is what is needed when the question is «who
14
+ * does this belong to?» and not «what is their email?».
15
+ *
16
+ * ── The color comes out of the name and nobody picks it ──────────────
17
+ *
18
+ * A hash of the name against the tone palette. It is deterministic —the same
19
+ * person always has the same color, on every screen— and that is why the color
20
+ * gets to mean something. If the consumer picked it, the same person would be
21
+ * green in one list and blue in another, and recognition without reading is lost
22
+ * entirely.
23
+ *
24
+ * The color here is NOT a state: it is identity. That is why it uses the tone
25
+ * palette but never `bad` nor `warn` — a person is not «in error».
26
+ * ==================================================================== */
27
+ const PALETTE = [
28
+ "bg-info/15 text-info",
29
+ "bg-good/15 text-good",
30
+ "bg-accent/25 text-ink",
31
+ "bg-primary/12 text-primary",
32
+ "bg-ink/10 text-ink",
33
+ ];
34
+ /** The initials: two, and from the first two WORDS. «Ana María Gutiérrez» gives
35
+ * `AG` and not `AM` — the surname distinguishes more than the middle name. */
36
+ function initials(name) {
37
+ const parts = name
38
+ .trim()
39
+ .split(/\s+/)
40
+ .filter((p) => p.length > 0);
41
+ const first = parts[0]?.[0] ?? "?";
42
+ const last = parts.length > 1 ? parts[parts.length - 1]?.[0] : undefined;
43
+ return `${first}${last ?? ""}`.toUpperCase();
44
+ }
45
+ /* The same five identities on the dark chrome. The canvas palette there composed its tints
46
+ over the navy and left ink letters on a dark disc — 1.61:1, unreadable (measured on the
47
+ application bar). Same index, so a person keeps their color across the two surfaces. */
48
+ const CHROME_PALETTE = [
49
+ "bg-chrome-link/20 text-chrome-link",
50
+ "bg-chrome-good/20 text-chrome-good",
51
+ "bg-chrome-accent/12 text-chrome-accent",
52
+ "bg-chrome-link/14 text-chrome-ink",
53
+ "bg-chrome-ink/14 text-chrome-ink",
54
+ ];
55
+ function toneOf(name, surface = "canvas") {
56
+ let h = 0;
57
+ for (const ch of name)
58
+ h = (h * 31 + ch.codePointAt(0)) % 100000;
59
+ const palette = surface === "chrome" ? CHROME_PALETTE : PALETTE;
60
+ return palette[h % palette.length];
61
+ }
62
+ /* The text doesn't go below `micro`: two initials at 10 px can't be read, and an
63
+ unreadable initial is a colored circle — which is exactly what we don't want.
64
+ What changes between sizes is the circle. */
65
+ const SIZE = {
66
+ sm: "size-5 text-micro",
67
+ md: "size-6 text-micro",
68
+ lg: "size-8 text-meta",
69
+ /* The head of a contact card: the face is what the card is about. */
70
+ xl: "size-12 text-body",
71
+ };
72
+ /* Two initials, so the overlap stays short of the second letter. The ring is
73
+ 1px: a 2px ring of the surface color reads as a gap between people, which
74
+ is the thing a stack is not. */
75
+ const OVERLAP = {
76
+ sm: "-ml-1.5",
77
+ md: "-ml-2",
78
+ lg: "-ml-2.5",
79
+ xl: "-ml-3.5",
80
+ };
81
+ export function Avatar({ name, size = "md",
82
+ /** The photo, if there is one. Without it the initials go, which is the normal
83
+ * case here. */
84
+ src, }) {
85
+ const surface = useSurface();
86
+ if (src !== undefined) {
87
+ return (
88
+ /* The photo lies over the initials, drawn invisible: an image has no text baseline, so
89
+ a chip with a photo sat on the image's bottom edge in a row aligned by baseline (a
90
+ `DataField`), higher than the label next to it. The hidden initials give the photo
91
+ the same baseline the initials disc has. */
92
+ _jsxs("span", { title: name, className: `relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-pill ${SIZE[size]}`, children: [_jsx("span", { "aria-hidden": true, className: "invisible", children: initials(name) }), _jsx("img", { src: src, alt: name, className: "absolute inset-0 size-full object-cover" })] }));
93
+ }
94
+ return (_jsx("span", { title: name, "aria-hidden": true, className: `inline-flex shrink-0 items-center justify-center rounded-pill font-medium select-none ${SIZE[size]} ${toneOf(name, surface)}`, children: initials(name) }));
95
+ }
96
+ /** The three contact links, built from what people type. `wa.me` takes digits only — a `+`,
97
+ * a space or a dash breaks it — and `tel:` takes digits and the leading `+`. */
98
+ function contactRows(contact, copy) {
99
+ const rows = [];
100
+ if (contact.email !== undefined && contact.email !== "") {
101
+ rows.push({
102
+ key: "email",
103
+ icon: Mail,
104
+ label: copy.email,
105
+ value: contact.email,
106
+ href: `mailto:${contact.email}`,
107
+ });
108
+ }
109
+ if (contact.phone !== undefined && contact.phone !== "") {
110
+ rows.push({
111
+ key: "call",
112
+ icon: Phone,
113
+ label: copy.call,
114
+ value: contact.phone,
115
+ href: `tel:${contact.phone.replace(/[^\d+]/g, "")}`,
116
+ });
117
+ }
118
+ if (contact.whatsapp !== undefined && contact.whatsapp !== "") {
119
+ rows.push({
120
+ key: "whatsapp",
121
+ icon: MessageCircle,
122
+ label: copy.whatsapp,
123
+ value: contact.whatsapp,
124
+ href: `https://wa.me/${contact.whatsapp.replace(/\D/g, "")}`,
125
+ });
126
+ }
127
+ return rows;
128
+ }
129
+ /**
130
+ * Who a person is and how to reach them: the face, the name, the role, and one row per way
131
+ * of contact — write, call, WhatsApp — then their record.
132
+ *
133
+ * Context: in an ERP the question after «who is the salesperson of this invoice?» is almost
134
+ * always «how do I reach her?». Copying an email out of another screen to paste it into the
135
+ * mail client is the detour this card removes. Each way of contact is a ROW and not a bare
136
+ * icon: the address and the number are drawn, because often what the person wants is to read
137
+ * the number aloud, not to dial it from the browser. Every row is a real link (`mailto:`,
138
+ * `tel:`, `wa.me`), so middle-click, «copy address» and the phone's own dialer work.
139
+ *
140
+ * It is what opens from a `UserChip` with `contact`, and it is exported on its own for the
141
+ * side panel of a record, where the same person is shown without a chip to open.
142
+ */
143
+ export function ContactCard({ name, src, role, contact = {}, onOpen }) {
144
+ const copy = useUiCopy().person;
145
+ const rows = contactRows(contact, copy);
146
+ return (
147
+ /* React events bubble through the popover's portal to the React parents of the chip —
148
+ a table row that opens on click among them. A click inside the card is not a click
149
+ on the row. */
150
+ _jsxs("div", { className: "flex w-64 max-w-full flex-col gap-3", onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx("span", { "aria-hidden": true, className: "flex shrink-0", children: _jsx(Avatar, { name: name, size: "xl", ...(src === undefined ? {} : { src }) }) }), _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate text-lead font-semibold text-ink", title: name, children: name }), role === undefined ? null : (_jsx("span", { className: "line-clamp-2 text-meta text-muted", title: role, children: role }))] })] }), rows.length === 0 ? null : (_jsx("ul", { className: "-mx-1 flex flex-col", children: rows.map(({ key, icon: Icon, label, value, href }) => (_jsx("li", { children: _jsxs("a", { href: href, "data-contact": key, ...(key === "whatsapp" ? { target: "_blank", rel: "noreferrer" } : {}), className: "flex min-w-0 items-center gap-2.5 rounded-control px-1 py-1.5 no-underline outline-primary outline-offset-2 hover:bg-ink/6 focus-visible:outline-2 forced-colors:outline-[Highlight]", children: [_jsx(Icon, { "aria-hidden": true, className: "size-(--size-icon-md) shrink-0 text-primary", strokeWidth: 2 }), _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "text-meta font-medium text-ink", children: label }), _jsx("span", { className: "truncate text-micro tabular-nums text-muted", children: value })] })] }) }, key))) })), onOpen === undefined ? null : (_jsx("div", { className: "flex justify-end border-t border-line pt-2", children: _jsx(Button, { role: "ghost", size: "sm", icon: ArrowRight, onClick: onOpen, children: copy.open }) }))] }));
151
+ }
152
+ /** The chip as the opener of its card. A native button that takes what `PopoverTrigger`
153
+ * hands its trigger — `ref`, the press handlers, `expanded` and `haspopup` — the same
154
+ * contract the system's buttons keep. */
155
+ function ChipButton({ expanded, haspopup, children, ref, ...native }) {
156
+ return (_jsx("button", { ...native, ref: ref, type: "button", ...(haspopup === undefined ? {} : { "aria-haspopup": haspopup }), ...(expanded === undefined ? {} : { "aria-expanded": expanded }),
157
+ /* The hover tint is a pseudo-element that spills past the box, not padding: padding
158
+ moved the chip off the column of values, and negative margins to undo it made the
159
+ chip wider than the room its parent measured — and it truncated itself. */
160
+ className: "relative isolate inline-flex max-w-full min-w-0 cursor-pointer items-center gap-2 rounded-control text-left outline-primary outline-offset-4 before:absolute before:-inset-x-1 before:-inset-y-0.5 before:-z-10 before:rounded-control before:transition-colors before:duration-(--duration-instant) hover:before:bg-ink/6 focus-visible:outline-2 aria-expanded:before:bg-ink/6 forced-colors:outline-[Highlight]", children: children }));
161
+ }
162
+ /**
163
+ * The person with their name next to them. For a cell, a list row, a field in read mode or
164
+ * a fact of a record.
165
+ *
166
+ * Context: a chip is a button ONLY when it has somewhere to go. With `contact` or `onOpen` it
167
+ * opens the `ContactCard` (press, Enter or Space — React Aria's `DialogTrigger` through
168
+ * `PopoverTrigger`: focus goes into the card, Escape and a click outside close it, and focus
169
+ * comes back to the chip). Without them it stays text: a button that does nothing is a lie a
170
+ * keyboard user pays for with one more Tab stop on every row.
171
+ *
172
+ * It opens on PRESS and not on hover: the card holds links, and content that has to be
173
+ * reached with the pointer or the keyboard cannot disappear when the pointer leaves the name.
174
+ */
175
+ export function UserChip({ name, size = "md", src, detail, role, contact, onOpen, }) {
176
+ const face = (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": true, className: "flex shrink-0", children: _jsx(Avatar, { name: name, size: size, ...(src === undefined ? {} : { src }) }) }), _jsx("span", { className: "min-w-0 truncate text-meta text-ink", children: name }), detail === undefined ? null : (_jsx("span", { className: "min-w-0 shrink truncate text-micro text-muted", children: detail }))] }));
177
+ const reachable = onOpen !== undefined ||
178
+ (contact !== undefined &&
179
+ Object.values(contact).some((v) => v !== undefined && v !== ""));
180
+ if (!reachable) {
181
+ return _jsx("span", { className: "inline-flex min-w-0 items-center gap-2", children: face });
182
+ }
183
+ return (_jsx(PopoverTrigger, { label: name, trigger: _jsx(ChipButton, { children: face }), children: (close) => (_jsx(ContactCard, { name: name, ...(src === undefined ? {} : { src }), ...(role === undefined ? {} : { role }), ...(contact === undefined ? {} : { contact }), ...(onOpen === undefined
184
+ ? {}
185
+ : {
186
+ onOpen: () => {
187
+ close();
188
+ onOpen();
189
+ },
190
+ }) })) }));
191
+ }
192
+ /** Several people in little width: the widget of a to-many relation.
193
+ *
194
+ * Context: they overlap and get cut off at `max`. Five avatars in a row are five
195
+ * things; five overlapped with a «+3» are ONE thing that has eight inside, which
196
+ * is what the cell means to say.
197
+ *
198
+ * The `title` carries the whole list: whoever needs the names has them without
199
+ * the column paying for their width. */
200
+ export function UserStack({ names, max = 4, size = "md", }) {
201
+ const copy = useUiCopy();
202
+ if (names.length === 0) {
203
+ return (_jsx("span", { className: "text-meta text-muted italic", title: copy.person.unassigned, children: copy.person.unassigned }));
204
+ }
205
+ const visible = names.length <= max ? names : names.slice(0, max - 1);
206
+ const rest = names.length - visible.length;
207
+ return (_jsxs("span", { className: "inline-flex items-center", title: names.join(", "), children: [visible.map((n, i) => (_jsx("span", { className: i === 0 ? "" : OVERLAP[size], children: _jsx("span", { className: "block rounded-pill ring-1 ring-surface", children: _jsx(Avatar, { name: n, size: size }) }) }, n))), rest <= 0 ? null : (_jsxs("span", { className: `${OVERLAP[size]} inline-flex items-center justify-center rounded-pill bg-ink/10 font-medium tabular-nums text-muted ring-1 ring-surface ${SIZE[size]}`, children: ["+", rest] }))] }));
208
+ }
@@ -0,0 +1,69 @@
1
+ import type { Tone } from "../../foundations/types.js";
2
+ import { type FormMode } from "../field/mode.js";
3
+ import type { LucideIcon } from "lucide-react";
4
+ import { type ReactNode } from "react";
5
+ /** What the floating summary of the related record says. */
6
+ export type RelationSummary = {
7
+ /** The title of the card. The relation's name when absent. */
8
+ title?: string;
9
+ /** The state of the related record — «al día», «con deuda». */
10
+ tag?: {
11
+ label: string;
12
+ tone?: Tone;
13
+ };
14
+ /** Two to four facts that answer the usual question about it: the debt, the credit
15
+ * limit, the city, the phone. More is a record, not a summary. */
16
+ facts?: readonly {
17
+ label: string;
18
+ value: ReactNode;
19
+ }[];
20
+ };
21
+ type RelationMedia = {
22
+ src?: string;
23
+ alt?: string;
24
+ icon?: LucideIcon;
25
+ };
26
+ export type RelationCardProps = {
27
+ name: string;
28
+ secondary?: ReactNode;
29
+ media?: RelationMedia;
30
+ summary?: RelationSummary;
31
+ /** «Abrir»: go to the related record. */
32
+ onOpen?: () => void;
33
+ /** The record's address, when it has one: «Abrir» becomes a real link. */
34
+ href?: string;
35
+ };
36
+ /**
37
+ * The summary of a related record: picture, title, its tag, a handful of facts and «Abrir».
38
+ *
39
+ * Context: it answers the question a person has about the customer of an invoice without
40
+ * leaving the invoice — «does it owe us?», «what is its NIT?» — and, when the answer is
41
+ * «I need more», one press takes them to the record. It is what floats from a
42
+ * `RelationValue`, and it is exported on its own for a side panel.
43
+ */
44
+ export declare function RelationCard({ name, secondary, media, summary, onOpen, href, }: RelationCardProps): import("react").JSX.Element;
45
+ export type RelationValueProps = {
46
+ /** The related record's name. Empty, `null` or absent = no record: said, in muted. */
47
+ name?: string | null;
48
+ /** What tells it apart: a NIT, a code, a city. Small and quiet, under the name. */
49
+ secondary?: ReactNode;
50
+ /** Its picture or icon, on the left. */
51
+ media?: RelationMedia;
52
+ /** Go to the related record. The name becomes a link. */
53
+ onOpen?: () => void;
54
+ /** The related record's address: the name becomes a real `<a>` (middle-click, copy). */
55
+ href?: string;
56
+ /** Present = an info button next to the name opens the `RelationCard`. */
57
+ summary?: RelationSummary;
58
+ /** The control that EDITS this relation — a `Picker` — drawn instead in edit mode. */
59
+ editor?: ReactNode;
60
+ /** Forces the mode; by default the form's. Only matters with `editor`. */
61
+ mode?: FormMode;
62
+ };
63
+ /**
64
+ * A many2one as a value: the related record's name as a link, with its secondary line and
65
+ * picture, and its summary one press away. Works in a table cell, a list row, a `Record.Facts`
66
+ * value and a form field (read mode; pass the `Picker` as `editor` for edit mode).
67
+ */
68
+ export declare function RelationValue({ name, secondary, media, onOpen, href, summary, editor, mode, }: RelationValueProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
69
+ export {};
@@ -0,0 +1,57 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { fillCopy, useUiCopy } from "../../foundations/copy.js";
4
+ import { cn, focusRing } from "../../foundations/variants.js";
5
+ import { Button, ButtonLink, IconButton } from "../button/Button.js";
6
+ import { CellStack, Thumb } from "../cell/CellStack.js";
7
+ import { FieldIdsContext } from "../field/context.js";
8
+ import { useFormMode } from "../field/mode.js";
9
+ import { PopoverTrigger } from "../overlay/PopoverTrigger.js";
10
+ import { Tag } from "../tag/Tag.js";
11
+ import { ArrowRight, Info } from "lucide-react";
12
+ import { useContext } from "react";
13
+ import { Link } from "react-aria-components";
14
+ /**
15
+ * The summary of a related record: picture, title, its tag, a handful of facts and «Abrir».
16
+ *
17
+ * Context: it answers the question a person has about the customer of an invoice without
18
+ * leaving the invoice — «does it owe us?», «what is its NIT?» — and, when the answer is
19
+ * «I need more», one press takes them to the record. It is what floats from a
20
+ * `RelationValue`, and it is exported on its own for a side panel.
21
+ */
22
+ export function RelationCard({ name, secondary, media, summary = {}, onOpen, href, }) {
23
+ const copy = useUiCopy();
24
+ const title = summary.title ?? name;
25
+ return (
26
+ /* React events bubble through the popover's portal to the React parents of the link —
27
+ a table row that opens on click among them. A click inside the card stays here. */
28
+ _jsxs("div", { className: "flex w-72 max-w-full flex-col gap-3", onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "flex min-w-0 items-start gap-3", children: [media === undefined ? null : (_jsx(Thumb, { size: "lg", ...(media.src === undefined ? {} : { src: media.src }), ...(media.alt === undefined ? {} : { alt: media.alt }), ...(media.icon === undefined ? {} : { icon: media.icon }) })), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsx("span", { className: "text-lead leading-tight font-semibold text-ink", children: title }), secondary === undefined ? null : (_jsx("span", { className: "truncate text-micro tabular-nums text-muted", children: secondary })), summary.tag === undefined ? null : (_jsx("span", { className: "flex", children: _jsx(Tag, { tone: summary.tag.tone ?? "neutral", children: summary.tag.label }) }))] })] }), summary.facts === undefined || summary.facts.length === 0 ? null : (_jsx("dl", { className: "grid grid-cols-2 gap-x-4 gap-y-2 border-t border-line pt-3", children: summary.facts.map((fact) => (_jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("dt", { className: "truncate text-micro text-muted", children: fact.label }), _jsx("dd", { className: "truncate text-meta tabular-nums text-ink", children: fact.value })] }, fact.label))) })), href !== undefined ? (_jsx("div", { className: "flex justify-end border-t border-line pt-2", children: _jsx(ButtonLink, { href: href, role: "ghost", size: "sm", icon: ArrowRight, ...(onOpen === undefined ? {} : { onClick: onOpen }), children: copy.relation.open }) })) : onOpen !== undefined ? (_jsx("div", { className: "flex justify-end border-t border-line pt-2", children: _jsx(Button, { role: "ghost", size: "sm", icon: ArrowRight, onClick: onOpen, children: copy.relation.open }) })) : null] }));
29
+ }
30
+ /**
31
+ * A many2one as a value: the related record's name as a link, with its secondary line and
32
+ * picture, and its summary one press away. Works in a table cell, a list row, a `Record.Facts`
33
+ * value and a form field (read mode; pass the `Picker` as `editor` for edit mode).
34
+ */
35
+ export function RelationValue({ name, secondary, media, onOpen, href, summary, editor, mode, }) {
36
+ const copy = useUiCopy();
37
+ const formMode = useFormMode(mode);
38
+ /* Inside a `Field` the value takes the control's height, so a form that goes from
39
+ editing to reading does not move (the same rule as `ReadValue`). */
40
+ const inField = useContext(FieldIdsContext) !== null;
41
+ if (editor !== undefined && formMode === "edit")
42
+ return _jsx(_Fragment, { children: editor });
43
+ const frame = (child) => inField ? (_jsx("span", { className: "flex min-h-(--size-control-md) min-w-0 items-center", children: child })) : (child);
44
+ if (name === undefined || name === null || name === "") {
45
+ return frame(_jsx("span", { className: "text-meta text-muted italic", children: copy.relation.none }));
46
+ }
47
+ const goes = href !== undefined || onOpen !== undefined;
48
+ const primary = goes ? (_jsx(Link, { ...(href === undefined ? {} : { href }), ...(onOpen === undefined ? {} : { onPress: onOpen }), className: ({ isFocusVisible }) => cn("cursor-pointer rounded-mark text-primary no-underline underline-offset-2 hover:underline", focusRing({ isFocusVisible })), children: name })) : (name);
49
+ return frame(_jsxs("span", { className: "inline-flex max-w-full min-w-0 items-center gap-1", children: [_jsx(CellStack, { primary: primary, ...(secondary === undefined ? {} : { secondary }), ...(media === undefined ? {} : { media }) }), summary === undefined ? null : (_jsx(PopoverTrigger, { label: fillCopy(copy.relationCard.summary, { name }), trigger: _jsx(IconButton, { icon: Info, label: fillCopy(copy.relationCard.summary, { name }), size: "sm" }), children: (close) => (_jsx(RelationCard, { name: name, summary: summary, ...(secondary === undefined ? {} : { secondary }), ...(media === undefined ? {} : { media }), ...(href === undefined ? {} : { href }), ...(onOpen === undefined
50
+ ? {}
51
+ : {
52
+ onOpen: () => {
53
+ close();
54
+ onOpen();
55
+ },
56
+ }) })) }))] }));
57
+ }
@@ -0,0 +1,26 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * The money summary of a document — subtotal, taxes, discounts and the TOTAL — under its
4
+ * lines, aligned to the right.
5
+ *
6
+ * Context: written as two more rows of the lines table, «IVA» and «Total» were the same size
7
+ * and weight as a product line, and the one number the document exists for had to be found.
8
+ * Every invoice people have ever read puts the summary apart, on the right, with the total
9
+ * last and largest: following that convention is reading the document the way it is already
10
+ * read (Jakob's law).
11
+ *
12
+ * Labels left and figures right of a narrow column, both in tabular figures, so the amounts
13
+ * line up digit by digit. The `total` row closes the block: a rule above it, and the figure
14
+ * at the title size.
15
+ */
16
+ export type TotalsRow = {
17
+ label: ReactNode;
18
+ value: ReactNode;
19
+ /** A note under the label — «incluido en el precio», «sobre Bs. 3.601,80». */
20
+ detail?: ReactNode;
21
+ /** The row the block exists for. Usually one, and last. */
22
+ total?: boolean;
23
+ };
24
+ export declare function Totals({ rows }: {
25
+ rows: readonly TotalsRow[];
26
+ }): import("react").JSX.Element;
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function Totals({ rows }) {
3
+ return (_jsx("dl", { className: "ml-auto flex w-full max-w-(--container-search) flex-col gap-1.5", children: rows.map((row, i) => row.total === true ? (_jsxs("div", { className: "mt-1 flex items-baseline justify-between gap-4 border-t border-line-strong pt-2.5", children: [_jsx("dt", { className: "text-body font-semibold text-ink", children: row.label }), _jsx("dd", { className: "text-title font-semibold tabular-nums text-ink", children: row.value })] }, i)) : (_jsxs("div", { className: "flex items-baseline justify-between gap-4", children: [_jsxs("dt", { className: "flex flex-col text-meta text-muted", children: [row.label, row.detail === undefined ? null : (_jsx("span", { className: "text-micro text-muted", children: row.detail }))] }), _jsx("dd", { className: "text-meta tabular-nums text-ink", children: row.value })] }, i))) }));
4
+ }
@@ -0,0 +1,77 @@
1
+ import type { Tone } from "../../foundations/types.js";
2
+ import type { ReactNode } from "react";
3
+ export declare const formatCount: (value: number, locale?: string) => string;
4
+ export declare const formatNumber: (value: number, locale?: string) => string;
5
+ export declare const formatDistance: (value: number, locale?: string) => string;
6
+ /** A label with its number, for the glance at the top of a view. */
7
+ export declare function KeyValue({ label, value }: {
8
+ label: string;
9
+ value: ReactNode;
10
+ }): import("react").JSX.Element;
11
+ /**
12
+ * A number with its unit: right aligned, with tabular figures.
13
+ *
14
+ * Context: this is the detail a table's look depends on. Tabular figures give every digit
15
+ * the same width, so `32.561` lines up across rows exactly as it did in monospace — without
16
+ * making a business report look like a terminal. The unit stays in the same face so the
17
+ * digits keep their column.
18
+ */
19
+ export declare function Numeric({ value, unit, format, }: {
20
+ value: number;
21
+ unit?: string;
22
+ format?: "integer" | "decimal" | "distance";
23
+ }): import("react").JSX.Element;
24
+ /**
25
+ * A real bar, never a spinner.
26
+ *
27
+ * Context: a spinner says «wait» and doesn't say how much; a bar says both
28
+ * things.
29
+ *
30
+ * ── Why the fill is FLAT and the gradient is asked for ──────────────
31
+ *
32
+ * The bar always lived with `bar-brand`, the blue→gold gradient. At 3 px and at
33
+ * 62 % that doesn't read as a brand: it reads as a blue bar with a broken tip.
34
+ * A gradient needs length to be a gradient, and a bar inside a list row doesn't
35
+ * have it.
36
+ *
37
+ * Now the fill is a flat tone —and the tone SAYS something: `good` finished,
38
+ * `warn` is running slow, `bad` failed— and the gradient is asked for with
39
+ * `tone="brand"`, which is the long bar of a live import: there it does have room
40
+ * to be read.
41
+ *
42
+ * And it got fatter, from 3 px to 6 px. At 3 px it isn't a bar, it's a line: it
43
+ * can't be told apart from a separator and the fill isn't visible unless it goes
44
+ * past 20 %.
45
+ */
46
+ export declare function ProgressBar({ value, label, width, tone,
47
+ /** The percentage next to it. It is turned off when the bar lives inside
48
+ * something that already says the figure —a cell with its column— and it
49
+ * would only mean reading it twice. */
50
+ showValue, }: {
51
+ /** 0 to 100. */
52
+ value: number;
53
+ label: string;
54
+ /** The activity bar wants to be longer than a list row; there is no reason for
55
+ * it to be the same number on both sides. */
56
+ width?: string;
57
+ tone?: Tone | "brand";
58
+ showValue?: boolean;
59
+ }): import("react").JSX.Element;
60
+ /**
61
+ * The content of a data cell, with what the system decides and the view does not.
62
+ *
63
+ * Context: `""` is a TRULY EMPTY cell and not a missing datum — they are two
64
+ * different things and the engine tells them apart, so the interface does too. A
65
+ * missing one is passed as `null` and drawn with its mark; an empty string is a
66
+ * value somebody typed, and it gets one too.
67
+ *
68
+ * It used to live in the application, where it couldn't serve the other five
69
+ * tables.
70
+ */
71
+ export declare function Cell({ value,
72
+ /** From how many characters it gets truncated. A 64 character identifier
73
+ * breaks the width of the whole table for a column nobody reads in full. */
74
+ max, }: {
75
+ value: string | number | null | undefined;
76
+ max?: number;
77
+ }): import("react").JSX.Element;
@@ -0,0 +1,119 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { currentCopyLocale, useUiCopy } from "../../foundations/copy.js";
4
+ /**
5
+ * Context: the mono/sans rule decides everything in here. The label goes in
6
+ * PROPORTIONAL —«orígenes», «mediana» are words, not data— and the value in
7
+ * tabular monospace, which is what makes four of these in a row keep their
8
+ * digits in a column.
9
+ *
10
+ * Formats follow the session locale, not a baked-in Spanish `Intl`.
11
+ */
12
+ function integerFormat(locale) {
13
+ return new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
14
+ }
15
+ function decimalFormat(locale) {
16
+ return new Intl.NumberFormat(locale, { maximumFractionDigits: 2 });
17
+ }
18
+ /** Four decimals and not two for distances — they live near zero
19
+ * (`0.0696`, `0.0149`) and with two they turn into `0.07` and `0.01`, which is
20
+ * losing exactly the part that tells one column from another. */
21
+ function distanceFormat(locale) {
22
+ return new Intl.NumberFormat(locale, {
23
+ minimumFractionDigits: 4,
24
+ maximumFractionDigits: 4,
25
+ });
26
+ }
27
+ export const formatCount = (value, locale = currentCopyLocale()) => integerFormat(locale).format(value);
28
+ export const formatNumber = (value, locale = currentCopyLocale()) => decimalFormat(locale).format(value);
29
+ export const formatDistance = (value, locale = currentCopyLocale()) => distanceFormat(locale).format(value);
30
+ /** A label with its number, for the glance at the top of a view. */
31
+ export function KeyValue({ label, value }) {
32
+ return (_jsxs("div", { className: "min-w-0", children: [_jsx("dt", { className: "text-micro text-muted", children: label }), _jsx("dd", { className: "mt-0.5 truncate text-lead tabular-nums text-ink", children: value })] }));
33
+ }
34
+ /**
35
+ * A number with its unit: right aligned, with tabular figures.
36
+ *
37
+ * Context: this is the detail a table's look depends on. Tabular figures give every digit
38
+ * the same width, so `32.561` lines up across rows exactly as it did in monospace — without
39
+ * making a business report look like a terminal. The unit stays in the same face so the
40
+ * digits keep their column.
41
+ */
42
+ export function Numeric({ value, unit, format = "integer", }) {
43
+ const text = format === "distance"
44
+ ? formatDistance(value)
45
+ : format === "decimal"
46
+ ? formatNumber(value)
47
+ : formatCount(value);
48
+ return (_jsxs("span", { className: "block text-right text-body tabular-nums whitespace-nowrap text-ink", children: [_jsx("span", { className: "tabular-nums", children: text }), unit === undefined ? null : _jsx("span", { className: "ml-1 text-muted", children: unit })] }));
49
+ }
50
+ /**
51
+ * A real bar, never a spinner.
52
+ *
53
+ * Context: a spinner says «wait» and doesn't say how much; a bar says both
54
+ * things.
55
+ *
56
+ * ── Why the fill is FLAT and the gradient is asked for ──────────────
57
+ *
58
+ * The bar always lived with `bar-brand`, the blue→gold gradient. At 3 px and at
59
+ * 62 % that doesn't read as a brand: it reads as a blue bar with a broken tip.
60
+ * A gradient needs length to be a gradient, and a bar inside a list row doesn't
61
+ * have it.
62
+ *
63
+ * Now the fill is a flat tone —and the tone SAYS something: `good` finished,
64
+ * `warn` is running slow, `bad` failed— and the gradient is asked for with
65
+ * `tone="brand"`, which is the long bar of a live import: there it does have room
66
+ * to be read.
67
+ *
68
+ * And it got fatter, from 3 px to 6 px. At 3 px it isn't a bar, it's a line: it
69
+ * can't be told apart from a separator and the fill isn't visible unless it goes
70
+ * past 20 %.
71
+ */
72
+ export function ProgressBar({ value, label, width = "w-24", tone = "brand",
73
+ /** The percentage next to it. It is turned off when the bar lives inside
74
+ * something that already says the figure —a cell with its column— and it
75
+ * would only mean reading it twice. */
76
+ showValue = true, }) {
77
+ const clamped = Math.max(0, Math.min(100, Math.round(value)));
78
+ const fill = tone === "brand" ? "bar-brand" : FILL[tone];
79
+ return (_jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { className: `h-(--size-track) overflow-hidden rounded-pill bg-ink/10 ${width}`, role: "progressbar", "aria-valuenow": clamped, "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": label, children: _jsx("span", { className: `block h-full rounded-pill transition-[width] duration-(--duration-panel) ease-out ${fill}`, style: { width: `${clamped}%` } }) }), showValue ? (_jsxs("span", { className: "text-meta tabular-nums text-muted", children: [clamped, "%"] })) : null] }));
80
+ }
81
+ const FILL = {
82
+ neutral: "bg-muted",
83
+ info: "bg-info",
84
+ good: "bg-good",
85
+ warn: "bg-warn",
86
+ bad: "bg-bad",
87
+ accent: "bg-accent",
88
+ };
89
+ /**
90
+ * The content of a data cell, with what the system decides and the view does not.
91
+ *
92
+ * Context: `""` is a TRULY EMPTY cell and not a missing datum — they are two
93
+ * different things and the engine tells them apart, so the interface does too. A
94
+ * missing one is passed as `null` and drawn with its mark; an empty string is a
95
+ * value somebody typed, and it gets one too.
96
+ *
97
+ * It used to live in the application, where it couldn't serve the other five
98
+ * tables.
99
+ */
100
+ export function Cell({ value,
101
+ /** From how many characters it gets truncated. A 64 character identifier
102
+ * breaks the width of the whole table for a column nobody reads in full. */
103
+ max = 48, }) {
104
+ const copy = useUiCopy();
105
+ if (value === null || value === undefined) {
106
+ return (_jsx("span", { className: "text-muted italic", title: copy.cell.missing, children: copy.cell.missing }));
107
+ }
108
+ if (typeof value === "number")
109
+ return _jsx(Numeric, { value: value, format: "decimal" });
110
+ if (value === "") {
111
+ return (_jsx("span", { className: "text-line-strong", title: copy.cell.empty, children: "\u00B7" }));
112
+ }
113
+ if (value.length <= max)
114
+ return _jsx(_Fragment, { children: value });
115
+ /* It is truncated with the whole value in the `title`: whoever needs the full
116
+ id has it a hover away, and whoever is reading the column next to it doesn't
117
+ pay for its width. */
118
+ return (_jsxs("span", { title: value, className: "truncate", children: [value.slice(0, max), "\u2026"] }));
119
+ }
@@ -0,0 +1,8 @@
1
+ export { Copyable, type CopyableProps } from "./Copyable.js";
2
+ export { DateValue, formatDateTime, formatRelative, } from "./Fecha.js";
3
+ export { Meter, type MeterLevel, meterLevel, type MeterProps, type MeterThresholds, } from "./Meter.js";
4
+ export { Metric, type MetricProps, Metrics } from "./Metric.js";
5
+ export { Avatar, type AvatarSize, ContactCard, type ContactCardProps, type PersonContact, UserChip, type UserChipProps, UserStack, } from "./Person.js";
6
+ export { RelationCard, type RelationCardProps, type RelationSummary, RelationValue, type RelationValueProps, } from "./Relation.js";
7
+ export { Totals, type TotalsRow } from "./Totals.js";
8
+ export { Cell, formatCount, formatDistance, formatNumber, KeyValue, Numeric, ProgressBar, } from "./Value.js";
@@ -0,0 +1,9 @@
1
+ /* Show a number or a datum so that it reads as such and not as text. */
2
+ export { Copyable } from "./Copyable.js";
3
+ export { DateValue, formatDateTime, formatRelative, } from "./Fecha.js";
4
+ export { Meter, meterLevel, } from "./Meter.js";
5
+ export { Metric, Metrics } from "./Metric.js";
6
+ export { Avatar, ContactCard, UserChip, UserStack, } from "./Person.js";
7
+ export { RelationCard, RelationValue, } from "./Relation.js";
8
+ export { Totals } from "./Totals.js";
9
+ export { Cell, formatCount, formatDistance, formatNumber, KeyValue, Numeric, ProgressBar, } from "./Value.js";