@crewlethq/ui 0.11.0 → 0.13.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 (165) hide show
  1. package/README.md +76 -5
  2. package/dist/AppShell/index.js +2 -1
  3. package/dist/Avatar/index.js +1 -1
  4. package/dist/{Avatar-QHYOFID3.css → Avatar-BPJ36QZR.css} +1 -1
  5. package/dist/AvatarPicker/index.js +6 -6
  6. package/dist/Breadcrumb/Breadcrumb.d.ts +68 -0
  7. package/dist/Breadcrumb/index.d.ts +3 -0
  8. package/dist/Breadcrumb/index.js +3 -0
  9. package/dist/Breadcrumb-YMWVM3SV.css +193 -0
  10. package/dist/ChoiceButtons/ChoiceButtons.d.ts +43 -0
  11. package/dist/ChoiceButtons/index.d.ts +3 -0
  12. package/dist/ChoiceButtons/index.js +3 -0
  13. package/dist/ChoiceButtons-DJ5EOGUA.css +127 -0
  14. package/dist/ChoiceList/ChoiceList.d.ts +45 -0
  15. package/dist/ChoiceList/index.d.ts +3 -0
  16. package/dist/ChoiceList/index.js +2 -0
  17. package/dist/ChoiceList-2PQJS4DX.css +109 -0
  18. package/dist/Combobox/index.js +2 -2
  19. package/dist/CommandPalette/CommandPalette.d.ts +48 -1
  20. package/dist/CommandPalette/index.js +3 -3
  21. package/dist/{CommandPalette-C5AMKGN2.css → CommandPalette-HBTNKHZQ.css} +55 -2
  22. package/dist/Composer/Composer.d.ts +27 -0
  23. package/dist/Composer/index.d.ts +3 -0
  24. package/dist/Composer/index.js +7 -0
  25. package/dist/Composer-ABGJIAIR.css +88 -0
  26. package/dist/DataTable/index.js +3 -3
  27. package/dist/DataView/index.js +11 -11
  28. package/dist/DateTimePicker/index.js +2 -2
  29. package/dist/Document/Document.d.ts +56 -0
  30. package/dist/Document/index.d.ts +3 -0
  31. package/dist/Document/index.js +2 -0
  32. package/dist/Document-PQGXJHB7.css +288 -0
  33. package/dist/EntityChip/index.js +1 -1
  34. package/dist/Highlight/Highlight.d.ts +45 -0
  35. package/dist/Highlight/index.d.ts +3 -0
  36. package/dist/Highlight/index.js +2 -0
  37. package/dist/Highlight-DXLV2R6L.css +29 -0
  38. package/dist/HuePicker/index.js +3 -3
  39. package/dist/ImageUpload/index.js +2 -2
  40. package/dist/{ImageUpload-L2GE4JUM.css → ImageUpload-NVGMN7DP.css} +9 -6
  41. package/dist/InboxList/InboxList.d.ts +116 -0
  42. package/dist/InboxList/index.d.ts +3 -0
  43. package/dist/InboxList/index.js +5 -0
  44. package/dist/InboxList-YMFWKGGS.css +252 -0
  45. package/dist/KeyChip/KeyChip.d.ts +21 -0
  46. package/dist/KeyChip/index.d.ts +3 -0
  47. package/dist/KeyChip/index.js +2 -0
  48. package/dist/KeyChip-4XJU4BBE.css +30 -0
  49. package/dist/Link/Link.d.ts +17 -2
  50. package/dist/Link/index.js +1 -1
  51. package/dist/{Link-6BEFNMN2.css → Link-WPMVLKLY.css} +36 -1
  52. package/dist/Listbox/index.d.ts +2 -0
  53. package/dist/Listbox/index.js +1 -1
  54. package/dist/Listbox/useHighlightRest.d.ts +35 -0
  55. package/dist/MarkdownEditor/MarkdownEditor.d.ts +93 -0
  56. package/dist/MarkdownEditor/format.d.ts +34 -0
  57. package/dist/MarkdownEditor/index.d.ts +5 -0
  58. package/dist/MarkdownEditor/index.js +12 -0
  59. package/dist/MarkdownEditor-ZLSHTOIZ.css +146 -0
  60. package/dist/Modal/Modal.d.ts +5 -1
  61. package/dist/Modal/index.js +1 -1
  62. package/dist/{Modal-BNT3TDV2.css → Modal-3YJ2GG2T.css} +33 -0
  63. package/dist/OrgLabel/index.js +1 -1
  64. package/dist/OrgNode/index.js +1 -1
  65. package/dist/OrgTable/index.js +5 -5
  66. package/dist/Outline/Outline.d.ts +72 -0
  67. package/dist/Outline/index.d.ts +3 -0
  68. package/dist/Outline/index.js +2 -0
  69. package/dist/Outline-VCXKKU3E.css +78 -0
  70. package/dist/Popover/Popover.d.ts +21 -1
  71. package/dist/Popover/index.js +1 -1
  72. package/dist/ReadingPane/ReadingPane.d.ts +95 -0
  73. package/dist/ReadingPane/index.d.ts +3 -0
  74. package/dist/ReadingPane/index.js +4 -0
  75. package/dist/ReadingPane-5UOLPWY5.css +194 -0
  76. package/dist/SegmentedControl/SegmentedControl.d.ts +9 -1
  77. package/dist/SegmentedControl/index.js +3 -3
  78. package/dist/{SegmentedControl-76HODRBT.css → SegmentedControl-PKBLJVSA.css} +18 -0
  79. package/dist/Select/Select.d.ts +22 -0
  80. package/dist/Select/index.js +2 -2
  81. package/dist/{Select-4ANVLHUY.css → Select-XMZ44RWY.css} +24 -0
  82. package/dist/SidebarNav/index.js +2 -1
  83. package/dist/{SidebarNav-X5NLZNWA.css → SidebarNav-KF66MLOM.css} +4 -19
  84. package/dist/Switcher/Switcher.d.ts +151 -0
  85. package/dist/Switcher/index.d.ts +3 -0
  86. package/dist/Switcher/index.js +16 -0
  87. package/dist/Switcher-B44Q4WF4.css +346 -0
  88. package/dist/Tabs/Tabs.d.ts +7 -1
  89. package/dist/Tabs/index.js +1 -1
  90. package/dist/{Tabs-5I5Q5KKM.css → Tabs-ITJYJFMU.css} +49 -0
  91. package/dist/TagsInput/index.js +2 -2
  92. package/dist/ThemeGallery/ThemeGallery.d.ts +65 -0
  93. package/dist/ThemeGallery/index.d.ts +3 -0
  94. package/dist/ThemeGallery/index.js +6 -0
  95. package/dist/ThemeGallery-NITASULQ.css +93 -0
  96. package/dist/ThemeSwatch/ThemeSwatch.d.ts +71 -0
  97. package/dist/ThemeSwatch/index.d.ts +3 -0
  98. package/dist/ThemeSwatch/index.js +2 -0
  99. package/dist/ThemeSwatch-4DL457ZB.css +256 -0
  100. package/dist/ThemeSwitcher/ThemeSwitcher.d.ts +5 -1
  101. package/dist/ThemeSwitcher/index.js +4 -4
  102. package/dist/ThemeToggle/ThemeToggle.d.ts +1 -1
  103. package/dist/ThemeToggle/index.js +1 -1
  104. package/dist/TimeWindowPicker/index.js +4 -4
  105. package/dist/Toaster/index.js +1 -1
  106. package/dist/{Toaster-NVDCEIP5.css → Toaster-KZ7YE2HA.css} +1 -0
  107. package/dist/Tree/index.d.ts +1 -1
  108. package/dist/Tree/index.js +1 -1
  109. package/dist/Tree/useTreeState.d.ts +37 -7
  110. package/dist/TreeCanvas/index.js +2 -2
  111. package/dist/TreeGrid/TreeGrid.d.ts +22 -2
  112. package/dist/TreeGrid/index.js +2 -2
  113. package/dist/{TreeGrid-NSW7VMOX.css → TreeGrid-FBX3MSJA.css} +19 -0
  114. package/dist/{chunk-EBBAAQDY.js → chunk-26PEPONN.js} +3 -1
  115. package/dist/{chunk-HV6IZFBK.js → chunk-4FFHJBEN.js} +2 -2
  116. package/dist/{chunk-JHQUT4WJ.js → chunk-4LDTQWS7.js} +1 -1
  117. package/dist/{chunk-ELZKOVEC.js → chunk-4Y7WKXAW.js} +1 -1
  118. package/dist/{chunk-MCPKHYMC.js → chunk-6LXMLQUM.js} +17 -4
  119. package/dist/{chunk-3UK4VFSK.js → chunk-6NIZCQUM.js} +1 -1
  120. package/dist/{chunk-ORNOOMVY.js → chunk-6TSZCBID.js} +2 -2
  121. package/dist/{chunk-ZMJHK7DR.js → chunk-75WOCYGC.js} +2 -2
  122. package/dist/chunk-77IEHFY4.js +299 -0
  123. package/dist/chunk-A5YKNRG6.js +94 -0
  124. package/dist/{chunk-HLBRPES3.js → chunk-B2CITO6R.js} +2 -2
  125. package/dist/{chunk-TEEJSTFG.js → chunk-CF6DTETT.js} +1 -1
  126. package/dist/{chunk-LAQK4V3Z.js → chunk-DJMLHCLD.js} +1 -1
  127. package/dist/{chunk-6MU7HNB4.js → chunk-F73MC7EZ.js} +47 -2
  128. package/dist/{chunk-ZPSOF6VL.js → chunk-FBSEJO2C.js} +1 -1
  129. package/dist/chunk-FYTIJM2T.js +40 -0
  130. package/dist/{chunk-7JGWSOLM.js → chunk-GG2QEZO2.js} +1 -1
  131. package/dist/chunk-H6VKCIWV.js +41 -0
  132. package/dist/{chunk-6SDGRKQ7.js → chunk-HKGS67VG.js} +94 -41
  133. package/dist/chunk-IG75YMUN.js +22 -0
  134. package/dist/{chunk-JCNUQ3US.js → chunk-JDPAA2JU.js} +1 -1
  135. package/dist/chunk-KLIEPFEZ.js +95 -0
  136. package/dist/{chunk-7LD6P32I.js → chunk-L3CTFFLZ.js} +2 -18
  137. package/dist/{chunk-UG5ZMAXW.js → chunk-LELD5KPA.js} +1 -1
  138. package/dist/chunk-MNFU2RNT.js +74 -0
  139. package/dist/{chunk-S3BCVWTU.js → chunk-MY374UUD.js} +49 -31
  140. package/dist/{chunk-K5Q2DK3P.js → chunk-NXDEPUVG.js} +1 -1
  141. package/dist/{chunk-PDUZO35I.js → chunk-O2ZDZZS7.js} +15 -6
  142. package/dist/chunk-QBXZLGK2.js +40 -0
  143. package/dist/chunk-QSLIN64M.js +47 -0
  144. package/dist/{chunk-5WEVLIB6.js → chunk-RZ7RGECB.js} +1 -1
  145. package/dist/chunk-S3IENQ3E.js +336 -0
  146. package/dist/{chunk-WEIINZPA.js → chunk-SBRE7IML.js} +6 -2
  147. package/dist/{chunk-IVFJD7WY.js → chunk-UCCC7YVZ.js} +3 -2
  148. package/dist/{chunk-CIOKUMVC.js → chunk-UZPHPW3Z.js} +7 -7
  149. package/dist/chunk-VWNC2L6W.js +110 -0
  150. package/dist/chunk-VYY56AMX.js +2 -0
  151. package/dist/chunk-WI6K5M5X.js +24 -0
  152. package/dist/{chunk-R4ZJB5YX.js → chunk-WY2NLRZN.js} +2 -2
  153. package/dist/{chunk-SEFMO2EV.js → chunk-XFE5W2I4.js} +44 -5
  154. package/dist/chunk-XYCADWZN.js +100 -0
  155. package/dist/chunk-YSJC7AZC.js +62 -0
  156. package/dist/chunk-ZAJHGDAW.js +93 -0
  157. package/dist/generated/index.d.ts +14 -0
  158. package/dist/generated/index.js +65 -50
  159. package/dist/index.js +65 -50
  160. package/dist/styles.css +4456 -1965
  161. package/dist/utils/highlight.d.ts +17 -0
  162. package/dist/utils/useMediaMatch.d.ts +12 -0
  163. package/package.json +3 -3
  164. package/dist/chunk-UAQIUSSK.js +0 -2
  165. /package/dist/{chunk-PQLMLG5K.js → chunk-6AR2T5Y4.js} +0 -0
@@ -1,14 +1,14 @@
1
+ import { Modal } from './chunk-CF6DTETT.js';
2
+ import { useListbox, useHighlightRest } from './chunk-F73MC7EZ.js';
1
3
  import { Kbd } from './chunk-SMBKXWML.js';
2
4
  import { Input } from './chunk-FTEJPHBI.js';
3
- import { useListbox } from './chunk-6MU7HNB4.js';
4
- import { Modal } from './chunk-TEEJSTFG.js';
5
5
  import { Count } from './chunk-BHHIIK3M.js';
6
6
  import { isComposing } from './chunk-R25RGPUB.js';
7
7
  import { VisuallyHidden } from './chunk-BZFWDIV6.js';
8
8
  import { cx } from './chunk-ODEQTPFM.js';
9
- import './CommandPalette-C5AMKGN2.css';
10
- import { useId, useMemo } from 'react';
11
- import { SearchGlyph } from '@crewlethq/icons/glyphs';
9
+ import './CommandPalette-HBTNKHZQ.css';
10
+ import { useId, useMemo, useRef, useEffect, useState } from 'react';
11
+ import { ChevronLeftGlyph, SearchGlyph } from '@crewlethq/icons/glyphs';
12
12
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
13
13
 
14
14
  function CommandPalette(props) {
@@ -33,6 +33,8 @@ function Palette({
33
33
  onScopeChange,
34
34
  groupsScope,
35
35
  scopesLabel = "Search in",
36
+ step,
37
+ onHighlight,
36
38
  className,
37
39
  ...rest
38
40
  }) {
@@ -48,11 +50,33 @@ function Palette({
48
50
  onCommit: (at) => {
49
51
  const item = flat[at];
50
52
  if (!item) return;
51
- item.onSelect();
52
- onClose();
53
+ if (item.onSelect() !== false) onClose();
53
54
  },
54
55
  onClose
55
56
  });
57
+ const offeredNow = useRef(flat);
58
+ offeredNow.current = flat;
59
+ const highlight = useHighlightRest(
60
+ onHighlight ? (key) => onHighlight(key === null ? null : offeredNow.current.find((item) => item.id === key) ?? null) : void 0
61
+ );
62
+ const byPointer = useRef(false);
63
+ const restingKey = flat[listbox.active]?.id ?? null;
64
+ useEffect(() => {
65
+ const pointer = byPointer.current;
66
+ byPointer.current = false;
67
+ if (restingKey === null) highlight.now(null);
68
+ else if (pointer) highlight.rest(restingKey);
69
+ else highlight.now(restingKey);
70
+ }, [restingKey, highlight]);
71
+ const stepKey = step?.label ?? null;
72
+ const [shownStep, setShownStep] = useState(stepKey);
73
+ const [before, setBefore] = useState(0);
74
+ if (stepKey !== shownStep) {
75
+ setShownStep(stepKey);
76
+ if (shownStep === null) setBefore(listbox.active);
77
+ const start = step?.startAt === void 0 ? -1 : flat.findIndex((item) => item.id === step.startAt);
78
+ listbox.setActive(stepKey === null ? before : Math.max(start, 0));
79
+ }
56
80
  const tabId = (scopeId) => `${id}-scope-${scopeId}`;
57
81
  const panelId = `${id}-panel`;
58
82
  const current = scopes?.find((candidate) => candidate.id === scope);
@@ -62,17 +86,24 @@ function Palette({
62
86
  listbox.setActive(0);
63
87
  }
64
88
  function onKeyDown(event) {
89
+ if (step && !isComposing(event) && !event.altKey && !event.ctrlKey && !event.metaKey) {
90
+ if (event.key === "Escape" || event.key === "Backspace" && query === "") {
91
+ event.preventDefault();
92
+ step.onBack();
93
+ return;
94
+ }
95
+ }
65
96
  if (listbox.onKeyDown(event)) return;
66
97
  if (!scopes || scopes.length === 0 || isComposing(event)) return;
67
98
  if (event.altKey || event.ctrlKey || event.metaKey) return;
68
- let step = 0;
69
- if (event.key === "Tab") step = event.shiftKey ? -1 : 1;
70
- else if (query === "" && !event.shiftKey && event.key === "ArrowRight") step = 1;
71
- else if (query === "" && !event.shiftKey && event.key === "ArrowLeft") step = -1;
72
- if (step === 0) return;
99
+ let by = 0;
100
+ if (event.key === "Tab") by = event.shiftKey ? -1 : 1;
101
+ else if (query === "" && !event.shiftKey && event.key === "ArrowRight") by = 1;
102
+ else if (query === "" && !event.shiftKey && event.key === "ArrowLeft") by = -1;
103
+ if (by === 0) return;
73
104
  event.preventDefault();
74
105
  const at = scopes.findIndex((candidate) => candidate.id === scope);
75
- const next = scopes[(at + step + scopes.length) % scopes.length];
106
+ const next = scopes[(at + by + scopes.length) % scopes.length];
76
107
  if (next) choose(next.id);
77
108
  }
78
109
  const active = listbox.active;
@@ -91,31 +122,49 @@ function Palette({
91
122
  className: cx("crewlet-palette", className),
92
123
  footerStart: footer,
93
124
  children: [
94
- /* @__PURE__ */ jsx(
95
- Input,
96
- {
97
- appearance: "command",
98
- containerClassName: "crewlet-palette__field",
99
- leading: /* @__PURE__ */ jsx(SearchGlyph, { size: "sm" }),
100
- trailing: escapeHint ? /* @__PURE__ */ jsx(Kbd, { subtle: true, "aria-hidden": true, className: "crewlet-palette__escape", children: "Esc" }) : void 0,
101
- value: query,
102
- onChange: (event) => {
103
- onQueryChange(event.target.value);
104
- listbox.setActive(0);
105
- },
106
- onKeyDown,
107
- placeholder,
108
- role: "combobox",
109
- "aria-label": label,
110
- "aria-expanded": true,
111
- "aria-controls": listbox.listId,
112
- "aria-autocomplete": "list",
113
- "aria-activedescendant": active >= 0 ? listbox.optionId(active) : void 0,
114
- "aria-describedby": current ? tabId(current.id) : void 0,
115
- autoComplete: "off",
116
- spellCheck: false
117
- }
118
- ),
125
+ /* @__PURE__ */ jsxs("div", { className: cx("crewlet-palette__bar", step && "is-step"), children: [
126
+ step ? /* @__PURE__ */ jsxs(
127
+ "button",
128
+ {
129
+ type: "button",
130
+ className: "crewlet-palette__step",
131
+ tabIndex: -1,
132
+ onMouseDown: (event) => event.preventDefault(),
133
+ onClick: step.onBack,
134
+ children: [
135
+ /* @__PURE__ */ jsx(ChevronLeftGlyph, { size: "sm" }),
136
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: "Back from" }),
137
+ " ",
138
+ step.label
139
+ ]
140
+ }
141
+ ) : null,
142
+ /* @__PURE__ */ jsx(
143
+ Input,
144
+ {
145
+ appearance: "command",
146
+ containerClassName: "crewlet-palette__field",
147
+ leading: step ? void 0 : /* @__PURE__ */ jsx(SearchGlyph, { size: "sm" }),
148
+ trailing: escapeHint ? /* @__PURE__ */ jsx(Kbd, { subtle: true, "aria-hidden": true, className: "crewlet-palette__escape", children: "Esc" }) : void 0,
149
+ value: query,
150
+ onChange: (event) => {
151
+ onQueryChange(event.target.value);
152
+ listbox.setActive(0);
153
+ },
154
+ onKeyDown,
155
+ placeholder,
156
+ role: "combobox",
157
+ "aria-label": step ? step.label : label,
158
+ "aria-expanded": true,
159
+ "aria-controls": listbox.listId,
160
+ "aria-autocomplete": "list",
161
+ "aria-activedescendant": active >= 0 ? listbox.optionId(active) : void 0,
162
+ "aria-describedby": current ? tabId(current.id) : void 0,
163
+ autoComplete: "off",
164
+ spellCheck: false
165
+ }
166
+ )
167
+ ] }),
119
168
  scopes ? /* @__PURE__ */ jsx("div", { className: "crewlet-palette__scopes", role: "tablist", "aria-label": scopesLabel, children: scopes.map((candidate) => {
120
169
  const selected = candidate.id === scope;
121
170
  return /* @__PURE__ */ jsxs(
@@ -143,6 +192,7 @@ function Palette({
143
192
  {
144
193
  className: "crewlet-palette__results",
145
194
  ref: listbox.listRef,
195
+ onMouseLeave: () => highlight.now(null),
146
196
  ...scopes ? { id: panelId, role: "tabpanel", "aria-labelledby": current ? tabId(current.id) : void 0 } : {},
147
197
  children: [
148
198
  /* @__PURE__ */ jsx("div", { className: "crewlet-palette__lead", "aria-live": "polite", children: lead }),
@@ -187,11 +237,14 @@ function Palette({
187
237
  className: "crewlet-listbox__option crewlet-palette__row",
188
238
  role: "option",
189
239
  "aria-selected": mine === active,
190
- onMouseEnter: listbox.optionHandlers(mine).onMouseEnter,
240
+ onMouseEnter: () => {
241
+ if (mine === active) highlight.rest(item.id);
242
+ else byPointer.current = true;
243
+ listbox.optionHandlers(mine).onMouseEnter();
244
+ },
191
245
  onMouseDown: (event) => event.preventDefault(),
192
246
  onClick: () => {
193
- item.onSelect();
194
- onClose();
247
+ if (item.onSelect() !== false) onClose();
195
248
  },
196
249
  children: [
197
250
  item.icon !== void 0 ? /* @__PURE__ */ jsx("span", { className: "crewlet-palette__icon", "aria-hidden": "true", children: item.icon }) : null,
@@ -0,0 +1,22 @@
1
+ import { useCallback, useSyncExternalStore } from 'react';
2
+
3
+ // src/utils/useMediaMatch.ts
4
+ function useMediaMatch(query) {
5
+ const subscribe = useCallback(
6
+ (notify) => {
7
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => {
8
+ };
9
+ const list = window.matchMedia(query);
10
+ list.addEventListener("change", notify);
11
+ return () => list.removeEventListener("change", notify);
12
+ },
13
+ [query]
14
+ );
15
+ const read = useCallback(
16
+ () => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(query).matches : false,
17
+ [query]
18
+ );
19
+ return useSyncExternalStore(subscribe, read, () => false);
20
+ }
21
+
22
+ export { useMediaMatch };
@@ -1,7 +1,7 @@
1
1
  import { today, CalendarGrid, shiftMonth, compareDays } from './chunk-GXQ3B46I.js';
2
2
  import { Button } from './chunk-OG6CL3EB.js';
3
3
  import { IconButton } from './chunk-4FQT6ZAY.js';
4
- import { Popover } from './chunk-PDUZO35I.js';
4
+ import { Popover } from './chunk-O2ZDZZS7.js';
5
5
  import { cx } from './chunk-ODEQTPFM.js';
6
6
  import './DateTimePicker-4AZBTODT.css';
7
7
  import { useMemo, useState, useEffect } from 'react';
@@ -0,0 +1,95 @@
1
+ import { ThemeSwatch } from './chunk-MNFU2RNT.js';
2
+ import { useHighlightRest } from './chunk-F73MC7EZ.js';
3
+ import { useRoving, tabStop } from './chunk-CBOICT64.js';
4
+ import { cx } from './chunk-ODEQTPFM.js';
5
+ import './ThemeGallery-NITASULQ.css';
6
+ import { useId } from 'react';
7
+ import { themeGroups, themeRegistry } from '@crewlethq/tokens';
8
+ import { jsx, jsxs } from 'react/jsx-runtime';
9
+
10
+ function ThemeGallery({
11
+ label = "Theme",
12
+ value,
13
+ onValueChange,
14
+ onPreview,
15
+ themes = themeRegistry,
16
+ groups = themeGroups,
17
+ columns = 3,
18
+ disabled = false,
19
+ className,
20
+ style,
21
+ ...rest
22
+ }) {
23
+ const id = useId();
24
+ const sections = [
25
+ ...groups.map((group) => ({ group, items: themes.filter((theme) => theme.group === group.id) })),
26
+ { group: null, items: themes.filter((theme) => !groups.some((group) => group.id === theme.group)) }
27
+ ].filter((section) => section.items.length > 0);
28
+ const flat = sections.flatMap((section) => section.items);
29
+ const { buttons, onKeyDown, onFocusAt } = useRoving(
30
+ flat.length,
31
+ disabled ? null : (index2) => onValueChange(flat[index2].id)
32
+ );
33
+ const stop = tabStop(
34
+ flat.map(() => ({ disabled })),
35
+ flat.findIndex((theme) => theme.id === value)
36
+ );
37
+ const preview = useHighlightRest(onPreview ? (key) => onPreview(key) : void 0);
38
+ let index = -1;
39
+ return /* @__PURE__ */ jsx(
40
+ "div",
41
+ {
42
+ ...rest,
43
+ className: cx("crewlet-theme-gallery", className),
44
+ style: { ...style, "--crewlet-theme-gallery-columns": columns },
45
+ onMouseLeave: () => preview.now(null),
46
+ children: /* @__PURE__ */ jsx(
47
+ "div",
48
+ {
49
+ className: "crewlet-theme-gallery__groups",
50
+ role: "radiogroup",
51
+ "aria-label": label,
52
+ "aria-disabled": disabled || void 0,
53
+ children: sections.map((section, position) => {
54
+ const headingId = `${id}-group-${position}`;
55
+ return /* @__PURE__ */ jsxs("div", { className: "crewlet-theme-gallery__group", children: [
56
+ section.group ? /* @__PURE__ */ jsx("span", { id: headingId, className: "crewlet-theme-gallery__heading", children: section.group.name }) : null,
57
+ /* @__PURE__ */ jsx("div", { className: "crewlet-theme-gallery__cards", children: section.items.map((theme) => {
58
+ index += 1;
59
+ const at = index;
60
+ const chosen = theme.id === value;
61
+ return /* @__PURE__ */ jsxs(
62
+ "button",
63
+ {
64
+ ref: (el) => {
65
+ buttons.current[at] = el;
66
+ },
67
+ type: "button",
68
+ role: "radio",
69
+ "aria-checked": chosen,
70
+ "aria-label": `${theme.name}, ${theme.tag}`,
71
+ "aria-describedby": section.group ? headingId : void 0,
72
+ tabIndex: at === stop ? 0 : -1,
73
+ disabled,
74
+ className: cx("crewlet-theme-gallery__card", chosen && "is-chosen"),
75
+ onClick: () => onValueChange(theme.id),
76
+ onFocus: () => onFocusAt(at),
77
+ onKeyDown: (event) => onKeyDown(event, true),
78
+ onMouseEnter: () => preview.rest(theme.id),
79
+ children: [
80
+ /* @__PURE__ */ jsx(ThemeSwatch, { swatch: theme.swatch, size: "card" }),
81
+ /* @__PURE__ */ jsx("span", { className: "crewlet-theme-gallery__name", children: theme.name })
82
+ ]
83
+ },
84
+ theme.id
85
+ );
86
+ }) })
87
+ ] }, section.group?.id ?? "-");
88
+ })
89
+ }
90
+ )
91
+ }
92
+ );
93
+ }
94
+
95
+ export { ThemeGallery };
@@ -1,8 +1,9 @@
1
+ import { useMediaMatch } from './chunk-IG75YMUN.js';
1
2
  import { IconButton } from './chunk-4FQT6ZAY.js';
2
3
  import { useModalLayer, focusables } from './chunk-R25RGPUB.js';
3
4
  import { cx } from './chunk-ODEQTPFM.js';
4
5
  import './AppShell-CEVLW4QG.css';
5
- import { createContext, useContext, useId, useState, useRef, useCallback, useEffect, useLayoutEffect, useSyncExternalStore } from 'react';
6
+ import { createContext, useContext, useId, useState, useRef, useCallback, useEffect, useLayoutEffect } from 'react';
6
7
  import { breakpoint } from '@crewlethq/tokens';
7
8
  import { MenuGlyph } from '@crewlethq/icons/glyphs';
8
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -14,23 +15,6 @@ function useAppShell() {
14
15
  if (!shell) throw new Error("useAppShell must be called inside an <AppShell>.");
15
16
  return shell;
16
17
  }
17
- function useMediaMatch(query) {
18
- const subscribe = useCallback(
19
- (notify) => {
20
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => {
21
- };
22
- const list = window.matchMedia(query);
23
- list.addEventListener("change", notify);
24
- return () => list.removeEventListener("change", notify);
25
- },
26
- [query]
27
- );
28
- const read = useCallback(
29
- () => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(query).matches : false,
30
- [query]
31
- );
32
- return useSyncExternalStore(subscribe, read, () => false);
33
- }
34
18
  function AppShell({
35
19
  sidebar,
36
20
  topbar,
@@ -1,7 +1,7 @@
1
1
  import { useLayerContainer } from './chunk-7RPGY3JE.js';
2
2
  import { VisuallyHidden } from './chunk-BZFWDIV6.js';
3
3
  import { cx } from './chunk-ODEQTPFM.js';
4
- import './Toaster-NVDCEIP5.css';
4
+ import './Toaster-KZ7YE2HA.css';
5
5
  import { createContext, useMemo, useState, useRef, useEffect, useContext, useCallback } from 'react';
6
6
  import { createPortal } from 'react-dom';
7
7
  import { XGlyph, CircleAlertGlyph, TriangleAlertGlyph, CircleCheckGlyph, InfoGlyph } from '@crewlethq/icons/glyphs';
@@ -0,0 +1,74 @@
1
+ import { cx } from './chunk-ODEQTPFM.js';
2
+ import './ThemeSwatch-4DL457ZB.css';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+
5
+ var PROPERTY = {
6
+ frame: "--crewlet-swatch-frame",
7
+ sheet: "--crewlet-swatch-sheet",
8
+ card: "--crewlet-swatch-card",
9
+ raised: "--crewlet-swatch-raised",
10
+ hairline: "--crewlet-swatch-hairline",
11
+ text: "--crewlet-swatch-text",
12
+ muted: "--crewlet-swatch-muted",
13
+ accent: "--crewlet-swatch-accent",
14
+ onAccent: "--crewlet-swatch-on-accent",
15
+ info: "--crewlet-swatch-info",
16
+ warning: "--crewlet-swatch-warning",
17
+ danger: "--crewlet-swatch-danger",
18
+ success: "--crewlet-swatch-success"
19
+ };
20
+ var STATES = ["info", "warning", "danger", "success"];
21
+ function paint(colours) {
22
+ const style = {};
23
+ for (const key of Object.keys(PROPERTY)) style[PROPERTY[key]] = colours[key];
24
+ return style;
25
+ }
26
+ function Half({ colours, size }) {
27
+ if (size === "chip") {
28
+ return /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__half", style: paint(colours), children: /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__dot" }) });
29
+ }
30
+ const pane = size === "pane";
31
+ return /* @__PURE__ */ jsxs("span", { className: "crewlet-theme-swatch__half", style: paint(colours), children: [
32
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-theme-swatch__rail", children: [
33
+ /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__rail-row is-current" }),
34
+ /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__rail-row" }),
35
+ /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__rail-row" }),
36
+ pane ? /* @__PURE__ */ jsxs(Fragment, { children: [
37
+ /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__rail-row" }),
38
+ /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__rail-row" })
39
+ ] }) : null
40
+ ] }),
41
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-theme-swatch__sheet", children: [
42
+ pane ? /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__title" }) : null,
43
+ /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__card", children: pane ? STATES.map((state) => /* @__PURE__ */ jsxs("span", { className: "crewlet-theme-swatch__item", children: [
44
+ /* @__PURE__ */ jsx("span", { className: `crewlet-theme-swatch__state crewlet-theme-swatch__state--${state}` }),
45
+ /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__line" }),
46
+ /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__muted" })
47
+ ] }, state)) : /* @__PURE__ */ jsxs(Fragment, { children: [
48
+ /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__line" }),
49
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-theme-swatch__row", children: [
50
+ /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__action" }),
51
+ STATES.map((state) => /* @__PURE__ */ jsx("span", { className: `crewlet-theme-swatch__state crewlet-theme-swatch__state--${state}` }, state))
52
+ ] })
53
+ ] }) }),
54
+ pane ? /* @__PURE__ */ jsxs("span", { className: "crewlet-theme-swatch__actions", children: [
55
+ /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__action", children: /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__label" }) }),
56
+ /* @__PURE__ */ jsx("span", { className: "crewlet-theme-swatch__secondary" })
57
+ ] }) : null
58
+ ] })
59
+ ] });
60
+ }
61
+ function ThemeSwatch({ swatch, size = "card", mode, label, className, ...rest }) {
62
+ const halves = mode ? [swatch[mode]] : [swatch.dark, swatch.light];
63
+ return /* @__PURE__ */ jsx(
64
+ "span",
65
+ {
66
+ ...rest,
67
+ className: cx("crewlet-theme-swatch", `crewlet-theme-swatch--${size}`, className),
68
+ ...label ? { role: "img", "aria-label": label } : { "aria-hidden": true },
69
+ children: halves.map((colours, index) => /* @__PURE__ */ jsx(Half, { colours, size }, index))
70
+ }
71
+ );
72
+ }
73
+
74
+ export { ThemeSwatch };
@@ -1,4 +1,4 @@
1
- import { useMemo, useState, useCallback, useRef, useEffect, useLayoutEffect } from 'react';
1
+ import { useMemo, useState, useRef, useCallback, useEffect, useLayoutEffect } from 'react';
2
2
  import { flushSync } from 'react-dom';
3
3
 
4
4
  // src/Tree/model.ts
@@ -506,14 +506,18 @@ function layoutConnectors(layout, shape = "elbow") {
506
506
  }
507
507
  return out;
508
508
  }
509
- function useTreeState(forest, selected) {
509
+ function useTreeState(forest, selected, { defaultExpanded = "all", expanded: held, onExpandedChange } = {}) {
510
510
  const model = useMemo(() => createTreeModel(forest), [forest]);
511
- const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
511
+ const [toggled, setToggled] = useState(() => /* @__PURE__ */ new Set());
512
512
  const expanded = useMemo(() => {
513
- const out = treeAllExpandable(model);
514
- for (const id of collapsed) out.delete(id);
515
- return out;
516
- }, [model, collapsed]);
513
+ const expandable = treeAllExpandable(model);
514
+ if (held) return new Set([...held].filter((id) => expandable.has(id)));
515
+ if (defaultExpanded === "all") {
516
+ for (const id of toggled) expandable.delete(id);
517
+ return expandable;
518
+ }
519
+ return new Set([...toggled].filter((id) => expandable.has(id)));
520
+ }, [model, toggled, held, defaultExpanded]);
517
521
  const rows = useMemo(() => treeVisible(model, expanded), [model, expanded]);
518
522
  const [wanted, setActive] = useState(null);
519
523
  const active = useMemo(() => {
@@ -525,34 +529,48 @@ function useTreeState(forest, selected) {
525
529
  }
526
530
  return treeFirst(model);
527
531
  }, [wanted, selected, model, rows]);
528
- const toggle = useCallback((id) => {
529
- setCollapsed((was) => {
530
- const next = new Set(was);
531
- if (next.has(id)) next.delete(id);
532
- else next.add(id);
533
- return next;
532
+ const current = useRef({ model, expanded, held, defaultExpanded, onExpandedChange });
533
+ current.current = { model, expanded, held, defaultExpanded, onExpandedChange };
534
+ const commit = useCallback((next) => {
535
+ const now = current.current;
536
+ now.onExpandedChange?.(next);
537
+ if (now.held) return;
538
+ const all = treeAllExpandable(now.model);
539
+ setToggled((was) => {
540
+ const kept = new Set(was);
541
+ for (const id of all) {
542
+ const differs = now.defaultExpanded === "all" ? !next.has(id) : next.has(id);
543
+ if (differs) kept.add(id);
544
+ else kept.delete(id);
545
+ }
546
+ return kept;
534
547
  });
535
548
  }, []);
536
- const current = useRef({ model, collapsed });
537
- current.current = { model, collapsed };
538
- const expandAll = useCallback(() => setCollapsed(/* @__PURE__ */ new Set()), []);
549
+ const toggle = useCallback(
550
+ (id) => {
551
+ const next = new Set(current.current.expanded);
552
+ if (next.has(id)) next.delete(id);
553
+ else next.add(id);
554
+ commit(next);
555
+ },
556
+ [commit]
557
+ );
558
+ const expandAll = useCallback(() => commit(treeAllExpandable(current.current.model)), [commit]);
539
559
  const collapseAll = useCallback(() => {
540
560
  const { model: shown } = current.current;
541
- const next = treeAllExpandable(shown);
542
- shown.roots.forEach((root) => next.delete(root));
543
- setCollapsed(next);
544
- }, []);
545
- const open2 = useCallback((id) => {
546
- const { model: shown, collapsed: closed } = current.current;
547
- const hidden = treeAncestors(shown, id).filter((ancestor) => closed.has(ancestor));
548
- if (hidden.length === 0) return false;
549
- setCollapsed((was) => {
550
- const next = new Set(was);
551
- hidden.forEach((ancestor) => next.delete(ancestor));
552
- return next;
553
- });
554
- return true;
555
- }, []);
561
+ const tops = new Set(shown.roots.filter((root) => treeAllExpandable(shown).has(root)));
562
+ commit(tops);
563
+ }, [commit]);
564
+ const open2 = useCallback(
565
+ (id) => {
566
+ const { model: shown, expanded: now } = current.current;
567
+ const hidden = treeAncestors(shown, id).filter((ancestor) => !now.has(ancestor));
568
+ if (hidden.length === 0) return false;
569
+ commit(/* @__PURE__ */ new Set([...now, ...hidden]));
570
+ return true;
571
+ },
572
+ [commit]
573
+ );
556
574
  const buffer = useRef({ text: "", at: 0 });
557
575
  const typed = (event, from, now) => {
558
576
  if (!isTypeAheadKey(event, buffer.current, now)) return void 0;
@@ -1,5 +1,5 @@
1
1
  import { NODE_HUES, NODE_HUE_NAMES } from './chunk-AYKXMIOR.js';
2
- import { SegmentedControl } from './chunk-EBBAAQDY.js';
2
+ import { SegmentedControl } from './chunk-26PEPONN.js';
3
3
  import { cx } from './chunk-ODEQTPFM.js';
4
4
  import { jsx } from 'react/jsx-runtime';
5
5
 
@@ -12,6 +12,9 @@ function Popover({
12
12
  align = "start",
13
13
  side = "bottom",
14
14
  width = "auto",
15
+ alignOffset,
16
+ sideOffset = LAYER_GAP,
17
+ initialFocus,
15
18
  role = "dialog",
16
19
  label,
17
20
  className,
@@ -29,8 +32,12 @@ function Popover({
29
32
  const container = useLayerContainer();
30
33
  const isControlled = useRef(controlled !== void 0);
31
34
  const report = useRef(onOpenChange);
35
+ const shift = useRef(alignOffset);
36
+ const start = useRef(initialFocus);
32
37
  isControlled.current = controlled !== void 0;
33
38
  report.current = onOpenChange;
39
+ shift.current = alignOffset;
40
+ start.current = initialFocus;
34
41
  const setOpen = useCallback((next, reason) => {
35
42
  if (!isControlled.current) setUncontrolled(next);
36
43
  report.current?.(next, reason);
@@ -52,8 +59,10 @@ function Popover({
52
59
  const rect = at.getBoundingClientRect();
53
60
  box.style.width = width === "match" ? `${rect.width}px` : "";
54
61
  const own = box.getBoundingClientRect();
62
+ const asked = shift.current;
63
+ const offset = typeof asked === "function" ? asked(box, at) : asked ?? 0;
55
64
  const local = {
56
- x: (align === "end" ? rect.right - own.width : rect.left) - (inHost ? host.x : 0),
65
+ x: (align === "end" ? rect.right - own.width : rect.left) + offset - (inHost ? host.x : 0),
57
66
  y: rect.top - (inHost ? host.y : 0),
58
67
  width: rect.width,
59
68
  height: rect.height
@@ -69,18 +78,18 @@ function Popover({
69
78
  close("anchor-gone");
70
79
  return;
71
80
  }
72
- const asked = width === "match" ? rect.width : void 0;
81
+ const matched = width === "match" ? rect.width : void 0;
73
82
  const spot = placePopup(
74
83
  local,
75
84
  { width: own.width, height: own.height },
76
85
  bounds,
77
- LAYER_GAP,
86
+ sideOffset,
78
87
  side === "top" ? "above" : "below"
79
88
  );
80
89
  setPlace(
81
- (was) => was && was.left === spot.left && was.top === spot.top && was.side === spot.side && was.width === asked ? was : { left: spot.left, top: spot.top, side: spot.side, width: asked }
90
+ (was) => was && was.left === spot.left && was.top === spot.top && was.side === spot.side && was.width === matched ? was : { left: spot.left, top: spot.top, side: spot.side, width: matched }
82
91
  );
83
- }, [align, close, container, side, width]);
92
+ }, [align, close, container, side, sideOffset, width]);
84
93
  useLayoutEffect(() => {
85
94
  if (!open) return;
86
95
  position();
@@ -104,7 +113,7 @@ function Popover({
104
113
  useEffect(() => {
105
114
  if (!placed || role !== "dialog") return;
106
115
  const box = panel.current;
107
- if (box && !box.contains(document.activeElement)) (focusables(box)[0] ?? box).focus();
116
+ if (box && !box.contains(document.activeElement)) (start.current?.() ?? focusables(box)[0] ?? box).focus();
108
117
  }, [placed, role]);
109
118
  const toggle = useCallback(() => {
110
119
  if (open) close("trigger");