@backmatter/studio 0.0.8 → 0.0.9

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 (65) hide show
  1. package/dist/chunks/UiKitApp-b8qFsmA-.js +2401 -0
  2. package/dist/chunks/WorkspaceRoutes-BKKEkAq7.js +90126 -0
  3. package/dist/chunks/{dashboard-bNp6AbKE.js → dashboard-BNUcbYU0.js} +380 -382
  4. package/dist/chunks/{index-DoDE-hwB.js → index-laWAz5_p.js} +111 -112
  5. package/dist/dashboard.js +2 -2
  6. package/dist/index.js +287 -146
  7. package/dist/site-widget.css +1615 -0
  8. package/dist/site-widget.js +30896 -630
  9. package/dist/studio.css +268 -67
  10. package/package.json +4 -4
  11. package/src/App.tsx +12 -29
  12. package/src/api.ts +55 -0
  13. package/src/chat/ChangeCard.tsx +2 -0
  14. package/src/chat/ChatMessage.tsx +73 -62
  15. package/src/chat/ChatPanel.tsx +110 -15
  16. package/src/chat/DocListCard.tsx +2 -0
  17. package/src/chat/LiveStatus.tsx +78 -8
  18. package/src/chat/Reveal.tsx +34 -0
  19. package/src/chat/Shimmer.tsx +65 -23
  20. package/src/chat/chat-helpers.ts +35 -0
  21. package/src/chat/motion.ts +73 -0
  22. package/src/chat/useStickToBottom.ts +129 -14
  23. package/src/chrome/ContentExplorerSidebar.tsx +23 -8
  24. package/src/chrome/content-tree-visibility.ts +55 -0
  25. package/src/devbar-state.ts +125 -0
  26. package/src/index.css +3 -131
  27. package/src/pages/CliAuthorizePage.tsx +61 -28
  28. package/src/pages/MediaGalleryPage.tsx +74 -11
  29. package/src/pages/MediaLightbox.tsx +93 -1
  30. package/src/pages/SettingsUsagePage.tsx +23 -1
  31. package/src/pages/media-grid.ts +13 -0
  32. package/src/pages/ui-kit/UiKitApp.tsx +164 -0
  33. package/src/pages/ui-kit/buttons.tsx +30 -0
  34. package/src/pages/ui-kit/catalog.tsx +41 -0
  35. package/src/pages/ui-kit/colors.tsx +95 -0
  36. package/src/pages/ui-kit/devbar.tsx +160 -0
  37. package/src/pages/ui-kit/feedback.tsx +39 -0
  38. package/src/pages/ui-kit/fields.tsx +47 -0
  39. package/src/pages/ui-kit/lists.tsx +19 -0
  40. package/src/pages/ui-kit/overlays.tsx +39 -0
  41. package/src/pages/ui-kit/path.ts +9 -0
  42. package/src/pages/ui-kit/sections.tsx +32 -0
  43. package/src/pages/ui-kit/surfaces.tsx +121 -0
  44. package/src/pages/ui-kit/topic.tsx +112 -0
  45. package/src/pages/ui-kit/topics.ts +71 -0
  46. package/src/pages/ui-kit/type.tsx +55 -0
  47. package/src/preview-frame-radius.ts +51 -0
  48. package/src/preview-frame.tsx +93 -49
  49. package/src/shell-resets.css +155 -0
  50. package/src/site-bar.tsx +1791 -425
  51. package/src/site-dashboard-href.ts +21 -0
  52. package/src/site-devbar-overlay-inject.ts +15 -0
  53. package/src/site-devbar-overlay.css +217 -0
  54. package/src/site-pointer.ts +1 -1
  55. package/src/site-publish-menu.tsx +33 -0
  56. package/src/site-widget-styles.ts +7 -0
  57. package/src/site-widget.tsx +61 -21
  58. package/src/studio-prose-ui.css +39 -0
  59. package/src/theme/ThemeProvider.tsx +4 -1
  60. package/src/workspace-path.ts +10 -6
  61. package/dist/chunks/EmbedPublishPage-CtIlaeGO.js +0 -149
  62. package/dist/chunks/UiKitPage-DcnSQ-Hf.js +0 -173
  63. package/dist/chunks/WorkspaceRoutes-DVpUOaMV.js +0 -59550
  64. package/dist/chunks/status-bar-action-C0fsAWvD.js +0 -30618
  65. package/src/pages/UiKitPage.tsx +0 -331
@@ -6,7 +6,10 @@ import {
6
6
  } from "@central-icons-react/round-outlined-radius-2-stroke-1.5";
7
7
  import { useEffect, useState } from "react";
8
8
  import * as stylex from "@stylexjs/stylex";
9
+ import { MAX_MEDIA_TEXT } from "@backmatter/core";
9
10
  import { colors, radius, space, type, z } from "../styles/tokens.stylex";
11
+ import { Field } from "../ui/Field";
12
+ import { Input } from "../ui/Input";
10
13
  import { Text } from "../ui/Text";
11
14
  import { cx, popup, popupClassName } from "../ui/popup";
12
15
  import type { CatalogMedia } from "./media-grid";
@@ -22,6 +25,8 @@ export function MediaLightbox({
22
25
  onPrev,
23
26
  onNext,
24
27
  onDelete,
28
+ canEdit = false,
29
+ onDescribe,
25
30
  }: {
26
31
  item: CatalogMedia;
27
32
  index: number;
@@ -30,6 +35,8 @@ export function MediaLightbox({
30
35
  onPrev: () => void;
31
36
  onNext: () => void;
32
37
  onDelete?: () => void;
38
+ canEdit?: boolean;
39
+ onDescribe?: (text: { alt?: string; caption?: string }) => Promise<void>;
33
40
  }) {
34
41
  const full = item.variants?.["1200"] ?? item.url;
35
42
  const [copied, setCopied] = useState(false);
@@ -41,6 +48,10 @@ export function MediaLightbox({
41
48
  useEffect(() => {
42
49
  const onKey = (event: KeyboardEvent) => {
43
50
  if (document.querySelector('[role="menu"], [role="alertdialog"]')) return;
51
+ const typing =
52
+ event.target instanceof HTMLElement &&
53
+ event.target.closest("input, textarea, select");
54
+ if (typing && event.key !== "Escape") return;
44
55
  if (event.key === "Escape" || event.key === " ") {
45
56
  event.preventDefault();
46
57
  onClose();
@@ -79,7 +90,12 @@ export function MediaLightbox({
79
90
  ‹
80
91
  </button>
81
92
  <div {...stylex.props(styles.frame)} onClick={(event) => event.stopPropagation()}>
82
- <StageImage key={item.id} thumb={item.thumbUrl} full={full} alt={item.filename} />
93
+ <StageImage
94
+ key={item.id}
95
+ thumb={item.thumbUrl}
96
+ full={full}
97
+ alt={item.alt?.trim() || item.filename}
98
+ />
83
99
  </div>
84
100
  <button
85
101
  type="button"
@@ -162,6 +178,12 @@ export function MediaLightbox({
162
178
  <Fact label="Type" value={kindLabel(item.contentType)} />
163
179
  <Fact label="Added" value={addedLabel(item.createdAt)} />
164
180
  </dl>
181
+ <MediaText
182
+ key={item.id}
183
+ item={item}
184
+ canEdit={canEdit}
185
+ onDescribe={onDescribe}
186
+ />
165
187
  <div {...stylex.props(styles.urlBlock)}>
166
188
  <div {...stylex.props(styles.urlHead)}>
167
189
  <Text size="xs" tone="muted">
@@ -219,6 +241,71 @@ function StageImage({ thumb, full, alt }: { thumb: string; full: string; alt: st
219
241
  );
220
242
  }
221
243
 
244
+ function MediaText({
245
+ item,
246
+ canEdit,
247
+ onDescribe,
248
+ }: {
249
+ item: CatalogMedia;
250
+ canEdit: boolean;
251
+ onDescribe?: (text: { alt?: string; caption?: string }) => Promise<void>;
252
+ }) {
253
+ const [alt, setAlt] = useState(item.alt ?? "");
254
+ const [caption, setCaption] = useState(item.caption ?? "");
255
+ const [error, setError] = useState<string | null>(null);
256
+
257
+ async function commit(field: "alt" | "caption", draft: string) {
258
+ const next = draft.trim();
259
+ const current = (item[field] ?? "").trim();
260
+ if (next === current || !onDescribe) return;
261
+ setError(null);
262
+ try {
263
+ await onDescribe({ [field]: next });
264
+ } catch (err) {
265
+ const code = err instanceof Error ? err.message : "";
266
+ setError(
267
+ code === "too_long" ? `Use ${MAX_MEDIA_TEXT} characters or fewer.` : code || "Could not save",
268
+ );
269
+ }
270
+ }
271
+
272
+ if (!canEdit) {
273
+ if (!item.alt?.trim() && !item.caption?.trim()) return null;
274
+ return (
275
+ <dl {...stylex.props(styles.facts)}>
276
+ {item.alt?.trim() ? <Fact label="Alt text" value={item.alt} /> : null}
277
+ {item.caption?.trim() ? <Fact label="Caption" value={item.caption} /> : null}
278
+ </dl>
279
+ );
280
+ }
281
+
282
+ return (
283
+ <div {...stylex.props(styles.textFields)}>
284
+ <Field label="Alt text">
285
+ <Input
286
+ value={alt}
287
+ maxLength={MAX_MEDIA_TEXT}
288
+ onChange={(event) => setAlt(event.target.value)}
289
+ onBlur={() => {
290
+ void commit("alt", alt);
291
+ }}
292
+ />
293
+ </Field>
294
+ <Field label="Caption">
295
+ <Input
296
+ value={caption}
297
+ maxLength={MAX_MEDIA_TEXT}
298
+ onChange={(event) => setCaption(event.target.value)}
299
+ onBlur={() => {
300
+ void commit("caption", caption);
301
+ }}
302
+ />
303
+ </Field>
304
+ {error ? <Text tone="danger">{error}</Text> : null}
305
+ </div>
306
+ );
307
+ }
308
+
222
309
  function Fact({ label, value }: { label: string; value: string }) {
223
310
  return (
224
311
  <div {...stylex.props(styles.fact)}>
@@ -405,6 +492,11 @@ const styles = stylex.create({
405
492
  gap: space.lg,
406
493
  margin: 0,
407
494
  },
495
+ textFields: {
496
+ display: "flex",
497
+ flexDirection: "column",
498
+ gap: space.lg,
499
+ },
408
500
  fact: {
409
501
  display: "flex",
410
502
  flexDirection: "column",
@@ -16,13 +16,18 @@ const SURFACES: { key: keyof WorkspaceUsage["dataRequests"]; label: string }[] =
16
16
  { key: "ai", label: "Assistant" },
17
17
  ];
18
18
 
19
+ const IMAGES: { key: "transformations" | "cachedServes" | "uncachedServes"; label: string }[] = [
20
+ { key: "transformations", label: "Transformations" },
21
+ { key: "cachedServes", label: "Cached serves" },
22
+ { key: "uncachedServes", label: "Uncached serves" },
23
+ ];
24
+
19
25
  const WORK: { key: keyof WorkspaceUsage["work"]; label: string }[] = [
20
26
  { key: "commits", label: "Commits" },
21
27
  { key: "commitFiles", label: "Files committed" },
22
28
  { key: "imports", label: "Imports" },
23
29
  { key: "importFiles", label: "Files imported" },
24
30
  { key: "saves", label: "Saves" },
25
- { key: "mediaTransforms", label: "Image transforms" },
26
31
  ];
27
32
 
28
33
  function formatBytes(value: number): string {
@@ -128,6 +133,23 @@ export function SettingsUsagePage() {
128
133
  </div>
129
134
  </dl>
130
135
  </Panel>
136
+ <Panel>
137
+ <Text as="h2" size="lg" weight="medium">
138
+ Images
139
+ </Text>
140
+ <dl {...stylex.props(styles.list)}>
141
+ {IMAGES.map((item) => (
142
+ <div key={item.key} {...stylex.props(styles.row)}>
143
+ <dt {...stylex.props(styles.label)}>{item.label}</dt>
144
+ <dd {...stylex.props(styles.value)}>{formatCount(usage.images[item.key])}</dd>
145
+ </div>
146
+ ))}
147
+ <div {...stylex.props(styles.row)}>
148
+ <dt {...stylex.props(styles.label)}>Transfer</dt>
149
+ <dd {...stylex.props(styles.value)}>{formatBytes(usage.images.bytes)}</dd>
150
+ </div>
151
+ </dl>
152
+ </Panel>
131
153
  <Panel>
132
154
  <Text as="h2" size="lg" weight="medium">
133
155
  AI
@@ -8,6 +8,8 @@ export type CatalogMedia = {
8
8
  thumbUrl: string;
9
9
  width: number | null;
10
10
  height: number | null;
11
+ alt?: string;
12
+ caption?: string;
11
13
  contentType?: string;
12
14
  byteSize?: number;
13
15
  createdAt?: string;
@@ -128,6 +130,17 @@ export function receivePage(
128
130
  };
129
131
  }
130
132
 
133
+ /** The saved alt text and caption replace that image wherever the grid is holding it. */
134
+ export function describeMedia(state: MediaGridState, media: CatalogMedia): MediaGridState {
135
+ return {
136
+ ...state,
137
+ pending: state.pending.map((item) =>
138
+ item.media?.id === media.id ? { ...item, media } : item,
139
+ ),
140
+ loaded: state.loaded.map((item) => (item.id === media.id ? media : item)),
141
+ };
142
+ }
143
+
131
144
  /** Drop a hidden row from the grid. A just-finished upload leaves its cell too. */
132
145
  export function removeMedia(state: MediaGridState, id: string): MediaGridState {
133
146
  const pending = state.pending.filter((item) => item.media?.id !== id);
@@ -0,0 +1,164 @@
1
+ // packages/studio/src/pages/ui-kit/UiKitApp.tsx
2
+ import { Navigate, NavLink, useLocation } from "react-router";
3
+ import * as stylex from "@stylexjs/stylex";
4
+ import { colors, radius, space, type } from "../../styles/tokens.stylex";
5
+ import { useTheme, type ThemePreference } from "../../theme/ThemeProvider";
6
+ import { Button } from "../../ui/Button";
7
+ import { Text } from "../../ui/Text";
8
+ import { UI_KIT_CATALOG } from "./catalog";
9
+ import { resolveUiKitTopic } from "./topics";
10
+
11
+ const THEME_OPTIONS: { value: ThemePreference; label: string }[] = [
12
+ { value: "system", label: "System" },
13
+ { value: "light", label: "Light" },
14
+ { value: "dark", label: "Dark" },
15
+ ];
16
+
17
+ const GROUPS = ["Foundations", "Components", "Host"] as const;
18
+
19
+ export function UiKitApp() {
20
+ const { pathname } = useLocation();
21
+ const path = pathname.replace(/\/$/, "") || "/";
22
+ const raw = path === "/__ui" ? undefined : path.slice("/__ui/".length);
23
+ const slug = resolveUiKitTopic(raw);
24
+ const { preference, setPreference } = useTheme();
25
+
26
+ if (path !== `/__ui/${slug}`) {
27
+ return <Navigate to={`/__ui/${slug}`} replace />;
28
+ }
29
+
30
+ const topic = UI_KIT_CATALOG.find((item) => item.slug === slug)!;
31
+ const Page = topic.Page;
32
+
33
+ return (
34
+ <div {...stylex.props(styles.frame)}>
35
+ <header {...stylex.props(styles.header)}>
36
+ <Text as="p" size="lg" weight="semibold">
37
+ UI kit
38
+ </Text>
39
+ <div {...stylex.props(styles.themes)} role="group" aria-label="Theme">
40
+ {THEME_OPTIONS.map((option) => (
41
+ <Button
42
+ key={option.value}
43
+ variant={preference === option.value ? "primary" : "secondary"}
44
+ aria-pressed={preference === option.value}
45
+ onClick={() => setPreference(option.value)}
46
+ >
47
+ {option.label}
48
+ </Button>
49
+ ))}
50
+ </div>
51
+ </header>
52
+ <div {...stylex.props(styles.body)}>
53
+ <nav {...stylex.props(styles.sidebar)} aria-label="UI kit">
54
+ {GROUPS.map((group) => (
55
+ <div key={group} {...stylex.props(styles.group)}>
56
+ <Text as="p" size="xs" tone="muted" weight="semibold">
57
+ {group}
58
+ </Text>
59
+ {UI_KIT_CATALOG.filter((item) => item.group === group).map((item) => (
60
+ <NavLink
61
+ key={item.slug}
62
+ to={`/__ui/${item.slug}`}
63
+ {...stylex.props(styles.link)}
64
+ >
65
+ {({ isActive }) => (
66
+ <span
67
+ {...stylex.props(styles.item, isActive && styles.itemActive)}
68
+ >
69
+ {item.title}
70
+ </span>
71
+ )}
72
+ </NavLink>
73
+ ))}
74
+ </div>
75
+ ))}
76
+ </nav>
77
+ <main {...stylex.props(styles.main)}>
78
+ <Page />
79
+ </main>
80
+ </div>
81
+ </div>
82
+ );
83
+ }
84
+
85
+ const hover = "@media (hover: hover)";
86
+
87
+ const styles = stylex.create({
88
+ frame: {
89
+ display: "flex",
90
+ flexDirection: "column",
91
+ minHeight: "100dvh",
92
+ backgroundColor: colors.bgApp,
93
+ color: colors.text,
94
+ fontFamily: type.sans,
95
+ },
96
+ header: {
97
+ display: "flex",
98
+ alignItems: "center",
99
+ justifyContent: "space-between",
100
+ gap: space.lg,
101
+ paddingInline: space.lg,
102
+ paddingBlock: space.md,
103
+ borderBottomWidth: 1,
104
+ borderBottomStyle: "solid",
105
+ borderBottomColor: colors.border,
106
+ backgroundColor: colors.bgSurface,
107
+ },
108
+ themes: {
109
+ display: "flex",
110
+ flexWrap: "wrap",
111
+ gap: space.sm,
112
+ },
113
+ body: {
114
+ display: "flex",
115
+ flex: 1,
116
+ minHeight: 0,
117
+ },
118
+ sidebar: {
119
+ width: "220px",
120
+ flexShrink: 0,
121
+ overflowY: "auto",
122
+ padding: space.lg,
123
+ backgroundColor: colors.bgSidebar,
124
+ borderRightWidth: 1,
125
+ borderRightStyle: "solid",
126
+ borderRightColor: colors.border,
127
+ },
128
+ group: {
129
+ display: "flex",
130
+ flexDirection: "column",
131
+ gap: space.xs,
132
+ marginBottom: space.lg,
133
+ },
134
+ link: {
135
+ display: "block",
136
+ color: "inherit",
137
+ textDecoration: "none",
138
+ },
139
+ item: {
140
+ display: "flex",
141
+ alignItems: "center",
142
+ borderRadius: radius.md,
143
+ paddingInline: space.md,
144
+ paddingBlock: space.sm,
145
+ fontSize: type.md,
146
+ fontWeight: 500,
147
+ color: colors.text,
148
+ [hover]: {
149
+ ":hover": {
150
+ backgroundColor: colors.bgHover,
151
+ },
152
+ },
153
+ },
154
+ itemActive: {
155
+ backgroundColor: colors.bgActive,
156
+ },
157
+ main: {
158
+ flex: 1,
159
+ minWidth: 0,
160
+ overflowY: "auto",
161
+ paddingInline: space.xxl,
162
+ paddingBlock: space.xxl,
163
+ },
164
+ });
@@ -0,0 +1,30 @@
1
+ // packages/studio/src/pages/ui-kit/buttons.tsx
2
+ import { Button, ButtonLink } from "../../ui/Button";
3
+ import { Columns, Row, Specimen, Topic } from "./topic";
4
+ import { UI_KIT_TOPICS } from "./topics";
5
+
6
+ const topic = UI_KIT_TOPICS.find((item) => item.slug === "buttons")!;
7
+
8
+ const VARIANTS = ["primary", "secondary", "ghost", "danger"] as const;
9
+
10
+ export function ButtonsTopic() {
11
+ return (
12
+ <Topic title={topic.title} lede={topic.lede}>
13
+ <Columns>
14
+ {VARIANTS.map((variant) => (
15
+ <Specimen key={variant} label={variant}>
16
+ <Row>
17
+ <Button variant={variant}>{variant}</Button>
18
+ <Button variant={variant} disabled>
19
+ disabled
20
+ </Button>
21
+ <ButtonLink variant={variant} to="/__ui/buttons">
22
+ link
23
+ </ButtonLink>
24
+ </Row>
25
+ </Specimen>
26
+ ))}
27
+ </Columns>
28
+ </Topic>
29
+ );
30
+ }
@@ -0,0 +1,41 @@
1
+ // packages/studio/src/pages/ui-kit/catalog.tsx
2
+ import type { ComponentType } from "react";
3
+ import { ButtonsTopic } from "./buttons";
4
+ import { ColorsTopic } from "./colors";
5
+ import { DevBarTopic } from "./devbar";
6
+ import { FeedbackTopic } from "./feedback";
7
+ import { FieldsTopic } from "./fields";
8
+ import { ListsTopic } from "./lists";
9
+ import { OverlaysTopic } from "./overlays";
10
+ import { SectionsTopic } from "./sections";
11
+ import { SurfacesTopic } from "./surfaces";
12
+ import { Topic } from "./topic";
13
+ import { TypeTopic } from "./type";
14
+ import { UI_KIT_TOPICS, type UiKitTopicSlug } from "./topics";
15
+
16
+ const PAGES = {
17
+ colors: ColorsTopic,
18
+ type: TypeTopic,
19
+ surfaces: SurfacesTopic,
20
+ sections: SectionsTopic,
21
+ buttons: ButtonsTopic,
22
+ fields: FieldsTopic,
23
+ lists: ListsTopic,
24
+ overlays: OverlaysTopic,
25
+ feedback: FeedbackTopic,
26
+ devbar: DevBarPage,
27
+ } satisfies Record<UiKitTopicSlug, ComponentType>;
28
+
29
+ function DevBarPage() {
30
+ const topic = UI_KIT_TOPICS.find((item) => item.slug === "devbar")!;
31
+ return (
32
+ <Topic title={topic.title} lede={topic.lede}>
33
+ <DevBarTopic />
34
+ </Topic>
35
+ );
36
+ }
37
+
38
+ export const UI_KIT_CATALOG = UI_KIT_TOPICS.map((topic) => ({
39
+ ...topic,
40
+ Page: PAGES[topic.slug],
41
+ }));
@@ -0,0 +1,95 @@
1
+ // packages/studio/src/pages/ui-kit/colors.tsx
2
+ import * as stylex from "@stylexjs/stylex";
3
+ import { colors, radius, space, type } from "../../styles/tokens.stylex";
4
+ import { Topic } from "./topic";
5
+ import { UI_KIT_TOPICS } from "./topics";
6
+
7
+ const topic = UI_KIT_TOPICS.find((item) => item.slug === "colors")!;
8
+
9
+ export function ColorsTopic() {
10
+ return (
11
+ <Topic title={topic.title} lede={topic.lede}>
12
+ <div {...stylex.props(styles.grid)}>
13
+ {SWATCHES.map(([name, fill]) => (
14
+ <figure key={name} {...stylex.props(styles.swatch)}>
15
+ <div {...stylex.props(styles.chip, fill)} />
16
+ <figcaption {...stylex.props(styles.name)}>{name}</figcaption>
17
+ </figure>
18
+ ))}
19
+ </div>
20
+ </Topic>
21
+ );
22
+ }
23
+
24
+ const styles = stylex.create({
25
+ grid: {
26
+ display: "grid",
27
+ gridTemplateColumns: "repeat(4, minmax(0, 1fr))",
28
+ gap: space.lg,
29
+ "@media (max-width: 800px)": {
30
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
31
+ },
32
+ },
33
+ swatch: {
34
+ display: "flex",
35
+ flexDirection: "column",
36
+ gap: space.sm,
37
+ margin: 0,
38
+ minWidth: 0,
39
+ },
40
+ chip: {
41
+ height: "72px",
42
+ borderRadius: radius.md,
43
+ borderWidth: 1,
44
+ borderStyle: "solid",
45
+ borderColor: colors.border,
46
+ },
47
+ name: {
48
+ fontFamily: type.mono,
49
+ fontSize: type.xs,
50
+ color: colors.textMuted,
51
+ },
52
+ bgApp: { backgroundColor: colors.bgApp },
53
+ bgSidebar: { backgroundColor: colors.bgSidebar },
54
+ bgPanel: { backgroundColor: colors.bgPanel },
55
+ bgPanelInset: { backgroundColor: colors.bgPanelInset },
56
+ bgSurface: { backgroundColor: colors.bgSurface },
57
+ bgHover: { backgroundColor: colors.bgHover },
58
+ bgActive: { backgroundColor: colors.bgActive },
59
+ bgInput: { backgroundColor: colors.bgInput },
60
+ bgStatus: { backgroundColor: colors.bgStatus },
61
+ text: { backgroundColor: colors.text },
62
+ textMuted: { backgroundColor: colors.textMuted },
63
+ textInverse: { backgroundColor: colors.textInverse },
64
+ border: { backgroundColor: colors.border },
65
+ accent: { backgroundColor: colors.accent },
66
+ accentHover: { backgroundColor: colors.accentHover },
67
+ accentMuted: { backgroundColor: colors.accentMuted },
68
+ danger: { backgroundColor: colors.danger },
69
+ success: { backgroundColor: colors.success },
70
+ overlay: { backgroundColor: colors.overlay },
71
+ focus: { backgroundColor: colors.focus },
72
+ });
73
+
74
+ const SWATCHES = [
75
+ ["bgApp", styles.bgApp],
76
+ ["bgSidebar", styles.bgSidebar],
77
+ ["bgPanel", styles.bgPanel],
78
+ ["bgPanelInset", styles.bgPanelInset],
79
+ ["bgSurface", styles.bgSurface],
80
+ ["bgHover", styles.bgHover],
81
+ ["bgActive", styles.bgActive],
82
+ ["bgInput", styles.bgInput],
83
+ ["bgStatus", styles.bgStatus],
84
+ ["text", styles.text],
85
+ ["textMuted", styles.textMuted],
86
+ ["textInverse", styles.textInverse],
87
+ ["border", styles.border],
88
+ ["accent", styles.accent],
89
+ ["accentHover", styles.accentHover],
90
+ ["accentMuted", styles.accentMuted],
91
+ ["danger", styles.danger],
92
+ ["success", styles.success],
93
+ ["overlay", styles.overlay],
94
+ ["focus", styles.focus],
95
+ ] as const;