@flytedan/flytebot-design-system 0.12.10 → 0.12.12

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.
package/dist/index.cjs CHANGED
@@ -73,6 +73,8 @@ __export(index_exports, {
73
73
  EmptyState: () => EmptyState,
74
74
  EntityRow: () => EntityRow,
75
75
  EntityRowMetrics: () => EntityRowMetrics,
76
+ FILE_VIEWER_CSV_ROW_LIMIT: () => FILE_VIEWER_CSV_ROW_LIMIT,
77
+ FILE_VIEWER_TEXT_LIMIT: () => FILE_VIEWER_TEXT_LIMIT,
76
78
  FeatureGate: () => FeatureGate,
77
79
  FileChip: () => FileChip,
78
80
  FileGrid: () => FileGrid,
@@ -80,6 +82,7 @@ __export(index_exports, {
80
82
  FilePickButton: () => FilePickButton,
81
83
  FileStrip: () => FileStrip,
82
84
  FileTile: () => FileTile,
85
+ FileViewer: () => FileViewer,
83
86
  FilterBar: () => FilterBar,
84
87
  Flag: () => Flag,
85
88
  FlytedeskBrand: () => FlytedeskBrand,
@@ -184,6 +187,7 @@ __export(index_exports, {
184
187
  markdownToText: () => markdownToText,
185
188
  meterFormats: () => meterFormats,
186
189
  modifierLabel: () => modifierLabel,
190
+ parseDelimited: () => parseDelimited,
187
191
  pastedTextName: () => pastedTextName,
188
192
  pluralizeUnit: () => pluralizeUnit,
189
193
  preferenceScore: () => preferenceScore,
@@ -3159,13 +3163,15 @@ function EntityRow({
3159
3163
  onPointerDown,
3160
3164
  className: ["fd-erow", tone === "danger" ? "fd-erow-danger" : "", className].filter(Boolean).join(" "),
3161
3165
  style: {
3162
- // display/alignItems/gap moved to the .fd-erow stylesheet rule (MP-114
3163
- // follow-up): an inline style always wins over a stylesheet rule regardless of
3164
- // specificity or media/container query, which would have made it impossible
3165
- // for the narrow-container rule below to ever switch this row from a flex row
3166
- // to a grid (see that rule's own comment for why it needs to).
3166
+ // display/alignItems/gap/padding moved to the .fd-erow stylesheet rule
3167
+ // (MP-114 follow-ups): an inline style always wins over a stylesheet rule
3168
+ // regardless of specificity or media/container query, which would have made
3169
+ // it impossible for the narrow-container rule below to ever switch this row
3170
+ // from a flex row to a grid — and padding specifically is also read back by
3171
+ // .fd-tlist-footer's own indent calc(), as --erow-pad-x, so it has to be a
3172
+ // real, readable stylesheet value rather than a number only this component
3173
+ // ever sees.
3167
3174
  height: "100%",
3168
- padding: "0 12px",
3169
3175
  cursor: draggable ? "grab" : void 0,
3170
3176
  // Own the gesture on touch too — without this, a touch-drag on a draggable row
3171
3177
  // starts the page scrolling instead of (or as well as) the pointer drag.
@@ -3173,7 +3179,12 @@ function EntityRow({
3173
3179
  ...style
3174
3180
  },
3175
3181
  children: [
3176
- draggable ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("i", { className: "ph ph-dots-six-vertical fd-erow-grip", "aria-hidden": "true", style: { fontSize: 14, flex: "none", color: "var(--text-muted)" } }) : null,
3182
+ draggable ? (
3183
+ // font-size moved to the .fd-erow-grip stylesheet rule, same reason as the
3184
+ // row's own padding above: .fd-tlist-footer's indent reads it back as
3185
+ // --erow-grip-size.
3186
+ /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("i", { className: "ph ph-dots-six-vertical fd-erow-grip", "aria-hidden": "true", style: { flex: "none", color: "var(--text-muted)" } })
3187
+ ) : null,
3177
3188
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "fd-erow-body", children: [
3178
3189
  /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "fd-erow-titleline", children: [
3179
3190
  titleIcon ? /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(Tooltip, { label: titleIcon.tooltip, children: /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)(
@@ -4665,7 +4676,7 @@ function useStagedFiles(upload, opts) {
4665
4676
  patch(att.id, { progress: 0, error: void 0 });
4666
4677
  Promise.resolve().then(() => upload(att.file, (p) => patch(att.id, { progress: Math.max(0, Math.min(99, p)) }), ac ? ac.signal : void 0)).then((res) => {
4667
4678
  const r = res || {};
4668
- patch(att.id, { progress: 100, url: r.url || att.url, thumb: r.thumb || att.thumb, error: void 0, remote: true });
4679
+ patch(att.id, Object.assign({}, r, { id: r.id || att.id, progress: 100, error: void 0 }));
4669
4680
  o.onUploaded && o.onUploaded(att);
4670
4681
  }).catch((err) => {
4671
4682
  if (ac && ac.signal.aborted) return;
@@ -9661,7 +9672,9 @@ function ChatComposer({
9661
9672
  const submit = () => {
9662
9673
  if (!canSend) return;
9663
9674
  onSubmit(text.trim(), staged.items.map((f) => {
9664
- const { file: _file, ...rest } = f;
9675
+ const { file, blobUrl, ...rest } = f;
9676
+ void file;
9677
+ void blobUrl;
9665
9678
  return rest;
9666
9679
  }));
9667
9680
  change("");
@@ -10356,8 +10369,219 @@ function AgentChatPanel({
10356
10369
  );
10357
10370
  }
10358
10371
 
10359
- // src/kits/query.ts
10372
+ // src/components/files/FileViewer.tsx
10360
10373
  var React47 = __toESM(require("react"), 1);
10374
+ var import_jsx_runtime78 = require("react/jsx-runtime");
10375
+ function previewKindOf(file) {
10376
+ const mime = (file.mime || "").toLowerCase();
10377
+ const ext = extensionOf(file.name || "");
10378
+ if (isImage(mime, file.name)) return "image";
10379
+ if (mime === "application/pdf" || ext === "pdf") return "pdf";
10380
+ if (mime === "text/markdown" || ext === "md" || ext === "markdown") return "markdown";
10381
+ if (mime === "application/json" || ext === "json") return "json";
10382
+ if (mime === "text/csv" || mime === "text/tab-separated-values" || ext === "csv" || ext === "tsv") return "csv";
10383
+ if (mime.startsWith("text/") || ext === "txt" || ext === "log") return "text";
10384
+ return "none";
10385
+ }
10386
+ var FILE_VIEWER_TEXT_LIMIT = 5e5;
10387
+ var FILE_VIEWER_CSV_ROW_LIMIT = 500;
10388
+ function parseDelimited(text, delimiter) {
10389
+ const rows = [];
10390
+ let row = [];
10391
+ let field = "";
10392
+ let inQuotes = false;
10393
+ for (let i = 0; i < text.length; i++) {
10394
+ const c = text[i];
10395
+ if (inQuotes) {
10396
+ if (c === '"') {
10397
+ if (text[i + 1] === '"') {
10398
+ field += '"';
10399
+ i++;
10400
+ } else inQuotes = false;
10401
+ } else field += c;
10402
+ continue;
10403
+ }
10404
+ if (c === '"') {
10405
+ inQuotes = true;
10406
+ continue;
10407
+ }
10408
+ if (c === delimiter) {
10409
+ row.push(field);
10410
+ field = "";
10411
+ continue;
10412
+ }
10413
+ if (c === "\n") {
10414
+ row.push(field);
10415
+ rows.push(row);
10416
+ row = [];
10417
+ field = "";
10418
+ continue;
10419
+ }
10420
+ if (c === "\r") continue;
10421
+ field += c;
10422
+ }
10423
+ if (field.length || row.length) {
10424
+ row.push(field);
10425
+ rows.push(row);
10426
+ }
10427
+ return rows;
10428
+ }
10429
+ function usePreview(file, kind, resolveUrl) {
10430
+ const [state, setState] = React47.useState({ status: "loading" });
10431
+ const [attempt, setAttempt] = React47.useState(0);
10432
+ const resolveRef = React47.useRef(resolveUrl);
10433
+ React47.useEffect(() => {
10434
+ resolveRef.current = resolveUrl;
10435
+ }, [resolveUrl]);
10436
+ React47.useEffect(() => {
10437
+ if (kind === "none") {
10438
+ setState({ status: "ready" });
10439
+ return;
10440
+ }
10441
+ let dead = false;
10442
+ const ac = typeof AbortController !== "undefined" ? new AbortController() : void 0;
10443
+ setState({ status: "loading" });
10444
+ resolveRef.current("inline").then((url) => {
10445
+ if (dead) return void 0;
10446
+ if (kind === "image" || kind === "pdf") {
10447
+ setState({ status: "ready", url });
10448
+ return void 0;
10449
+ }
10450
+ return fetch(url, ac ? { signal: ac.signal } : void 0).then((res) => {
10451
+ if (!res.ok) throw new Error("The preview didn't load (" + res.status + ").");
10452
+ return res.text();
10453
+ }).then((body) => {
10454
+ if (dead) return;
10455
+ setState({ status: "ready", url, text: body.length > FILE_VIEWER_TEXT_LIMIT ? body.slice(0, FILE_VIEWER_TEXT_LIMIT) : body });
10456
+ });
10457
+ }).catch((err) => {
10458
+ if (dead || ac && ac.signal.aborted) return;
10459
+ setState({ status: "error", error: err && err.message || "The preview didn't load." });
10460
+ });
10461
+ return () => {
10462
+ dead = true;
10463
+ if (ac) ac.abort();
10464
+ };
10465
+ }, [file.id, kind, attempt]);
10466
+ return { ...state, retry: React47.useCallback(() => setAttempt((a) => a + 1), []) };
10467
+ }
10468
+ function PreviewBody({ file, kind, state }) {
10469
+ if (kind === "image") return /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("img", { className: "fd-fv-image", src: state.url, alt: file.name || "Preview" });
10470
+ if (kind === "pdf") return /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("iframe", { className: "fd-fv-pdf", src: state.url, title: file.name || "PDF preview" });
10471
+ if (kind === "markdown") return /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(Markdown, { className: "fd-fv-markdown", source: state.text || "" });
10472
+ if (kind === "json") {
10473
+ let pretty = state.text || "";
10474
+ try {
10475
+ pretty = JSON.stringify(JSON.parse(pretty), null, 2);
10476
+ } catch {
10477
+ }
10478
+ return /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(CodeBlock, { code: pretty, language: "json" });
10479
+ }
10480
+ if (kind === "csv") {
10481
+ const tsv = extensionOf(file.name || "") === "tsv" || (file.mime || "").toLowerCase().includes("tab-separated");
10482
+ const rows = parseDelimited(state.text || "", tsv ? " " : ",").filter((r) => r.length > 1 || r[0] !== "");
10483
+ const head = rows[0] || [];
10484
+ const dataRows = rows.slice(1);
10485
+ const shown = dataRows.slice(0, FILE_VIEWER_CSV_ROW_LIMIT);
10486
+ return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { className: "fd-fv-csv", children: [
10487
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("table", { className: "fd-fv-csv-table", children: [
10488
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("tr", { children: head.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("th", { children: c }, i)) }) }),
10489
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("tbody", { children: shown.map((r, ri) => /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("tr", { children: head.map((_, ci) => /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("td", { children: r[ci] ?? "" }, ci)) }, ri)) })
10490
+ ] }),
10491
+ dataRows.length > FILE_VIEWER_CSV_ROW_LIMIT ? /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("p", { className: "fd-fv-csv-note fd-body-sm fd-muted", children: [
10492
+ "Showing the first ",
10493
+ FILE_VIEWER_CSV_ROW_LIMIT.toLocaleString(),
10494
+ " of ",
10495
+ dataRows.length.toLocaleString(),
10496
+ " rows."
10497
+ ] }) : null
10498
+ ] });
10499
+ }
10500
+ if (kind === "text") return /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(CodeBlock, { code: state.text || "", language: extensionOf(file.name || "") || void 0 });
10501
+ return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { className: "fd-fv-fallback", children: [
10502
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "fd-fv-fallback-icon", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("i", { className: "ph ph-" + iconForMime(file.mime, file.name) }) }),
10503
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "fd-fv-fallback-title", children: "No preview available" }),
10504
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("span", { className: "fd-fv-fallback-meta", children: [
10505
+ file.mime || "Unknown type",
10506
+ file.size ? " \xB7 " + formatBytes(file.size) : ""
10507
+ ] })
10508
+ ] });
10509
+ }
10510
+ function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", onClose, actions, className = "" }) {
10511
+ const kind = React47.useMemo(() => previewKindOf(file), [file]);
10512
+ const preview = usePreview(file, kind, resolveUrl);
10513
+ const [tab, setTab] = React47.useState("preview");
10514
+ const [downloading, setDownloading] = React47.useState(false);
10515
+ const [downloadError, setDownloadError] = React47.useState(null);
10516
+ const hasSide = sidePanel != null;
10517
+ React47.useEffect(() => {
10518
+ setTab("preview");
10519
+ }, [file.id]);
10520
+ const download = () => {
10521
+ setDownloading(true);
10522
+ setDownloadError(null);
10523
+ resolveUrl("attachment").then((url) => {
10524
+ const a = document.createElement("a");
10525
+ a.href = url;
10526
+ a.rel = "noopener";
10527
+ a.download = file.name || "";
10528
+ document.body.appendChild(a);
10529
+ a.click();
10530
+ a.remove();
10531
+ }).catch((err) => setDownloadError(err && err.message || "Download failed.")).finally(() => setDownloading(false));
10532
+ };
10533
+ return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)(
10534
+ "section",
10535
+ {
10536
+ className: ["fd-fv", hasSide && tab === "side" ? "is-tab-side" : "is-tab-preview", className].filter(Boolean).join(" "),
10537
+ "aria-label": file.name || "File",
10538
+ children: [
10539
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("header", { className: "fd-fv-head", children: [
10540
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "fd-fv-head-icon", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("i", { className: "ph ph-" + iconForMime(file.mime, file.name) }) }),
10541
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("span", { className: "fd-fv-head-titles", children: [
10542
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "fd-fv-head-title", title: file.name, children: file.name }),
10543
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("span", { className: "fd-fv-head-meta", children: [
10544
+ file.mime || "Unknown type",
10545
+ file.size ? " \xB7 " + formatBytes(file.size) : ""
10546
+ ] })
10547
+ ] }),
10548
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("span", { className: "fd-fv-head-actions", children: [
10549
+ actions,
10550
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(Button, { variant: "secondary", size: "sm", icon: "download-simple", loading: downloading, onClick: download, children: "Download" }),
10551
+ onClose ? /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(IconButton, { icon: "x", label: "Close", onClick: onClose }) : null
10552
+ ] })
10553
+ ] }),
10554
+ downloadError ? /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("div", { className: "fd-fv-download-error", role: "alert", children: downloadError }) : null,
10555
+ hasSide ? /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(
10556
+ Tabs,
10557
+ {
10558
+ className: "fd-fv-tabs",
10559
+ tabs: [{ value: "preview", label: "Preview" }, { value: "side", label: sidePanelLabel }],
10560
+ value: tab,
10561
+ onChange: (v) => setTab(v)
10562
+ }
10563
+ ) : null,
10564
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { className: "fd-fv-body", children: [
10565
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("div", { className: "fd-fv-preview", role: "region", "aria-label": "Preview of " + (file.name || "file"), children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(
10566
+ LoadingRegion,
10567
+ {
10568
+ state: preview.status === "loading" ? "loading" : preview.status === "error" ? "error" : "ready",
10569
+ error: preview.error,
10570
+ label: "Loading preview of " + (file.name || "file"),
10571
+ onRetry: preview.status === "error" ? preview.retry : void 0,
10572
+ skeleton: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(Skeleton, { shape: "card", h: 220 }),
10573
+ children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(PreviewBody, { file, kind, state: preview })
10574
+ }
10575
+ ) }),
10576
+ hasSide ? /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("div", { className: "fd-fv-side", role: "region", "aria-label": sidePanelLabel, children: sidePanel }) : null
10577
+ ] })
10578
+ ]
10579
+ }
10580
+ );
10581
+ }
10582
+
10583
+ // src/kits/query.ts
10584
+ var React48 = __toESM(require("react"), 1);
10361
10585
  function eqFilter(get2) {
10362
10586
  return (row, value) => Array.isArray(value) ? value.includes(get2(row)) : get2(row) === value;
10363
10587
  }
@@ -10389,22 +10613,22 @@ function compare(a, b, dir) {
10389
10613
  var API = null;
10390
10614
  var PREFS = null;
10391
10615
  function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
10392
- const [query, setQuery] = React47.useState(() => {
10616
+ const [query, setQuery] = React48.useState(() => {
10393
10617
  const store = PREFS || window.PlannerPrefs;
10394
10618
  const saved = prefsKey && store ? store.getTable(prefsKey) : {};
10395
10619
  return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
10396
10620
  });
10397
- const savePref = React47.useCallback((patch2) => {
10621
+ const savePref = React48.useCallback((patch2) => {
10398
10622
  const store = PREFS || window.PlannerPrefs;
10399
10623
  if (prefsKey && store) store.setTable(prefsKey, patch2);
10400
10624
  }, [prefsKey]);
10401
- const [res, setRes] = React47.useState(null);
10402
- const [loading, setLoading] = React47.useState(true);
10403
- const seq2 = React47.useRef(0);
10625
+ const [res, setRes] = React48.useState(null);
10626
+ const [loading, setLoading] = React48.useState(true);
10627
+ const seq2 = React48.useRef(0);
10404
10628
  const depKey = (deps || []).join("|");
10405
10629
  const paramKey = JSON.stringify(params || {});
10406
10630
  const queryKey = JSON.stringify(query);
10407
- React47.useEffect(() => {
10631
+ React48.useEffect(() => {
10408
10632
  const id = ++seq2.current;
10409
10633
  setLoading(true);
10410
10634
  const t = setTimeout(() => {
@@ -10746,6 +10970,8 @@ function createVersionStore(initialState, options) {
10746
10970
  EmptyState,
10747
10971
  EntityRow,
10748
10972
  EntityRowMetrics,
10973
+ FILE_VIEWER_CSV_ROW_LIMIT,
10974
+ FILE_VIEWER_TEXT_LIMIT,
10749
10975
  FeatureGate,
10750
10976
  FileChip,
10751
10977
  FileGrid,
@@ -10753,6 +10979,7 @@ function createVersionStore(initialState, options) {
10753
10979
  FilePickButton,
10754
10980
  FileStrip,
10755
10981
  FileTile,
10982
+ FileViewer,
10756
10983
  FilterBar,
10757
10984
  Flag,
10758
10985
  FlytedeskBrand,
@@ -10857,6 +11084,7 @@ function createVersionStore(initialState, options) {
10857
11084
  markdownToText,
10858
11085
  meterFormats,
10859
11086
  modifierLabel,
11087
+ parseDelimited,
10860
11088
  pastedTextName,
10861
11089
  pluralizeUnit,
10862
11090
  preferenceScore,