@crewlethq/ui 0.83.0 → 0.84.1

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 (82) hide show
  1. package/README.md +38 -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 +3 -3
  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/Avatar/index.js +1 -1
  15. package/dist/{Avatar-MNE3WBZO.css → Avatar-CRSLRUVT.css} +11 -10
  16. package/dist/BriefCard/index.js +2 -1
  17. package/dist/CatalogTile/CatalogTile.d.ts +23 -5
  18. package/dist/CatalogTile/index.d.ts +1 -1
  19. package/dist/CatalogTile/index.js +2 -1
  20. package/dist/{CatalogTile-6VF36JWA.css → CatalogTile-TMOSGSV3.css} +4 -3
  21. package/dist/CommentThread/index.js +1 -1
  22. package/dist/CompactNav/CompactNav.d.ts +10 -6
  23. package/dist/CompactNav/index.js +1 -1
  24. package/dist/DeepWorkCard/index.js +2 -2
  25. package/dist/DirectoryList/index.js +1 -1
  26. package/dist/{DirectoryList-XZOWYOA3.css → DirectoryList-IXQIMNFS.css} +8 -1
  27. package/dist/EntityChip/index.js +1 -1
  28. package/dist/ImageUpload/index.js +1 -1
  29. package/dist/InboxList/InboxList.d.ts +28 -2
  30. package/dist/InboxList/index.d.ts +1 -1
  31. package/dist/InboxList/index.js +6 -1
  32. package/dist/{InboxList-OFGSX5IH.css → InboxList-X5Q5WWAI.css} +63 -2
  33. package/dist/ListDetail/ListDetail.d.ts +6 -0
  34. package/dist/ListDetail/index.js +1 -1
  35. package/dist/{ListDetail-N3L7PB46.css → ListDetail-JGESKTR7.css} +38 -0
  36. package/dist/OrgLabel/index.js +1 -1
  37. package/dist/OrgNode/index.js +1 -1
  38. package/dist/OrgTable/index.js +1 -1
  39. package/dist/PeopleRail/index.js +1 -1
  40. package/dist/PresenceStack/index.js +1 -1
  41. package/dist/Principal/index.js +1 -1
  42. package/dist/PrincipalPicker/index.js +1 -1
  43. package/dist/ReadingPane/ReadingPane.d.ts +17 -1
  44. package/dist/ReadingPane/index.d.ts +1 -1
  45. package/dist/ReadingPane/index.js +2 -1
  46. package/dist/{ReadingPane-VLYOWMCM.css → ReadingPane-VFTRDPC5.css} +19 -0
  47. package/dist/SectionFrame/index.js +2 -2
  48. package/dist/ShareDialog/index.js +1 -1
  49. package/dist/SheetGrid/index.js +1 -1
  50. package/dist/SidebarNav/SidebarNav.d.ts +18 -2
  51. package/dist/SidebarNav/index.d.ts +1 -1
  52. package/dist/SidebarNav/index.js +1 -1
  53. package/dist/{SidebarNav-E7PZGGA6.css → SidebarNav-IKBKGXW4.css} +20 -0
  54. package/dist/TableCells/index.js +1 -1
  55. package/dist/ThreadSummary/index.js +2 -2
  56. package/dist/VersionTimeline/index.js +1 -1
  57. package/dist/{chunk-Q7N2ATMC.js → chunk-64JNKLLP.js} +57 -9
  58. package/dist/chunk-6C7GQOBV.js +10 -0
  59. package/dist/{chunk-YRNNUCQP.js → chunk-7BDBO2NP.js} +1 -1
  60. package/dist/{chunk-QM3432PH.js → chunk-7IWZN5Q2.js} +3 -3
  61. package/dist/{chunk-25JQNHTC.js → chunk-BRK3HXGZ.js} +8 -2
  62. package/dist/{chunk-QE4OIE3Q.js → chunk-DJIWCW5Z.js} +6 -4
  63. package/dist/{chunk-QPHD4UUG.js → chunk-F6ZUM66S.js} +160 -25
  64. package/dist/{chunk-MVMVMHYI.js → chunk-HDEKHP65.js} +1 -1
  65. package/dist/{chunk-BXGBGYKH.js → chunk-HKUZ2GYX.js} +1 -1
  66. package/dist/{chunk-ZB3E2Z4Y.js → chunk-J5T5TDM2.js} +1 -1
  67. package/dist/{chunk-C46DDVTK.js → chunk-LTD7KLOU.js} +1 -1
  68. package/dist/{chunk-YBC2CYPD.js → chunk-O4VTQMEF.js} +1 -1
  69. package/dist/{chunk-HTBTAIH6.js → chunk-OM2LABPS.js} +2 -2
  70. package/dist/{chunk-PGBKZSSQ.js → chunk-ORONAXSF.js} +10 -7
  71. package/dist/{chunk-YP2FS6SH.js → chunk-QPYGJ7R7.js} +1 -1
  72. package/dist/{chunk-ICE3D63E.js → chunk-QWRAWKB2.js} +50 -9
  73. package/dist/{chunk-SRDNGGZE.js → chunk-RKIJAXQA.js} +1 -1
  74. package/dist/{chunk-CF4M6N6A.js → chunk-XYSSFI5C.js} +1 -7
  75. package/dist/{chunk-IRONPJB4.js → chunk-YTLWOHOY.js} +1 -1
  76. package/dist/generated/index.js +33 -32
  77. package/dist/index.js +33 -32
  78. package/dist/styles.css +306 -99
  79. package/dist/utils/developing.d.ts +10 -0
  80. package/package.json +3 -3
  81. package/dist/ActionCard-47WSZ2O5.css +0 -110
  82. package/dist/{chunk-CV2LU2T2.js → chunk-IYI6HWKN.js} +1 -1
@@ -36,6 +36,7 @@
36
36
  * own prop types, so one list of places feeds both shapes.
37
37
  */
38
38
  import { type MouseEventHandler, type ReactNode } from 'react';
39
+ import type { CountTone } from '../Count/index.js';
39
40
  export interface SidebarNavProps {
40
41
  /**
41
42
  * Names the navigation region. A page with two of them ("Sections" and
@@ -148,6 +149,20 @@ export interface NavFigure {
148
149
  */
149
150
  label: string;
150
151
  }
152
+ /**
153
+ * A figure of what waits on the reader, and how loudly it asks.
154
+ *
155
+ * `attention` (the default) is the accent's filled pill, the one hue in the
156
+ * chrome: what the reader is asked to act on, the unread in an inbox.
157
+ * `quiet` keeps the pill's round shape and its place at the row's end, in
158
+ * the quiet count's grey: work that is the reader's own and is waiting, but
159
+ * not calling (the open tasks assigned to them). The vocabulary is `Count`'s
160
+ * own (`CountTone`), so a figure is quiet or calling in the same words
161
+ * wherever it stands.
162
+ */
163
+ export interface NavBadge extends NavFigure {
164
+ tone?: CountTone | undefined;
165
+ }
151
166
  /** A quiet figure: how many of something the destination holds. */
152
167
  export interface NavCount extends NavFigure {
153
168
  /**
@@ -180,9 +195,10 @@ export interface NavItemProps {
180
195
  * of what is waiting on the reader is the one thing in the rail that asks
181
196
  * them to act, which is what the accent means; it is a FILL carrying a
182
197
  * figure rather than a figure in a hue, so it still reads to somebody who
183
- * cannot separate the hue. Anything else is a `count`.
198
+ * cannot separate the hue. Anything else is a `count`. `tone: 'quiet'`
199
+ * keeps the pill and draws it in the quiet count's grey (`NavBadge`).
184
200
  */
185
- badge?: NavFigure | undefined;
201
+ badge?: NavBadge | undefined;
186
202
  href?: string | undefined;
187
203
  /** Whether this row is the screen the reader is on. */
188
204
  current?: boolean | undefined;
@@ -1,4 +1,4 @@
1
1
  import '../HitArea/HitArea.css';
2
2
  import './SidebarNav.css';
3
3
  export { NavGroup, NavItem, SidebarNav } from './SidebarNav.js';
4
- export type { NavCount, NavFigure, NavGroupAction, NavGroupProps, NavItemProps, NavLinkProps, SidebarNavProps, } from './SidebarNav.js';
4
+ export type { NavBadge, NavCount, NavFigure, NavGroupAction, NavGroupProps, NavItemProps, NavLinkProps, SidebarNavProps, } from './SidebarNav.js';
@@ -1,4 +1,4 @@
1
- export { NavGroup, NavItem, SidebarNav } from '../chunk-25JQNHTC.js';
1
+ export { NavGroup, NavItem, SidebarNav } from '../chunk-BRK3HXGZ.js';
2
2
  import '../chunk-TEV5REGB.js';
3
3
  import '../chunk-HBDA76EZ.js';
4
4
  import '../chunk-2YTOOH3A.js';
@@ -415,6 +415,26 @@
415
415
  line-height: 1;
416
416
  }
417
417
 
418
+ /*
419
+ * A QUIET BADGE keeps the pill, its size and its place at the row's end, and
420
+ * takes the quiet count's grey: a neutral wash (`--color-fill-soft`, the
421
+ * ground of a quiet label at rest) carrying the secondary ink, for work that
422
+ * is the reader's own and waiting, but not calling. The secondary ink rather
423
+ * than the count's tertiary, because on a wash over a hovered row the
424
+ * tertiary measured 4.41:1 in light; it is the ink the compact rail's quiet
425
+ * figure takes, so the two shapes draw the same grey. On the reader's own row
426
+ * its ink lifts to the row's, as the count's does; the suite measures the
427
+ * pair on every ground a row can have.
428
+ */
429
+ .crewlet-nav-item__badge--quiet {
430
+ background: var(--color-fill-soft);
431
+ color: var(--color-text-secondary);
432
+ }
433
+
434
+ .crewlet-nav-item__row[aria-current='page'] .crewlet-nav-item__badge--quiet {
435
+ color: inherit;
436
+ }
437
+
418
438
  /* Beside a count the badge is the second figure, not a second push to the
419
439
  end: two auto margins would split the room between them and float the
420
440
  count into the middle of the row. */
@@ -3,7 +3,7 @@ import '../chunk-QF5UNP4T.js';
3
3
  import '../chunk-LBGKLYBY.js';
4
4
  import '../chunk-EJNJMHQG.js';
5
5
  import '../chunk-RE3FBGQQ.js';
6
- import '../chunk-IRONPJB4.js';
6
+ import '../chunk-YTLWOHOY.js';
7
7
  import '../chunk-XZY6S3UC.js';
8
8
  import '../chunk-CYQBDZ6M.js';
9
9
  import '../chunk-UT6CW3C7.js';
@@ -1,6 +1,6 @@
1
- export { THREAD_SUMMARY_LABELS, ThreadSummary } from '../chunk-YP2FS6SH.js';
1
+ export { THREAD_SUMMARY_LABELS, ThreadSummary } from '../chunk-QPYGJ7R7.js';
2
2
  import '../chunk-LBGKLYBY.js';
3
- import '../chunk-IRONPJB4.js';
3
+ import '../chunk-YTLWOHOY.js';
4
4
  import '../chunk-XZY6S3UC.js';
5
5
  import '../chunk-CYQBDZ6M.js';
6
6
  import '../chunk-ODEQTPFM.js';
@@ -2,7 +2,7 @@ export { VersionTimeline } from '../chunk-L5VU3LZT.js';
2
2
  import '../chunk-HF5L56YQ.js';
3
3
  import '../chunk-EJNJMHQG.js';
4
4
  import '../chunk-RE3FBGQQ.js';
5
- import '../chunk-IRONPJB4.js';
5
+ import '../chunk-YTLWOHOY.js';
6
6
  import '../chunk-XZY6S3UC.js';
7
7
  import '../chunk-4MJN2KN6.js';
8
8
  import '../chunk-DGXZ635L.js';
@@ -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';
@@ -2,14 +2,14 @@ import { Skeleton } from './chunk-I4IUX3JF.js';
2
2
  import { PrincipalAvatar } from './chunk-HF5L56YQ.js';
3
3
  import { RelativeTime } from './chunk-EJNJMHQG.js';
4
4
  import { Card } from './chunk-U2IQNDDR.js';
5
- import { AvatarStack } from './chunk-IRONPJB4.js';
6
- import { ArtifactKindGround } from './chunk-ZB3E2Z4Y.js';
5
+ import { AvatarStack } from './chunk-YTLWOHOY.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';
@@ -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;
@@ -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 = {