@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
@@ -1,232 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { usePagination } from "../listing/pages.js";
4
- import { Pagination } from "../listing/Pagination.js";
5
- import { fillCopy, useUiCopy } from "../../foundations/copy.js";
6
- import { Empty } from "../../primitives/notice/Notice.js";
7
- import { Tabs } from "../../primitives/tabs/Tabs.js";
8
- import { Tag } from "../../primitives/tag/Tag.js";
9
- import { DateValue } from "../../primitives/value/Fecha.js";
10
- import { Avatar } from "../../primitives/value/Person.js";
11
- import { ArrowRight, ChevronRight, History, MessageSquare, PanelRightClose, PanelRightOpen, Rows3, Zap, } from "lucide-react";
12
- import { Lock } from "lucide-react";
13
- import { useState } from "react";
14
- /* The tab names come from the copy table: the same three words on every record. */
15
- const TAB_LABEL = {
16
- all: (copy) => copy.chatter.tabAll,
17
- messages: (copy) => copy.chatter.tabMessages,
18
- events: (copy) => copy.chatter.tabEvents,
19
- };
20
- /* El ícono de cada pestaña. Con tres, es lo que se reconoce sin leer — y en el
21
- chatter angosto, lo único que sobrevive cuando el rótulo se trunca. */
22
- const TAB_ICON = {
23
- all: Rows3,
24
- messages: MessageSquare,
25
- events: History,
26
- };
27
- /* UN SOLO ÍCONO para todos los eventos: el rayo, que es lo que pasó — algo se
28
- ejecutó. Había uno por tono y eso convertía la historia en un muestrario:
29
- cinco formas y cinco colores para decir cinco veces «pasó algo».
30
- Lo que SÍ hay que distinguir es si falló, y eso lo dice el contenedor. */
31
- const EVENT_ICON = Zap;
32
- /* DOS estados y no seis: pasó, o falló.
33
- *
34
- * El rojo de un fallo queda en la fila entera: es el único color, y tiene que
35
- * verse sin señalar. El hover no lo reemplaza. Es una marca metida adentro,
36
- * redondeada, para no pintar el riel. */
37
- const ROW_SKIN = {
38
- ok: "",
39
- failed: "bg-bad/6",
40
- };
41
- const ROW_HOVER = {
42
- ok: "group-hover/entry:bg-ink/4",
43
- failed: "group-hover/entry:bg-bad/10",
44
- };
45
- const NODE_SKIN = {
46
- ok: "bg-ink/8 text-muted",
47
- failed: "bg-bad/12 text-bad",
48
- };
49
- function at(when) {
50
- return when instanceof Date ? when.getTime() : new Date(when).getTime();
51
- }
52
- function MessageEntry({ message }) {
53
- const copy = useUiCopy();
54
- /* Mismo relleno que un evento: las dos clases de entrada viven en la misma
55
- lista y con relleno distinto se leen como dos listas pegadas. Antes el
56
- mensaje iba a ras del borde y el evento tenía caja, así que el nombre de
57
- quien escribió quedaba más cerca del filo que el texto de al lado. */
58
- return (_jsxs("li", { className: "flex min-w-0 gap-2.5 px-2.5 py-2", children: [_jsx(Avatar, { name: message.author, size: "sm" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5", children: [_jsx("span", { className: "text-meta font-medium text-ink", children: message.author }), _jsx(DateValue, { when: message.at, format: "relative" }), message.internal !== true ? null : (_jsxs("span", { className: "inline-flex items-center gap-1 text-micro text-muted", title: copy.chatter.internalNote, children: [_jsx(Lock, { className: "size-3", "aria-hidden": true }), copy.chatter.internal] }))] }), _jsx("p", { className: `mt-0.5 text-body whitespace-pre-line ${message.internal === true
59
- ? "rounded-control border border-warn/40 bg-warn/6 px-2 py-1 text-ink"
60
- : "text-ink"}`, children: message.body })] })] }));
61
- }
62
- /** El cambio de estado, cuando lo hubo: de qué a qué.
63
- *
64
- * Es la línea más útil de una historia de gestión y la que peor se contaba: «el
65
- * proyecto cambió de estado» obliga a buscar el evento anterior para saber de
66
- * dónde venía. Dos fichas y una flecha se leen sin buscar nada.
67
- *
68
- * El ESTADO VIEJO va apagado y el nuevo en tinta: lo que importa es dónde
69
- * quedó, y el de antes está para dar contexto, no para competir.
70
- *
71
- * La cápsula es la de `Tag`: pastilla, pelo, mismo relleno. Dos radios
72
- * distintos para la misma idea de «una ficha» es una familia de más. */
73
- const STATE_CHIP = "inline-flex max-w-32 min-w-0 items-center truncate rounded-pill border border-line-strong bg-ink/5 px-2 py-0.5 text-micro";
74
- function Moved({ from, to }) {
75
- return (_jsxs("span", { className: "inline-flex min-w-0 items-center gap-1", children: [from === undefined ? null : (_jsxs(_Fragment, { children: [_jsx("span", { className: `${STATE_CHIP} text-muted`, children: from }), _jsx(ArrowRight, { className: "size-3 shrink-0 text-muted/60", strokeWidth: 2.5, "aria-hidden": true })] })), _jsx("span", { className: `${STATE_CHIP} font-medium text-ink`, children: to })] }));
76
- }
77
- /**
78
- * UN EVENTO, como línea de una línea de tiempo.
79
- *
80
- * ── Por qué hay un riel y no sólo puntos ────────────────────────────
81
- *
82
- * El riel —la línea vertical que une los puntos— es lo que convierte una lista
83
- * en una CRONOLOGÍA. Sin él son renglones sueltos que casualmente tienen fecha;
84
- * con él se lee que una cosa pasó después de la otra, que es la pregunta que
85
- * alguien trae cuando abre esto.
86
- *
87
- * ── El color es del punto y de nada más ─────────────────────────────
88
- *
89
- * El tono pinta el punto, y el texto se queda en tinta normal. Un renglón entero
90
- * en rojo no se lee más urgente: se lee peor, y cuando hay tres seguidos deja de
91
- * significar nada. El punto alcanza para encontrar los rojos de un vistazo, que
92
- * es para lo que el color sirve en una lista larga.
93
- */
94
- /** El error: la primera línea, y el resto a un clic.
95
- *
96
- * Un stack de cuarenta renglones adentro de una historia tapa la historia; el
97
- * primer renglón casi siempre alcanza. Pero el resto tiene que estar —quien
98
- * mira esta pantalla muchas veces no tiene acceso a los logs— así que se
99
- * guarda, no se descarta.
100
- *
101
- * Sin color propio: el rojo ya lo puso el nodo. Esto es una cita textual de la
102
- * máquina, así que se ve como lo que es — monoespaciada sobre el piso. */
103
- function ErrorLine({ error }) {
104
- const copy = useUiCopy();
105
- const [open, setOpen] = useState(false);
106
- const lineas = error.split("\n");
107
- const primera = lineas[0] ?? "";
108
- const hayMas = lineas.length > 1 || primera.length > 120;
109
- return (_jsxs("div", { className: "min-w-0 rounded-control border border-line bg-ground px-2 py-1", children: [_jsx("p", { className: `min-w-0 font-mono text-micro text-ink ${open ? "break-words whitespace-pre-wrap" : "truncate"}`, children: open ? error : primera }), !hayMas ? null : (_jsx("button", { type: "button", onClick: () => setOpen(!open), className: "mt-0.5 cursor-pointer text-micro text-muted underline-offset-2 hover:text-ink hover:underline", children: open ? copy.chatter.less : copy.chatter.more }))] }));
110
- }
111
- /**
112
- * UN EVENTO: una FILA que se aprieta, no un renglón.
113
- *
114
- * ── Qué se ve sin abrir ─────────────────────────────────────────────
115
- *
116
- * El rótulo, quién —si lo hubo— y cuándo. Nada más. Es lo que alguien lee para
117
- * recorrer la historia; todo lo demás es para cuando ese renglón no alcanzó.
118
- *
119
- * ── Un solo ícono, y el contenedor dice si falló ────────────────────
120
- *
121
- * El rayo, porque eso es lo que pasó: algo se ejecutó. Había un ícono y un color
122
- * por tono, y seis colores no señalan nada — una historia de veinte eventos
123
- * quedaba como una bolsa de caramelos, y el que falló no saltaba más que los
124
- * otros. El rojo sirve exactamente mientras sea el único.
125
- *
126
- * Lo que distingue un error es el TINTE DE LA FILA, translúcido. Y sirve dos
127
- * veces: dice que falló, y dice que la fila es una zona que se puede apretar —
128
- * que era lo que no se notaba.
129
- */
130
- function EventEntry({ event }) {
131
- const failed = event.tone === "bad" || event.error !== undefined;
132
- const skin = failed ? "failed" : "ok";
133
- const Icon = event.icon ?? EVENT_ICON;
134
- /* Arranca CERRADO: lo de adentro es para cuando el renglón no alcanzó. */
135
- const [open, setOpen] = useState(false);
136
- const hayMas = event.moved !== undefined || event.detail !== undefined || event.error !== undefined;
137
- const fila = (_jsxs(_Fragment, { children: [_jsx("span", { className: `relative z-10 flex size-6 shrink-0 items-center justify-center rounded-pill ring-2 ring-surface ${NODE_SKIN[skin]}`, children: _jsx(Icon, { className: "size-3", strokeWidth: 2.5, "aria-hidden": true }) }), _jsx("span", { className: "min-w-0 flex-1 truncate text-meta text-ink", children: event.label }), event.by === undefined ? null : (_jsx("span", { className: "hidden shrink-0 truncate text-micro text-muted @xs:inline", children: event.by })), _jsx("span", { className: "shrink-0", children: _jsx(DateValue, { when: event.at, format: "relative" }) }), hayMas ? (_jsx(ChevronRight, { className: `size-3.5 shrink-0 text-muted transition-transform duration-(--duration-instant) ${open ? "rotate-90" : ""}`, strokeWidth: 2.5, "aria-hidden": true })) : (
138
- /* Hueco del mismo ancho: sin él, las filas que no se abren corren la
139
- fecha y la columna deja de estar alineada. */
140
- _jsx("span", { className: "size-3.5 shrink-0", "aria-hidden": true }))] }));
141
- return (_jsxs("li", { className: "group/event relative @container", children: [_jsx("span", { "aria-hidden": true, className: "absolute top-8 left-5.5 w-px bg-line group-last/event:hidden" }), hayMas ? (_jsxs("button", { type: "button", onClick: () => setOpen(!open), "aria-expanded": open, className: `group/entry relative flex w-full min-w-0 cursor-pointer items-center rounded-box px-2.5 py-2 text-left focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-primary ${ROW_SKIN[skin]}`, children: [_jsx("span", { "aria-hidden": true, className: `pointer-events-none absolute inset-x-1 inset-y-0.5 rounded-control ${ROW_HOVER[skin]}` }), _jsx("span", { className: "relative z-10 flex min-w-0 flex-1 items-center gap-2.5", children: fila })] })) : (_jsx("div", { className: `flex w-full min-w-0 items-center gap-2.5 rounded-box px-2.5 py-2 ${failed ? "bg-bad/6" : ""}`, children: fila })), !open ? null : (
142
- /* Lo de adentro, alineado bajo el rótulo y sobre el piso: es el DETALLE
143
- de esa línea, no una línea más de la historia. */
144
- _jsxs("div", { className: "mt-0.5 mb-1 ml-11 flex min-w-0 flex-col gap-1.5 rounded-box bg-ground px-2.5 py-2", children: [event.moved === undefined ? null : (_jsx(Moved, { ...(event.moved.from === undefined ? {} : { from: event.moved.from }), to: event.moved.to })), event.by === undefined ? null : (_jsx("span", { className: "text-micro text-muted @xs:hidden", children: event.by })), event.detail === undefined ? null : (_jsx("div", { className: "min-w-0 text-micro text-muted", children: event.detail })), event.error === undefined ? null : _jsx(ErrorLine, { error: event.error })] }))] }));
145
- }
146
- export function Chatter({ messages, events, composer, current, onCurrentChange, open, onToggle, label: ownLabel, layout = "page", embedded = false, }) {
147
- const copy = useUiCopy();
148
- const label = ownLabel ?? copy.chatter.history;
149
- const [owned, setOwned] = useState("all");
150
- const view = current ?? owned;
151
- const setView = onCurrentChange ?? setOwned;
152
- const hasMessages = messages !== undefined;
153
- const hasEvents = events !== undefined;
154
- /* Las tres pestañas sólo cuando hay DOS fuentes: con una sola, «todo» y esa
155
- una son la misma lista, y tres solapas donde dos dicen lo mismo es un
156
- control que no decide nada. */
157
- const tabs = hasMessages && hasEvents ? ["all", "messages", "events"] : [];
158
- const showing = tabs.length === 0 ? "all" : view;
159
- const entries = [
160
- ...(showing === "events" ? [] : (messages ?? [])).map((message) => ({
161
- kind: "message",
162
- at: at(message.at),
163
- message,
164
- })),
165
- ...(showing === "messages" ? [] : (events ?? [])).map((event) => ({
166
- kind: "event",
167
- at: at(event.at),
168
- event,
169
- })),
170
- ].sort((a, b) => b.at - a.at);
171
- const total = (messages?.length ?? 0) + (events?.length ?? 0);
172
- /* Cortado acá y no por quien compone: es la misma lista en todos los
173
- registros, y una historia que sólo hace scroll es una historia que se
174
- abandona a la mitad. */
175
- const pages = usePagination(entries, { pageSize: 15 });
176
- const page = layout === "page";
177
- /* Plegado. Embebido es la última fila de la hoja: el marco lo pone la hoja
178
- y el hover es una marca redondeada adentro, para no pintar la línea que
179
- la separa del papel. Suelta, o al costado, sí es una tarjeta. */
180
- if (!open) {
181
- const strip = page && embedded;
182
- const face = (_jsxs(_Fragment, { children: [_jsx(MessageSquare, { className: "size-4 shrink-0 text-muted", "aria-hidden": true }), _jsx("span", { className: page
183
- ? "min-w-0 truncate text-meta text-muted"
184
- : "min-w-0 truncate text-meta text-muted @4xl:hidden", children: label }), total === 0 ? null : _jsx(Tag, { tone: "neutral", children: total }), _jsx(PanelRightOpen, { className: page
185
- ? "ml-auto size-4 shrink-0 text-muted"
186
- : "ml-auto size-4 shrink-0 text-muted @4xl:ml-0", "aria-hidden": true })] }));
187
- return (_jsxs("button", { type: "button", onClick: onToggle, title: fillCopy(copy.chatter.open, { label }), className: page
188
- ? strip
189
- ? "group/strip relative flex w-full shrink-0 cursor-pointer items-center px-3 py-2.5"
190
- : "flex w-full shrink-0 cursor-pointer items-center gap-2 rounded-box border border-line bg-surface px-3 py-2.5 hover:bg-ground"
191
- : "flex w-full shrink-0 cursor-pointer items-center gap-2 self-start rounded-box border border-line bg-surface px-2.5 py-2 hover:bg-ground @4xl:w-auto @4xl:flex-col @4xl:px-1.5 @4xl:py-2.5", children: [strip ? (_jsx("span", { "aria-hidden": true, className: "pointer-events-none absolute inset-x-1 inset-y-0.5 rounded-control group-hover/strip:bg-ground" })) : null, strip ? (_jsx("span", { className: "relative z-10 flex min-w-0 flex-1 items-center gap-2", children: face })) : (face)] }));
192
- }
193
- return (_jsxs("aside", { className: page
194
- ? embedded
195
- ? "flex w-full flex-col"
196
- : "flex w-full flex-col rounded-box border border-line bg-surface"
197
- : "flex w-full shrink-0 flex-col self-start rounded-box border border-line bg-surface @4xl:w-(--container-chatter)", children: [_jsxs("header", { className: `flex min-w-0 items-end gap-1 border-b border-line bg-ground px-2 pt-2 ${page && embedded ? "" : "rounded-t-box"}`, children: [tabs.length === 0 ? (_jsxs("span", { className: "px-2 py-1.5 text-meta font-medium text-ink", children: [label, total === 0 ? null : (_jsx("span", { className: "ml-1.5 text-micro text-muted", children: total }))] })) : (_jsx(Tabs, { label: label, current: showing, onSelect: (id) => setView(id), tabs: tabs.map((tab) => ({
198
- id: tab,
199
- label: TAB_LABEL[tab](copy),
200
- icon: TAB_ICON[tab],
201
- count: tab === "messages"
202
- ? (messages?.length ?? 0)
203
- : tab === "events"
204
- ? (events?.length ?? 0)
205
- : total,
206
- })) })), _jsx("button", { type: "button", onClick: onToggle, title: fillCopy(copy.chatter.fold, { label }), className: "mb-1.5 ml-auto inline-flex size-(--size-control-sm) shrink-0 cursor-pointer items-center justify-center rounded-pill text-muted transition-colors duration-(--duration-instant) hover:bg-ink/6 hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary", children: _jsx(PanelRightClose, { className: "size-(--size-icon-md)", "aria-hidden": true }) })] }), _jsxs("div", { className: page
207
- ? "flex w-full min-w-0 max-w-(--container-prose) flex-col gap-3 px-3.5 py-3"
208
- : "flex min-w-0 flex-col gap-3 px-2.5 py-2.5", children: [composer === undefined || showing === "events" ? null : composer, entries.length === 0 ? (_jsx(Empty, { sentence: copy.chatter.empty })) : (_jsxs(_Fragment, { children: [_jsx("ol", { className: "flex flex-col", children: pages.pageItems.map((entry) => entry.kind === "message" ? (_jsx(MessageEntry, { message: entry.message }, `m:${entry.message.id}`)) : (_jsx(EventEntry, { event: entry.event }, `e:${entry.event.id}`))) }), entries.length <= 15 ? null : _jsx(Pagination, { ...pages.bridge })] }))] })] }));
209
- }
210
- /** Whether the chatter is open, when the page has no better answer.
211
- *
212
- * **Starts folded.** It began the other way round —a history nobody opens is a
213
- * history nobody reads— and seeing it took the argument apart: open, it holds a
214
- * 22rem column of context on every record you open, before anybody asked for
215
- * any. Folded it still says how many there are, which is the half that has to
216
- * be free; reading them is a click, and a click you chose.
217
- *
218
- * It is the same answer for both layouts. A default that depends on the width
219
- * would mean the same record shows a different screen depending on how many
220
- * panels happen to be open next to it, and that is not a default, it is a
221
- * surprise. */
222
- export function useChatter(initial = false) {
223
- const [open, setOpen] = useState(initial);
224
- return {
225
- open,
226
- toggle: () => setOpen((current) => !current),
227
- /** Open it without closing it if it already was. What a button from outside
228
- * needs —«events 12»— when pressing it twice must not hide the very thing
229
- * it promised to show. */
230
- show: () => setOpen(true),
231
- };
232
- }
@@ -1,3 +0,0 @@
1
- export { Chatter, type ChatterEvent, type ChatterLayout, type ChatterProps, type ChatterView, useChatter, } from "./Chatter.js";
2
- export { type Message, MessageComposer, type MessageComposerProps, type MessageKind, writeANote, } from "./Messages.js";
3
- export { Timeline, type TimelineItemProps, type TimelineProps, } from "./Timeline.js";
@@ -1,4 +0,0 @@
1
- /* What a record has been through, beside it: messages and events in one column. */
2
- export { Chatter, useChatter, } from "./Chatter.js";
3
- export { MessageComposer, writeANote, } from "./Messages.js";
4
- export { Timeline, } from "./Timeline.js";
@@ -1,40 +0,0 @@
1
- import { type PaletteCommand } from "./commands.js";
2
- /**
3
- * The search box and the list, without the modal around them.
4
- *
5
- * Context: split out so the filtering can be rendered on its own — in a test, with a query
6
- * already typed — since the modal is a portal that only exists in a browser.
7
- */
8
- export declare function CommandSearch({ commands, onRun, query, onQueryChange, placeholder, }: {
9
- commands: readonly PaletteCommand[];
10
- onRun: (id: string) => void;
11
- query?: string;
12
- onQueryChange?: (next: string) => void;
13
- placeholder?: string;
14
- }): import("react").JSX.Element;
15
- /**
16
- * A modal search over everything the app can do — go to a screen, create a record — opened
17
- * with ⌘K (Ctrl+K) and run with Enter.
18
- *
19
- * Context: it is controlled (`open`, `onOpenChange`) and does not bind the shortcut itself,
20
- * because an app has ONE palette and many screens: the key is bound once, where the palette
21
- * lives, with `useCommandPaletteShortcut`. Running a command closes it — the palette is a
22
- * path, not a place to stay.
23
- */
24
- export declare function CommandPalette({ open, onOpenChange, commands, onRun, placeholder, }: {
25
- open: boolean;
26
- onOpenChange: (open: boolean) => void;
27
- commands: readonly PaletteCommand[];
28
- /** A command was chosen. The palette closes on its own. */
29
- onRun: (id: string) => void;
30
- /** The search box's prompt and its accessible name. By default, the copy table's. */
31
- placeholder?: string;
32
- }): import("react").JSX.Element;
33
- /**
34
- * Binds ⌘K (Ctrl+K on Windows and Linux) to open the palette, anywhere in the page.
35
- *
36
- * Context: both modifiers are accepted on every platform. Telling them apart by the user
37
- * agent guesses wrong on a Mac with a PC keyboard, and the cost of accepting both is that
38
- * Ctrl+K on a Mac — «delete to the end of the line» in a text box — opens the palette.
39
- */
40
- export declare function useCommandPaletteShortcut(setOpen: (open: boolean) => void): void;
@@ -1,114 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { commandMatches, groupCommands, } from "./commands.js";
4
- import { AriaLocale } from "../../foundations/aria.js";
5
- import { useUiCopy } from "../../foundations/copy.js";
6
- import { tv } from "../../foundations/variants.js";
7
- import { Search, X } from "lucide-react";
8
- import { useEffect, useMemo } from "react";
9
- import { Autocomplete, Button as AriaButton, Dialog, Header, Input, Keyboard, Menu, MenuItem, MenuSection, Modal, ModalOverlay, SearchField, useFilter, } from "react-aria-components";
10
- /* ==================================================================== *
11
- * COMMAND PALETTE — ⌘K, type three letters, Enter.
12
- *
13
- * An ERP has a hundred screens and twenty «new» buttons, and the people who
14
- * use it all day stop wanting to look for any of them: they know the name of
15
- * what they want. The palette is the shortest path from knowing the name to
16
- * being there — one key to open, a few letters, Enter — without leaving the
17
- * keyboard and without knowing in which menu of which module it lives.
18
- *
19
- * ┌──────────────────────────────────────────────┐
20
- * │ 🔍 fact ✕ │
21
- * ├──────────────────────────────────────────────┤
22
- * │ IR A… │
23
- * │ ▌🧾 Facturas │ ← Enter runs it
24
- * │ CREAR… │
25
- * │ + Nueva factura ⌘N │
26
- * └──────────────────────────────────────────────┘
27
- *
28
- * React Aria's `Autocomplete` drives it: focus stays in the search box while
29
- * the arrows walk the list (virtual focus), the first match is highlighted as
30
- * you type so Enter always does something, and the modal traps focus, closes
31
- * on Escape and on a click outside, and gives the focus back.
32
- *
33
- * The match is the locale's (`useFilter` inside `AriaLocale`): «facturacion»
34
- * finds «Facturación» because nobody types the accent when they are in a hurry.
35
- * ==================================================================== */
36
- const overlay = tv({
37
- base: "fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-scrim/40 px-4 pt-24 transition-opacity duration-(--duration-quick) ease-(--ease-out)",
38
- variants: {
39
- isEntering: { true: "opacity-0" },
40
- isExiting: { true: "opacity-0" },
41
- },
42
- });
43
- const surface = tv({
44
- base: "w-full max-w-xl overflow-hidden rounded-panel border border-line-strong bg-surface text-ink shadow-window outline-0 transition-[opacity,translate] duration-(--duration-quick) ease-(--ease-out) forced-colors:bg-[Canvas]",
45
- variants: {
46
- isEntering: { true: "-translate-y-1 opacity-0" },
47
- isExiting: { true: "opacity-0" },
48
- },
49
- });
50
- const item = tv({
51
- base: "flex cursor-default items-center gap-2 rounded-control px-2 py-1.5 text-meta text-ink outline-0 select-none",
52
- variants: {
53
- isFocused: {
54
- true: "bg-primary/8 forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]",
55
- },
56
- isPressed: { true: "bg-primary/12" },
57
- isDisabled: { true: "text-muted forced-colors:text-[GrayText]" },
58
- },
59
- });
60
- /**
61
- * The search box and the list, without the modal around them.
62
- *
63
- * Context: split out so the filtering can be rendered on its own — in a test, with a query
64
- * already typed — since the modal is a portal that only exists in a browser.
65
- */
66
- export function CommandSearch({ commands, onRun, query, onQueryChange, placeholder, }) {
67
- const copy = useUiCopy();
68
- const { contains } = useFilter({ sensitivity: "base" });
69
- const byId = useMemo(() => new Map(commands.map((c) => [c.id, c])), [commands]);
70
- const groups = useMemo(() => groupCommands(commands), [commands]);
71
- const hint = placeholder ?? copy.search.placeholder;
72
- return (_jsxs(Autocomplete, { ...(query === undefined ? {} : { inputValue: query }), ...(onQueryChange === undefined ? {} : { onInputChange: onQueryChange }), filter: (_text, input, node) => {
73
- const command = byId.get(String(node.key));
74
- return command === undefined || commandMatches(command, input, contains);
75
- }, children: [_jsxs(SearchField, { "aria-label": hint, autoFocus: true, className: "group flex items-center gap-2 border-b border-line px-3", children: [_jsx(Search, { className: "size-(--size-icon-md) shrink-0 text-muted", strokeWidth: 2, "aria-hidden": true }), _jsx(Input, { placeholder: hint, className: "h-(--size-control-lg) min-w-0 flex-1 bg-transparent text-body text-ink outline-0 placeholder:text-muted [&::-webkit-search-cancel-button]:hidden" }), _jsx(AriaButton, { className: "inline-flex shrink-0 items-center justify-center rounded-mark p-1 text-muted transition-colors duration-(--duration-instant) group-empty:invisible hover:bg-ink/6 hover:text-ink pressed:bg-ink/10", children: _jsx(X, { className: "size-(--size-icon-md)", strokeWidth: 2, "aria-hidden": true }) })] }), _jsx(Menu, { "aria-label": hint, onAction: (key) => onRun(String(key)), renderEmptyState: () => (_jsx("p", { className: "px-3 py-6 text-center text-meta text-muted italic", children: copy.menu.empty })), className: "max-h-96 overflow-y-auto p-1 outline-0", children: groups.map((group) => (_jsxs(MenuSection, { id: group.section ?? "-", className: "not-first:mt-1", children: [group.section === undefined ? null : (_jsx(Header, { className: "px-2 pt-2 pb-1 text-micro font-medium text-muted", children: group.section })), group.commands.map((command) => {
76
- const Icon = command.icon;
77
- return (_jsxs(MenuItem, { id: command.id, textValue: command.label, className: item, children: [_jsx("span", { className: "flex size-(--size-icon-md) shrink-0 items-center justify-center text-muted", children: Icon === undefined ? null : (_jsx(Icon, { className: "size-(--size-icon-md)", strokeWidth: 2, "aria-hidden": true })) }), _jsx("span", { className: "min-w-0 flex-1 truncate", children: command.label }), command.shortcut === undefined ? null : (_jsx(Keyboard, { className: "shrink-0 rounded-mark border border-line px-1.5 font-sans text-micro text-muted tabular-nums", children: command.shortcut }))] }, command.id));
78
- })] }, group.section ?? ""))) })] }));
79
- }
80
- /**
81
- * A modal search over everything the app can do — go to a screen, create a record — opened
82
- * with ⌘K (Ctrl+K) and run with Enter.
83
- *
84
- * Context: it is controlled (`open`, `onOpenChange`) and does not bind the shortcut itself,
85
- * because an app has ONE palette and many screens: the key is bound once, where the palette
86
- * lives, with `useCommandPaletteShortcut`. Running a command closes it — the palette is a
87
- * path, not a place to stay.
88
- */
89
- export function CommandPalette({ open, onOpenChange, commands, onRun, placeholder, }) {
90
- const copy = useUiCopy();
91
- return (_jsx(AriaLocale, { children: _jsx(ModalOverlay, { isOpen: open, onOpenChange: onOpenChange, isDismissable: true, className: overlay, children: _jsx(Modal, { className: surface, children: _jsx(Dialog, { "aria-label": placeholder ?? copy.search.placeholder, className: "outline-0", children: _jsx(CommandSearch, { commands: commands, onRun: (id) => {
92
- onOpenChange(false);
93
- onRun(id);
94
- }, ...(placeholder === undefined ? {} : { placeholder }) }) }) }) }) }));
95
- }
96
- /**
97
- * Binds ⌘K (Ctrl+K on Windows and Linux) to open the palette, anywhere in the page.
98
- *
99
- * Context: both modifiers are accepted on every platform. Telling them apart by the user
100
- * agent guesses wrong on a Mac with a PC keyboard, and the cost of accepting both is that
101
- * Ctrl+K on a Mac — «delete to the end of the line» in a text box — opens the palette.
102
- */
103
- export function useCommandPaletteShortcut(setOpen) {
104
- useEffect(() => {
105
- const onKey = (e) => {
106
- if (e.key.toLowerCase() !== "k" || !(e.metaKey || e.ctrlKey) || e.altKey)
107
- return;
108
- e.preventDefault();
109
- setOpen(true);
110
- };
111
- document.addEventListener("keydown", onKey);
112
- return () => document.removeEventListener("keydown", onKey);
113
- }, [setOpen]);
114
- }
@@ -1,30 +0,0 @@
1
- import type { LucideIcon } from "lucide-react";
2
- /** Something the palette can run: go to a screen, create a record, open a report. */
3
- export interface PaletteCommand {
4
- id: string;
5
- label: string;
6
- /** The heading it is listed under: «Ir a…», «Crear…». Commands without one go first,
7
- * without a heading. */
8
- section?: string;
9
- icon?: LucideIcon;
10
- /** The key that runs it without the palette, as it should be read: «⌘N», «Alt+F». It is
11
- * only drawn — the palette is where the shortcut gets learned, not where it is bound. */
12
- shortcut?: string;
13
- /** Other words that should find it: «factura» finds «Nueva venta» when the people who use
14
- * it call it that. */
15
- keywords?: readonly string[];
16
- }
17
- /** The commands under each heading, headings in the order they first appear. */
18
- export declare function groupCommands(commands: readonly PaletteCommand[]): {
19
- section: string | undefined;
20
- commands: PaletteCommand[];
21
- }[];
22
- /**
23
- * Whether a command answers a query. `contains` is the locale's comparison — React Aria's
24
- * `useFilter` in the palette — so «facturacion» finds «Facturación» and an uppercase query
25
- * finds a lowercase label.
26
- *
27
- * Every word of the query has to appear, in any order, in the label or the keywords:
28
- * «nueva fac» finds «Nueva factura», and so does «factura nueva».
29
- */
30
- export declare function commandMatches(command: PaletteCommand, query: string, contains: (text: string, part: string) => boolean): boolean;
@@ -1,30 +0,0 @@
1
- /** The commands under each heading, headings in the order they first appear. */
2
- export function groupCommands(commands) {
3
- const groups = new Map();
4
- /* The commands with no heading are listed first, wherever they were written. */
5
- if (commands.some((c) => c.section === undefined))
6
- groups.set(undefined, []);
7
- for (const command of commands) {
8
- const group = groups.get(command.section);
9
- if (group === undefined)
10
- groups.set(command.section, [command]);
11
- else
12
- group.push(command);
13
- }
14
- return [...groups].map(([section, list]) => ({ section, commands: list }));
15
- }
16
- /**
17
- * Whether a command answers a query. `contains` is the locale's comparison — React Aria's
18
- * `useFilter` in the palette — so «facturacion» finds «Facturación» and an uppercase query
19
- * finds a lowercase label.
20
- *
21
- * Every word of the query has to appear, in any order, in the label or the keywords:
22
- * «nueva fac» finds «Nueva factura», and so does «factura nueva».
23
- */
24
- export function commandMatches(command, query, contains) {
25
- const words = query.trim().split(/\s+/).filter(Boolean);
26
- if (words.length === 0)
27
- return true;
28
- const haystack = [command.label, ...(command.keywords ?? [])].join(" ");
29
- return words.every((word) => contains(haystack, word));
30
- }