@crewlethq/ui 0.83.0 → 0.84.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 (66) hide show
  1. package/README.md +34 -0
  2. package/dist/ActionCard/ActionCard.d.ts +7 -7
  3. package/dist/ActionCard/index.js +1 -1
  4. package/dist/ActionCard-YZKL3CKR.css +130 -0
  5. package/dist/AppShell/AppShell.d.ts +14 -11
  6. package/dist/AppShell/index.js +1 -1
  7. package/dist/{AppShell-U7D2MUUF.css → AppShell-NX5HCD4O.css} +84 -44
  8. package/dist/ArtifactBar/index.js +1 -1
  9. package/dist/ArtifactCard/index.js +2 -2
  10. package/dist/ArtifactKindIllustration/index.js +1 -1
  11. package/dist/{ArtifactKinds-3EH4KR3F.css → ArtifactKinds-27TE3KJL.css} +7 -7
  12. package/dist/ArtifactMakeRow/index.js +1 -1
  13. package/dist/ArtifactNameCell/index.js +1 -1
  14. package/dist/BriefCard/index.js +2 -1
  15. package/dist/CatalogTile/CatalogTile.d.ts +23 -5
  16. package/dist/CatalogTile/index.d.ts +1 -1
  17. package/dist/CatalogTile/index.js +2 -1
  18. package/dist/{CatalogTile-6VF36JWA.css → CatalogTile-TMOSGSV3.css} +4 -3
  19. package/dist/CompactNav/CompactNav.d.ts +10 -6
  20. package/dist/CompactNav/index.js +1 -1
  21. package/dist/DataView/index.js +1 -1
  22. package/dist/DirectoryList/index.js +1 -1
  23. package/dist/{DirectoryList-XZOWYOA3.css → DirectoryList-IXQIMNFS.css} +8 -1
  24. package/dist/InboxList/InboxList.d.ts +28 -2
  25. package/dist/InboxList/index.d.ts +1 -1
  26. package/dist/InboxList/index.js +6 -1
  27. package/dist/{InboxList-OFGSX5IH.css → InboxList-X5Q5WWAI.css} +63 -2
  28. package/dist/ListDetail/ListDetail.d.ts +6 -0
  29. package/dist/ListDetail/index.js +1 -1
  30. package/dist/{ListDetail-N3L7PB46.css → ListDetail-JGESKTR7.css} +38 -0
  31. package/dist/ReadingPane/ReadingPane.d.ts +17 -1
  32. package/dist/ReadingPane/index.d.ts +1 -1
  33. package/dist/ReadingPane/index.js +2 -1
  34. package/dist/{ReadingPane-VLYOWMCM.css → ReadingPane-VFTRDPC5.css} +19 -0
  35. package/dist/RichEditor/index.js +1 -1
  36. package/dist/SectionFrame/index.js +2 -2
  37. package/dist/ShareDialog/index.js +1 -1
  38. package/dist/SidebarNav/SidebarNav.d.ts +18 -2
  39. package/dist/SidebarNav/index.d.ts +1 -1
  40. package/dist/SidebarNav/index.js +1 -1
  41. package/dist/{SidebarNav-E7PZGGA6.css → SidebarNav-IKBKGXW4.css} +20 -0
  42. package/dist/{chunk-AUSFKGKL.js → chunk-3AMWXJQX.js} +1 -1
  43. package/dist/{chunk-Q7N2ATMC.js → chunk-64JNKLLP.js} +57 -9
  44. package/dist/chunk-6C7GQOBV.js +10 -0
  45. package/dist/{chunk-YRNNUCQP.js → chunk-7BDBO2NP.js} +1 -1
  46. package/dist/{chunk-25JQNHTC.js → chunk-BRK3HXGZ.js} +8 -2
  47. package/dist/{chunk-QM3432PH.js → chunk-C4JTX25K.js} +2 -2
  48. package/dist/{chunk-QE4OIE3Q.js → chunk-DJIWCW5Z.js} +6 -4
  49. package/dist/{chunk-QPHD4UUG.js → chunk-F6ZUM66S.js} +160 -25
  50. package/dist/{chunk-MVMVMHYI.js → chunk-HDEKHP65.js} +1 -1
  51. package/dist/{chunk-BXGBGYKH.js → chunk-HKUZ2GYX.js} +1 -1
  52. package/dist/{chunk-ZB3E2Z4Y.js → chunk-J5T5TDM2.js} +1 -1
  53. package/dist/{chunk-C46DDVTK.js → chunk-LTD7KLOU.js} +1 -1
  54. package/dist/{chunk-YBC2CYPD.js → chunk-O4VTQMEF.js} +1 -1
  55. package/dist/{chunk-HTBTAIH6.js → chunk-OM2LABPS.js} +2 -2
  56. package/dist/{chunk-PGBKZSSQ.js → chunk-ORONAXSF.js} +10 -7
  57. package/dist/{chunk-ICE3D63E.js → chunk-QWRAWKB2.js} +50 -9
  58. package/dist/{chunk-SRDNGGZE.js → chunk-RKIJAXQA.js} +1 -1
  59. package/dist/{chunk-CF4M6N6A.js → chunk-XYSSFI5C.js} +1 -7
  60. package/dist/generated/index.js +31 -30
  61. package/dist/index.js +31 -30
  62. package/dist/styles.css +294 -88
  63. package/dist/utils/developing.d.ts +10 -0
  64. package/package.json +3 -3
  65. package/dist/ActionCard-47WSZ2O5.css +0 -110
  66. package/dist/{Editor-5D5Z3LBQ.js → Editor-VGNB35NJ.js} +1 -1
@@ -1,8 +1,9 @@
1
1
  import { Eyebrow } from './chunk-RK4XPHVG.js';
2
+ import { returnFocus } from './chunk-4ITETIGZ.js';
2
3
  import { useHeadingLevel, HeadingLevelProvider, nextHeadingLevel, headingTag } from './chunk-PQADV6P6.js';
3
4
  import { cx } from './chunk-ODEQTPFM.js';
4
- import './ReadingPane-VLYOWMCM.css';
5
- import { createContext, useId, useContext } from 'react';
5
+ import './ReadingPane-VFTRDPC5.css';
6
+ import { createContext, useId, useRef, useState, useEffect, useLayoutEffect, createElement, useContext } from 'react';
6
7
  import { ClockGlyph, CheckGlyph } from '@crewlethq/icons/glyphs';
7
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
9
 
@@ -12,19 +13,62 @@ function usePane(part) {
12
13
  if (pane === null) throw new Error(`${part} must be drawn inside a ReadingPane.`);
13
14
  return pane;
14
15
  }
15
- function ReadingPane({ headingLevel, className, children, ...rest }) {
16
+ var READING_PANE_ENTER = { animations: ["crewlet-reading-pane-enter"] };
17
+ function ReadingPane({ headingLevel, contentKey, focusTarget, className, children, ...rest }) {
16
18
  const titleId = useId();
17
19
  const outer = useHeadingLevel();
18
20
  const level = headingLevel ?? outer;
19
21
  const named = rest["aria-label"] !== void 0 || rest["aria-labelledby"] !== void 0;
20
- return /* @__PURE__ */ jsx("div", { className: "crewlet-reading-pane", children: /* @__PURE__ */ jsx(
22
+ const pane = useRef(null);
23
+ const column = useRef(null);
24
+ const [drawnKey, setDrawnKey] = useState(contentKey);
25
+ const [moved, setMoved] = useState(false);
26
+ if (drawnKey !== contentKey) {
27
+ setDrawnKey(contentKey);
28
+ setMoved(true);
29
+ }
30
+ const owned = useRef(false);
31
+ useEffect(() => {
32
+ const root = pane.current;
33
+ if (!root) return void 0;
34
+ const doc = root.ownerDocument;
35
+ const inside = (node) => node instanceof Node && root.contains(node);
36
+ const focusIn = () => {
37
+ owned.current = true;
38
+ };
39
+ const focusOut = (event) => {
40
+ if (event.relatedTarget !== null && !inside(event.relatedTarget)) owned.current = false;
41
+ };
42
+ const press = (event) => {
43
+ owned.current = inside(event.target);
44
+ };
45
+ root.addEventListener("focusin", focusIn);
46
+ root.addEventListener("focusout", focusOut);
47
+ doc.addEventListener("pointerdown", press, true);
48
+ return () => {
49
+ root.removeEventListener("focusin", focusIn);
50
+ root.removeEventListener("focusout", focusOut);
51
+ doc.removeEventListener("pointerdown", press, true);
52
+ };
53
+ }, []);
54
+ const settled = useRef(contentKey);
55
+ useLayoutEffect(() => {
56
+ if (contentKey === settled.current) return;
57
+ settled.current = contentKey;
58
+ if (!owned.current) return;
59
+ const target = focusTarget?.() ?? column.current?.querySelector(".crewlet-reading-pane__title");
60
+ returnFocus(target, { preventScroll: true });
61
+ }, [contentKey, focusTarget]);
62
+ return /* @__PURE__ */ jsx("div", { ref: pane, className: "crewlet-reading-pane", children: /* @__PURE__ */ createElement(
21
63
  "article",
22
64
  {
23
65
  ...rest,
66
+ key: contentKey,
67
+ ref: column,
24
68
  "aria-labelledby": named ? rest["aria-labelledby"] : titleId,
25
- className: cx("crewlet-reading-pane__column", className),
26
- children: /* @__PURE__ */ jsx(ReadingPaneContext.Provider, { value: { titleId, level }, children: /* @__PURE__ */ jsx(HeadingLevelProvider, { level: nextHeadingLevel(level), children }) })
27
- }
69
+ className: cx("crewlet-reading-pane__column", moved && "crewlet-reading-pane__column--entering", className)
70
+ },
71
+ /* @__PURE__ */ jsx(ReadingPaneContext.Provider, { value: { titleId, level }, children: /* @__PURE__ */ jsx(HeadingLevelProvider, { level: nextHeadingLevel(level), children }) })
28
72
  ) });
29
73
  }
30
74
  function ReadingPaneByline({ start, leading, note, className, children, ...rest }) {
@@ -46,7 +90,11 @@ function ReadingPaneByline({ start, leading, note, className, children, ...rest
46
90
  function ReadingPaneTitle({ className, children, ...rest }) {
47
91
  const { titleId, level } = usePane("ReadingPane.Title");
48
92
  const Heading = headingTag(level);
49
- return /* @__PURE__ */ jsx(Heading, { id: titleId, ...rest, className: cx("crewlet-reading-pane__title", className), children });
93
+ return (
94
+ // Focusable from code alone, for the pane to hand focus to when its item
95
+ // changes; never a tab stop.
96
+ /* @__PURE__ */ jsx(Heading, { id: titleId, tabIndex: -1, ...rest, className: cx("crewlet-reading-pane__title", className), children })
97
+ );
50
98
  }
51
99
  function ReadingPaneLead({ className, children, ...rest }) {
52
100
  return /* @__PURE__ */ jsx("div", { ...rest, className: cx("crewlet-reading-pane__lead", className), children });
@@ -109,4 +157,4 @@ ReadingPane.Reasoning = ReadingPaneReasoning;
109
157
  ReadingPane.Outcome = ReadingPaneOutcome;
110
158
  ReadingPane.Section = ReadingPaneSection;
111
159
 
112
- export { ReadingPane, ReadingPaneByline, ReadingPaneLead, ReadingPaneOutcome, ReadingPaneQuote, ReadingPaneReasoning, ReadingPaneSection, ReadingPaneTitle };
160
+ export { READING_PANE_ENTER, ReadingPane, ReadingPaneByline, ReadingPaneLead, ReadingPaneOutcome, ReadingPaneQuote, ReadingPaneReasoning, ReadingPaneSection, ReadingPaneTitle };
@@ -0,0 +1,10 @@
1
+ // src/utils/developing.ts
2
+ function developing() {
3
+ try {
4
+ return process.env.NODE_ENV !== "production";
5
+ } catch {
6
+ return false;
7
+ }
8
+ }
9
+
10
+ export { developing };
@@ -1,4 +1,4 @@
1
- import { SidebarNav } from './chunk-25JQNHTC.js';
1
+ import { SidebarNav } from './chunk-BRK3HXGZ.js';
2
2
  import { VisuallyHidden } from './chunk-UT6CW3C7.js';
3
3
  import { cx } from './chunk-ODEQTPFM.js';
4
4
  import './SectionFrame-M62UUVFQ.css';
@@ -4,7 +4,7 @@ import { HIT_AREA_CLASS } from './chunk-2YTOOH3A.js';
4
4
  import { VisuallyHidden } from './chunk-UT6CW3C7.js';
5
5
  import { cx } from './chunk-ODEQTPFM.js';
6
6
  import './HitArea-QUOS6ZDJ.css';
7
- import './SidebarNav-E7PZGGA6.css';
7
+ import './SidebarNav-IKBKGXW4.css';
8
8
  import { createContext, useId, useState, useContext } from 'react';
9
9
  import { ChevronDownGlyph } from '@crewlethq/icons/glyphs';
10
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -136,7 +136,13 @@ function NavItem({
136
136
  count || badge || state ? /* @__PURE__ */ jsx(VisuallyHidden, { children: `${spoken([count, badge])}${state}` }) : null
137
137
  ] }),
138
138
  count ? /* @__PURE__ */ jsx(Figure, { figure: count, mark: count.mark, className: "crewlet-nav-item__count" }) : null,
139
- badge ? /* @__PURE__ */ jsx(Figure, { figure: badge, className: "crewlet-nav-item__badge" }) : null
139
+ badge ? /* @__PURE__ */ jsx(
140
+ Figure,
141
+ {
142
+ figure: badge,
143
+ className: cx("crewlet-nav-item__badge", badge.tone === "quiet" && "crewlet-nav-item__badge--quiet")
144
+ }
145
+ ) : null
140
146
  ] });
141
147
  const rowClass = cx("crewlet-nav-item__row", HIT_AREA_CLASS);
142
148
  let row;
@@ -3,13 +3,13 @@ import { PrincipalAvatar } from './chunk-HF5L56YQ.js';
3
3
  import { RelativeTime } from './chunk-EJNJMHQG.js';
4
4
  import { Card } from './chunk-U2IQNDDR.js';
5
5
  import { AvatarStack } from './chunk-IRONPJB4.js';
6
- import { ArtifactKindGround } from './chunk-ZB3E2Z4Y.js';
6
+ import { ArtifactKindGround } from './chunk-J5T5TDM2.js';
7
7
  import { Checkbox } from './chunk-YQ7Q52XU.js';
8
8
  import { ArtifactAccessGlyph } from './chunk-DOY3KC6I.js';
9
9
  import { ArtifactKindGlyph } from './chunk-RSKW25Y5.js';
10
10
  import { VisuallyHidden } from './chunk-UT6CW3C7.js';
11
11
  import { cx } from './chunk-ODEQTPFM.js';
12
- import './ArtifactKinds-3EH4KR3F.css';
12
+ import './ArtifactKinds-27TE3KJL.css';
13
13
  import './ArtifactCard-B2QE5CVL.css';
14
14
  import { useId } from 'react';
15
15
  import { PinGlyph } from '@crewlethq/icons/glyphs';
@@ -1,8 +1,9 @@
1
+ import { StatusDot } from './chunk-CYQBDZ6M.js';
1
2
  import { useHeadingLevel, headingTag } from './chunk-PQADV6P6.js';
2
3
  import { cx } from './chunk-ODEQTPFM.js';
3
- import './CatalogTile-6VF36JWA.css';
4
+ import './CatalogTile-TMOSGSV3.css';
4
5
  import { useId } from 'react';
5
- import { jsxs, jsx } from 'react/jsx-runtime';
6
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
7
 
7
8
  function CatalogTile({
8
9
  as: Element = "article",
@@ -22,6 +23,7 @@ function CatalogTile({
22
23
  const Heading = headingTag(headingLevel ?? level);
23
24
  const titleId = useId();
24
25
  const foot = meta !== void 0 || action !== void 0;
26
+ const state = status ? /* @__PURE__ */ jsx("span", { className: "crewlet-catalog-tile__status", children: /* @__PURE__ */ jsx(StatusDot, { tone: status.tone, children: status.label }) }) : null;
25
27
  if (density === "compact") {
26
28
  return /* @__PURE__ */ jsxs(
27
29
  Element,
@@ -39,7 +41,7 @@ function CatalogTile({
39
41
  /* @__PURE__ */ jsx("span", { className: "crewlet-catalog-tile__mark", "aria-hidden": "true", children: mark }),
40
42
  /* @__PURE__ */ jsxs("span", { className: "crewlet-catalog-tile__title", children: [
41
43
  /* @__PURE__ */ jsx(Heading, { id: titleId, className: "crewlet-catalog-tile__name", children: title }),
42
- status === void 0 || status === null ? null : /* @__PURE__ */ jsx("span", { className: "crewlet-catalog-tile__status", children: status }),
44
+ state,
43
45
  meta === void 0 || meta === null ? null : /* @__PURE__ */ jsx("span", { className: "crewlet-catalog-tile__meta", children: meta })
44
46
  ] }),
45
47
  action === void 0 ? null : /* @__PURE__ */ jsx("span", { className: "crewlet-catalog-tile__action", children: action })
@@ -67,7 +69,7 @@ function CatalogTile({
67
69
  /* @__PURE__ */ jsx("span", { className: "crewlet-catalog-tile__mark", "aria-hidden": "true", children: mark }),
68
70
  /* @__PURE__ */ jsxs("span", { className: "crewlet-catalog-tile__title", children: [
69
71
  /* @__PURE__ */ jsx(Heading, { id: titleId, className: "crewlet-catalog-tile__name", children: title }),
70
- status === void 0 || status === null ? null : /* @__PURE__ */ jsx("span", { className: "crewlet-catalog-tile__status", children: status })
72
+ state
71
73
  ] })
72
74
  ] }),
73
75
  description === void 0 ? null : /* @__PURE__ */ jsx("p", { className: "crewlet-catalog-tile__description", children: description }),
@@ -1,15 +1,108 @@
1
1
  import { Eyebrow } from './chunk-RK4XPHVG.js';
2
+ import { developing } from './chunk-6C7GQOBV.js';
3
+ import { usePresence, releaseFocus } from './chunk-PCAT2EFB.js';
2
4
  import { useHeadingLevel, headingTag } from './chunk-PQADV6P6.js';
3
5
  import { VisuallyHidden } from './chunk-UT6CW3C7.js';
4
6
  import { cx } from './chunk-ODEQTPFM.js';
5
- import './InboxList-OFGSX5IH.css';
6
- import { useId, useState } from 'react';
7
+ import './InboxList-X5Q5WWAI.css';
8
+ import { useId, useState, useRef, useLayoutEffect, useEffect, useCallback, cloneElement, Children, isValidElement } from 'react';
7
9
  import { ChevronDownGlyph } from '@crewlethq/icons/glyphs';
8
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
11
 
10
12
  function InboxList({ label, className, children, ...rest }) {
11
13
  return /* @__PURE__ */ jsx("nav", { ...rest, "aria-label": label, className: cx("crewlet-inbox-list", className), children });
12
14
  }
15
+ var INBOX_ROW_LEAVE = { transitions: ["grid-template-rows"] };
16
+ function rowsOf(children) {
17
+ const given = [];
18
+ Children.forEach(children, (child) => {
19
+ if (isValidElement(child)) given.push(child.key !== null);
20
+ });
21
+ return Children.toArray(children).filter((child) => isValidElement(child)).map((element, index) => ({
22
+ key: String(element.key),
23
+ element,
24
+ keyed: given[index] === true,
25
+ component: typeof element.type !== "string"
26
+ }));
27
+ }
28
+ function sameKeys(a, b) {
29
+ return a.length === b.length && a.every((key, index) => key === b[index]);
30
+ }
31
+ function keepPlaces(previous, next, kept) {
32
+ const placed = new Set(next);
33
+ const order = [...next];
34
+ previous.forEach((key, index) => {
35
+ if (placed.has(key) || !kept.has(key)) return;
36
+ let at = 0;
37
+ for (let back = index - 1; back >= 0; back -= 1) {
38
+ if (placed.has(previous[back])) {
39
+ at = order.indexOf(previous[back]) + 1;
40
+ break;
41
+ }
42
+ }
43
+ order.splice(at, 0, key);
44
+ placed.add(key);
45
+ });
46
+ return order;
47
+ }
48
+ function useLeavingRows(children) {
49
+ const rows = rowsOf(children);
50
+ const keys = rows.map((row) => row.key);
51
+ const [shown, setRows] = useState(() => ({
52
+ keys,
53
+ order: keys,
54
+ leaving: /* @__PURE__ */ new Map()
55
+ }));
56
+ const drawn = useRef(/* @__PURE__ */ new Map());
57
+ useLayoutEffect(() => {
58
+ drawn.current = new Map(rows.map((row) => [row.key, row]));
59
+ });
60
+ let now = shown;
61
+ if (!sameKeys(shown.keys, keys)) {
62
+ const present = new Set(keys);
63
+ const leaving = new Map([...shown.leaving].filter(([key]) => !present.has(key)));
64
+ for (const key of shown.keys) {
65
+ if (present.has(key)) continue;
66
+ const last = drawn.current.get(key);
67
+ if (last?.keyed && last.component) leaving.set(key, last.element);
68
+ }
69
+ now = { keys, order: keepPlaces(shown.order, keys, new Set(leaving.keys())), leaving };
70
+ setRows(now);
71
+ }
72
+ const warned = useRef(false);
73
+ useEffect(() => {
74
+ if (warned.current || !developing()) return;
75
+ if (rows.some((row) => row.component && !row.keyed)) {
76
+ warned.current = true;
77
+ console.warn(
78
+ "[crewlet] InboxGroup was handed a row without a key of its own. Give each row a stable key, such as its id: a row without one is told apart by its position, and is not kept while it leaves."
79
+ );
80
+ }
81
+ });
82
+ const drop = useCallback((key) => {
83
+ setRows((was) => {
84
+ if (!was.leaving.has(key)) return was;
85
+ const leaving = new Map(was.leaving);
86
+ leaving.delete(key);
87
+ return { ...was, leaving, order: was.order.filter((each) => each !== key) };
88
+ });
89
+ }, []);
90
+ if (now.leaving.size === 0) return rows.map((row) => row.element);
91
+ const live = new Map(rows.map((row) => [row.key, row.element]));
92
+ return now.order.map((key) => {
93
+ const row = live.get(key);
94
+ if (row) return row;
95
+ const last = now.leaving.get(key);
96
+ if (!last) return null;
97
+ return cloneElement(last, {
98
+ leaving: true,
99
+ onLeft: () => {
100
+ last.props.onLeft?.();
101
+ drop(key);
102
+ }
103
+ });
104
+ });
105
+ }
13
106
  function InboxGroup({
14
107
  label,
15
108
  hideLabel = false,
@@ -19,10 +112,12 @@ function InboxGroup({
19
112
  open: controlledOpen,
20
113
  defaultOpen = false,
21
114
  onOpenChange,
115
+ arrived,
22
116
  className,
23
117
  children,
24
118
  ...rest
25
119
  }) {
120
+ const rows = useLeavingRows(children);
26
121
  const level = useHeadingLevel();
27
122
  const Heading = headingTag(headingLevel ?? level);
28
123
  const labelId = useId();
@@ -40,6 +135,7 @@ function InboxGroup({
40
135
  " ",
41
136
  /* @__PURE__ */ jsx("span", { className: "crewlet-inbox-list__count", children: count })
42
137
  ] });
138
+ const arrival = arrived === void 0 ? null : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "crewlet-inbox-list__arrival" }, arrived);
43
139
  return /* @__PURE__ */ jsxs(
44
140
  "section",
45
141
  {
@@ -54,27 +150,31 @@ function InboxGroup({
54
150
  // A DISCLOSURE INSIDE THE HEADING, never a heading inside a button: a
55
151
  // reader moving by heading still lands on the group, and the button
56
152
  // in it says whether the rows are shown.
57
- /* @__PURE__ */ jsx(Heading, { className: "crewlet-inbox-list__label", children: /* @__PURE__ */ jsxs(
58
- "button",
59
- {
60
- type: "button",
61
- id: labelId,
62
- className: "crewlet-inbox-list__disclosure",
63
- "aria-expanded": open,
64
- "aria-controls": rowsId,
65
- onClick: toggle,
66
- children: [
67
- /* @__PURE__ */ jsx(ChevronDownGlyph, { "aria-hidden": true, size: "sm", className: "crewlet-inbox-list__chevron" }),
68
- /* @__PURE__ */ jsx(Eyebrow, { children: label }),
69
- counted
70
- ]
71
- }
72
- ) })
153
+ /* @__PURE__ */ jsxs(Heading, { className: "crewlet-inbox-list__label", children: [
154
+ /* @__PURE__ */ jsxs(
155
+ "button",
156
+ {
157
+ type: "button",
158
+ id: labelId,
159
+ className: "crewlet-inbox-list__disclosure",
160
+ "aria-expanded": open,
161
+ "aria-controls": rowsId,
162
+ onClick: toggle,
163
+ children: [
164
+ /* @__PURE__ */ jsx(ChevronDownGlyph, { "aria-hidden": true, size: "sm", className: "crewlet-inbox-list__chevron" }),
165
+ /* @__PURE__ */ jsx(Eyebrow, { children: label }),
166
+ counted
167
+ ]
168
+ }
169
+ ),
170
+ arrival
171
+ ] })
73
172
  ) : /* @__PURE__ */ jsxs(Heading, { id: labelId, className: "crewlet-inbox-list__label", children: [
74
173
  /* @__PURE__ */ jsx(Eyebrow, { children: label }),
75
- counted
174
+ counted,
175
+ arrival
76
176
  ] }),
77
- /* @__PURE__ */ jsx("ul", { id: rowsId, className: "crewlet-inbox-list__rows", hidden: !open, children })
177
+ /* @__PURE__ */ jsx("ul", { id: rowsId, className: "crewlet-inbox-list__rows", hidden: !open, children: rows })
78
178
  ]
79
179
  }
80
180
  );
@@ -99,10 +199,29 @@ function InboxRow({
99
199
  outcomeTone = "neutral",
100
200
  href,
101
201
  onClick,
202
+ leaving = false,
203
+ onLeft,
204
+ arrived = false,
102
205
  className,
103
206
  children,
104
207
  ...rest
105
208
  }) {
209
+ const self = useRef(null);
210
+ const presence = usePresence(!leaving, self, INBOX_ROW_LEAVE);
211
+ const gone = !presence.present;
212
+ useLayoutEffect(() => {
213
+ if (presence.leaving) releaseFocus(self.current);
214
+ }, [presence.leaving]);
215
+ const told = useRef(false);
216
+ useEffect(() => {
217
+ if (!gone) {
218
+ told.current = false;
219
+ return;
220
+ }
221
+ if (told.current) return;
222
+ told.current = true;
223
+ onLeft?.();
224
+ }, [gone, onLeft]);
106
225
  const contents = /* @__PURE__ */ jsxs(Fragment, { children: [
107
226
  leading === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
108
227
  /* @__PURE__ */ jsx("span", { className: "crewlet-inbox-row__leading", children: leading }),
@@ -157,10 +276,26 @@ function InboxRow({
157
276
  );
158
277
  const current = selected ? "true" : void 0;
159
278
  const control = href !== void 0 ? /* @__PURE__ */ jsx("a", { className: controlClass, href, "aria-current": current, onClick, children: contents }) : /* @__PURE__ */ jsx("button", { type: "button", className: controlClass, "aria-current": current, onClick, children: contents });
160
- return /* @__PURE__ */ jsxs("li", { ...rest, className: cx("crewlet-inbox-row", selected && "crewlet-inbox-row--selected", className), children: [
161
- control,
162
- aside === void 0 || aside === null || aside === false ? null : /* @__PURE__ */ jsx("div", { className: "crewlet-inbox-row__aside", children: aside })
163
- ] });
279
+ if (gone) return null;
280
+ return /* @__PURE__ */ jsx(
281
+ "li",
282
+ {
283
+ ...rest,
284
+ ref: self,
285
+ inert: presence.leaving,
286
+ className: cx(
287
+ "crewlet-inbox-row",
288
+ selected && "crewlet-inbox-row--selected",
289
+ arrived && "crewlet-inbox-row--arrived",
290
+ presence.leaving && "crewlet-inbox-row--leaving",
291
+ className
292
+ ),
293
+ children: /* @__PURE__ */ jsxs("div", { className: "crewlet-inbox-row__body", children: [
294
+ control,
295
+ aside === void 0 || aside === null || aside === false ? null : /* @__PURE__ */ jsx("div", { className: "crewlet-inbox-row__aside", children: aside })
296
+ ] })
297
+ }
298
+ );
164
299
  }
165
300
  function InboxMore({ className, children, ...rest }) {
166
301
  return /* @__PURE__ */ jsx("div", { ...rest, className: cx("crewlet-inbox-list__more", className), children });
@@ -173,4 +308,4 @@ InboxList.Row = InboxRow;
173
308
  InboxList.More = InboxMore;
174
309
  InboxList.Toggle = InboxToggle;
175
310
 
176
- export { InboxGroup, InboxList, InboxMore, InboxRow, InboxToggle };
311
+ export { INBOX_ROW_LEAVE, InboxGroup, InboxList, InboxMore, InboxRow, InboxToggle };
@@ -3,7 +3,7 @@ import { HIT_AREA_CLASS } from './chunk-2YTOOH3A.js';
3
3
  import { VisuallyHidden } from './chunk-UT6CW3C7.js';
4
4
  import { cx } from './chunk-ODEQTPFM.js';
5
5
  import './HitArea-QUOS6ZDJ.css';
6
- import './ArtifactKinds-3EH4KR3F.css';
6
+ import './ArtifactKinds-27TE3KJL.css';
7
7
  import './ArtifactMakeRow-6UX2LTF7.css';
8
8
  import { useId } from 'react';
9
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -7,7 +7,7 @@ import { Button } from './chunk-DGXZ635L.js';
7
7
  import { returnFocus } from './chunk-4ITETIGZ.js';
8
8
  import { IconButton } from './chunk-UAUP6XGK.js';
9
9
  import { cx } from './chunk-ODEQTPFM.js';
10
- import './ArtifactKinds-3EH4KR3F.css';
10
+ import './ArtifactKinds-27TE3KJL.css';
11
11
  import './ArtifactBar-X46WJNW7.css';
12
12
  import { useId, useState, useRef, useEffect } from 'react';
13
13
  import { ChevronLeftGlyph, EllipsisGlyph, ChevronDownGlyph } from '@crewlethq/icons/glyphs';
@@ -1,6 +1,6 @@
1
1
  import { VisuallyHidden } from './chunk-UT6CW3C7.js';
2
2
  import { cx } from './chunk-ODEQTPFM.js';
3
- import './ArtifactKinds-3EH4KR3F.css';
3
+ import './ArtifactKinds-27TE3KJL.css';
4
4
  import './ArtifactKindIllustration-D2J3HJMJ.css';
5
5
  import { useId } from 'react';
6
6
  import { CheckGlyph } from '@crewlethq/icons/glyphs';
@@ -1,7 +1,7 @@
1
1
  import { ArtifactKindGlyph } from './chunk-RSKW25Y5.js';
2
2
  import { VisuallyHidden } from './chunk-UT6CW3C7.js';
3
3
  import { cx } from './chunk-ODEQTPFM.js';
4
- import './ArtifactKinds-3EH4KR3F.css';
4
+ import './ArtifactKinds-27TE3KJL.css';
5
5
  import './ArtifactNameCell-3AHLUSE5.css';
6
6
  import { FolderGlyph, PinGlyph } from '@crewlethq/icons/glyphs';
7
7
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -1,5 +1,5 @@
1
1
  import { cx } from './chunk-ODEQTPFM.js';
2
- import './ListDetail-N3L7PB46.css';
2
+ import './ListDetail-JGESKTR7.css';
3
3
  import { jsx, jsxs } from 'react/jsx-runtime';
4
4
 
5
5
  function ListDetail({ list, children, showing = "list", className, ...rest }) {
@@ -1,6 +1,6 @@
1
1
  import { cx } from './chunk-ODEQTPFM.js';
2
- import './ArtifactKinds-3EH4KR3F.css';
3
- import './ActionCard-47WSZ2O5.css';
2
+ import './ArtifactKinds-27TE3KJL.css';
3
+ import './ActionCard-YZKL3CKR.css';
4
4
  import { jsxs, jsx } from 'react/jsx-runtime';
5
5
 
6
6
  var ACTION_CARD_GROUND = {
@@ -12,11 +12,11 @@ import { IconButton } from './chunk-UAUP6XGK.js';
12
12
  import { VisuallyHidden } from './chunk-UT6CW3C7.js';
13
13
  import { cx } from './chunk-ODEQTPFM.js';
14
14
  import './HitArea-QUOS6ZDJ.css';
15
- import './AppShell-U7D2MUUF.css';
15
+ import './AppShell-NX5HCD4O.css';
16
16
  import './ResizeHandle-2UNSZ4ZQ.css';
17
17
  import { createContext, forwardRef, useContext, useSyncExternalStore, useCallback, useId, useState, useRef, useEffect, useLayoutEffect } from 'react';
18
18
  import { breakpoint } from '@crewlethq/tokens';
19
- import { ChevronUpGlyph, ChevronsUpDownGlyph, ChevronRightGlyph, PlusGlyph, MinusGlyph, MenuGlyph } from '@crewlethq/icons/glyphs';
19
+ import { ChevronUpGlyph, ChevronsUpDownGlyph, ChevronRightGlyph, ChevronsRightGlyph, ChevronsLeftGlyph, MenuGlyph } from '@crewlethq/icons/glyphs';
20
20
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
21
21
 
22
22
  function AccountRow({
@@ -246,6 +246,7 @@ function AppShell({
246
246
  const narrow = useMediaMatch(NARROW);
247
247
  const [drawerOpen, setDrawerOpen] = useState(false);
248
248
  const [scroller, setScroller] = useState(null);
249
+ const [gauge, setGauge] = useState(null);
249
250
  const [drawerZ, setDrawerZ] = useState(null);
250
251
  const rail = useRef(null);
251
252
  const closeDrawer = useCallback(() => setDrawerOpen(false), []);
@@ -274,18 +275,19 @@ function AppShell({
274
275
  );
275
276
  const [gutter, setGutter] = useState(0);
276
277
  useLayoutEffect(() => {
277
- if (!scroller) return void 0;
278
- const measure = () => setGutter(Math.max(0, scroller.offsetWidth - scroller.clientWidth));
278
+ const ruler = gauge ?? scroller;
279
+ if (!ruler) return void 0;
280
+ const measure = () => setGutter(Math.max(0, ruler.offsetWidth - ruler.clientWidth));
279
281
  measure();
280
282
  if (typeof ResizeObserver === "undefined") return void 0;
281
283
  const observer = new ResizeObserver(unlessRailMoving(measure));
282
- observer.observe(scroller);
284
+ observer.observe(ruler);
283
285
  const held = heldMeasures.current;
284
286
  return () => {
285
287
  observer.disconnect();
286
288
  held.delete(measure);
287
289
  };
288
- }, [scroller, unlessRailMoving]);
290
+ }, [gauge, scroller, unlessRailMoving]);
289
291
  const root = useRef(null);
290
292
  const sheet = useRef(null);
291
293
  const asideColumn = hasAside && asideShape === "column";
@@ -433,6 +435,7 @@ function AppShell({
433
435
  /* @__PURE__ */ jsxs("div", { ref: sheet, className: "crewlet-app-shell__sheet", children: [
434
436
  topbar,
435
437
  banner ? /* @__PURE__ */ jsx("div", { className: "crewlet-app-shell__banner", children: banner }) : null,
438
+ fill === true ? /* @__PURE__ */ jsx("div", { className: "crewlet-app-shell__gauge", "aria-hidden": "true", children: /* @__PURE__ */ jsx("div", { ref: setGauge, className: "crewlet-app-shell__gauge-ruler" }) }) : null,
436
439
  /* @__PURE__ */ jsxs("div", { className: "crewlet-app-shell__body", children: [
437
440
  /* @__PURE__ */ jsx("main", { id: main, ref: setMain, className: "crewlet-app-shell__main", tabIndex: -1, children: /* @__PURE__ */ jsx("div", { ref: content, className: "crewlet-app-shell__content", children: /* @__PURE__ */ jsx(ScreenWidthContext.Provider, { value: screenWidth, children }) }) }),
438
441
  hasPeek ? /* @__PURE__ */ jsx(PeekContext.Provider, { value: { shape: peekShape }, children: peek }) : null
@@ -619,7 +622,7 @@ function RailFold({
619
622
  label,
620
623
  size: "sm",
621
624
  variant: "ghost",
622
- icon: compact ? /* @__PURE__ */ jsx(PlusGlyph, { size: "sm" }) : /* @__PURE__ */ jsx(MinusGlyph, { size: "sm" }),
625
+ icon: compact ? /* @__PURE__ */ jsx(ChevronsRightGlyph, { size: "sm" }) : /* @__PURE__ */ jsx(ChevronsLeftGlyph, { size: "sm" }),
623
626
  "aria-controls": railId,
624
627
  "aria-expanded": !compact,
625
628
  "aria-keyshortcuts": keys ? ariaKeyshortcuts(keys, apple) : void 0,