@sincpro/web-ui 0.1.0 → 1.0.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 (198) hide show
  1. package/README.md +40 -13
  2. package/dist/components/activity/Activity.d.ts +69 -0
  3. package/dist/components/activity/Activity.js +279 -0
  4. package/dist/components/activity/ActivityBadge.d.ts +16 -0
  5. package/dist/components/activity/ActivityBadge.js +29 -0
  6. package/dist/components/{chatter → activity}/Messages.d.ts +2 -2
  7. package/dist/components/{chatter → activity}/Messages.js +9 -6
  8. package/dist/components/activity/index.d.ts +4 -0
  9. package/dist/components/activity/index.js +5 -0
  10. package/dist/components/assist/AssistMark.d.ts +3 -3
  11. package/dist/components/assist/AssistMark.js +1 -1
  12. package/dist/components/assist/assist.d.ts +1 -1
  13. package/dist/components/assist/assist.js +1 -1
  14. package/dist/components/calendar/RecordCalendar.d.ts +48 -0
  15. package/dist/components/calendar/RecordCalendar.js +101 -0
  16. package/dist/components/calendar/index.d.ts +1 -0
  17. package/dist/components/calendar/index.js +1 -0
  18. package/dist/components/form/Sheet.d.ts +4 -0
  19. package/dist/components/form/Sheet.js +16 -5
  20. package/dist/components/gantt/RecordGantt.d.ts +44 -0
  21. package/dist/components/gantt/RecordGantt.js +110 -0
  22. package/dist/components/gantt/index.d.ts +1 -0
  23. package/dist/components/gantt/index.js +1 -0
  24. package/dist/components/index.d.ts +4 -1
  25. package/dist/components/index.js +4 -1
  26. package/dist/components/lines/LineItems.d.ts +45 -5
  27. package/dist/components/lines/LineItems.js +106 -18
  28. package/dist/components/listing/BoardColumns.d.ts +39 -0
  29. package/dist/components/listing/BoardColumns.js +138 -0
  30. package/dist/components/listing/Listing.js +1 -1
  31. package/dist/components/listing/ListingBar.d.ts +46 -2
  32. package/dist/components/listing/ListingBar.js +47 -5
  33. package/dist/components/listing/PivotExplorer.d.ts +34 -0
  34. package/dist/components/listing/PivotExplorer.js +86 -0
  35. package/dist/components/listing/RecordCard.d.ts +6 -8
  36. package/dist/components/listing/RecordCard.js +13 -7
  37. package/dist/components/listing/Row.d.ts +25 -2
  38. package/dist/components/listing/Row.js +30 -13
  39. package/dist/components/listing/index.d.ts +2 -0
  40. package/dist/components/listing/index.js +2 -0
  41. package/dist/components/listing/set.d.ts +1 -1
  42. package/dist/components/navigation/index.d.ts +0 -2
  43. package/dist/components/navigation/index.js +2 -3
  44. package/dist/components/record/RecordAudit.d.ts +17 -0
  45. package/dist/components/record/RecordAudit.js +27 -0
  46. package/dist/components/record/RecordHeader.d.ts +27 -17
  47. package/dist/components/record/RecordHeader.js +64 -20
  48. package/dist/components/record/RecordNotices.d.ts +37 -0
  49. package/dist/components/record/RecordNotices.js +75 -0
  50. package/dist/components/record/RecordRibbon.d.ts +13 -0
  51. package/dist/components/record/RecordRibbon.js +22 -0
  52. package/dist/components/record/ThumbBar.js +1 -1
  53. package/dist/components/record/index.d.ts +2 -0
  54. package/dist/components/record/index.js +2 -0
  55. package/dist/components/search/CriteriaChips.d.ts +6 -1
  56. package/dist/components/search/CriteriaChips.js +71 -20
  57. package/dist/components/search/SearchBar.d.ts +4 -1
  58. package/dist/components/search/SearchBar.js +15 -7
  59. package/dist/components/timeline/RecordTimeline.d.ts +60 -0
  60. package/dist/components/timeline/RecordTimeline.js +214 -0
  61. package/dist/components/timeline/TimelineToolbar.d.ts +30 -0
  62. package/dist/components/timeline/TimelineToolbar.js +31 -0
  63. package/dist/components/timeline/index.d.ts +3 -0
  64. package/dist/components/timeline/index.js +3 -0
  65. package/dist/components/timeline/time.d.ts +43 -0
  66. package/dist/components/timeline/time.js +135 -0
  67. package/dist/foundations/copy.d.ts +194 -18
  68. package/dist/foundations/copy.es.js +142 -15
  69. package/dist/foundations/copy.js +133 -11
  70. package/dist/foundations/index.d.ts +1 -1
  71. package/dist/foundations/match.d.ts +23 -0
  72. package/dist/foundations/match.js +63 -0
  73. package/dist/foundations/slots.d.ts +1 -1
  74. package/dist/foundations/slots.js +8 -3
  75. package/dist/foundations/types.d.ts +7 -0
  76. package/dist/foundations/types.js +0 -36
  77. package/dist/primitives/block/index.d.ts +1 -1
  78. package/dist/primitives/block/index.js +1 -1
  79. package/dist/primitives/cell/CellStack.d.ts +7 -1
  80. package/dist/primitives/cell/CellStack.js +5 -2
  81. package/dist/primitives/choice/ComboBox.d.ts +3 -0
  82. package/dist/primitives/choice/ComboBox.js +10 -6
  83. package/dist/primitives/choice/KanbanState.d.ts +14 -0
  84. package/dist/primitives/choice/KanbanState.js +37 -0
  85. package/dist/primitives/choice/Priority.d.ts +20 -0
  86. package/dist/primitives/choice/Priority.js +41 -0
  87. package/dist/primitives/choice/SearchField.d.ts +10 -1
  88. package/dist/primitives/choice/SearchField.js +21 -2
  89. package/dist/primitives/choice/Slider.d.ts +6 -1
  90. package/dist/primitives/choice/Slider.js +2 -2
  91. package/dist/primitives/choice/index.d.ts +2 -0
  92. package/dist/primitives/choice/index.js +2 -0
  93. package/dist/primitives/date/Calendar.js +30 -6
  94. package/dist/primitives/date/DatePicker.d.ts +11 -3
  95. package/dist/primitives/date/DatePicker.js +12 -8
  96. package/dist/primitives/date/DateTime.d.ts +35 -0
  97. package/dist/primitives/date/DateTime.js +100 -0
  98. package/dist/primitives/date/index.d.ts +1 -0
  99. package/dist/primitives/date/index.js +4 -2
  100. package/dist/primitives/dialog/Dialog.d.ts +9 -3
  101. package/dist/primitives/dialog/Dialog.js +41 -5
  102. package/dist/primitives/field/DropZone.js +1 -1
  103. package/dist/primitives/field/Field.d.ts +14 -2
  104. package/dist/primitives/field/Field.js +25 -14
  105. package/dist/primitives/field/copy.d.ts +16 -0
  106. package/dist/primitives/field/copy.js +69 -0
  107. package/dist/primitives/field/index.d.ts +2 -0
  108. package/dist/primitives/field/index.js +4 -0
  109. package/dist/primitives/field/note.d.ts +55 -0
  110. package/dist/primitives/field/note.js +117 -0
  111. package/dist/primitives/index.d.ts +1 -0
  112. package/dist/primitives/index.js +1 -0
  113. package/dist/primitives/menu/Menu.d.ts +28 -0
  114. package/dist/primitives/menu/Menu.js +47 -17
  115. package/dist/primitives/notice/Callout.js +1 -1
  116. package/dist/primitives/notice/Flag.d.ts +13 -0
  117. package/dist/primitives/notice/Flag.js +31 -0
  118. package/dist/primitives/notice/index.d.ts +2 -1
  119. package/dist/primitives/notice/index.js +2 -1
  120. package/dist/primitives/overlay/Preview.d.ts +19 -0
  121. package/dist/primitives/overlay/Preview.js +69 -0
  122. package/dist/primitives/overlay/index.d.ts +1 -0
  123. package/dist/primitives/overlay/index.js +3 -1
  124. package/dist/primitives/picker/MatchedText.d.ts +10 -0
  125. package/dist/primitives/picker/MatchedText.js +12 -0
  126. package/dist/primitives/picker/Picker.d.ts +67 -4
  127. package/dist/primitives/picker/Picker.js +142 -23
  128. package/dist/primitives/table/CellCopy.d.ts +20 -0
  129. package/dist/primitives/table/CellCopy.js +23 -0
  130. package/dist/primitives/table/DataTable.d.ts +55 -1
  131. package/dist/primitives/table/DataTable.js +126 -40
  132. package/dist/primitives/table/EditableTable.d.ts +102 -2
  133. package/dist/primitives/table/EditableTable.js +287 -32
  134. package/dist/primitives/table/ReportTable.js +2 -1
  135. package/dist/primitives/table/RowActions.js +7 -2
  136. package/dist/primitives/table/SheetTable.d.ts +4 -1
  137. package/dist/primitives/table/SheetTable.js +38 -5
  138. package/dist/primitives/table/Table.d.ts +5 -0
  139. package/dist/primitives/table/Table.js +17 -8
  140. package/dist/primitives/table/index.d.ts +2 -2
  141. package/dist/primitives/table/index.js +1 -1
  142. package/dist/primitives/tabs/Tabs.d.ts +2 -2
  143. package/dist/primitives/tabs/Tabs.js +1 -1
  144. package/dist/primitives/tag/Tags.d.ts +2 -0
  145. package/dist/primitives/tag/Tags.js +2 -1
  146. package/dist/primitives/tag/TagsField.d.ts +13 -0
  147. package/dist/primitives/tag/TagsField.js +39 -10
  148. package/dist/primitives/tag/choices.d.ts +2 -0
  149. package/dist/primitives/tree/Tree.d.ts +56 -0
  150. package/dist/primitives/tree/Tree.js +74 -0
  151. package/dist/primitives/tree/index.d.ts +1 -0
  152. package/dist/primitives/tree/index.js +3 -0
  153. package/dist/primitives/value/Avatar.d.ts +16 -0
  154. package/dist/primitives/value/Avatar.js +91 -0
  155. package/dist/primitives/value/Copyable.d.ts +11 -0
  156. package/dist/primitives/value/Copyable.js +27 -6
  157. package/dist/primitives/value/Fecha.js +10 -1
  158. package/dist/primitives/value/Person.d.ts +6 -51
  159. package/dist/primitives/value/Person.js +9 -148
  160. package/dist/primitives/value/ProgressBar.d.ts +31 -0
  161. package/dist/primitives/value/ProgressBar.js +53 -0
  162. package/dist/primitives/value/Relation.d.ts +22 -5
  163. package/dist/primitives/value/Relation.js +42 -14
  164. package/dist/primitives/value/Totals.d.ts +102 -7
  165. package/dist/primitives/value/Totals.js +52 -2
  166. package/dist/primitives/value/Value.d.ts +0 -37
  167. package/dist/primitives/value/Value.js +0 -39
  168. package/dist/primitives/value/contact.d.ts +30 -0
  169. package/dist/primitives/value/contact.js +51 -0
  170. package/dist/primitives/value/index.d.ts +5 -4
  171. package/dist/primitives/value/index.js +5 -4
  172. package/dist/tokens.css +179 -7
  173. package/dist/views/list/List.d.ts +32 -3
  174. package/dist/views/list/List.js +51 -7
  175. package/dist/views/list/ListView.d.ts +1 -1
  176. package/dist/views/list/ListView.js +70 -13
  177. package/dist/views/list/columns.js +8 -1
  178. package/dist/views/list/derive.d.ts +12 -1
  179. package/dist/views/list/derive.js +29 -3
  180. package/dist/views/list/presentations.d.ts +5 -2
  181. package/dist/views/list/presentations.js +35 -3
  182. package/dist/views/list/types.d.ts +57 -1
  183. package/dist/views/list/useNarrowList.d.ts +10 -0
  184. package/dist/views/list/useNarrowList.js +20 -6
  185. package/dist/views/record/Record.d.ts +41 -8
  186. package/dist/views/record/Record.js +113 -28
  187. package/dist/views/record/index.d.ts +1 -1
  188. package/package.json +15 -4
  189. package/dist/components/chatter/Chatter.d.ts +0 -161
  190. package/dist/components/chatter/Chatter.js +0 -232
  191. package/dist/components/chatter/index.d.ts +0 -3
  192. package/dist/components/chatter/index.js +0 -4
  193. package/dist/components/navigation/CommandPalette.d.ts +0 -40
  194. package/dist/components/navigation/CommandPalette.js +0 -114
  195. package/dist/components/navigation/commands.d.ts +0 -30
  196. package/dist/components/navigation/commands.js +0 -30
  197. /package/dist/components/{chatter → activity}/Timeline.d.ts +0 -0
  198. /package/dist/components/{chatter → activity}/Timeline.js +0 -0
@@ -3,14 +3,29 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useUiCopy } from "../../foundations/copy.js";
4
4
  import { buttonClasses, } from "../button/Button.js";
5
5
  import { Popover } from "../overlay/Popover.js";
6
- import { Check, ChevronDown } from "lucide-react";
7
- import { Autocomplete, Button as AriaButton, Header, Input, Menu as AriaMenu, MenuItem, MenuSection, MenuTrigger, SearchField, Separator, useFilter, } from "react-aria-components";
6
+ import { Check, ChevronDown, ChevronRight } from "lucide-react";
7
+ import { Autocomplete, Button as AriaButton, Header, Input, Keyboard, Menu as AriaMenu, MenuItem, MenuSection, MenuTrigger, SearchField, Separator, SubmenuTrigger, useFilter, } from "react-aria-components";
8
8
  const WIDTH = {
9
9
  /** A menu of short verbs hanging off an icon: "duplicate", "delete". */
10
10
  sm: "min-w-44 max-w-64",
11
11
  md: "min-w-56 max-w-80",
12
12
  lg: "min-w-80 max-w-(--container-short)",
13
13
  };
14
+ /** A submenu opens beside its row, flush against the parent panel and pulled up by the
15
+ * panel's padding plus its hairline (`p-1` + 1), so its first row lines up with the row that
16
+ * opened it — a submenu that starts half a row lower reads as belonging to the next one. */
17
+ const SUBMENU_OFFSET = 0;
18
+ const SUBMENU_CROSS_OFFSET = -5;
19
+ /** Every option of the tree by id, children included: a submenu is a separate React Aria
20
+ * menu with its own `onAction`, and both have to reach the same handlers. */
21
+ export function indexOptions(options, into = new Map()) {
22
+ for (const o of options) {
23
+ into.set(o.id, o);
24
+ if (o.items !== undefined)
25
+ indexOptions(o.items, into);
26
+ }
27
+ return into;
28
+ }
14
29
  export function Menu({ label, groups, onSelect, surface = "canvas", align = "start", icon: Trigger,
15
30
  /** The menu as a control of a list ("filters ▾") carries the chevron; the
16
31
  * actions `⋯` does not, because the icon already says there is more. */
@@ -29,18 +44,41 @@ chevron = true, active = false, iconOnly = false, variant = "button", searchPlac
29
44
  : surface === "chrome"
30
45
  ? `border-chrome-muted/35 bg-chrome-raised text-chrome-ink hover:border-chrome-accent/45 ${active ? "border-chrome-accent/60" : ""}`
31
46
  : `border-line-strong bg-surface hover:border-primary/45 hover:bg-primary/5 ${active ? "border-primary/60 text-primary" : "text-ink"}`;
32
- const hasMarks = groups.some((g) => g.options.some((o) => o.checked !== undefined || o.icon !== undefined));
33
- const hasHints = groups.some((g) => g.options.some((o) => o.hint !== undefined));
34
- const byId = new Map(groups.flatMap((g) => g.options.map((o) => [o.id, o])));
35
- const menu = (_jsx(AriaMenu, { "aria-label": label, shouldCloseOnSelect: closeOnSelect, onAction: (key) => {
47
+ const byId = indexOptions(groups.flatMap((g) => g.options));
48
+ const menu = (_jsx(MenuList, { label: label, groups: groups, width: width, closeOnSelect: closeOnSelect, onAction: (key) => {
36
49
  const option = byId.get(String(key));
37
50
  option?.onSelect?.();
38
51
  onSelect?.(String(key));
39
- }, renderEmptyState: () => (_jsx("p", { className: "px-3 py-3 text-meta text-muted italic", children: emptyText ?? copy.menu.empty })), className: "max-h-[inherit] overflow-y-auto p-1 outline-0", children: groups.map((g, gi) => (_jsxs(MenuSection, { className: "[&:not(:first-child)]:mt-1 [&:not(:first-child)]:border-t [&:not(:first-child)]:border-line [&:not(:first-child)]:pt-1", children: [g.label === undefined ? null : (
52
+ }, emptyText: emptyText ?? copy.menu.empty }));
53
+ return (_jsxs(MenuTrigger, { children: [_jsx(AriaButton, { "aria-label": iconOnly ? label : undefined, ...(role === undefined ? {} : { "data-emphasis": role }), className: ({ isPressed, isFocusVisible }) => role === undefined
54
+ ? `inline-flex h-(--size-control-sm) shrink-0 cursor-default items-center gap-1.5 border text-micro font-medium whitespace-nowrap outline-0 transition-colors duration-(--duration-instant) ${isFocusVisible ? "outline-2 outline-offset-2 outline-primary" : ""} ${iconOnly ? "w-(--size-control-sm) justify-center rounded-pill" : "rounded-control px-2.5"} ${triggerClass(isPressed)}`
55
+ : `${buttonClasses(role, size, surface)} gap-1.5`, children: _jsxs("span", { className: "contents", title: title ?? (iconOnly ? label : undefined), children: [Trigger === undefined ? (iconOnly ? (_jsx(ChevronDown, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true })) : null) : (_jsx(Trigger, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true })), iconOnly ? null : label, chevron && !iconOnly ? (_jsx(ChevronDown, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true })) : null] }) }), _jsx(Popover, { placement: align === "end" ? "bottom end" : "bottom start", className: WIDTH[width], children: searchPlaceholder === undefined ? (menu) : (
56
+ /* The search box stays at the top: in a menu of twenty-five entries,
57
+ scrolling loses exactly the control that explains why only three are
58
+ visible. It filters on label AND hint — half the entries of this
59
+ product are told apart only by the hint. */
60
+ _jsxs(Autocomplete, { filter: contains, children: [_jsx(SearchField, { "aria-label": searchPlaceholder, autoFocus: true, className: "border-b border-line px-2 pt-2 pb-2", children: _jsx(Input, { placeholder: searchPlaceholder, className: "h-(--size-control-sm) w-full rounded-control border border-line-strong bg-surface px-2.5 text-meta text-ink outline-0 placeholder:text-muted focus-visible:border-primary" }) }), menu] })) })] }));
61
+ }
62
+ /**
63
+ * The panel of a `Menu`: its groups and rows, and the submenus hanging from them. Internal —
64
+ * it is not exported from the family — and separate from the trigger for two reasons: a
65
+ * submenu is this same panel one level down, and a panel can be rendered on the server
66
+ * without the trigger's popover, which is how the tests see the rows.
67
+ */
68
+ export function MenuList({ label, groups, onAction, closeOnSelect, emptyText, width = "md", }) {
69
+ /* Per panel: a submenu without icons does not inherit its parent's mark column. */
70
+ const hasMarks = groups.some((g) => g.options.some((o) => o.checked !== undefined || o.icon !== undefined));
71
+ const hasHints = groups.some((g) => g.options.some((o) => o.hint !== undefined));
72
+ const row = (o) => {
73
+ const Icon = o.icon;
74
+ const opens = o.items !== undefined;
75
+ const checked = !opens && o.checked === true;
76
+ return (_jsxs(MenuItem, { id: o.id, textValue: o.hint === undefined ? o.label : `${o.label} ${o.hint}`, isDisabled: o.disabled, className: `flex w-full cursor-default rounded-control ${hasHints ? "items-start py-1.5" : "items-center py-1"} gap-2 px-2 text-left outline-0 transition-colors duration-(--duration-instant) data-focused:bg-primary/8 data-open:bg-primary/8 disabled:opacity-45`, children: [hasMarks ? (_jsx("span", { className: `${hasHints ? "mt-0.5" : ""} flex size-(--size-icon-sm) shrink-0 items-center justify-center`, children: checked ? (_jsx(Check, { className: "size-(--size-icon-sm) text-primary", strokeWidth: 2.5, "aria-hidden": true })) : Icon !== undefined ? (_jsx(Icon, { className: "size-(--size-icon-sm) text-muted", strokeWidth: 2, "aria-hidden": true })) : null })) : null, _jsxs("span", { className: "min-w-0 flex-1", children: [_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: `truncate text-meta ${checked ? "font-medium text-ink" : "text-ink"}`, children: o.label }), o.badge === undefined ? null : typeof o.badge === "string" ? (_jsx("span", { className: "inline-flex shrink-0 items-center rounded-pill border border-warn/30 bg-warn/10 px-2 py-0.5 text-micro text-warn", children: o.badge })) : (_jsx("span", { className: "ml-auto shrink-0", children: o.badge }))] }), o.hint === undefined ? null : (_jsx("span", { className: "mt-0.5 block truncate text-micro text-muted", children: o.hint }))] }), o.shortcut === undefined ? null : (_jsx(Keyboard, { className: "shrink-0 self-center pl-3 font-mono text-micro text-muted", children: o.shortcut })), opens ? (_jsx(ChevronRight, { className: "size-(--size-icon-sm) shrink-0 self-center text-muted", strokeWidth: 2, "aria-hidden": true })) : null] }, o.id));
77
+ };
78
+ return (_jsx(AriaMenu, { "aria-label": label, shouldCloseOnSelect: closeOnSelect, onAction: onAction, renderEmptyState: () => (_jsx("p", { className: "px-3 py-3 text-meta text-muted italic", children: emptyText })), className: "max-h-[inherit] overflow-y-auto p-1 outline-0", children: groups.map((g, gi) => (_jsxs(MenuSection, { className: "[&:not(:first-child)]:mt-1 [&:not(:first-child)]:border-t [&:not(:first-child)]:border-line [&:not(:first-child)]:pt-1", children: [g.label === undefined ? null : (
40
79
  /* Same skeleton as the row: the label sits on the text column. */
41
80
  _jsxs(Header, { className: "flex items-center gap-2 px-2 pt-1 pb-0.5 text-micro font-medium text-muted", children: [hasMarks ? (_jsx("span", { className: "size-(--size-icon-sm) shrink-0", "aria-hidden": true })) : null, g.label] })), g.options.flatMap((o) => {
42
- const Icon = o.icon;
43
- const item = (_jsxs(MenuItem, { id: o.id, textValue: o.hint === undefined ? o.label : `${o.label} ${o.hint}`, isDisabled: o.disabled, className: `flex w-full cursor-default rounded-control ${hasHints ? "items-start py-1.5" : "items-center py-1"} gap-2 px-2 text-left outline-0 transition-colors duration-(--duration-instant) data-focused:bg-primary/8 disabled:opacity-45`, children: [hasMarks ? (_jsx("span", { className: `${hasHints ? "mt-0.5" : ""} flex size-(--size-icon-sm) shrink-0 items-center justify-center`, children: o.checked === true ? (_jsx(Check, { className: "size-(--size-icon-sm) text-primary", strokeWidth: 2.5, "aria-hidden": true })) : Icon !== undefined ? (_jsx(Icon, { className: "size-(--size-icon-sm) text-muted", strokeWidth: 2, "aria-hidden": true })) : null })) : null, _jsxs("span", { className: "min-w-0 flex-1", children: [_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: `truncate text-meta ${o.checked === true ? "font-medium text-ink" : "text-ink"}`, children: o.label }), o.badge === undefined ? null : typeof o.badge === "string" ? (_jsx("span", { className: "inline-flex shrink-0 items-center rounded-pill border border-warn/30 bg-warn/10 px-2 py-0.5 text-micro text-warn", children: o.badge })) : (_jsx("span", { className: "ml-auto shrink-0", children: o.badge }))] }), o.hint === undefined ? null : (_jsx("span", { className: "mt-0.5 block truncate text-micro text-muted", children: o.hint }))] })] }, o.id));
81
+ const item = o.items === undefined ? (row(o)) : (_jsxs(SubmenuTrigger, { children: [row(o), _jsx(Popover, { offset: SUBMENU_OFFSET, crossOffset: SUBMENU_CROSS_OFFSET, className: WIDTH[width], children: _jsx(MenuList, { label: o.label, groups: [{ options: [...o.items] }], onAction: onAction, closeOnSelect: closeOnSelect, emptyText: emptyText, width: width }) })] }, o.id));
44
82
  return o.divider === true
45
83
  ? [
46
84
  _jsx(Separator, { className: "my-1 h-px border-0 bg-line" }, `${o.id}-divider`),
@@ -48,12 +86,4 @@ chevron = true, active = false, iconOnly = false, variant = "button", searchPlac
48
86
  ]
49
87
  : [item];
50
88
  })] }, g.label ?? gi))) }));
51
- return (_jsxs(MenuTrigger, { children: [_jsx(AriaButton, { "aria-label": iconOnly ? label : undefined, ...(role === undefined ? {} : { "data-emphasis": role }), className: ({ isPressed, isFocusVisible }) => role === undefined
52
- ? `inline-flex h-(--size-control-sm) shrink-0 cursor-default items-center gap-1.5 border text-micro font-medium whitespace-nowrap outline-0 transition-colors duration-(--duration-instant) ${isFocusVisible ? "outline-2 outline-offset-2 outline-primary" : ""} ${iconOnly ? "w-(--size-control-sm) justify-center rounded-pill" : "rounded-control px-2.5"} ${triggerClass(isPressed)}`
53
- : `${buttonClasses(role, size, surface)} gap-1.5`, children: _jsxs("span", { className: "contents", title: title ?? (iconOnly ? label : undefined), children: [Trigger === undefined ? (iconOnly ? (_jsx(ChevronDown, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true })) : null) : (_jsx(Trigger, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true })), iconOnly ? null : label, chevron && !iconOnly ? (_jsx(ChevronDown, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true })) : null] }) }), _jsx(Popover, { placement: align === "end" ? "bottom end" : "bottom start", className: WIDTH[width], children: searchPlaceholder === undefined ? (menu) : (
54
- /* The search box stays at the top: in a menu of twenty-five entries,
55
- scrolling loses exactly the control that explains why only three are
56
- visible. It filters on label AND hint — half the entries of this
57
- product are told apart only by the hint. */
58
- _jsxs(Autocomplete, { filter: contains, children: [_jsx(SearchField, { "aria-label": searchPlaceholder, autoFocus: true, className: "border-b border-line px-2 pt-2 pb-2", children: _jsx(Input, { placeholder: searchPlaceholder, className: "h-(--size-control-sm) w-full rounded-control border border-line-strong bg-surface px-2.5 text-meta text-ink outline-0 placeholder:text-muted focus-visible:border-primary" }) }), menu] })) })] }));
59
89
  }
@@ -24,5 +24,5 @@ export function Callout({ children, tone = "info", title, action, onDismiss, dis
24
24
  /* `status` y no `alert`: `alert` interrumpe al lector de pantalla y se
25
25
  reserva para lo que pasó JUSTO AHORA. Un aviso que ya estaba cuando la
26
26
  pantalla abrió no interrumpe nada. */
27
- role: "status", className: `flex min-w-0 items-start gap-2.5 rounded-box border ${SKIN[tone]} ${byDensity(d, "px-3 py-2.5", "px-2.5 py-2")}`, children: [_jsx(Icon, { className: "mt-0.5 size-(--size-icon-sm) shrink-0", strokeWidth: 2, "aria-hidden": true }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [title === undefined ? null : (_jsx("p", { className: "text-meta font-medium text-ink", children: title })), _jsx("div", { className: "min-w-0 text-meta text-ink", children: children })] }), action === undefined ? null : _jsx("span", { className: "shrink-0", children: action }), onDismiss === undefined ? null : (_jsx("button", { type: "button", onClick: onDismiss, "aria-label": dismissLabel ?? "cerrar", title: dismissLabel ?? "cerrar", className: "-mr-1 shrink-0 rounded-control p-0.5 text-muted transition-colors duration-(--duration-instant) hover:bg-ink/6 hover:text-ink", children: _jsx(X, { className: "size-(--size-icon-sm)", strokeWidth: 2 }) }))] }));
27
+ role: "status", className: `flex min-w-0 items-start gap-2.5 rounded-box border ${SKIN[tone]} ${byDensity(d, "px-3 py-2.5", "px-2.5 py-2")}`, children: [_jsx(Icon, { className: "mt-0.5 size-(--size-icon-sm) shrink-0", strokeWidth: 2, "aria-hidden": true }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-wrap items-start gap-x-3 gap-y-2", children: [_jsxs("div", { className: "flex min-w-0 flex-1 basis-(--container-name) flex-col gap-1", children: [title === undefined ? null : (_jsx("p", { className: "text-meta font-medium text-ink", children: title })), _jsx("div", { className: "min-w-0 text-meta text-ink", children: children })] }), action === undefined ? null : _jsx("span", { className: "shrink-0", children: action })] }), onDismiss === undefined ? null : (_jsx("button", { type: "button", onClick: onDismiss, "aria-label": dismissLabel ?? "cerrar", title: dismissLabel ?? "cerrar", className: "-mr-1 shrink-0 rounded-control p-0.5 text-muted transition-colors duration-(--duration-instant) hover:bg-ink/6 hover:text-ink", children: _jsx(X, { className: "size-(--size-icon-sm)", strokeWidth: 2 }) }))] }));
28
28
  }
@@ -0,0 +1,13 @@
1
+ import type { Tone } from "../../foundations/types.js";
2
+ import type { ReactNode } from "react";
3
+ export type FlagTone = Extract<Tone, "bad" | "warn" | "info" | "good">;
4
+ export type FlagProps = {
5
+ tone: FlagTone;
6
+ /** The sentence the mark stands for — «Sin existencias». It names the mark and heads
7
+ * the popover. */
8
+ label: string;
9
+ /** What explains it, inside the popover: the figures, what to do. */
10
+ children?: ReactNode;
11
+ };
12
+ /** A toned mark that opens its sentence. See the file's header. */
13
+ export declare function Flag({ tone, label, children }: FlagProps): import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { tv } from "../../foundations/variants.js";
4
+ import { Popover } from "../overlay/Popover.js";
5
+ import { CircleAlert, CircleCheck, Info, TriangleAlert } from "lucide-react";
6
+ import { Button, Dialog, DialogTrigger, Heading } from "react-aria-components";
7
+ const ICON = {
8
+ bad: CircleAlert,
9
+ warn: TriangleAlert,
10
+ info: Info,
11
+ good: CircleCheck,
12
+ };
13
+ const mark = tv({
14
+ base: "inline-flex size-5 shrink-0 cursor-default items-center justify-center rounded-pill outline-0 transition-colors duration-(--duration-instant) forced-colors:text-[ButtonText]",
15
+ variants: {
16
+ tone: {
17
+ bad: "bg-bad/12 text-bad hover:bg-bad/20 aria-expanded:bg-bad/20",
18
+ warn: "bg-warn/15 text-warn hover:bg-warn/25 aria-expanded:bg-warn/25",
19
+ info: "bg-info/12 text-info hover:bg-info/20 aria-expanded:bg-info/20",
20
+ good: "bg-good/12 text-good hover:bg-good/20 aria-expanded:bg-good/20",
21
+ },
22
+ isFocusVisible: {
23
+ true: "outline-2 outline-offset-1 outline-primary forced-colors:outline-[Highlight]",
24
+ },
25
+ },
26
+ });
27
+ /** A toned mark that opens its sentence. See the file's header. */
28
+ export function Flag({ tone, label, children }) {
29
+ const Icon = ICON[tone];
30
+ return (_jsxs(DialogTrigger, { children: [_jsx("span", { className: "inline-flex shrink-0", onClick: (e) => e.stopPropagation(), children: _jsx(Button, { "aria-label": label, className: (state) => mark({ ...state, tone }), children: _jsx(Icon, { className: "size-(--size-icon-sm)", strokeWidth: 2.25, "aria-hidden": true }) }) }), _jsx(Popover, { placement: "bottom start", className: "max-w-72 rounded-box", children: _jsxs(Dialog, { className: "flex flex-col gap-1 px-3 py-2.5 outline-0", children: [_jsx(Heading, { slot: "title", className: "text-meta font-medium text-ink", children: label }), children === undefined ? null : (_jsx("div", { className: "text-micro leading-relaxed text-muted", children: children }))] }) })] }));
31
+ }
@@ -1,3 +1,4 @@
1
1
  export { Callout, type CalloutProps } from "./Callout.js";
2
+ export { Flag, type FlagProps, type FlagTone } from "./Flag.js";
2
3
  export { Empty, Failed, ICONO_ROTO, Missing, Skeleton, } from "./Notice.js";
3
- export { ToastLayer, ToastProvider, type ToastSpec, useToast, useToastIfAny, } from "./Toast.js";
4
+ export { ToastProvider, type ToastSpec, useToast, useToastIfAny, } from "./Toast.js";
@@ -1,5 +1,6 @@
1
1
  /* How a view says that there is nothing, that it failed, or that nobody knows
2
2
  how to draw it. */
3
3
  export { Callout } from "./Callout.js";
4
+ export { Flag } from "./Flag.js";
4
5
  export { Empty, Failed, ICONO_ROTO, Missing, Skeleton, } from "./Notice.js";
5
- export { ToastLayer, ToastProvider, useToast, useToastIfAny, } from "./Toast.js";
6
+ export { ToastProvider, useToast, useToastIfAny, } from "./Toast.js";
@@ -0,0 +1,19 @@
1
+ import { type PopoverProps } from "./Popover.js";
2
+ import { type ReactElement, type ReactNode } from "react";
3
+ export type PreviewProps = {
4
+ /** What the preview is of — a link to the record, written as an element: a React Aria
5
+ * `Link`, or anything that forwards `ref` and spreads native props. It must be
6
+ * focusable: the keyboard reaches the preview through it. */
7
+ trigger: ReactElement;
8
+ /** The accessible name of the floating card: «Resumen de Distribuidora Andina». */
9
+ label: string;
10
+ /** What floats: a `RelationCard`, a few facts. It may hold actions — «Abrir». */
11
+ children: ReactNode;
12
+ placement?: PopoverProps["placement"];
13
+ /** Milliseconds resting on the trigger before it opens. 600 by default — see above. */
14
+ delay?: number;
15
+ closeDelay?: number;
16
+ /** The trigger stays, the preview does not open. */
17
+ disabled?: boolean;
18
+ };
19
+ export declare function Preview({ trigger, label, children, placement, delay, closeDelay, disabled, }: PreviewProps): import("react").JSX.Element;
@@ -0,0 +1,69 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Popover } from "./Popover.js";
4
+ import { isValidElement } from "react";
5
+ import { setInteractionModality } from "react-aria";
6
+ import { Focusable, Link, PreviewTrigger } from "react-aria-components";
7
+ /**
8
+ * A glance at a record without leaving the screen: the customer of an invoice, the product
9
+ * of a line — who or what it is, shown when the pointer rests on its name, the keyboard
10
+ * stops on it or a finger presses it long.
11
+ *
12
+ * Context: it sits between the two other things that float from a mark.
13
+ *
14
+ * - `Tooltip` is a LABEL: a few words about a control, never interactive — React Aria makes
15
+ * it a `role="tooltip"` the keyboard cannot enter. A customer's debt, phone and «Abrir»
16
+ * are not a label.
17
+ * - `PopoverTrigger` is ASKED FOR: a press on a button opens a dialog that takes the focus
18
+ * until it is closed. Right for a small form; too much ceremony to see whose NIT it is.
19
+ *
20
+ * `Preview` is React Aria's `PreviewTrigger`: a NON-modal popover (`role="dialog"`, named by
21
+ * `label`) that opens on hover, on keyboard focus and on long press, and that MAY hold
22
+ * interactive content. It does not take the focus when it opens — the person who tabs past
23
+ * a column of customers keeps walking the table — but Tab from the trigger goes into it,
24
+ * Escape closes it and returns to the trigger, and the pointer can travel diagonally from the
25
+ * name to the card without it closing (React Aria's safe area). A long press moves the focus
26
+ * into it, so a touch screen reader lands on the card. All of that is React Aria's; this
27
+ * only floats it on the system's `Popover` — the surface, hairline, radius and `float`
28
+ * shadow everything that opens over the page has.
29
+ *
30
+ * ── The delays: 600 ms to open, 200 ms to close ──────────────────────
31
+ *
32
+ * They are React Aria's defaults, written here so a change in the library does not change
33
+ * the system silently. 600 ms is the line between crossing and resting: in a table the
34
+ * pointer crosses customer names all day on its way to something else, and a card of 18rem
35
+ * that covered four rows on every pass would be noise. Below ~400 ms it opens on the way;
36
+ * above a second it feels broken to the person who did stop to look. Keyboard focus waits the
37
+ * same, so tabbing quickly does not open a card per row. Once one is open the next opens at
38
+ * once (React Aria's warm-up): comparing the customers of six invoices is a run down the
39
+ * column, not six waits. It is shorter than a tooltip's on purpose — a preview is asked for
40
+ * by resting on a NAME you want to know about; a tooltip names an icon you mostly know.
41
+ * 200 ms to close is only for the jittery exit; the trip to the card is covered by the
42
+ * safe area, not by the delay.
43
+ *
44
+ * ── The trigger ──────────────────────────────────────────────────────
45
+ *
46
+ * React Aria hands the trigger its hover, focus, long-press and `aria-*` props through
47
+ * context. A React Aria `Link` — the name of a `RelationValue` — reads that context itself,
48
+ * so it is passed as is. Anything else — a system `Button`, a `ButtonLink`, a plain `<a>` —
49
+ * goes through `Focusable`, the adapter that clones those props and a `ref` onto an element
50
+ * that forwards `ref` and spreads native props (the same one `TooltipTrigger` uses). Wrapping
51
+ * the `Link` too would hand it the props twice.
52
+ */
53
+ const PREVIEW_DELAY = 600;
54
+ const PREVIEW_CLOSE_DELAY = 200;
55
+ export function Preview({ trigger, label, children, placement = "bottom start", delay = PREVIEW_DELAY, closeDelay = PREVIEW_CLOSE_DELAY, disabled = false, }) {
56
+ const readsContext = isValidElement(trigger) && trigger.type === Link;
57
+ return (
58
+ /* React Aria opens a preview on hover only once it KNOWS the pointer is in use — and it
59
+ learns that from a press, not from a move. On a page just loaded, before any click, the
60
+ pointer rested on a name and nothing opened. Entering the trigger with a mouse says it,
61
+ in the capture phase so it is said before React Aria's own hover handler asks. */
62
+ _jsx("span", { className: "contents", onPointerOverCapture: (event) => {
63
+ if (event.pointerType === "mouse")
64
+ setInteractionModality("pointer");
65
+ }, children: _jsxs(PreviewTrigger, { delay: delay, closeDelay: closeDelay, isDisabled: disabled, children: [readsContext ? (trigger) : (
66
+ /* React Aria types the child as an intrinsic element; a component that forwards
67
+ `ref` and spreads native props is the same thing to it at runtime. */
68
+ _jsx(Focusable, { children: trigger })), _jsx(Popover, { placement: placement, "aria-label": label, children: _jsx("div", { className: "max-h-[inherit] max-w-sm overflow-auto p-3", children: children }) })] }) }));
69
+ }
@@ -1,3 +1,4 @@
1
1
  export { Popover, type PopoverProps } from "./Popover.js";
2
2
  export { PopoverTrigger, type PopoverTriggerProps, } from "./PopoverTrigger.js";
3
+ export { Preview, type PreviewProps } from "./Preview.js";
3
4
  export { Tooltip, type TooltipProps, TooltipTrigger, } from "./Tooltip.js";
@@ -1,5 +1,7 @@
1
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. */
2
+ combobox, un formulario chico abierto desde un botón, el nombre de un ícono, el vistazo
3
+ a un registro al pasar por su nombre. */
3
4
  export { Popover } from "./Popover.js";
4
5
  export { PopoverTrigger, } from "./PopoverTrigger.js";
6
+ export { Preview } from "./Preview.js";
5
7
  export { Tooltip, TooltipTrigger, } from "./Tooltip.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A text with the part that matched the search marked — internal to the lists that filter
3
+ * as you type (`Picker`, `TagsField`). A `<mark>`, so it is also said as marked text, and
4
+ * bold besides the tint: in forced colors the tint is gone and the weight is what remains.
5
+ */
6
+ export declare function MatchedText({ text, query, locale, }: {
7
+ text: string;
8
+ query: string;
9
+ locale?: string;
10
+ }): string | (string | import("react").JSX.Element)[];
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { matchSpans } from "../../foundations/match.js";
3
+ /**
4
+ * A text with the part that matched the search marked — internal to the lists that filter
5
+ * as you type (`Picker`, `TagsField`). A `<mark>`, so it is also said as marked text, and
6
+ * bold besides the tint: in forced colors the tint is gone and the weight is what remains.
7
+ */
8
+ export function MatchedText({ text, query, locale, }) {
9
+ if (query.trim() === "")
10
+ return text;
11
+ return matchSpans(text, query, locale).map((span, index) => span.hit ? (_jsx("mark", { className: "rounded-mark bg-accent/25 font-semibold text-current", children: span.text }, index)) : (span.text));
12
+ }
@@ -1,3 +1,4 @@
1
+ import type { OpenTarget } from "../../foundations/types.js";
1
2
  import { type FormMode } from "../field/mode.js";
2
3
  import type { LucideIcon } from "lucide-react";
3
4
  import { type ReactNode } from "react";
@@ -18,19 +19,53 @@ export interface PickerItem {
18
19
  * on the box and finding the product, and having to remember what it was
19
20
  * called. Drawing every code of every option is not an alternative. */
20
21
  haystack?: string;
22
+ /** The header this item is listed under. Consecutive items with the same section share
23
+ * one header; an item without it is listed loose, outside any section. */
24
+ section?: string;
25
+ /** A person or a company: its face before the name — the photo, or its initials when
26
+ * `true`. Who is being chosen is recognised before it is read, in the list and in the field. */
27
+ avatar?: true | string;
28
+ /** What is read at a glance on the right, in tabular figures: the balance owed, the stock,
29
+ * a state badge, a priority — a widget, not text. Where `meta` describes, this COMPARES:
30
+ * lined up in one column, eight customers' debts are read top to bottom. */
31
+ aside?: ReactNode;
21
32
  }
33
+ /**
34
+ * What the list shows and how many are left out of it. Capped at 8 with the footer count,
35
+ * unless the list loads incrementally — then everything that arrived is listed and the
36
+ * scroll asks for more.
37
+ */
38
+ export declare function shownRows(filtered: readonly PickerItem[], { loading, incremental }: {
39
+ loading: boolean;
40
+ incremental: boolean;
41
+ }): {
42
+ visible: readonly PickerItem[];
43
+ remaining: number;
44
+ };
22
45
  export type PickerProps = {
23
46
  id?: string;
24
47
  items: readonly PickerItem[];
25
48
  value?: string;
26
49
  onChange?: (id: string) => void;
27
50
  placeholder?: string;
28
- /** What to do with the ones that don't fit. Without this, the footer only
29
- * informs. */
30
- onBrowseAll?: () => void;
51
+ /** «Buscar más…»: the last option of the list, always there when this is — the record
52
+ * may not be among the eight, or two of them may need their columns to tell apart. It
53
+ * gets what was typed, so the list that chooses (`List onChoose`, in a modal of the
54
+ * workspace) opens on the same search. */
55
+ onBrowseAll?: (query: string) => void;
56
+ /** «Crear «…»»: a record made from the typed text alone (Odoo's `name_create`); it returns
57
+ * the new id — or a promise of it — and the field chooses it. Offered while something is
58
+ * typed that no option is called exactly. The consumer appends the new item to `items`. */
59
+ onCreate?: (name: string) => string | Promise<string>;
60
+ /** «Crear y editar…»: the new record needs more than a name — open its form (in a workspace,
61
+ * a modal) with the typed text as the name. Choosing it there is the consumer's. */
62
+ onCreateAndEdit?: (name: string) => void;
31
63
  /** Open the chosen record. Drawn as a trailing affordance, the way a many2one
32
64
  * offers "open" next to "pick". Absent when there is nothing chosen. */
33
- onOpen?: (id: string) => void;
65
+ onOpen?: (id: string, to: OpenTarget) => void;
66
+ /** Where the chosen record can open, the first being the button's (a modal, by default: the
67
+ * form stays behind); the rest go in «Abrir en…». `onOpen` is told which was picked. */
68
+ openTargets?: readonly OpenTarget[];
34
69
  emptyText?: string;
35
70
  /** Placeholder of the field while it is being typed into. It is NOT a second
36
71
  * box — the field itself is the search. */
@@ -39,10 +74,38 @@ export type PickerProps = {
39
74
  * while loading reads as "there is nothing", which is a different answer. */
40
75
  loading?: boolean;
41
76
  loadingText?: string;
77
+ /** The list grows from the backend: called when its end scrolls into view. The consumer
78
+ * appends the next page to `items`; with nothing more to fetch it does nothing. Turns off
79
+ * the cap of 8 and the footer count. */
80
+ onLoadMore?: () => void;
81
+ /** A page asked for by `onLoadMore` is on its way: a quiet row at the end says so. Unlike
82
+ * `loading`, the rows already there stay. */
83
+ loadingMore?: boolean;
42
84
  describedBy?: string;
43
85
  invalid?: boolean;
44
86
  disabled?: boolean;
87
+ /** The name of the control when it is NOT inside a `Field` — a cell of an editable table.
88
+ * Inside a field, the field's label names it. */
89
+ "aria-label"?: string;
45
90
  /** Forces the mode. By default it inherits the form's: in read mode it draws the name. */
46
91
  mode?: FormMode;
47
92
  };
48
93
  export declare function Picker({ mode, ...props }: PickerProps): import("react").JSX.Element;
94
+ /**
95
+ * The list of a `Picker`: its rows, the section headers, the «Buscar más…» row and the loading
96
+ * row at the end. Internal — not exported from the family — and apart from the combobox so it
97
+ * can be rendered on the server without a popover, which is how the tests see the rows.
98
+ */
99
+ export declare function PickerOptions({ items, marks, hints, remaining, query, emptyText, onLoadMore, loadingMore, "aria-label": ariaLabel, }: {
100
+ items: readonly PickerItem[];
101
+ marks: boolean;
102
+ hints: boolean;
103
+ remaining: number;
104
+ /** What was typed: it is marked in every row, so each row says why it is there. */
105
+ query?: string;
106
+ emptyText: string;
107
+ onLoadMore?: () => void;
108
+ loadingMore?: boolean;
109
+ /** Only when rendered outside the combobox, which otherwise names the list. */
110
+ "aria-label"?: string;
111
+ }): import("react").JSX.Element;