@photon-ai/pho-ui 4.3.0 → 4.4.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 (89) hide show
  1. package/dist/chunks/animated-height-I44sUq0n.js +48 -0
  2. package/dist/chunks/animated-height-I44sUq0n.js.map +1 -0
  3. package/dist/chunks/{basic-page-65eFgq8o.js → basic-page-ijv-ijUB.js} +534 -519
  4. package/dist/chunks/basic-page-ijv-ijUB.js.map +1 -0
  5. package/dist/chunks/bubble-eaoLJP4N.js +88 -0
  6. package/dist/chunks/bubble-eaoLJP4N.js.map +1 -0
  7. package/dist/chunks/{button-CdqS_t5a.js → button-CgUS0j3D.js} +4 -4
  8. package/dist/chunks/button-CgUS0j3D.js.map +1 -0
  9. package/dist/chunks/chat-DUqWlXqt.js +619 -0
  10. package/dist/chunks/chat-DUqWlXqt.js.map +1 -0
  11. package/dist/chunks/{code-block-BuDIo8Q0.js → code-block-BGgbp-6Q.js} +12 -12
  12. package/dist/chunks/{code-block-BuDIo8Q0.js.map → code-block-BGgbp-6Q.js.map} +1 -1
  13. package/dist/chunks/collapsible-Dv306QOT.js +40 -0
  14. package/dist/chunks/collapsible-Dv306QOT.js.map +1 -0
  15. package/dist/chunks/command-BfCCRaoc.js +225 -0
  16. package/dist/chunks/command-BfCCRaoc.js.map +1 -0
  17. package/dist/chunks/{copy-button-B_B15mEi.js → copy-button-D8XyP0oR.js} +10 -10
  18. package/dist/chunks/copy-button-D8XyP0oR.js.map +1 -0
  19. package/dist/chunks/{data-table-BeUwsjmX.js → data-table-Mog6m3d9.js} +2 -2
  20. package/dist/chunks/{data-table-BeUwsjmX.js.map → data-table-Mog6m3d9.js.map} +1 -1
  21. package/dist/chunks/{description-list-C-mwe4ki.js → description-list-8qm83WZB.js} +17 -17
  22. package/dist/chunks/description-list-8qm83WZB.js.map +1 -0
  23. package/dist/chunks/{dialog-BYiAFetB.js → dialog-DeM4axJ6.js} +47 -51
  24. package/dist/chunks/{dialog-BYiAFetB.js.map → dialog-DeM4axJ6.js.map} +1 -1
  25. package/dist/chunks/{error-BAQgnde7.js → error-VvHnteyf.js} +2 -2
  26. package/dist/chunks/{error-BAQgnde7.js.map → error-VvHnteyf.js.map} +1 -1
  27. package/dist/chunks/{filter-bar-DTaLgQr0.js → filter-bar-BcpeTKCC.js} +38 -36
  28. package/dist/chunks/filter-bar-BcpeTKCC.js.map +1 -0
  29. package/dist/chunks/{log-list-BERiO4UJ.js → log-list-Didt-MV4.js} +2 -2
  30. package/dist/chunks/{log-list-BERiO4UJ.js.map → log-list-Didt-MV4.js.map} +1 -1
  31. package/dist/chunks/message-CTml-TrB.js +89 -0
  32. package/dist/chunks/message-CTml-TrB.js.map +1 -0
  33. package/dist/chunks/{scroll-area-Zik8t1Mk.js → scroll-area-DSwD9th7.js} +2 -2
  34. package/dist/chunks/{scroll-area-Zik8t1Mk.js.map → scroll-area-DSwD9th7.js.map} +1 -1
  35. package/dist/chunks/{sidebar-DRPe5CAh.js → sidebar-BmloUwJQ.js} +57 -57
  36. package/dist/chunks/sidebar-BmloUwJQ.js.map +1 -0
  37. package/dist/chunks/{toast-72Wwlzg9.js → toast-DUvjSZF7.js} +3 -3
  38. package/dist/chunks/{toast-72Wwlzg9.js.map → toast-DUvjSZF7.js.map} +1 -1
  39. package/dist/chunks/{trace-Hku-rImp.js → trace-CjOi3_IP.js} +2 -2
  40. package/dist/chunks/{trace-Hku-rImp.js.map → trace-CjOi3_IP.js.map} +1 -1
  41. package/dist/components/accordion.js +3 -3
  42. package/dist/components/accordion.js.map +1 -1
  43. package/dist/components/bubble.js +6 -0
  44. package/dist/components/button.js +1 -1
  45. package/dist/components/chat.js +5 -0
  46. package/dist/components/code-block.js +1 -1
  47. package/dist/components/collapsible.js +1 -1
  48. package/dist/components/command.js +1 -1
  49. package/dist/components/copy-button.js +1 -1
  50. package/dist/components/data-table.js +1 -1
  51. package/dist/components/description-list.js +1 -1
  52. package/dist/components/dialog.js +1 -1
  53. package/dist/components/error.js +1 -1
  54. package/dist/components/filter-bar.js +1 -1
  55. package/dist/components/log-list.js +1 -1
  56. package/dist/components/message.js +7 -0
  57. package/dist/components/scroll-area.js +1 -1
  58. package/dist/components/toast.js +1 -1
  59. package/dist/components/trace.js +1 -1
  60. package/dist/index.js +145 -136
  61. package/dist/sections/basic-page.js +2 -2
  62. package/dist/sections/sidebar.js +1 -1
  63. package/dist/src/components/bubble/bubble.d.ts +112 -0
  64. package/dist/src/components/bubble/index.d.ts +1 -0
  65. package/dist/src/components/chat/chat.d.ts +301 -0
  66. package/dist/src/components/chat/index.d.ts +1 -0
  67. package/dist/src/components/collapsible/collapsible.d.ts +1 -1
  68. package/dist/src/components/command/command.d.ts +14 -4
  69. package/dist/src/components/command/index.d.ts +1 -1
  70. package/dist/src/components/filter-bar/filter-bar.d.ts +5 -0
  71. package/dist/src/components/message/index.d.ts +2 -0
  72. package/dist/src/components/message/message-status.d.ts +22 -0
  73. package/dist/src/components/message/message.d.ts +78 -0
  74. package/dist/src/index.d.ts +3 -0
  75. package/dist/src/sections/sidebar/sidebar.d.ts +10 -1
  76. package/dist/src/utils/animated-height.d.ts +6 -0
  77. package/package.json +13 -1
  78. package/dist/chunks/animated-height-CCO-Ocoz.js +0 -48
  79. package/dist/chunks/animated-height-CCO-Ocoz.js.map +0 -1
  80. package/dist/chunks/basic-page-65eFgq8o.js.map +0 -1
  81. package/dist/chunks/button-CdqS_t5a.js.map +0 -1
  82. package/dist/chunks/collapsible-if7ncPbI.js +0 -28
  83. package/dist/chunks/collapsible-if7ncPbI.js.map +0 -1
  84. package/dist/chunks/command-ZagVyDx7.js +0 -192
  85. package/dist/chunks/command-ZagVyDx7.js.map +0 -1
  86. package/dist/chunks/copy-button-B_B15mEi.js.map +0 -1
  87. package/dist/chunks/description-list-C-mwe4ki.js.map +0 -1
  88. package/dist/chunks/filter-bar-DTaLgQr0.js.map +0 -1
  89. package/dist/chunks/sidebar-DRPe5CAh.js.map +0 -1
@@ -1,22 +1,22 @@
1
1
  "use client";
2
2
  import { t as y } from "./cn-ChIgwEl3.js";
3
- import { a as P } from "./button-CdqS_t5a.js";
3
+ import { a as P } from "./button-CgUS0j3D.js";
4
4
  import { t as R } from "./createReactComponent-BqCmnqfX.js";
5
5
  import { t as S } from "./IconCheck-D2WuculQ.js";
6
6
  import { t as L } from "./IconSearch-BKAHkLyX.js";
7
7
  import { t as $ } from "./IconX-pZ9vrtSr.js";
8
8
  import { t as g } from "./popover-BhBJO346.js";
9
9
  import { t as w } from "./input-group-fc9_jD4d.js";
10
- import { t as d } from "./command-ZagVyDx7.js";
10
+ import { t as d } from "./command-BfCCRaoc.js";
11
11
  import { useRef as D, useState as T } from "react";
12
- import { jsx as a, jsxs as u } from "react/jsx-runtime";
12
+ import { jsx as a, jsxs as p } from "react/jsx-runtime";
13
13
  var E = [["path", {
14
14
  d: "M12 5l0 14",
15
15
  key: "svg-0"
16
16
  }], ["path", {
17
17
  d: "M5 12l14 0",
18
18
  key: "svg-1"
19
- }]], F = R("outline", "plus", "Plus", E), O = [
19
+ }]], F = R("outline", "plus", "Plus", E), z = [
20
20
  {
21
21
  label: "Last hour",
22
22
  hours: 1
@@ -33,7 +33,7 @@ var E = [["path", {
33
33
  label: "Last 30 days",
34
34
  hours: 720
35
35
  }
36
- ], _ = 8;
36
+ ], O = 8;
37
37
  function k(e) {
38
38
  return e == null ? !1 : typeof e == "string" ? e !== "" : Array.isArray(e) ? e.length > 0 : e.from != null || e.to != null;
39
39
  }
@@ -46,31 +46,31 @@ function x(e) {
46
46
  ...r ? { timeZone: "UTC" } : {}
47
47
  }).format(n);
48
48
  }
49
- function z(e, r) {
49
+ function _(e, r) {
50
50
  if (!k(r) || r == null) return "";
51
51
  if (e.type === "date-range") {
52
52
  const l = r;
53
53
  return l.preset ? l.preset : l.from && l.to ? `${x(l.from)} to ${x(l.to)}` : l.from ? `From ${x(l.from)}` : l.to ? `Until ${x(l.to)}` : "";
54
54
  }
55
- const n = (l) => e.options.find((p) => p.value === l)?.label ?? l;
55
+ const n = (l) => e.options.find((u) => u.value === l)?.label ?? l;
56
56
  return Array.isArray(r) ? r.map(n).join(", ") : typeof r == "string" ? n(r) : "";
57
57
  }
58
58
  var C = "outline-pho-brand flex h-8 min-w-0 cursor-pointer items-center gap-1.5 rounded-full text-base transition-[color,background-color] duration-100 ease-linear focus-visible:outline-2 focus-visible:-outline-offset-2 [&>svg]:size-4 [&>svg]:shrink-0";
59
59
  function B({ definition: e, value: r, onChange: n }) {
60
- const [l, p] = T(!1), i = D(null), c = k(r), t = z(e, r);
61
- return /* @__PURE__ */ u(g.Root, {
60
+ const [l, u] = T(!1), i = D(null), c = k(r), t = _(e, r);
61
+ return /* @__PURE__ */ p(g.Root, {
62
62
  open: l,
63
- onOpenChange: p,
64
- children: [/* @__PURE__ */ u("span", {
63
+ onOpenChange: u,
64
+ children: [/* @__PURE__ */ p("span", {
65
65
  "data-filter-chip": e.key,
66
66
  "data-set": c || void 0,
67
67
  className: y("ring-pho-primary inline-flex max-w-full shrink-0 items-stretch rounded-full ring-1 ring-inset", c ? "bg-pho-secondary" : "bg-transparent"),
68
- children: [/* @__PURE__ */ u(g.Trigger, {
68
+ children: [/* @__PURE__ */ p(g.Trigger, {
69
69
  "aria-label": c ? void 0 : `${e.label} filter`,
70
70
  className: y(C, "hover:bg-pho-ghost-hover", c ? "pr-1.5 pl-3" : "pr-3 pl-2.5", c ? "rounded-r-none" : ""),
71
- children: [c ? null : /* @__PURE__ */ a(F, { "aria-hidden": !0 }), c ? /* @__PURE__ */ u("span", {
71
+ children: [c ? null : /* @__PURE__ */ a(F, { "aria-hidden": !0 }), c ? /* @__PURE__ */ p("span", {
72
72
  className: "flex min-w-0 items-baseline gap-1",
73
- children: [/* @__PURE__ */ u("span", {
73
+ children: [/* @__PURE__ */ p("span", {
74
74
  className: "text-pho-description shrink-0",
75
75
  children: [e.label, ":"]
76
76
  }), /* @__PURE__ */ a("span", {
@@ -100,14 +100,14 @@ function B({ definition: e, value: r, onChange: n }) {
100
100
  value: r,
101
101
  focusRef: i,
102
102
  onChange: (m) => {
103
- n(m), m?.preset && p(!1);
103
+ n(m), m?.preset && u(!1);
104
104
  }
105
105
  }) : /* @__PURE__ */ a(G, {
106
106
  definition: e,
107
107
  value: r,
108
108
  focusRef: i,
109
109
  onChange: (m) => {
110
- n(m), e.multiple || p(!1);
110
+ n(m), e.multiple || u(!1);
111
111
  }
112
112
  })
113
113
  })
@@ -115,7 +115,7 @@ function B({ definition: e, value: r, onChange: n }) {
115
115
  });
116
116
  }
117
117
  function G({ definition: e, value: r, focusRef: n, onChange: l }) {
118
- const [p, i] = T(""), c = e.searchable ?? e.options.length > _, t = new Set(r == null ? [] : Array.isArray(r) ? r : [r]), m = p.trim().toLowerCase(), f = m ? e.options.filter((s) => s.label.toLowerCase().includes(m) || s.value.toLowerCase().includes(m)) : e.options, v = (s) => {
118
+ const [u, i] = T(""), c = e.searchable ?? e.options.length > O, t = new Set(r == null ? [] : Array.isArray(r) ? r : [r]), m = u.trim().toLowerCase(), f = m ? e.options.filter((s) => s.label.toLowerCase().includes(m) || s.value.toLowerCase().includes(m)) : e.options, v = (s) => {
119
119
  if (e.multiple) {
120
120
  const b = new Set(t);
121
121
  b.has(s.value) ? b.delete(s.value) : b.add(s.value);
@@ -125,27 +125,28 @@ function G({ definition: e, value: r, focusRef: n, onChange: l }) {
125
125
  }
126
126
  l(t.has(s.value) ? void 0 : s.value);
127
127
  };
128
- return /* @__PURE__ */ u(d.Root, {
128
+ return /* @__PURE__ */ p(d.Root, {
129
129
  label: `${e.label} options`,
130
130
  ref: (s) => {
131
131
  c || (n.current = s);
132
132
  },
133
133
  className: "rounded-base",
134
- children: [c ? /* @__PURE__ */ u(d.InputGroup, { children: [/* @__PURE__ */ a(L, {
134
+ children: [c ? /* @__PURE__ */ p(d.InputGroup, { children: [/* @__PURE__ */ a(L, {
135
135
  "aria-hidden": !0,
136
136
  className: "text-pho-description pointer-events-none absolute left-3 size-4"
137
137
  }), /* @__PURE__ */ a(d.Input, {
138
138
  ref: (s) => {
139
139
  n.current = s;
140
140
  },
141
- value: p,
141
+ value: u,
142
142
  onValueChange: i,
143
143
  placeholder: e.searchPlaceholder ?? "Search",
144
144
  "aria-label": `Search ${e.label.toLowerCase()} options`,
145
145
  className: "py-2 pr-3 pl-9 text-base"
146
- })] }) : null, /* @__PURE__ */ u(d.List, { children: [/* @__PURE__ */ a(d.Empty, { children: "Nothing matches." }), f.map((s) => /* @__PURE__ */ a(d.Item, {
146
+ })] }) : null, /* @__PURE__ */ p(d.List, { children: [/* @__PURE__ */ a(d.Empty, { children: "Nothing matches." }), f.map((s) => /* @__PURE__ */ a(d.Item, {
147
147
  value: s.value,
148
- title: s.label,
148
+ size: "sm",
149
+ title: s.title ?? s.label,
149
150
  "aria-checked": t.has(s.value),
150
151
  icon: t.has(s.value) ? /* @__PURE__ */ a(S, { "aria-hidden": !0 }) : null,
151
152
  onSelect: () => v(s)
@@ -154,7 +155,7 @@ function G({ definition: e, value: r, focusRef: n, onChange: l }) {
154
155
  }
155
156
  var I = "bg-pho-primary ring-pho-primary text-pho-primary focus-visible:ring-pho-brand w-full min-w-0 rounded-sm px-2 py-1 text-base ring-1 transition-shadow duration-100 ease-linear outline-none ring-inset focus-visible:ring-2";
156
157
  function U({ definition: e, value: r, focusRef: n, onChange: l }) {
157
- const p = e.presets ?? O, i = (t) => t == null ? "" : A.test(t) ? t : t.slice(0, 10), c = (t, m) => {
158
+ const u = e.presets ?? z, i = (t) => t == null ? "" : A.test(t) ? t : t.slice(0, 10), c = (t, m) => {
158
159
  const f = {
159
160
  from: i(r?.from) || null,
160
161
  to: i(r?.to) || null,
@@ -162,7 +163,7 @@ function U({ definition: e, value: r, focusRef: n, onChange: l }) {
162
163
  };
163
164
  l(f.from == null && f.to == null ? void 0 : f);
164
165
  };
165
- return /* @__PURE__ */ u("div", {
166
+ return /* @__PURE__ */ p("div", {
166
167
  className: "flex flex-col",
167
168
  children: [/* @__PURE__ */ a(d.Root, {
168
169
  label: `${e.label} presets`,
@@ -170,8 +171,9 @@ function U({ definition: e, value: r, focusRef: n, onChange: l }) {
170
171
  n.current = t;
171
172
  },
172
173
  className: "rounded-t-[inherit]",
173
- children: /* @__PURE__ */ a(d.List, { children: p.map((t) => /* @__PURE__ */ a(d.Item, {
174
+ children: /* @__PURE__ */ a(d.List, { children: u.map((t) => /* @__PURE__ */ a(d.Item, {
174
175
  value: t.label,
176
+ size: "sm",
175
177
  title: t.label,
176
178
  "aria-checked": r?.preset === t.label,
177
179
  icon: r?.preset === t.label ? /* @__PURE__ */ a(S, { "aria-hidden": !0 }) : null,
@@ -181,9 +183,9 @@ function U({ definition: e, value: r, focusRef: n, onChange: l }) {
181
183
  preset: t.label
182
184
  })
183
185
  }, t.label)) })
184
- }), /* @__PURE__ */ u("div", {
186
+ }), /* @__PURE__ */ p("div", {
185
187
  className: "border-pho-secondary grid grid-cols-2 gap-2 border-t p-2",
186
- children: [/* @__PURE__ */ u("label", {
188
+ children: [/* @__PURE__ */ p("label", {
187
189
  className: "text-pho-description flex min-w-0 flex-col gap-1 text-xs",
188
190
  children: ["From", /* @__PURE__ */ a("input", {
189
191
  type: "date",
@@ -192,7 +194,7 @@ function U({ definition: e, value: r, focusRef: n, onChange: l }) {
192
194
  onChange: (t) => c("from", t.target.value),
193
195
  className: I
194
196
  })]
195
- }), /* @__PURE__ */ u("label", {
197
+ }), /* @__PURE__ */ p("label", {
196
198
  className: "text-pho-description flex min-w-0 flex-col gap-1 text-xs",
197
199
  children: ["To", /* @__PURE__ */ a("input", {
198
200
  type: "date",
@@ -205,25 +207,25 @@ function U({ definition: e, value: r, focusRef: n, onChange: l }) {
205
207
  })]
206
208
  });
207
209
  }
208
- function j({ definitions: e, filters: r, onFiltersChange: n, search: l, onSearchChange: p, searchPlaceholder: i = "Search", searchLabel: c, sticky: t, className: m, children: f, ...v }) {
210
+ function j({ definitions: e, filters: r, onFiltersChange: n, search: l, onSearchChange: u, searchPlaceholder: i = "Search", searchLabel: c, sticky: t, className: m, children: f, ...v }) {
209
211
  const s = e.filter((o) => k(r[o.key])).length, b = (o, h) => {
210
212
  const N = { ...r };
211
213
  h === void 0 ? delete N[o] : N[o] = h, n(N);
212
214
  };
213
- return /* @__PURE__ */ u("div", {
215
+ return /* @__PURE__ */ p("div", {
214
216
  "data-filter-bar": "",
215
217
  className: y("flex min-w-0 flex-wrap items-center gap-2", t && "sticky top-0 z-10 bg-[var(--pho-surface,var(--background-color-pho-page))] py-3", m),
216
218
  ...v,
217
219
  children: [
218
- p != null ? /* @__PURE__ */ u(w.Root, {
220
+ u != null ? /* @__PURE__ */ p(w.Root, {
219
221
  size: "sm",
220
222
  className: "w-64 max-w-full",
221
223
  children: [/* @__PURE__ */ a(w.Addon, { children: /* @__PURE__ */ a(L, {}) }), /* @__PURE__ */ a(w.Input, {
222
224
  type: "search",
223
225
  value: l ?? "",
224
- onChange: (o) => p(o.target.value),
226
+ onChange: (o) => u(o.target.value),
225
227
  onKeyDown: (o) => {
226
- o.key === "Escape" && (o.preventDefault(), p(""));
228
+ o.key === "Escape" && (o.preventDefault(), u(""));
227
229
  },
228
230
  placeholder: i,
229
231
  "aria-label": c ?? i
@@ -252,8 +254,8 @@ j.displayName = "FilterBar";
252
254
  export {
253
255
  k as i,
254
256
  j as n,
255
- z as r,
256
- O as t
257
+ _ as r,
258
+ z as t
257
259
  };
258
260
 
259
- //# sourceMappingURL=filter-bar-DTaLgQr0.js.map
261
+ //# sourceMappingURL=filter-bar-BcpeTKCC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter-bar-BcpeTKCC.js","names":[],"sources":["../../../../node_modules/.pnpm/@tabler+icons-react@3.46.0_react@19.2.8/node_modules/@tabler/icons-react/dist/esm/icons/IconPlus.mjs","../../src/components/filter-bar/filter-bar.tsx"],"sourcesContent":["/**\n * @license @tabler/icons-react v3.46.0 - MIT\n *\n * This source code is licensed under the MIT license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createReactComponent from '../createReactComponent.mjs';\n\nconst __iconNode = [[\"path\", { \"d\": \"M12 5l0 14\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M5 12l14 0\", \"key\": \"svg-1\" }]];\nconst IconPlus = createReactComponent(\"outline\", \"plus\", \"Plus\", __iconNode);\n\nexport { __iconNode, IconPlus as default };\n//# sourceMappingURL=IconPlus.mjs.map\n","import {\n useRef,\n useState,\n type ComponentProps,\n type ReactNode,\n type Ref,\n type RefObject,\n} from \"react\";\nimport { IconCheck, IconPlus, IconSearch, IconX } from \"@tabler/icons-react\";\nimport { Button } from \"../button\";\nimport { Command } from \"../command\";\nimport { InputGroup } from \"../input-group\";\nimport { Popover } from \"../popover\";\nimport { cn } from \"../../utils/cn\";\n\n/**\n * Filter Bar — the row that pins above a log list: a search field for an\n * id, then one chip per filter. A chip with nothing chosen reads\n * `+ Status`; with a value it reads `Status: Failed` and carries its own\n * clear. Each chip opens a small popover: a keyboard-first list of\n * options (with a search field past eight of them), or presets and a\n * from / to pair for a date range.\n *\n * Design notes:\n * - Fully controlled. The page owns `filters` and `search` and hands\n * them back changed; the bar keeps no state but which popover is open.\n * That keeps the query in one place (the URL, a store) and lets the\n * list refetch from it.\n * - Chips are real buttons (the trigger, and the clear beside it), so\n * the bar is one tab stop per control and Escape closes a popover\n * back onto its chip.\n * - No scroll container of its own: chips wrap. `sticky` pins it to the\n * top of the nearest scroll pane on the surface it sits on.\n */\n\nexport interface FilterOption {\n value: string;\n label: string;\n /**\n * Rich option row (a badge, mono path). Search and the set chip still\n * use `label` and `value` strings.\n */\n title?: ReactNode;\n}\n\ninterface FilterDefinitionBase {\n /** The key in `filters` this chip reads and writes. */\n key: string;\n /** The chip's word — \"Status\", \"Event type\". Sentence case. */\n label: string;\n}\n\nexport interface SelectFilterDefinition extends FilterDefinitionBase {\n type?: \"select\";\n options: readonly FilterOption[];\n /** Several values at once (`string[]`); one at a time by default. */\n multiple?: boolean;\n /**\n * A search field above the options. On by default past eight options,\n * off below; say so to override.\n */\n searchable?: boolean;\n /** The search field's placeholder. @default \"Search\" */\n searchPlaceholder?: string;\n}\n\n/** A relative preset for a date-range filter — \"Last 7 days\" is 168 hours. */\nexport interface DateRangePreset {\n label: string;\n hours: number;\n}\n\nexport interface DateRangeFilterDefinition extends FilterDefinitionBase {\n type: \"date-range\";\n /** The relative presets listed above the from / to pair. */\n presets?: readonly DateRangePreset[];\n}\n\nexport type FilterDefinition =\n SelectFilterDefinition | DateRangeFilterDefinition;\n\n/**\n * A date-range value. A preset stores an absolute ISO timestamp in `from`\n * (computed when chosen) and its label in `preset`; the from / to pair\n * stores plain `YYYY-MM-DD` dates. Either side may be open (`null`).\n */\nexport interface DateRangeValue {\n from: string | null;\n to: string | null;\n preset?: string;\n}\n\nexport type FilterValue = string | string[] | DateRangeValue;\n\n/** The page's filter state — one entry per definition key that is set. */\nexport type FilterValues = Record<string, FilterValue | undefined>;\n\n/** The relative presets a date-range chip lists when given none. */\nexport const FILTER_DATE_PRESETS: readonly DateRangePreset[] = [\n { label: \"Last hour\", hours: 1 },\n { label: \"Last 24 hours\", hours: 24 },\n { label: \"Last 7 days\", hours: 24 * 7 },\n { label: \"Last 30 days\", hours: 24 * 30 },\n];\n\n/** Past this many options a select chip shows a search field. */\nconst SEARCHABLE_PAST = 8;\n\n/** Whether a filter value counts as set — an empty string or list does not. */\nexport function isFilterSet(value: FilterValue | undefined): boolean {\n if (value == null) return false;\n if (typeof value === \"string\") return value !== \"\";\n if (Array.isArray(value)) return value.length > 0;\n return value.from != null || value.to != null;\n}\n\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nfunction formatDay(value: string): string {\n // A plain date is a calendar day: format it in UTC so the day survives\n // every viewer timezone. A timestamp is a moment: format it locally.\n const dateOnly = DATE_ONLY.test(value);\n const date = new Date(dateOnly ? `${value}T00:00:00Z` : value);\n if (Number.isNaN(date.getTime())) return value;\n return new Intl.DateTimeFormat(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n ...(dateOnly ? { timeZone: \"UTC\" } : {}),\n }).format(date);\n}\n\n/** The words a set chip shows after its label. */\nexport function formatFilterValue(\n definition: FilterDefinition,\n value: FilterValue | undefined,\n): string {\n if (!isFilterSet(value) || value == null) return \"\";\n if (definition.type === \"date-range\") {\n const range = value as DateRangeValue;\n if (range.preset) return range.preset;\n if (range.from && range.to) {\n return `${formatDay(range.from)} to ${formatDay(range.to)}`;\n }\n if (range.from) return `From ${formatDay(range.from)}`;\n if (range.to) return `Until ${formatDay(range.to)}`;\n return \"\";\n }\n const labelOf = (item: string) =>\n definition.options.find((option) => option.value === item)?.label ?? item;\n if (Array.isArray(value)) return value.map(labelOf).join(\", \");\n return typeof value === \"string\" ? labelOf(value) : \"\";\n}\n\n/* --------------------------------- chips -------------------------------- */\n\nconst CHIP_TRIGGER =\n \"outline-pho-brand flex h-8 min-w-0 cursor-pointer items-center gap-1.5 rounded-full text-base transition-[color,background-color] duration-100 ease-linear focus-visible:outline-2 focus-visible:-outline-offset-2 [&>svg]:size-4 [&>svg]:shrink-0\";\n\nfunction FilterChip({\n definition,\n value,\n onChange,\n}: {\n definition: FilterDefinition;\n value: FilterValue | undefined;\n onChange: (next: FilterValue | undefined) => void;\n}) {\n const [open, setOpen] = useState(false);\n const focusRef = useRef<HTMLElement | null>(null);\n const set = isFilterSet(value);\n const display = formatFilterValue(definition, value);\n return (\n <Popover.Root open={open} onOpenChange={setOpen}>\n <span\n data-filter-chip={definition.key}\n data-set={set || undefined}\n className={cn(\n \"ring-pho-primary inline-flex max-w-full shrink-0 items-stretch rounded-full ring-1 ring-inset\",\n set ? \"bg-pho-secondary\" : \"bg-transparent\",\n )}\n >\n <Popover.Trigger\n aria-label={set ? undefined : `${definition.label} filter`}\n className={cn(\n CHIP_TRIGGER,\n \"hover:bg-pho-ghost-hover\",\n set ? \"pr-1.5 pl-3\" : \"pr-3 pl-2.5\",\n set ? \"rounded-r-none\" : \"\",\n )}\n >\n {set ? null : <IconPlus aria-hidden />}\n {set ? (\n <span className=\"flex min-w-0 items-baseline gap-1\">\n <span className=\"text-pho-description shrink-0\">\n {definition.label}:\n </span>\n <span className=\"text-pho-primary max-w-56 truncate font-medium\">\n {display}\n </span>\n </span>\n ) : (\n <span className=\"text-pho-secondary\">{definition.label}</span>\n )}\n </Popover.Trigger>\n {set ? (\n <button\n type=\"button\"\n aria-label={`Clear ${definition.label.toLowerCase()} filter`}\n onClick={() => onChange(undefined)}\n className={cn(\n CHIP_TRIGGER,\n \"text-pho-secondary hover:bg-pho-ghost-hover hover:text-pho-secondary-hover rounded-l-none pr-2.5 pl-1 [&>svg]:size-3.5\",\n )}\n >\n <IconX aria-hidden />\n </button>\n ) : null}\n </span>\n <Popover.Portal>\n <Popover.Positioner align=\"start\" sideOffset={6}>\n <Popover.Popup\n aria-label={`${definition.label} filter`}\n initialFocus={focusRef}\n className={cn(\n \"w-64 p-0\",\n definition.type === \"date-range\" && \"w-72\",\n )}\n >\n {definition.type === \"date-range\" ? (\n <DateRangePanel\n definition={definition}\n value={value as DateRangeValue | undefined}\n focusRef={focusRef}\n onChange={(next) => {\n onChange(next);\n if (next?.preset) setOpen(false);\n }}\n />\n ) : (\n <OptionsPanel\n definition={definition}\n value={value as string | string[] | undefined}\n focusRef={focusRef}\n onChange={(next) => {\n onChange(next);\n if (!definition.multiple) setOpen(false);\n }}\n />\n )}\n </Popover.Popup>\n </Popover.Positioner>\n </Popover.Portal>\n </Popover.Root>\n );\n}\n\nfunction OptionsPanel({\n definition,\n value,\n focusRef,\n onChange,\n}: {\n definition: SelectFilterDefinition;\n value: string | string[] | undefined;\n focusRef: RefObject<HTMLElement | null>;\n onChange: (next: string | string[] | undefined) => void;\n}) {\n const [query, setQuery] = useState(\"\");\n const searchable =\n definition.searchable ?? definition.options.length > SEARCHABLE_PAST;\n const chosen = new Set(\n value == null ? [] : Array.isArray(value) ? value : [value],\n );\n const needle = query.trim().toLowerCase();\n const options = needle\n ? definition.options.filter(\n (option) =>\n option.label.toLowerCase().includes(needle) ||\n option.value.toLowerCase().includes(needle),\n )\n : definition.options;\n const pick = (option: FilterOption) => {\n if (definition.multiple) {\n const next = new Set(chosen);\n if (next.has(option.value)) next.delete(option.value);\n else next.add(option.value);\n // Keep the definition's order, so the chip reads the same way\n // whatever order the values were chosen in.\n const ordered = definition.options\n .map((item) => item.value)\n .filter((item) => next.has(item));\n onChange(ordered.length > 0 ? ordered : undefined);\n return;\n }\n onChange(chosen.has(option.value) ? undefined : option.value);\n };\n return (\n <Command.Root\n label={`${definition.label} options`}\n // Without a search field the list itself takes focus, so the arrow\n // keys work from the first frame.\n ref={(node: HTMLDivElement | null) => {\n if (!searchable) focusRef.current = node;\n }}\n className=\"rounded-base\"\n >\n {searchable ? (\n <Command.InputGroup>\n <IconSearch\n aria-hidden\n className=\"text-pho-description pointer-events-none absolute left-3 size-4\"\n />\n <Command.Input\n ref={(node: HTMLInputElement | null) => {\n focusRef.current = node;\n }}\n value={query}\n onValueChange={setQuery}\n placeholder={definition.searchPlaceholder ?? \"Search\"}\n aria-label={`Search ${definition.label.toLowerCase()} options`}\n className=\"py-2 pr-3 pl-9 text-base\"\n />\n </Command.InputGroup>\n ) : null}\n <Command.List>\n <Command.Empty>Nothing matches.</Command.Empty>\n {options.map((option) => (\n <Command.Item\n key={option.value}\n value={option.value}\n size=\"sm\"\n title={option.title ?? option.label}\n aria-checked={chosen.has(option.value)}\n icon={chosen.has(option.value) ? <IconCheck aria-hidden /> : null}\n onSelect={() => pick(option)}\n />\n ))}\n </Command.List>\n </Command.Root>\n );\n}\n\nconst DATE_INPUT =\n \"bg-pho-primary ring-pho-primary text-pho-primary focus-visible:ring-pho-brand w-full min-w-0 rounded-sm px-2 py-1 text-base ring-1 transition-shadow duration-100 ease-linear outline-none ring-inset focus-visible:ring-2\";\n\nfunction DateRangePanel({\n definition,\n value,\n focusRef,\n onChange,\n}: {\n definition: DateRangeFilterDefinition;\n value: DateRangeValue | undefined;\n focusRef: RefObject<HTMLElement | null>;\n onChange: (next: DateRangeValue | undefined) => void;\n}) {\n const presets = definition.presets ?? FILTER_DATE_PRESETS;\n // A preset's `from` is a timestamp; the day field shows its date.\n const day = (side: string | null | undefined) =>\n side == null ? \"\" : DATE_ONLY.test(side) ? side : side.slice(0, 10);\n const setSide = (side: \"from\" | \"to\", next: string) => {\n // Editing either day leaves a preset behind: the pair is the value now,\n // and a preset's timestamp becomes the day it fell on.\n const range: DateRangeValue = {\n from: day(value?.from) || null,\n to: day(value?.to) || null,\n [side]: next || null,\n };\n onChange(range.from == null && range.to == null ? undefined : range);\n };\n return (\n <div className=\"flex flex-col\">\n <Command.Root\n label={`${definition.label} presets`}\n ref={(node: HTMLDivElement | null) => {\n focusRef.current = node;\n }}\n className=\"rounded-t-[inherit]\"\n >\n <Command.List>\n {presets.map((preset) => (\n <Command.Item\n key={preset.label}\n value={preset.label}\n size=\"sm\"\n title={preset.label}\n aria-checked={value?.preset === preset.label}\n icon={\n value?.preset === preset.label ? (\n <IconCheck aria-hidden />\n ) : null\n }\n onSelect={() =>\n onChange({\n from: new Date(\n Date.now() - preset.hours * 60 * 60 * 1000,\n ).toISOString(),\n to: null,\n preset: preset.label,\n })\n }\n />\n ))}\n </Command.List>\n </Command.Root>\n {/* The day fields stand outside the command list: its arrow-key\n handling would otherwise swallow the native date stepping. */}\n <div className=\"border-pho-secondary grid grid-cols-2 gap-2 border-t p-2\">\n <label className=\"text-pho-description flex min-w-0 flex-col gap-1 text-xs\">\n From\n <input\n type=\"date\"\n value={day(value?.from)}\n max={day(value?.to) || undefined}\n onChange={(event) => setSide(\"from\", event.target.value)}\n className={DATE_INPUT}\n />\n </label>\n <label className=\"text-pho-description flex min-w-0 flex-col gap-1 text-xs\">\n To\n <input\n type=\"date\"\n value={day(value?.to)}\n min={day(value?.from) || undefined}\n onChange={(event) => setSide(\"to\", event.target.value)}\n className={DATE_INPUT}\n />\n </label>\n </div>\n </div>\n );\n}\n\n/* ---------------------------------- bar --------------------------------- */\n\nexport interface FilterBarProps extends Omit<\n ComponentProps<\"div\">,\n \"children\"\n> {\n /** One chip per definition, in this order. */\n definitions: readonly FilterDefinition[];\n /** The values, keyed by definition key. Absent or empty means unset. */\n filters: FilterValues;\n /** The whole map, changed — set, cleared, or cleared all. */\n onFiltersChange: (next: FilterValues) => void;\n /** The search field's text. The field renders only with `onSearchChange`. */\n search?: string;\n onSearchChange?: (value: string) => void;\n /** A teaching phrase — \"Find an event by id\". @default \"Search\" */\n searchPlaceholder?: string;\n /** The field's accessible name when the placeholder is too short to be one. */\n searchLabel?: string;\n /**\n * Pin the bar to the top of the nearest scroll pane. It paints the\n * surface it sits on (`--pho-surface`) so rows scroll under it.\n */\n sticky?: boolean;\n /** Controls after the chips — a range picker, an export button. */\n children?: ReactNode;\n ref?: Ref<HTMLDivElement>;\n}\n\nexport function FilterBar({\n definitions,\n filters,\n onFiltersChange,\n search,\n onSearchChange,\n searchPlaceholder = \"Search\",\n searchLabel,\n sticky,\n className,\n children,\n ...props\n}: FilterBarProps) {\n const setCount = definitions.filter((definition) =>\n isFilterSet(filters[definition.key]),\n ).length;\n const change = (key: string, next: FilterValue | undefined) => {\n const rest = { ...filters };\n if (next === undefined) delete rest[key];\n else rest[key] = next;\n onFiltersChange(rest);\n };\n return (\n <div\n data-filter-bar=\"\"\n className={cn(\n \"flex min-w-0 flex-wrap items-center gap-2\",\n sticky &&\n \"sticky top-0 z-10 bg-[var(--pho-surface,var(--background-color-pho-page))] py-3\",\n className,\n )}\n {...props}\n >\n {onSearchChange != null ? (\n <InputGroup.Root size=\"sm\" className=\"w-64 max-w-full\">\n <InputGroup.Addon>\n <IconSearch />\n </InputGroup.Addon>\n <InputGroup.Input\n type=\"search\"\n value={search ?? \"\"}\n onChange={(event) => onSearchChange(event.target.value)}\n onKeyDown={(event) => {\n if (event.key !== \"Escape\") return;\n event.preventDefault();\n onSearchChange(\"\");\n }}\n placeholder={searchPlaceholder}\n aria-label={searchLabel ?? searchPlaceholder}\n />\n </InputGroup.Root>\n ) : null}\n {definitions.map((definition) => (\n <FilterChip\n key={definition.key}\n definition={definition}\n value={filters[definition.key]}\n onChange={(next) => change(definition.key, next)}\n />\n ))}\n {setCount > 1 ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n const rest = { ...filters };\n for (const definition of definitions) delete rest[definition.key];\n onFiltersChange(rest);\n }}\n >\n Clear filters\n </Button>\n ) : null}\n {children}\n </div>\n );\n}\n\nFilterBar.displayName = \"FilterBar\";\n"],"x_google_ignoreList":[0],"mappings":"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"}
@@ -3,7 +3,7 @@ import { t as a } from "./cn-ChIgwEl3.js";
3
3
  import { n as k } from "./link-provider-DXzDQ08N.js";
4
4
  import { n as j } from "./use-copy-D6JwKUll.js";
5
5
  import { n as O } from "./spinner-BzLUo4Xk.js";
6
- import { a as A } from "./button-CdqS_t5a.js";
6
+ import { a as A } from "./button-CgUS0j3D.js";
7
7
  import { i as D } from "./status-view-C8tabjDu.js";
8
8
  import { t as g } from "./skeleton-DwjSl4vC.js";
9
9
  import { t as h } from "./tooltip-BTF7GChl.js";
@@ -241,4 +241,4 @@ export {
241
241
  pe as t
242
242
  };
243
243
 
244
- //# sourceMappingURL=log-list-BERiO4UJ.js.map
244
+ //# sourceMappingURL=log-list-Didt-MV4.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"log-list-BERiO4UJ.js","names":[],"sources":["../../src/components/log-list/log-list.tsx"],"sourcesContent":["import {\n useEffect,\n useId,\n useRef,\n type ComponentProps,\n type MouseEvent,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { Badge, type BadgeColor, type BadgeSize } from \"../badge\";\nimport { Button } from \"../button\";\nimport { Skeleton } from \"../skeleton\";\nimport { Spinner } from \"../spinner\";\nimport { StatusView } from \"../status-view\";\nimport { Tooltip } from \"../tooltip\";\nimport { cn } from \"../../utils/cn\";\nimport { useCopy } from \"../../utils/use-copy\";\nimport { useLinkComponent } from \"../../utils/link-provider\";\n\n/**\n * Log List — rows of requests, deliveries, or messages, newest first,\n * each one line: a mono id you can copy, a status, the method, what\n * happened, and how long ago. Reach for it where a `DataTable` would be\n * columns of the same few fields: a log reads left to right as one line,\n * and the page filters it from a `FilterBar` pinned above.\n *\n * Design notes:\n * - The surface is `BasicPage.Card`'s: hairline border, `base` radius,\n * hairline rules between rows. It sits in a `Stack` as any card does.\n * - The row is one link (or one button). It stretches over the row from\n * the label, the way a `DataTable` row's does, so the whole row is one\n * pointer target and one tab stop; the copy control sits above it.\n * - The id truncates in the middle, keeping the prefix that says what it\n * is and the tail that tells it apart. The full id is a tooltip away,\n * and one click copies it.\n * - `LoadMore` is the sentinel at the end: it asks for the next page as\n * it scrolls into view, and offers a button for keyboards and for\n * browsers without an observer.\n */\n\n/** Same chrome as `BasicPage.Card`. */\nconst LOG_LIST_SURFACE =\n \"rounded-base border border-pho-secondary bg-pho-primary divide-y divide-pho-secondary\";\n\n/** Middle truncation: the prefix and this many after it, then the tail. */\nconst ID_HEAD = 12;\nconst ID_TAIL = 5;\n\n/**\n * Keep the start and the end of an id and fold the middle into an\n * ellipsis — `pho_req_01m04axjqrfzxv4xjet40a84gb` reads\n * `pho_req_01m0…a84gb`. Ids that fit are returned whole.\n */\nexport function truncateMiddle(\n value: string,\n head = ID_HEAD,\n tail = ID_TAIL,\n): string {\n if (value.length <= head + tail + 1) return value;\n return `${value.slice(0, head)}…${value.slice(-tail)}`;\n}\n\n/* -------------------------------- status -------------------------------- */\n\nconst SUCCESS_WORDS = new Set([\n \"success\",\n \"succeeded\",\n \"successful\",\n \"delivered\",\n \"ok\",\n \"sent\",\n \"received\",\n \"completed\",\n \"complete\",\n \"done\",\n \"active\",\n]);\nconst ERROR_WORDS = new Set([\n \"failed\",\n \"failure\",\n \"error\",\n \"errored\",\n \"rejected\",\n \"undelivered\",\n \"timeout\",\n \"timed_out\",\n \"timed out\",\n \"canceled\",\n \"cancelled\",\n]);\nconst WARNING_WORDS = new Set([\n \"pending\",\n \"queued\",\n \"retrying\",\n \"processing\",\n \"sending\",\n \"in_progress\",\n \"in progress\",\n \"scheduled\",\n \"waiting\",\n]);\n\n/**\n * The `Badge` color for a log status: an HTTP status code (2xx success,\n * 3xx gray, 4xx warning, 5xx error) or a word (`delivered` / `failed` /\n * `pending`). Anything else is gray.\n */\nexport function logStatusColor(status: number | string): BadgeColor {\n if (typeof status === \"number\") {\n if (status < 300) return \"success\";\n if (status < 400) return \"gray\";\n if (status < 500) return \"warning\";\n return \"error\";\n }\n const word = status.trim().toLowerCase();\n if (/^\\d{3}$/.test(word)) return logStatusColor(Number(word));\n if (SUCCESS_WORDS.has(word)) return \"success\";\n if (ERROR_WORDS.has(word)) return \"error\";\n if (WARNING_WORDS.has(word)) return \"warning\";\n return \"gray\";\n}\n\n/** A status word as a label: `timed_out` reads `Timed out`. */\nexport function logStatusLabel(status: number | string): string {\n if (typeof status === \"number\") return String(status);\n const word = status.trim().replace(/_/g, \" \");\n return word.charAt(0).toUpperCase() + word.slice(1);\n}\n\nexport interface LogStatusProps extends Omit<\n ComponentProps<\"span\">,\n \"children\" | \"color\"\n> {\n /** An HTTP status code or a status word. */\n value: number | string;\n /** Override the derived word. */\n label?: ReactNode;\n /** Badge size. @default \"sm\" */\n size?: BadgeSize;\n}\n\n/** The status slot — a low-contrast `Badge` colored by `logStatusColor`. */\nfunction Status({\n value,\n label,\n size = \"sm\",\n className,\n ...props\n}: LogStatusProps) {\n return (\n <Badge\n color={logStatusColor(value)}\n contrast=\"low\"\n size={size}\n className={cn(\"tabular-nums\", className)}\n {...props}\n >\n {label ?? logStatusLabel(value)}\n </Badge>\n );\n}\n\n/** The method slot — `POST`, `GET` as a quiet mono chip. */\nfunction Method({\n className,\n ...props\n}: ComponentProps<\"span\"> & { ref?: Ref<HTMLSpanElement> }) {\n return (\n <span\n className={cn(\n \"bg-pho-secondary text-pho-secondary inline-flex h-5 min-w-12 shrink-0 items-center justify-center rounded-sm px-1.5 font-mono text-xs font-medium whitespace-nowrap\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/* ---------------------------------- id ---------------------------------- */\n\n/**\n * The id cell: a mono button that copies the full id, with a tooltip\n * holding the whole of it. The cell is a fixed width in characters so\n * ids line up down the list.\n */\nfunction IdCell({ id, cellId }: { id: string; cellId: string }) {\n const { copied, copy } = useCopy();\n return (\n <Tooltip.Root>\n <Tooltip.Trigger\n id={cellId}\n aria-label={copied ? `${id} copied` : `Copy ${id}`}\n data-copied={copied || undefined}\n onClick={() => void copy(id)}\n className={cn(\n \"text-pho-secondary outline-pho-brand hover:text-pho-primary relative z-10 -mx-1 w-[calc(18ch+0.5rem)] shrink-0 cursor-pointer truncate rounded-sm px-1 py-0.5 text-left font-mono text-xs transition-colors duration-100 ease-linear focus-visible:outline-2\",\n \"data-[copied]:text-pho-success\",\n )}\n >\n {copied ? \"Copied\" : truncateMiddle(id)}\n </Tooltip.Trigger>\n <output className=\"sr-only\" aria-live=\"polite\">\n {copied ? \"Copied\" : \"\"}\n </output>\n <Tooltip.Portal>\n <Tooltip.Positioner>\n <Tooltip.Popup className=\"font-mono\">{id}</Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n );\n}\n\n/* ---------------------------------- row --------------------------------- */\n\nexport interface LogListRowProps extends Omit<\n ComponentProps<\"div\">,\n \"children\" | \"title\" | \"onClick\" | \"id\"\n> {\n /** The record's id — mono, middle-truncated, copied on click. */\n id?: string;\n /** The status slot — usually `LogList.Status`. */\n status?: ReactNode;\n /** The method — `POST`, `GET`; rendered in `LogList.Method`. */\n method?: string;\n /** What happened — the route, the event type, the message. */\n label: ReactNode;\n /** The label in mono — for a route or an event type. */\n code?: boolean;\n /**\n * The line under the label, with real content: who received what from\n * whom, the destination that answered. A sentence, with its period.\n */\n description?: ReactNode;\n /** Trailing: how long it took — \"59 ms\". */\n duration?: ReactNode;\n /** Trailing: when — \"2 min ago\". */\n time?: ReactNode;\n /** Trailing content before the duration and time. */\n children?: ReactNode;\n /** The row is a link to its page, through the host's `LinkProvider`. */\n href?: string;\n /** The row is a button. */\n onClick?: (event: MouseEvent<HTMLElement>) => void;\n /**\n * Replace the row's link element — a router's `Link`, the way Base UI\n * components take `render`. The row's classes and handlers merge in.\n */\n render?: useRender.RenderProp;\n /** The row whose page is open beside the list — it keeps the wash. */\n current?: boolean;\n ref?: Ref<HTMLDivElement>;\n}\n\nfunction Row({\n id,\n status,\n method,\n label,\n code,\n description,\n duration,\n time,\n children,\n href,\n onClick,\n render,\n current,\n className,\n ...props\n}: LogListRowProps) {\n const Link = useLinkComponent();\n const base = useId();\n const interactive = href != null || onClick != null || render != null;\n const cellIds = {\n id: `${base}-id`,\n status: `${base}-status`,\n method: `${base}-method`,\n meta: `${base}-meta`,\n };\n const hasMeta = children != null || duration != null || time != null;\n const describedBy = [\n id != null && cellIds.id,\n status != null && cellIds.status,\n method != null && cellIds.method,\n hasMeta && cellIds.meta,\n ]\n .filter(Boolean)\n .join(\" \");\n const primary = useRender({\n render: render ?? (href != null ? <Link href={href} /> : undefined),\n defaultTagName: href != null ? \"a\" : onClick != null ? \"button\" : \"span\",\n props: {\n className: cn(\n \"text-pho-primary block min-w-0 max-w-full truncate text-left text-base font-medium outline-none\",\n code && \"font-mono text-xs\",\n // The one link, stretched over the row; the ring is the row's.\n interactive &&\n \"cursor-pointer after:absolute after:inset-0 after:content-['']\",\n ),\n onClick,\n type:\n render == null && href == null && onClick != null\n ? \"button\"\n : undefined,\n \"aria-current\": current ? \"page\" : undefined,\n \"aria-describedby\": describedBy || undefined,\n \"data-log-primary\": \"\",\n children: label,\n },\n });\n return (\n <div\n data-log-row=\"\"\n data-current={current || undefined}\n className={cn(\n \"relative flex min-h-12 min-w-0 items-center gap-3 px-5 py-2.5\",\n interactive &&\n \"hover:bg-pho-primary-hover outline-pho-brand transition-colors has-[[data-log-primary]:focus-visible]:outline-2 has-[[data-log-primary]:focus-visible]:-outline-offset-2\",\n current && \"bg-pho-primary-hover\",\n // The wash follows the card's rounding (minus the 1px border).\n \"first:rounded-t-[calc(var(--radius-base)-1px)] last:rounded-b-[calc(var(--radius-base)-1px)]\",\n className,\n )}\n {...props}\n >\n {id != null ? <IdCell id={id} cellId={cellIds.id} /> : null}\n {status != null ? (\n <span id={cellIds.status} className=\"flex shrink-0 items-center\">\n {status}\n </span>\n ) : null}\n {method != null ? <Method id={cellIds.method}>{method}</Method> : null}\n <div data-log-primary-cell=\"\" className=\"min-w-0 flex-1\">\n {primary}\n {description != null ? (\n <p className=\"text-pho-description truncate text-xs\">{description}</p>\n ) : null}\n </div>\n {hasMeta ? (\n <div\n id={cellIds.meta}\n className=\"text-pho-description ml-auto flex shrink-0 items-center gap-3 text-xs whitespace-nowrap tabular-nums\"\n >\n {children}\n {duration != null ? <span>{duration}</span> : null}\n {time != null ? <span>{time}</span> : null}\n </div>\n ) : null}\n </div>\n );\n}\n\n/* -------------------------------- states -------------------------------- */\n\nexport interface LogListEmptyProps extends Omit<\n ComponentProps<\"div\">,\n \"title\" | \"children\"\n> {\n /** One short line naming the state. @default \"Nothing here yet.\" */\n title?: ReactNode;\n /** What fills it, or why it is empty. */\n description?: ReactNode;\n /** An action, when there is one. */\n children?: ReactNode;\n}\n\n/** The list with no rows — `StatusView` at card size. */\nfunction Empty({\n title = \"Nothing here yet.\",\n description,\n children,\n className,\n ...props\n}: LogListEmptyProps) {\n return (\n <div className={cn(\"flex justify-center\", className)} {...props}>\n <StatusView\n size=\"sm\"\n title={title}\n description={description}\n actions={children}\n />\n </div>\n );\n}\n\n/** Bones in the rows' place while the first page is on its way. */\nfunction Loading({ rows = 5 }: { rows?: number }) {\n return (\n <>\n {Array.from({ length: rows }, (_, index) => (\n <div\n key={index}\n aria-hidden\n className=\"flex min-h-12 items-center gap-3 px-5 py-2.5\"\n >\n <Skeleton className=\"h-3.5 w-32\" />\n <Skeleton className=\"h-5 w-12 rounded-full\" />\n <Skeleton className=\"h-3.5 w-48\" />\n <Skeleton className=\"ml-auto h-3.5 w-16\" />\n </div>\n ))}\n </>\n );\n}\n\nexport interface LogListLoadMoreProps extends Omit<\n ComponentProps<\"div\">,\n \"children\"\n> {\n /** Asked for as the sentinel scrolls into view, and on the button. */\n onLoadMore: () => void;\n /** The next page is on its way — the sentinel shows a spinner and waits. */\n loading?: boolean;\n /** Nothing left to load — renders nothing. @default true */\n hasMore?: boolean;\n /** How far ahead of the viewport to ask. @default \"200px\" */\n rootMargin?: string;\n /** The button's words. @default \"Load more\" */\n label?: ReactNode;\n}\n\n/**\n * The sentinel at the end of the list. An `IntersectionObserver` asks for\n * the next page as it comes into view (`rootMargin` ahead of it); while\n * `loading` it shows a spinner and asks for nothing. The button is the\n * same request for keyboards and for a browser without the observer.\n * SSR-safe: the observer is created in an effect.\n */\nfunction LoadMore({\n onLoadMore,\n loading,\n hasMore = true,\n rootMargin = \"200px\",\n label = \"Load more\",\n className,\n ...props\n}: LogListLoadMoreProps) {\n const ref = useRef<HTMLDivElement | null>(null);\n const latest = useRef(onLoadMore);\n latest.current = onLoadMore;\n useEffect(() => {\n const el = ref.current;\n if (!hasMore || loading || el == null) return;\n if (typeof IntersectionObserver === \"undefined\") return;\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) latest.current();\n },\n { rootMargin },\n );\n observer.observe(el);\n return () => observer.disconnect();\n }, [hasMore, loading, rootMargin]);\n if (!hasMore) return null;\n return (\n <div\n ref={ref}\n data-log-load-more=\"\"\n aria-busy={loading || undefined}\n className={cn(\n \"flex min-h-12 items-center justify-center px-5 py-2\",\n className,\n )}\n {...props}\n >\n {loading ? (\n <Spinner aria-label=\"Loading more\" className=\"text-pho-secondary\" />\n ) : (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => onLoadMore()}>\n {label}\n </Button>\n )}\n </div>\n );\n}\n\n/* ---------------------------------- root -------------------------------- */\n\nexport interface LogListRootProps extends ComponentProps<\"div\"> {\n ref?: Ref<HTMLDivElement>;\n}\n\n/** The card the rows stack in. Mark it `aria-busy` while bones show. */\nfunction Root({ className, ...props }: LogListRootProps) {\n return (\n <div\n data-log-list=\"\"\n className={cn(LOG_LIST_SURFACE, \"min-w-0\", className)}\n {...props}\n />\n );\n}\n\n/**\n * Log List — a card of one-line log rows.\n *\n * ```tsx\n * <LogList.Root>\n * <LogList.Row\n * id=\"pho_req_01m04axjqrfzxv4xjet40a84gb\"\n * status={<LogList.Status value={200} />}\n * method=\"POST\"\n * code\n * label=\"/v1/messages\"\n * description=\"+1 332 259 3374 received a message from +1 332 222 1111.\"\n * duration=\"59 ms\"\n * time=\"2 min ago\"\n * href=\"/requests/pho_req_01m04axjqrfzxv4xjet40a84gb\"\n * />\n * <LogList.LoadMore onLoadMore={fetchNextPage} loading={isFetching} hasMore={hasNextPage} />\n * </LogList.Root>\n * ```\n */\nexport const LogList = {\n Root,\n Row,\n Status,\n Method,\n Empty,\n Loading,\n LoadMore,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"log-list-Didt-MV4.js","names":[],"sources":["../../src/components/log-list/log-list.tsx"],"sourcesContent":["import {\n useEffect,\n useId,\n useRef,\n type ComponentProps,\n type MouseEvent,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { Badge, type BadgeColor, type BadgeSize } from \"../badge\";\nimport { Button } from \"../button\";\nimport { Skeleton } from \"../skeleton\";\nimport { Spinner } from \"../spinner\";\nimport { StatusView } from \"../status-view\";\nimport { Tooltip } from \"../tooltip\";\nimport { cn } from \"../../utils/cn\";\nimport { useCopy } from \"../../utils/use-copy\";\nimport { useLinkComponent } from \"../../utils/link-provider\";\n\n/**\n * Log List — rows of requests, deliveries, or messages, newest first,\n * each one line: a mono id you can copy, a status, the method, what\n * happened, and how long ago. Reach for it where a `DataTable` would be\n * columns of the same few fields: a log reads left to right as one line,\n * and the page filters it from a `FilterBar` pinned above.\n *\n * Design notes:\n * - The surface is `BasicPage.Card`'s: hairline border, `base` radius,\n * hairline rules between rows. It sits in a `Stack` as any card does.\n * - The row is one link (or one button). It stretches over the row from\n * the label, the way a `DataTable` row's does, so the whole row is one\n * pointer target and one tab stop; the copy control sits above it.\n * - The id truncates in the middle, keeping the prefix that says what it\n * is and the tail that tells it apart. The full id is a tooltip away,\n * and one click copies it.\n * - `LoadMore` is the sentinel at the end: it asks for the next page as\n * it scrolls into view, and offers a button for keyboards and for\n * browsers without an observer.\n */\n\n/** Same chrome as `BasicPage.Card`. */\nconst LOG_LIST_SURFACE =\n \"rounded-base border border-pho-secondary bg-pho-primary divide-y divide-pho-secondary\";\n\n/** Middle truncation: the prefix and this many after it, then the tail. */\nconst ID_HEAD = 12;\nconst ID_TAIL = 5;\n\n/**\n * Keep the start and the end of an id and fold the middle into an\n * ellipsis — `pho_req_01m04axjqrfzxv4xjet40a84gb` reads\n * `pho_req_01m0…a84gb`. Ids that fit are returned whole.\n */\nexport function truncateMiddle(\n value: string,\n head = ID_HEAD,\n tail = ID_TAIL,\n): string {\n if (value.length <= head + tail + 1) return value;\n return `${value.slice(0, head)}…${value.slice(-tail)}`;\n}\n\n/* -------------------------------- status -------------------------------- */\n\nconst SUCCESS_WORDS = new Set([\n \"success\",\n \"succeeded\",\n \"successful\",\n \"delivered\",\n \"ok\",\n \"sent\",\n \"received\",\n \"completed\",\n \"complete\",\n \"done\",\n \"active\",\n]);\nconst ERROR_WORDS = new Set([\n \"failed\",\n \"failure\",\n \"error\",\n \"errored\",\n \"rejected\",\n \"undelivered\",\n \"timeout\",\n \"timed_out\",\n \"timed out\",\n \"canceled\",\n \"cancelled\",\n]);\nconst WARNING_WORDS = new Set([\n \"pending\",\n \"queued\",\n \"retrying\",\n \"processing\",\n \"sending\",\n \"in_progress\",\n \"in progress\",\n \"scheduled\",\n \"waiting\",\n]);\n\n/**\n * The `Badge` color for a log status: an HTTP status code (2xx success,\n * 3xx gray, 4xx warning, 5xx error) or a word (`delivered` / `failed` /\n * `pending`). Anything else is gray.\n */\nexport function logStatusColor(status: number | string): BadgeColor {\n if (typeof status === \"number\") {\n if (status < 300) return \"success\";\n if (status < 400) return \"gray\";\n if (status < 500) return \"warning\";\n return \"error\";\n }\n const word = status.trim().toLowerCase();\n if (/^\\d{3}$/.test(word)) return logStatusColor(Number(word));\n if (SUCCESS_WORDS.has(word)) return \"success\";\n if (ERROR_WORDS.has(word)) return \"error\";\n if (WARNING_WORDS.has(word)) return \"warning\";\n return \"gray\";\n}\n\n/** A status word as a label: `timed_out` reads `Timed out`. */\nexport function logStatusLabel(status: number | string): string {\n if (typeof status === \"number\") return String(status);\n const word = status.trim().replace(/_/g, \" \");\n return word.charAt(0).toUpperCase() + word.slice(1);\n}\n\nexport interface LogStatusProps extends Omit<\n ComponentProps<\"span\">,\n \"children\" | \"color\"\n> {\n /** An HTTP status code or a status word. */\n value: number | string;\n /** Override the derived word. */\n label?: ReactNode;\n /** Badge size. @default \"sm\" */\n size?: BadgeSize;\n}\n\n/** The status slot — a low-contrast `Badge` colored by `logStatusColor`. */\nfunction Status({\n value,\n label,\n size = \"sm\",\n className,\n ...props\n}: LogStatusProps) {\n return (\n <Badge\n color={logStatusColor(value)}\n contrast=\"low\"\n size={size}\n className={cn(\"tabular-nums\", className)}\n {...props}\n >\n {label ?? logStatusLabel(value)}\n </Badge>\n );\n}\n\n/** The method slot — `POST`, `GET` as a quiet mono chip. */\nfunction Method({\n className,\n ...props\n}: ComponentProps<\"span\"> & { ref?: Ref<HTMLSpanElement> }) {\n return (\n <span\n className={cn(\n \"bg-pho-secondary text-pho-secondary inline-flex h-5 min-w-12 shrink-0 items-center justify-center rounded-sm px-1.5 font-mono text-xs font-medium whitespace-nowrap\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/* ---------------------------------- id ---------------------------------- */\n\n/**\n * The id cell: a mono button that copies the full id, with a tooltip\n * holding the whole of it. The cell is a fixed width in characters so\n * ids line up down the list.\n */\nfunction IdCell({ id, cellId }: { id: string; cellId: string }) {\n const { copied, copy } = useCopy();\n return (\n <Tooltip.Root>\n <Tooltip.Trigger\n id={cellId}\n aria-label={copied ? `${id} copied` : `Copy ${id}`}\n data-copied={copied || undefined}\n onClick={() => void copy(id)}\n className={cn(\n \"text-pho-secondary outline-pho-brand hover:text-pho-primary relative z-10 -mx-1 w-[calc(18ch+0.5rem)] shrink-0 cursor-pointer truncate rounded-sm px-1 py-0.5 text-left font-mono text-xs transition-colors duration-100 ease-linear focus-visible:outline-2\",\n \"data-[copied]:text-pho-success\",\n )}\n >\n {copied ? \"Copied\" : truncateMiddle(id)}\n </Tooltip.Trigger>\n <output className=\"sr-only\" aria-live=\"polite\">\n {copied ? \"Copied\" : \"\"}\n </output>\n <Tooltip.Portal>\n <Tooltip.Positioner>\n <Tooltip.Popup className=\"font-mono\">{id}</Tooltip.Popup>\n </Tooltip.Positioner>\n </Tooltip.Portal>\n </Tooltip.Root>\n );\n}\n\n/* ---------------------------------- row --------------------------------- */\n\nexport interface LogListRowProps extends Omit<\n ComponentProps<\"div\">,\n \"children\" | \"title\" | \"onClick\" | \"id\"\n> {\n /** The record's id — mono, middle-truncated, copied on click. */\n id?: string;\n /** The status slot — usually `LogList.Status`. */\n status?: ReactNode;\n /** The method — `POST`, `GET`; rendered in `LogList.Method`. */\n method?: string;\n /** What happened — the route, the event type, the message. */\n label: ReactNode;\n /** The label in mono — for a route or an event type. */\n code?: boolean;\n /**\n * The line under the label, with real content: who received what from\n * whom, the destination that answered. A sentence, with its period.\n */\n description?: ReactNode;\n /** Trailing: how long it took — \"59 ms\". */\n duration?: ReactNode;\n /** Trailing: when — \"2 min ago\". */\n time?: ReactNode;\n /** Trailing content before the duration and time. */\n children?: ReactNode;\n /** The row is a link to its page, through the host's `LinkProvider`. */\n href?: string;\n /** The row is a button. */\n onClick?: (event: MouseEvent<HTMLElement>) => void;\n /**\n * Replace the row's link element — a router's `Link`, the way Base UI\n * components take `render`. The row's classes and handlers merge in.\n */\n render?: useRender.RenderProp;\n /** The row whose page is open beside the list — it keeps the wash. */\n current?: boolean;\n ref?: Ref<HTMLDivElement>;\n}\n\nfunction Row({\n id,\n status,\n method,\n label,\n code,\n description,\n duration,\n time,\n children,\n href,\n onClick,\n render,\n current,\n className,\n ...props\n}: LogListRowProps) {\n const Link = useLinkComponent();\n const base = useId();\n const interactive = href != null || onClick != null || render != null;\n const cellIds = {\n id: `${base}-id`,\n status: `${base}-status`,\n method: `${base}-method`,\n meta: `${base}-meta`,\n };\n const hasMeta = children != null || duration != null || time != null;\n const describedBy = [\n id != null && cellIds.id,\n status != null && cellIds.status,\n method != null && cellIds.method,\n hasMeta && cellIds.meta,\n ]\n .filter(Boolean)\n .join(\" \");\n const primary = useRender({\n render: render ?? (href != null ? <Link href={href} /> : undefined),\n defaultTagName: href != null ? \"a\" : onClick != null ? \"button\" : \"span\",\n props: {\n className: cn(\n \"text-pho-primary block min-w-0 max-w-full truncate text-left text-base font-medium outline-none\",\n code && \"font-mono text-xs\",\n // The one link, stretched over the row; the ring is the row's.\n interactive &&\n \"cursor-pointer after:absolute after:inset-0 after:content-['']\",\n ),\n onClick,\n type:\n render == null && href == null && onClick != null\n ? \"button\"\n : undefined,\n \"aria-current\": current ? \"page\" : undefined,\n \"aria-describedby\": describedBy || undefined,\n \"data-log-primary\": \"\",\n children: label,\n },\n });\n return (\n <div\n data-log-row=\"\"\n data-current={current || undefined}\n className={cn(\n \"relative flex min-h-12 min-w-0 items-center gap-3 px-5 py-2.5\",\n interactive &&\n \"hover:bg-pho-primary-hover outline-pho-brand transition-colors has-[[data-log-primary]:focus-visible]:outline-2 has-[[data-log-primary]:focus-visible]:-outline-offset-2\",\n current && \"bg-pho-primary-hover\",\n // The wash follows the card's rounding (minus the 1px border).\n \"first:rounded-t-[calc(var(--radius-base)-1px)] last:rounded-b-[calc(var(--radius-base)-1px)]\",\n className,\n )}\n {...props}\n >\n {id != null ? <IdCell id={id} cellId={cellIds.id} /> : null}\n {status != null ? (\n <span id={cellIds.status} className=\"flex shrink-0 items-center\">\n {status}\n </span>\n ) : null}\n {method != null ? <Method id={cellIds.method}>{method}</Method> : null}\n <div data-log-primary-cell=\"\" className=\"min-w-0 flex-1\">\n {primary}\n {description != null ? (\n <p className=\"text-pho-description truncate text-xs\">{description}</p>\n ) : null}\n </div>\n {hasMeta ? (\n <div\n id={cellIds.meta}\n className=\"text-pho-description ml-auto flex shrink-0 items-center gap-3 text-xs whitespace-nowrap tabular-nums\"\n >\n {children}\n {duration != null ? <span>{duration}</span> : null}\n {time != null ? <span>{time}</span> : null}\n </div>\n ) : null}\n </div>\n );\n}\n\n/* -------------------------------- states -------------------------------- */\n\nexport interface LogListEmptyProps extends Omit<\n ComponentProps<\"div\">,\n \"title\" | \"children\"\n> {\n /** One short line naming the state. @default \"Nothing here yet.\" */\n title?: ReactNode;\n /** What fills it, or why it is empty. */\n description?: ReactNode;\n /** An action, when there is one. */\n children?: ReactNode;\n}\n\n/** The list with no rows — `StatusView` at card size. */\nfunction Empty({\n title = \"Nothing here yet.\",\n description,\n children,\n className,\n ...props\n}: LogListEmptyProps) {\n return (\n <div className={cn(\"flex justify-center\", className)} {...props}>\n <StatusView\n size=\"sm\"\n title={title}\n description={description}\n actions={children}\n />\n </div>\n );\n}\n\n/** Bones in the rows' place while the first page is on its way. */\nfunction Loading({ rows = 5 }: { rows?: number }) {\n return (\n <>\n {Array.from({ length: rows }, (_, index) => (\n <div\n key={index}\n aria-hidden\n className=\"flex min-h-12 items-center gap-3 px-5 py-2.5\"\n >\n <Skeleton className=\"h-3.5 w-32\" />\n <Skeleton className=\"h-5 w-12 rounded-full\" />\n <Skeleton className=\"h-3.5 w-48\" />\n <Skeleton className=\"ml-auto h-3.5 w-16\" />\n </div>\n ))}\n </>\n );\n}\n\nexport interface LogListLoadMoreProps extends Omit<\n ComponentProps<\"div\">,\n \"children\"\n> {\n /** Asked for as the sentinel scrolls into view, and on the button. */\n onLoadMore: () => void;\n /** The next page is on its way — the sentinel shows a spinner and waits. */\n loading?: boolean;\n /** Nothing left to load — renders nothing. @default true */\n hasMore?: boolean;\n /** How far ahead of the viewport to ask. @default \"200px\" */\n rootMargin?: string;\n /** The button's words. @default \"Load more\" */\n label?: ReactNode;\n}\n\n/**\n * The sentinel at the end of the list. An `IntersectionObserver` asks for\n * the next page as it comes into view (`rootMargin` ahead of it); while\n * `loading` it shows a spinner and asks for nothing. The button is the\n * same request for keyboards and for a browser without the observer.\n * SSR-safe: the observer is created in an effect.\n */\nfunction LoadMore({\n onLoadMore,\n loading,\n hasMore = true,\n rootMargin = \"200px\",\n label = \"Load more\",\n className,\n ...props\n}: LogListLoadMoreProps) {\n const ref = useRef<HTMLDivElement | null>(null);\n const latest = useRef(onLoadMore);\n latest.current = onLoadMore;\n useEffect(() => {\n const el = ref.current;\n if (!hasMore || loading || el == null) return;\n if (typeof IntersectionObserver === \"undefined\") return;\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries.some((entry) => entry.isIntersecting)) latest.current();\n },\n { rootMargin },\n );\n observer.observe(el);\n return () => observer.disconnect();\n }, [hasMore, loading, rootMargin]);\n if (!hasMore) return null;\n return (\n <div\n ref={ref}\n data-log-load-more=\"\"\n aria-busy={loading || undefined}\n className={cn(\n \"flex min-h-12 items-center justify-center px-5 py-2\",\n className,\n )}\n {...props}\n >\n {loading ? (\n <Spinner aria-label=\"Loading more\" className=\"text-pho-secondary\" />\n ) : (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => onLoadMore()}>\n {label}\n </Button>\n )}\n </div>\n );\n}\n\n/* ---------------------------------- root -------------------------------- */\n\nexport interface LogListRootProps extends ComponentProps<\"div\"> {\n ref?: Ref<HTMLDivElement>;\n}\n\n/** The card the rows stack in. Mark it `aria-busy` while bones show. */\nfunction Root({ className, ...props }: LogListRootProps) {\n return (\n <div\n data-log-list=\"\"\n className={cn(LOG_LIST_SURFACE, \"min-w-0\", className)}\n {...props}\n />\n );\n}\n\n/**\n * Log List — a card of one-line log rows.\n *\n * ```tsx\n * <LogList.Root>\n * <LogList.Row\n * id=\"pho_req_01m04axjqrfzxv4xjet40a84gb\"\n * status={<LogList.Status value={200} />}\n * method=\"POST\"\n * code\n * label=\"/v1/messages\"\n * description=\"+1 332 259 3374 received a message from +1 332 222 1111.\"\n * duration=\"59 ms\"\n * time=\"2 min ago\"\n * href=\"/requests/pho_req_01m04axjqrfzxv4xjet40a84gb\"\n * />\n * <LogList.LoadMore onLoadMore={fetchNextPage} loading={isFetching} hasMore={hasNextPage} />\n * </LogList.Root>\n * ```\n */\nexport const LogList = {\n Root,\n Row,\n Status,\n Method,\n Empty,\n Loading,\n LoadMore,\n};\n"],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ "use client";
2
+ import { t as r } from "./cn-ChIgwEl3.js";
3
+ import { createContext as l, useContext as d } from "react";
4
+ import { jsx as s } from "react/jsx-runtime";
5
+ var i = l("start");
6
+ function m({ className: e, ...a }) {
7
+ return /* @__PURE__ */ s("div", {
8
+ "data-message-group": "",
9
+ className: r("flex min-w-0 flex-col gap-2", e),
10
+ ...a
11
+ });
12
+ }
13
+ function f({ align: e = "start", className: a, ...t }) {
14
+ return /* @__PURE__ */ s(i.Provider, {
15
+ value: e,
16
+ children: /* @__PURE__ */ s("div", {
17
+ "data-message": "",
18
+ "data-align": e,
19
+ className: r("group/message @container relative flex w-full min-w-0 gap-2 text-base", e === "end" && "flex-row-reverse", a),
20
+ ...t
21
+ })
22
+ });
23
+ }
24
+ function g({ className: e, ...a }) {
25
+ return /* @__PURE__ */ s("div", {
26
+ "data-message-avatar": "",
27
+ className: r("flex shrink-0 self-end group-has-[[data-message-footer]]/message:-translate-y-[1.375rem]", e),
28
+ ...a
29
+ });
30
+ }
31
+ function c({ className: e, ...a }) {
32
+ const t = d(i);
33
+ return /* @__PURE__ */ s("div", {
34
+ "data-message-content": "",
35
+ className: r("grid w-max max-w-full flex-none items-end gap-x-1.5 gap-y-1 break-words", "[&>[data-message-header]]:[grid-area:header]", "[&>[data-bubble]]:max-w-[80cqw] [&>[data-bubble]]:[grid-area:main]", "[&>[data-bubble-group]]:max-w-[80cqw] [&>[data-bubble-group]]:[grid-area:main]", "[&>[data-bubble][data-variant=ghost]]:max-w-full", "[&>[data-message-time]]:[grid-area:time]", "[&>[data-message-footer]]:[grid-area:status]", t === "end" ? "ml-auto grid-cols-[auto_minmax(0,max-content)] [grid-template-areas:'._header'_'time_main'_'._status'] [&>[data-bubble-group]]:justify-self-end [&>[data-bubble]]:justify-self-end [&>[data-message-header]]:justify-self-end" : "grid-cols-[minmax(0,max-content)_auto] [grid-template-areas:'header_.'_'main_time'_'status_.'] [&>[data-bubble-group]]:justify-self-start [&>[data-bubble]]:justify-self-start", e),
36
+ ...a
37
+ });
38
+ }
39
+ function p({ className: e, ...a }) {
40
+ return /* @__PURE__ */ s("div", {
41
+ "data-message-header": "",
42
+ className: r("text-pho-secondary flex w-full max-w-full min-w-0 items-center gap-1.5 px-1 text-xs font-medium group-has-[[data-bubble][data-variant=ghost]]/message:px-0", e),
43
+ ...a
44
+ });
45
+ }
46
+ function x({ className: e, ...a }) {
47
+ return /* @__PURE__ */ s("div", {
48
+ "data-message-time": "",
49
+ className: r("text-pho-description shrink-0 self-end text-xs whitespace-nowrap tabular-nums opacity-0 transition-opacity duration-100 ease-linear group-focus-within/message:opacity-100 group-hover/message:opacity-100", e),
50
+ ...a
51
+ });
52
+ }
53
+ function b({ className: e, ...a }) {
54
+ const t = d(i);
55
+ return /* @__PURE__ */ s("div", {
56
+ "data-message-footer": "",
57
+ className: r("text-pho-description flex max-w-full min-w-0 items-center gap-1.5 px-1 text-xs group-has-[[data-bubble][data-variant=ghost]]/message:px-0", t === "end" && "justify-self-end", e),
58
+ ...a
59
+ });
60
+ }
61
+ var y = {
62
+ Root: f,
63
+ Group: m,
64
+ Avatar: g,
65
+ Content: c,
66
+ Header: p,
67
+ Time: x,
68
+ Footer: b
69
+ };
70
+ function u(e, a) {
71
+ if (!e || a == null) return null;
72
+ const t = a.trim().toLowerCase(), n = t.split(/[\s.]+/)[0];
73
+ return n ? n === "failed" ? t : n : null;
74
+ }
75
+ function _(e, a) {
76
+ const t = e[a];
77
+ if (!t) return !1;
78
+ const n = u(t.outbound === !0, t.status);
79
+ if (n == null) return !1;
80
+ const o = e[a + 1];
81
+ return o ? u(o.outbound === !0, o.status) !== n : !0;
82
+ }
83
+ export {
84
+ _ as n,
85
+ y as r,
86
+ u as t
87
+ };
88
+
89
+ //# sourceMappingURL=message-CTml-TrB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-CTml-TrB.js","names":[],"sources":["../../src/components/message/message.tsx","../../src/components/message/message-status.ts"],"sourcesContent":["import { createContext, useContext, type ComponentProps } from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/**\n * Message — one turn in a thread: the row that seats an avatar beside a\n * column of header, bubble, and footer, on the author's side. It owns the\n * row layout and nothing else: the surface is `Bubble`'s, the scrolling\n * and the composer are `Chat`'s.\n *\n * Design notes:\n * - `align=\"end\"` mirrors the row — avatar on the right, column\n * right-aligned — for the reader's own messages.\n * - The avatar sits at the row's bottom, level with the bubble. When a\n * `Footer` (status) sits under the bubble, the avatar shifts up by one\n * line so it stays beside the surface.\n * - `Time` sits beside the bubble, last-line aligned — right of inbound,\n * left of outbound — and is invisible until the row is hovered or\n * focused. `Footer` is the status caption under the bubble (Delivered,\n * Failed). Inbound has no status.\n */\n\nexport type MessageAlign = \"start\" | \"end\";\n\nconst MessageContext = createContext<MessageAlign>(\"start\");\n\n/* --------------------------------- group -------------------------------- */\n\n/** Consecutive messages from one author, spaced as one turn. */\nfunction Group({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-message-group=\"\"\n className={cn(\"flex min-w-0 flex-col gap-2\", className)}\n {...props}\n />\n );\n}\n\n/* --------------------------------- root --------------------------------- */\n\nexport interface MessageRootProps extends ComponentProps<\"div\"> {\n /** Which side the author sits on. @default \"start\" */\n align?: MessageAlign;\n}\n\n/** The row: avatar beside the content column, mirrored for `end`. */\nfunction Root({ align = \"start\", className, ...props }: MessageRootProps) {\n return (\n <MessageContext.Provider value={align}>\n <div\n data-message=\"\"\n data-align={align}\n className={cn(\n \"group/message @container relative flex w-full min-w-0 gap-2 text-base\",\n align === \"end\" && \"flex-row-reverse\",\n className,\n )}\n {...props}\n />\n </MessageContext.Provider>\n );\n}\n\n/* -------------------------------- avatar -------------------------------- */\n\n/**\n * The avatar's seat — pass an `Avatar.Root`. Bottom-aligned with the\n * bubble; lifts by one footer line when the message has a `Footer`.\n */\nfunction Avatar({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-message-avatar=\"\"\n className={cn(\n // Footer line = xs line-height (18px) + the column's 4px gap.\n \"flex shrink-0 self-end group-has-[[data-message-footer]]/message:-translate-y-[1.375rem]\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/* -------------------------------- content ------------------------------- */\n\n/**\n * Header over the bubble; time beside it; status under it. Inbound is\n * `[bubble][time]`. Outbound is `[time][bubble]` with `Footer` below\n * the bubble column.\n */\nfunction Content({ className, ...props }: ComponentProps<\"div\">) {\n const align = useContext(MessageContext);\n return (\n <div\n data-message-content=\"\"\n className={cn(\n \"grid w-max max-w-full flex-none items-end gap-x-1.5 gap-y-1 break-words\",\n \"[&>[data-message-header]]:[grid-area:header]\",\n \"[&>[data-bubble]]:max-w-[80cqw] [&>[data-bubble]]:[grid-area:main]\",\n \"[&>[data-bubble-group]]:max-w-[80cqw] [&>[data-bubble-group]]:[grid-area:main]\",\n \"[&>[data-bubble][data-variant=ghost]]:max-w-full\",\n \"[&>[data-message-time]]:[grid-area:time]\",\n \"[&>[data-message-footer]]:[grid-area:status]\",\n align === \"end\"\n ? \"ml-auto grid-cols-[auto_minmax(0,max-content)] [grid-template-areas:'._header'_'time_main'_'._status'] [&>[data-bubble-group]]:justify-self-end [&>[data-bubble]]:justify-self-end [&>[data-message-header]]:justify-self-end\"\n : \"grid-cols-[minmax(0,max-content)_auto] [grid-template-areas:'header_.'_'main_time'_'status_.'] [&>[data-bubble-group]]:justify-self-start [&>[data-bubble]]:justify-self-start\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/** Who — the sender's name over the bubble, secondary ink. */\nfunction Header({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-message-header=\"\"\n className={cn(\n \"text-pho-secondary flex w-full max-w-full min-w-0 items-center gap-1.5 px-1 text-xs font-medium group-has-[[data-bubble][data-variant=ghost]]/message:px-0\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * Clock beside the bubble, last-line aligned. Description ink, tabular.\n * Inbound sits to the right; outbound to the left. The slot stays in\n * layout; the digits show when the row is hovered or focused.\n */\nfunction Time({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-message-time=\"\"\n className={cn(\n \"text-pho-description shrink-0 self-end text-xs whitespace-nowrap tabular-nums opacity-0 transition-opacity duration-100 ease-linear group-focus-within/message:opacity-100 group-hover/message:opacity-100\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * Status under the bubble — Delivered, Failed. Inbound leaves it off.\n * Description ink.\n */\nfunction Footer({ className, ...props }: ComponentProps<\"div\">) {\n const align = useContext(MessageContext);\n return (\n <div\n data-message-footer=\"\"\n className={cn(\n \"text-pho-description flex max-w-full min-w-0 items-center gap-1.5 px-1 text-xs group-has-[[data-bubble][data-variant=ghost]]/message:px-0\",\n align === \"end\" && \"justify-self-end\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * A turn in a thread. `Root` takes `align`; `Avatar` seats an\n * `Avatar.Root`; `Content` places `Header` over the bubble, `Time`\n * beside it, and `Footer` under it.\n *\n * ```tsx\n * <Message.Root align=\"end\">\n * <Message.Content>\n * <Bubble.Root variant=\"inverse\" align=\"end\">\n * <Bubble.Content>On my way.</Bubble.Content>\n * </Bubble.Root>\n * <Message.Time>2:47 PM</Message.Time>\n * <Message.Footer>Delivered.</Message.Footer>\n * </Message.Content>\n * </Message.Root>\n * ```\n */\nexport const Message = { Root, Group, Avatar, Content, Header, Time, Footer };\n","/**\n * One row in a thread, enough to decide whether its outbound status\n * caption should show. Inbound rows leave `outbound` off.\n */\nexport interface MessageStatusItem {\n outbound?: boolean;\n /** Delivered, Sent, Failed — or a token like `delivered`. */\n status?: string | null;\n}\n\n/**\n * The coalesce key for a row: outbound status word, lowercased. Inbound\n * and empty statuses are `null`, so they break a run. A failure keeps its\n * whole text (`failed. 30007`) so two different error codes never\n * collapse into one caption.\n */\nexport function messageStatusKey(\n outbound: boolean,\n status: string | null | undefined,\n): string | null {\n if (!outbound || status == null) return null;\n const text = status.trim().toLowerCase();\n const word = text.split(/[\\s.]+/)[0];\n if (!word) return null;\n return word === \"failed\" ? text : word;\n}\n\n/**\n * Whether this row should render its outbound status (Delivered, Sent,\n * Failed). Only the last message in a consecutive same-status run shows\n * it. Inbound never does.\n */\nexport function shouldShowMessageStatus(\n messages: readonly MessageStatusItem[],\n index: number,\n): boolean {\n const current = messages[index];\n if (!current) return false;\n const key = messageStatusKey(current.outbound === true, current.status);\n if (key == null) return false;\n const next = messages[index + 1];\n if (!next) return true;\n return messageStatusKey(next.outbound === true, next.status) !== key;\n}\n"],"mappings":";;;;AAuBA,IAAM,IAAiB,EAA4B,OAAO;AAK1D,SAAS,EAAM,EAAE,WAAA,GAAW,GAAG,EAAA,GAAgC;AAC7D,SACE,gBAAA,EAAC,OAAD;AAAA,IACE,sBAAmB;AAAA,IACnB,WAAW,EAAG,+BAA+B,CAAS;AAAA,IACtD,GAAI;AAAA,EACL,CAAA;AAEL;AAUA,SAAS,EAAK,EAAE,OAAA,IAAQ,SAAS,WAAA,GAAW,GAAG,EAAA,GAA2B;AACxE,SACE,gBAAA,EAAC,EAAe,UAAhB;AAAA,IAAyB,OAAO;AAAA,IAC9B,UAAA,gBAAA,EAAC,OAAD;AAAA,MACE,gBAAa;AAAA,MACb,cAAY;AAAA,MACZ,WAAW,EACT,yEACA,MAAU,SAAS,oBACnB,CACF;AAAA,MACA,GAAI;AAAA,IACL,CAAA;AAAA,EACsB,CAAA;AAE7B;AAQA,SAAS,EAAO,EAAE,WAAA,GAAW,GAAG,EAAA,GAAgC;AAC9D,SACE,gBAAA,EAAC,OAAD;AAAA,IACE,uBAAoB;AAAA,IACpB,WAAW,EAET,4FACA,CACF;AAAA,IACA,GAAI;AAAA,EACL,CAAA;AAEL;AASA,SAAS,EAAQ,EAAE,WAAA,GAAW,GAAG,EAAA,GAAgC;AAC/D,QAAM,IAAQ,EAAW,CAAc;AACvC,SACE,gBAAA,EAAC,OAAD;AAAA,IACE,wBAAqB;AAAA,IACrB,WAAW,EACT,2EACA,gDACA,sEACA,kFACA,oDACA,4CACA,gDACA,MAAU,QACN,kOACA,kLACJ,CACF;AAAA,IACA,GAAI;AAAA,EACL,CAAA;AAEL;AAGA,SAAS,EAAO,EAAE,WAAA,GAAW,GAAG,EAAA,GAAgC;AAC9D,SACE,gBAAA,EAAC,OAAD;AAAA,IACE,uBAAoB;AAAA,IACpB,WAAW,EACT,8JACA,CACF;AAAA,IACA,GAAI;AAAA,EACL,CAAA;AAEL;AAOA,SAAS,EAAK,EAAE,WAAA,GAAW,GAAG,EAAA,GAAgC;AAC5D,SACE,gBAAA,EAAC,OAAD;AAAA,IACE,qBAAkB;AAAA,IAClB,WAAW,EACT,8MACA,CACF;AAAA,IACA,GAAI;AAAA,EACL,CAAA;AAEL;AAMA,SAAS,EAAO,EAAE,WAAA,GAAW,GAAG,EAAA,GAAgC;AAC9D,QAAM,IAAQ,EAAW,CAAc;AACvC,SACE,gBAAA,EAAC,OAAD;AAAA,IACE,uBAAoB;AAAA,IACpB,WAAW,EACT,6IACA,MAAU,SAAS,oBACnB,CACF;AAAA,IACA,GAAI;AAAA,EACL,CAAA;AAEL;AAmBA,IAAa,IAAU;AAAA,EAAE,MAAA;AAAA,EAAM,OAAA;AAAA,EAAO,QAAA;AAAA,EAAQ,SAAA;AAAA,EAAS,QAAA;AAAA,EAAQ,MAAA;AAAA,EAAM,QAAA;AAAO;ACrK5E,SAAgB,EACd,GACA,GACe;AACf,MAAI,CAAC,KAAY,KAAU,KAAM,QAAO;AACxC,QAAM,IAAO,EAAO,KAAK,EAAE,YAAY,GACjC,IAAO,EAAK,MAAM,QAAQ,EAAE,CAAA;AAClC,SAAK,IACE,MAAS,WAAW,IAAO,IADhB;AAEpB;AAOA,SAAgB,EACd,GACA,GACS;AACT,QAAM,IAAU,EAAS,CAAA;AACzB,MAAI,CAAC,EAAS,QAAO;AACrB,QAAM,IAAM,EAAiB,EAAQ,aAAa,IAAM,EAAQ,MAAM;AACtE,MAAI,KAAO,KAAM,QAAO;AACxB,QAAM,IAAO,EAAS,IAAQ,CAAA;AAC9B,SAAK,IACE,EAAiB,EAAK,aAAa,IAAM,EAAK,MAAM,MAAM,IAD/C;AAEpB"}
@@ -133,7 +133,7 @@ function D({ className: t, ref: e, ...n }) {
133
133
  l
134
134
  ]), /* @__PURE__ */ i(c.Viewport, {
135
135
  render: /* @__PURE__ */ i($.div, { layoutScroll: !0 }),
136
- className: d("outline-pho-brand h-full min-h-0 w-full overscroll-contain rounded-[inherit] focus-visible:outline-2 focus-visible:-outline-offset-1", t),
136
+ className: d("outline-pho-brand h-full min-h-0 w-full rounded-[inherit] focus-visible:outline-2 focus-visible:-outline-offset-1", t),
137
137
  ...n,
138
138
  ref: (a) => {
139
139
  s.current = a, M(e, a);
@@ -165,4 +165,4 @@ export {
165
165
  Z as t
166
166
  };
167
167
 
168
- //# sourceMappingURL=scroll-area-Zik8t1Mk.js.map
168
+ //# sourceMappingURL=scroll-area-DSwD9th7.js.map