@elaraai/e3-ui-components 1.0.31 → 1.0.32

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.js CHANGED
@@ -28,7 +28,7 @@ import { jsx as jsx$1, Fragment, jsxs } from "react/jsx-runtime";
28
28
  import * as React from "react";
29
29
  import React__default, { useId, useMemo, useContext, createContext, useRef, useEffect, useState, useCallback, useSyncExternalStore, memo, forwardRef, useLayoutEffect, Fragment as Fragment$1, Component } from "react";
30
30
  import { Box, Flex, Text, HStack, Input, useSlotRecipe, useRecipe, VStack, useToken, Drawer, Portal, Field, NativeSelect, Textarea, chakra, Tooltip, SegmentGroup, Menu, HoverCard, useChakraContext, Spinner, Code, Clipboard, IconButton, Badge, Button, Tabs, useTabs } from "@chakra-ui/react";
31
- import { implementUIComponent, getSomeorUndefined, formatPredicate, SliceEditPopover, SlicePredicateBuilder, EastChakraSelect, EastChakraStringInput, EastChakraDateTimeInput, EastChakraFloatInput, EastChakraIntegerInput, formatTick, useSliceReactivity, SliceRailCluster, EastChakraComponent, ClauseChip, StateImpl, NavImpl, SliceImpl, SliceApplyImpl, OverlayImpl, ClipboardImpl, DownloadImpl, ShareImpl, createUIStore, UIStoreProvider } from "@elaraai/east-ui-components";
31
+ import { implementUIComponent, getSomeorUndefined, formatPredicate, SliceEditPopover, SlicePredicateBuilder, EastChakraSelect, EastChakraStringInput, EastChakraDateTimeInput, EastChakraFloatInput, EastChakraIntegerInput, formatTick, useSliceReactivity, usePersistedState, SliceRailCluster, EastChakraComponent, ClauseChip, StateImpl, NavImpl, SliceImpl, SliceApplyImpl, OverlayImpl, ClipboardImpl, DownloadImpl, ShareImpl, createUIStore, UIStoreProvider } from "@elaraai/east-ui-components";
32
32
  import { createPortal } from "react-dom";
33
33
  import { sliceMatches } from "@elaraai/east-ui/internal";
34
34
  import { QueryClient as QueryClient$1, QueryClientProvider, useMutation, useQuery } from "@tanstack/react-query";
@@ -62910,6 +62910,39 @@ const URGENCY_RANK = {
62910
62910
  due: 1,
62911
62911
  routine: 2
62912
62912
  };
62913
+ const URGENCY_GROUP_LABEL = {
62914
+ overdue: "Overdue",
62915
+ due: "Due today",
62916
+ routine: "Routine"
62917
+ };
62918
+ function buildGroups(rows, option) {
62919
+ if (option.key === "none") {
62920
+ return [{ label: "", decisions: rows, pastSla: 0, total: 0, bulk: false }];
62921
+ }
62922
+ const labelFor = (d2) => {
62923
+ var _a2;
62924
+ return option.key === "urgency" ? URGENCY_GROUP_LABEL[d2.urgency.type] : option.key === "kind" ? d2.kind : ((_a2 = option.accessor) == null ? void 0 : _a2.call(option, d2)) ?? "";
62925
+ };
62926
+ const out = /* @__PURE__ */ new Map();
62927
+ for (const d2 of rows) {
62928
+ const label = labelFor(d2);
62929
+ let group2 = out.get(label);
62930
+ if (group2 === void 0) {
62931
+ group2 = {
62932
+ label,
62933
+ decisions: [],
62934
+ pastSla: 0,
62935
+ total: 0,
62936
+ bulk: option.key === "urgency" && d2.urgency.type === "routine"
62937
+ };
62938
+ out.set(label, group2);
62939
+ }
62940
+ group2.decisions.push(d2);
62941
+ group2.total += d2.value;
62942
+ if (d2.urgency.type === "overdue") group2.pastSla += 1;
62943
+ }
62944
+ return [...out.values()];
62945
+ }
62913
62946
  const FACETS = [
62914
62947
  { key: "evidence", label: "Evidence" },
62915
62948
  { key: "options", label: "Options" },
@@ -63026,7 +63059,8 @@ const Row = memo(function Row2({ decision, handle, selected: selected2, narrow,
63026
63059
  ),
63027
63060
  /* @__PURE__ */ jsx$1(Box, { as: "button", css: button({ variant: "ghost", size: "xs" }), onClick: handleReject, children: "Reject" })
63028
63061
  ] });
63029
- const title = /* @__PURE__ */ jsxs(Box, { minW: 0, cursor: selected2 ? void 0 : "pointer", onClick: handleSelect, children: [
63062
+ const fullTitle = `${decision.kind} · ${decision.title}${summary !== void 0 ? ` · ${summary}` : ""}`;
63063
+ const title = /* @__PURE__ */ jsxs(Box, { css: rs.titleText, title: fullTitle, cursor: selected2 ? void 0 : "pointer", onClick: handleSelect, children: [
63030
63064
  /* @__PURE__ */ jsx$1(Text, { as: "span", fontWeight: "semibold", children: decision.kind }),
63031
63065
  /* @__PURE__ */ jsx$1(Text, { as: "span", color: "fg.subtle", children: " · " }),
63032
63066
  /* @__PURE__ */ jsx$1(Text, { as: "span", children: decision.title }),
@@ -63111,10 +63145,39 @@ const RoutineGroup = memo(function RoutineGroup2({ routine, acceptAll, leaving,
63111
63145
  ] })
63112
63146
  ] });
63113
63147
  });
63148
+ const GroupHead = memo(function GroupHead2({ group: group2, collapsible, collapsed, onToggle, acceptAll }) {
63149
+ const dq = useSlotRecipe({ key: "decisionQueue" });
63150
+ const button = useRecipe({ key: "button" });
63151
+ const rs = dq({});
63152
+ const format2 = group2.decisions[0] ? getSomeorUndefined(group2.decisions[0].format) : void 0;
63153
+ const handleToggle = useCallback(() => {
63154
+ if (collapsible) onToggle(group2.label);
63155
+ }, [collapsible, onToggle, group2.label]);
63156
+ const handleAcceptAll = useCallback((e3) => {
63157
+ e3.stopPropagation();
63158
+ if (acceptAll) acceptAll(group2.decisions);
63159
+ }, [acceptAll, group2.decisions]);
63160
+ return /* @__PURE__ */ jsxs(Box, { css: rs.groupHead, ...collapsible ? { "data-collapsible": "" } : {}, onClick: handleToggle, children: [
63161
+ collapsible && /* @__PURE__ */ jsx$1(Box, { as: "span", css: rs.groupCaret, children: collapsed ? "▸" : "▾" }),
63162
+ /* @__PURE__ */ jsxs(Box, { as: "span", css: rs.groupLabel, children: [
63163
+ group2.label,
63164
+ " · ",
63165
+ group2.decisions.length
63166
+ ] }),
63167
+ /* @__PURE__ */ jsxs(Box, { as: "span", css: rs.groupSummary, children: [
63168
+ formatTick(group2.total, format2),
63169
+ group2.pastSla > 0 ? ` · ${group2.pastSla} past SLA` : ""
63170
+ ] }),
63171
+ acceptAll && /* @__PURE__ */ jsx$1(Box, { as: "button", css: button({ variant: "solid", size: "xs" }), onClick: handleAcceptAll, children: "Accept all" })
63172
+ ] });
63173
+ });
63114
63174
  const EastChakraDecisionQueue = memo(function EastChakraDecisionQueue2({ value, storageKey }) {
63115
63175
  var _a2;
63116
63176
  const eyebrow = useSlotRecipe({ key: "eyebrowRow" });
63117
63177
  const es = eyebrow({});
63178
+ const dq = useSlotRecipe({ key: "decisionQueue" });
63179
+ const qs = dq({});
63180
+ const chip = useRecipe({ key: "chip" });
63118
63181
  const handleRef = value.handle;
63119
63182
  const handle = useDecisionHandle(handleRef);
63120
63183
  const decisions = handle.decisions;
@@ -63183,6 +63246,25 @@ const EastChakraDecisionQueue = memo(function EastChakraDecisionQueue2({ value,
63183
63246
  return facets === void 0 ? null : new Set(facets.map((f2) => f2.type));
63184
63247
  }, [value.facets]);
63185
63248
  const defaultExpanded = getSomeorUndefined(value.defaultExpanded);
63249
+ const groupDefs = useMemo(
63250
+ () => getSomeorUndefined(value.groups) ?? [],
63251
+ [value.groups]
63252
+ );
63253
+ const defaultGroupKey = getSomeorUndefined(value.groupBy);
63254
+ const grouped = defaultGroupKey !== void 0;
63255
+ const collapsible = grouped && (getSomeorUndefined(value.collapsible) ?? true);
63256
+ const groupOptions = useMemo(() => [
63257
+ { key: "urgency", label: "Urgency" },
63258
+ { key: "kind", label: "Kind" },
63259
+ ...groupDefs.map((g2) => ({ key: g2.label, label: g2.label, accessor: g2.value })),
63260
+ { key: "none", label: "None" }
63261
+ ], [groupDefs]);
63262
+ const { state: toolbar, setState: setToolbar } = usePersistedState(`${storageKey}.toolbar`, {
63263
+ groupKey: defaultGroupKey ?? "urgency",
63264
+ // The urgency grouping's routine tail ships collapsed.
63265
+ collapsed: [URGENCY_GROUP_LABEL.routine]
63266
+ });
63267
+ const activeOption = groupOptions.find((o2) => o2.key === toolbar.groupKey) ?? groupOptions[0];
63186
63268
  const selectedId = handle.selected ?? (defaultExpanded == null ? void 0 : defaultExpanded.id) ?? null;
63187
63269
  const resolve2 = useCallback((ds, hook2, reason) => {
63188
63270
  if (hook2) for (const d2 of ds) {
@@ -63197,15 +63279,15 @@ const EastChakraDecisionQueue = memo(function EastChakraDecisionQueue2({ value,
63197
63279
  const apply2 = useCallback((d2) => resolve2([d2], onApply, "apply"), [resolve2, onApply]);
63198
63280
  const reject2 = useCallback((d2) => resolve2([d2], onReject, "reject"), [resolve2, onReject]);
63199
63281
  const acceptAll = useCallback((ds) => resolve2(ds, onApply, "apply"), [resolve2, onApply]);
63200
- const { active, routine, pastSla, visible } = useMemo(() => {
63282
+ const { merged, active, routine, pastSla, visible } = useMemo(() => {
63201
63283
  const scoped = decisions ?? [];
63202
63284
  const now2 = /* @__PURE__ */ new Date();
63203
63285
  const live = sliceState !== null ? scoped.filter((d2) => sliceMatches(sliceState, DECISION_SLICE_CONFIG, d2, now2)) : scoped;
63204
- const merged = [...live];
63286
+ const merged2 = [...live];
63205
63287
  for (const { decision } of exiting.values()) {
63206
- if (!merged.some((d2) => d2.id === decision.id)) merged.push(decision);
63288
+ if (!merged2.some((d2) => d2.id === decision.id)) merged2.push(decision);
63207
63289
  }
63208
- merged.sort((a2, b2) => {
63290
+ merged2.sort((a2, b2) => {
63209
63291
  var _a3, _b2;
63210
63292
  const r2 = URGENCY_RANK[a2.urgency.type] - URGENCY_RANK[b2.urgency.type];
63211
63293
  if (r2 !== 0) return r2;
@@ -63215,17 +63297,55 @@ const EastChakraDecisionQueue = memo(function EastChakraDecisionQueue2({ value,
63215
63297
  return b2.value - a2.value;
63216
63298
  });
63217
63299
  return {
63218
- active: merged.filter((d2) => d2.urgency.type !== "routine"),
63219
- routine: merged.filter((d2) => d2.urgency.type === "routine"),
63220
- pastSla: merged.filter((d2) => d2.urgency.type === "overdue").length,
63300
+ merged: merged2,
63301
+ active: merged2.filter((d2) => d2.urgency.type !== "routine"),
63302
+ routine: merged2.filter((d2) => d2.urgency.type === "routine"),
63303
+ pastSla: merged2.filter((d2) => d2.urgency.type === "overdue").length,
63221
63304
  visible: live.length
63222
63305
  };
63223
63306
  }, [decisions, exiting, sliceState]);
63307
+ const groups = useMemo(
63308
+ () => grouped ? buildGroups(merged, activeOption) : [],
63309
+ [grouped, merged, activeOption]
63310
+ );
63311
+ const allCollapsed = groups.length > 0 && groups.every((g2) => g2.label === "" || toolbar.collapsed.includes(g2.label));
63312
+ const toggleGroup = useCallback((label) => {
63313
+ setToolbar((prev2) => ({
63314
+ ...prev2,
63315
+ collapsed: prev2.collapsed.includes(label) ? prev2.collapsed.filter((l2) => l2 !== label) : [...prev2.collapsed, label]
63316
+ }));
63317
+ }, [setToolbar]);
63318
+ const toggleAll = useCallback(() => {
63319
+ const next2 = allCollapsed ? [] : groups.map((g2) => g2.label).filter((l2) => l2 !== "");
63320
+ setToolbar((prev2) => ({ ...prev2, collapsed: next2 }));
63321
+ }, [setToolbar, allCollapsed, groups]);
63322
+ const setGroupKey = useCallback((key) => {
63323
+ setToolbar((prev2) => prev2.groupKey === key ? prev2 : { ...prev2, groupKey: key });
63324
+ }, [setToolbar]);
63224
63325
  const exitingReasons = useMemo(() => {
63225
63326
  const m2 = /* @__PURE__ */ new Map();
63226
63327
  for (const [id3, e3] of exiting) m2.set(id3, e3.reason);
63227
63328
  return m2;
63228
63329
  }, [exiting]);
63330
+ const renderRow = (d2) => /* @__PURE__ */ jsx$1(
63331
+ Row,
63332
+ {
63333
+ decision: d2,
63334
+ handle,
63335
+ selected: selectedId === d2.id,
63336
+ narrow,
63337
+ leverPayloads,
63338
+ modify,
63339
+ evidence,
63340
+ defaultFacet,
63341
+ facetInclude,
63342
+ apply: apply2,
63343
+ reject: reject2,
63344
+ leaving: exitingReasons.get(d2.id),
63345
+ storageKey
63346
+ },
63347
+ d2.id
63348
+ );
63229
63349
  if (decisions === null) {
63230
63350
  return /* @__PURE__ */ jsx$1(Box, { layerStyle: "surface.frameless", p: "16px", children: /* @__PURE__ */ jsx$1(Text, { color: "fg.muted", fontSize: "13px", children: "Loading decisions…" }) });
63231
63351
  }
@@ -63253,33 +63373,45 @@ const EastChakraDecisionQueue = memo(function EastChakraDecisionQueue2({ value,
63253
63373
  ] })
63254
63374
  ] })
63255
63375
  ] }),
63256
- /* @__PURE__ */ jsxs(
63376
+ grouped && /* @__PURE__ */ jsxs(Box, { css: qs.toolbar, children: [
63377
+ /* @__PURE__ */ jsx$1(Box, { as: "span", css: qs.segLabel, children: "Group by" }),
63378
+ groupOptions.map((o2) => /* @__PURE__ */ jsx$1(
63379
+ Box,
63380
+ {
63381
+ as: "button",
63382
+ css: { ...chip({ tone: toolbar.groupKey === o2.key ? "brand" : "neutral", size: "sm" }), cursor: "pointer" },
63383
+ "aria-pressed": toolbar.groupKey === o2.key,
63384
+ onClick: () => setGroupKey(o2.key),
63385
+ children: o2.label
63386
+ },
63387
+ o2.key
63388
+ )),
63389
+ collapsible && /* @__PURE__ */ jsx$1(Box, { as: "button", css: qs.collapseAll, onClick: toggleAll, children: allCollapsed ? "Expand all" : "Collapse all" })
63390
+ ] }),
63391
+ /* @__PURE__ */ jsx$1(
63257
63392
  Box,
63258
63393
  {
63259
63394
  maxHeight: getSomeorUndefined(value.maxHeight),
63260
63395
  overflowY: value.maxHeight.type === "some" ? "auto" : void 0,
63261
- children: [
63262
- active.map((d2) => /* @__PURE__ */ jsx$1(
63263
- Row,
63264
- {
63265
- decision: d2,
63266
- handle,
63267
- selected: selectedId === d2.id,
63268
- narrow,
63269
- leverPayloads,
63270
- modify,
63271
- evidence,
63272
- defaultFacet,
63273
- facetInclude,
63274
- apply: apply2,
63275
- reject: reject2,
63276
- leaving: exitingReasons.get(d2.id),
63277
- storageKey
63278
- },
63279
- d2.id
63280
- )),
63396
+ children: grouped ? groups.map((g2) => {
63397
+ const isCollapsed = collapsible && g2.label !== "" && toolbar.collapsed.includes(g2.label);
63398
+ return /* @__PURE__ */ jsxs(Box, { children: [
63399
+ g2.label !== "" && /* @__PURE__ */ jsx$1(
63400
+ GroupHead,
63401
+ {
63402
+ group: g2,
63403
+ collapsible,
63404
+ collapsed: isCollapsed,
63405
+ onToggle: toggleGroup,
63406
+ acceptAll: g2.bulk ? acceptAll : void 0
63407
+ }
63408
+ ),
63409
+ !isCollapsed && g2.decisions.map(renderRow)
63410
+ ] }, g2.label || "_flat");
63411
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
63412
+ active.map(renderRow),
63281
63413
  /* @__PURE__ */ jsx$1(RoutineGroup, { routine, acceptAll, leaving: exitingReasons, narrow })
63282
- ]
63414
+ ] })
63283
63415
  }
63284
63416
  )
63285
63417
  ] });