@backmatter/studio 0.0.7 → 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 (72) 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-DCHRI0WD.js → dashboard-BNUcbYU0.js} +380 -382
  4. package/dist/chunks/{index-BS0-Bvbk.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 +303 -98
  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/AppShell.tsx +47 -10
  24. package/src/chrome/AppSidebar.tsx +9 -34
  25. package/src/chrome/ContentExplorerSidebar.tsx +149 -96
  26. package/src/chrome/UserMenu.tsx +3 -1
  27. package/src/chrome/content-tree-visibility.ts +55 -0
  28. package/src/chrome/explorer-icons.tsx +2 -4
  29. package/src/devbar-state.ts +125 -0
  30. package/src/index.css +3 -131
  31. package/src/pages/CliAuthorizePage.tsx +61 -28
  32. package/src/pages/MediaGalleryPage.tsx +144 -85
  33. package/src/pages/MediaLightbox.tsx +195 -96
  34. package/src/pages/SelectRepositoriesLink.tsx +28 -4
  35. package/src/pages/SettingsGeneralPage.tsx +25 -28
  36. package/src/pages/SettingsGithubPage.tsx +413 -124
  37. package/src/pages/SettingsUsagePage.tsx +23 -1
  38. package/src/pages/media-grid.ts +14 -1
  39. package/src/pages/ui-kit/UiKitApp.tsx +164 -0
  40. package/src/pages/ui-kit/buttons.tsx +30 -0
  41. package/src/pages/ui-kit/catalog.tsx +41 -0
  42. package/src/pages/ui-kit/colors.tsx +95 -0
  43. package/src/pages/ui-kit/devbar.tsx +160 -0
  44. package/src/pages/ui-kit/feedback.tsx +39 -0
  45. package/src/pages/ui-kit/fields.tsx +47 -0
  46. package/src/pages/ui-kit/lists.tsx +19 -0
  47. package/src/pages/ui-kit/overlays.tsx +39 -0
  48. package/src/pages/ui-kit/path.ts +9 -0
  49. package/src/pages/ui-kit/sections.tsx +32 -0
  50. package/src/pages/ui-kit/surfaces.tsx +121 -0
  51. package/src/pages/ui-kit/topic.tsx +112 -0
  52. package/src/pages/ui-kit/topics.ts +71 -0
  53. package/src/pages/ui-kit/type.tsx +55 -0
  54. package/src/preview-frame-radius.ts +51 -0
  55. package/src/preview-frame.tsx +93 -49
  56. package/src/shell-resets.css +155 -0
  57. package/src/site-bar.tsx +1791 -425
  58. package/src/site-dashboard-href.ts +21 -0
  59. package/src/site-devbar-overlay-inject.ts +15 -0
  60. package/src/site-devbar-overlay.css +217 -0
  61. package/src/site-pointer.ts +1 -1
  62. package/src/site-publish-menu.tsx +33 -0
  63. package/src/site-widget-styles.ts +7 -0
  64. package/src/site-widget.tsx +61 -21
  65. package/src/studio-prose-ui.css +39 -0
  66. package/src/theme/ThemeProvider.tsx +4 -1
  67. package/src/workspace-path.ts +18 -1
  68. package/dist/chunks/EmbedPublishPage-88VsmOhi.js +0 -149
  69. package/dist/chunks/UiKitPage-X4hkFL2d.js +0 -173
  70. package/dist/chunks/WorkspaceRoutes-DXzdWqN3.js +0 -59523
  71. package/dist/chunks/status-bar-action-wj1KZ51m.js +0 -30612
  72. package/src/pages/UiKitPage.tsx +0 -331
@@ -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;
@@ -0,0 +1,160 @@
1
+ // packages/studio/src/pages/ui-kit/devbar.tsx
2
+ // Specimens of the host bar. This file stays free of StyleX so the markup
3
+ // test can render it under bun, which does not compile StyleX.
4
+ import { useState, type CSSProperties } from "react";
5
+ import type { DevbarActivity } from "../../devbar-state";
6
+ import { SiteBar } from "../../site-bar";
7
+
8
+ const noop = () => {};
9
+
10
+ const stageStyle: CSSProperties = {
11
+ position: "relative",
12
+ height: 96,
13
+ overflow: "hidden",
14
+ transform: "translateZ(0)",
15
+ ["--bm-devbar-right" as string]: "auto",
16
+ ["--bm-devbar-left" as string]: "12px",
17
+ };
18
+
19
+ const sampleDoc = {
20
+ locale: "en",
21
+ type: "page",
22
+ slug: "home",
23
+ workspaceId: "ws_sample",
24
+ };
25
+
26
+ export function DevBarTopic() {
27
+ const [play, setPlay] = useState(0);
28
+
29
+ return (
30
+ <div style={{ display: "flex", flexDirection: "column", gap: 40 }}>
31
+ <style>{`
32
+ .ui-kit-devbar-label {
33
+ margin: 0 0 8px;
34
+ font-size: 14px;
35
+ font-weight: 600;
36
+ letter-spacing: -0.01em;
37
+ line-height: 1.3;
38
+ }
39
+ .ui-kit-devbar-stage {
40
+ width: 100%;
41
+ }
42
+ .ui-kit-devbar-reset {
43
+ align-self: flex-start;
44
+ height: 28px;
45
+ padding: 0 10px;
46
+ border: 1px solid currentColor;
47
+ border-radius: 8px;
48
+ background: transparent;
49
+ color: inherit;
50
+ font: 500 13px/1 ui-sans-serif, system-ui, sans-serif;
51
+ cursor: pointer;
52
+ }
53
+ .ui-kit-devbar-reset:focus-visible {
54
+ outline: 2px solid currentColor;
55
+ outline-offset: 2px;
56
+ }
57
+ `}</style>
58
+ <button
59
+ type="button"
60
+ className="ui-kit-devbar-reset"
61
+ onClick={() => setPlay((next) => next + 1)}
62
+ >
63
+ Reset
64
+ </button>
65
+ <Bar play={play} heading="Preview" siteMode="preview" />
66
+ <Bar play={play} heading="Live" siteMode="live" />
67
+ <Bar play={play} heading="Branch" siteMode="preview" branch="feat/sale" doc />
68
+ <Bar play={play} heading="Exiting" siteMode="preview" exiting />
69
+ <Bar play={play} heading="Agent" siteMode="preview" doc chatOpen />
70
+ <Bar play={play} heading="Publish" siteMode="live" doc changeCount={3} />
71
+ <Bar
72
+ play={play}
73
+ heading="Changes"
74
+ siteMode="preview"
75
+ doc
76
+ changeCount={3}
77
+ lineDiff={{ added: 14, removed: 2 }}
78
+ />
79
+ <Bar
80
+ play={play}
81
+ heading="Editing"
82
+ siteMode="preview"
83
+ doc
84
+ activity={{ type: "editing", field: "hero.title" }}
85
+ />
86
+ <Bar
87
+ play={play}
88
+ heading="Publishing"
89
+ siteMode="preview"
90
+ doc
91
+ activity={{
92
+ type: "publishing",
93
+ progress: 0.45,
94
+ steps: [
95
+ { label: "Commit", state: "done" },
96
+ { label: "PR #42", state: "done" },
97
+ { label: "Deploying 0:14", state: "active" },
98
+ ],
99
+ }}
100
+ />
101
+ <Bar
102
+ play={play}
103
+ heading="Published"
104
+ siteMode="live"
105
+ doc
106
+ activity={{ type: "published", sha: "3a9f21", when: "just now" }}
107
+ />
108
+ </div>
109
+ );
110
+ }
111
+
112
+ function Bar({
113
+ play,
114
+ heading,
115
+ siteMode,
116
+ branch,
117
+ exiting = false,
118
+ doc = false,
119
+ chatOpen = false,
120
+ changeCount = 0,
121
+ lineDiff,
122
+ activity = null,
123
+ }: {
124
+ play: number;
125
+ heading: string;
126
+ siteMode: "preview" | "live";
127
+ branch?: string;
128
+ exiting?: boolean;
129
+ doc?: boolean;
130
+ chatOpen?: boolean;
131
+ changeCount?: number;
132
+ lineDiff?: { added: number; removed: number };
133
+ activity?: DevbarActivity | null;
134
+ }) {
135
+ return (
136
+ <section style={{ display: "flex", flexDirection: "column" }}>
137
+ <h2 className="ui-kit-devbar-label">{heading}</h2>
138
+ <div className="ui-kit-devbar-stage" style={stageStyle}>
139
+ <SiteBar
140
+ key={play}
141
+ label="page/home"
142
+ branch={branch}
143
+ doc={doc ? sampleDoc : null}
144
+ siteMode={siteMode}
145
+ exiting={exiting}
146
+ chatOpen={chatOpen}
147
+ publishOpen={false}
148
+ changeCount={changeCount}
149
+ lineDiff={lineDiff}
150
+ activity={activity}
151
+ entrance="play"
152
+ onExit={noop}
153
+ onEnterPreview={noop}
154
+ onToggleChat={noop}
155
+ onTogglePublish={noop}
156
+ />
157
+ </div>
158
+ </section>
159
+ );
160
+ }
@@ -0,0 +1,39 @@
1
+ // packages/studio/src/pages/ui-kit/feedback.tsx
2
+ import { StatusBar, StatusItem } from "../../chrome/StatusBar";
3
+ import { Text } from "../../ui/Text";
4
+ import { Columns, Specimen, Topic } from "./topic";
5
+ import { UI_KIT_TOPICS } from "./topics";
6
+
7
+ const topic = UI_KIT_TOPICS.find((item) => item.slug === "feedback")!;
8
+
9
+ export function FeedbackTopic() {
10
+ return (
11
+ <Topic title={topic.title} lede={topic.lede}>
12
+ <Columns>
13
+ <Specimen label="StatusBar" block>
14
+ <StatusBar>
15
+ <StatusItem>main</StatusItem>
16
+ <StatusItem>2 uncommitted</StatusItem>
17
+ </StatusBar>
18
+ </Specimen>
19
+ <Specimen label="StatusBar" block>
20
+ <StatusBar>
21
+ <StatusItem>feat/sale</StatusItem>
22
+ <StatusItem>Unsaved</StatusItem>
23
+ </StatusBar>
24
+ </Specimen>
25
+ <Specimen label="danger" block>
26
+ <StatusBar>
27
+ <StatusItem tone="danger">Save failed</StatusItem>
28
+ </StatusBar>
29
+ </Specimen>
30
+ <Specimen label="success">
31
+ <Text tone="success">Success</Text>
32
+ </Specimen>
33
+ <Specimen label="danger">
34
+ <Text tone="danger">Danger</Text>
35
+ </Specimen>
36
+ </Columns>
37
+ </Topic>
38
+ );
39
+ }
@@ -0,0 +1,47 @@
1
+ // packages/studio/src/pages/ui-kit/fields.tsx
2
+ import { useState } from "react";
3
+ import { Field } from "../../ui/Field";
4
+ import { Input, Select, Textarea } from "../../ui/Input";
5
+ import { Columns, Specimen, Topic } from "./topic";
6
+ import { UI_KIT_TOPICS } from "./topics";
7
+
8
+ const topic = UI_KIT_TOPICS.find((item) => item.slug === "fields")!;
9
+
10
+ export function FieldsTopic() {
11
+ const [locale, setLocale] = useState("en");
12
+
13
+ return (
14
+ <Topic title={topic.title} lede={topic.lede}>
15
+ <Columns>
16
+ <Specimen label="Input" block>
17
+ <Field label="Text" hint="Shown under the field.">
18
+ <Input defaultValue="Ada Lovelace" autoComplete="off" spellCheck={false} />
19
+ </Field>
20
+ </Specimen>
21
+ <Specimen label="disabled" block>
22
+ <Field label="Disabled">
23
+ <Input defaultValue="Locked" disabled />
24
+ </Field>
25
+ </Specimen>
26
+ <Specimen label="Select" block>
27
+ <Field label="Select">
28
+ <Select
29
+ aria-label="Locale"
30
+ value={locale}
31
+ onValueChange={(next) => setLocale(next ?? "en")}
32
+ options={[
33
+ { value: "en", label: "English (en)" },
34
+ { value: "da", label: "Dansk (da)" },
35
+ ]}
36
+ />
37
+ </Field>
38
+ </Specimen>
39
+ <Specimen label="Textarea" block>
40
+ <Field label="Body">
41
+ <Textarea mono rows={4} defaultValue={"---\ntitle: Hello\n---\n\nParagraph."} />
42
+ </Field>
43
+ </Specimen>
44
+ </Columns>
45
+ </Topic>
46
+ );
47
+ }
@@ -0,0 +1,19 @@
1
+ // packages/studio/src/pages/ui-kit/lists.tsx
2
+ import { List, ListRow } from "../../ui/ListRow";
3
+ import { Specimen, Topic } from "./topic";
4
+ import { UI_KIT_TOPICS } from "./topics";
5
+
6
+ const topic = UI_KIT_TOPICS.find((item) => item.slug === "lists")!;
7
+
8
+ export function ListsTopic() {
9
+ return (
10
+ <Topic title={topic.title} lede={topic.lede}>
11
+ <Specimen label="List" block>
12
+ <List>
13
+ <ListRow to="/__ui/lists" title="hello" meta="en" />
14
+ <ListRow to="/__ui/lists" title="about" meta="da" />
15
+ </List>
16
+ </Specimen>
17
+ </Topic>
18
+ );
19
+ }
@@ -0,0 +1,39 @@
1
+ // packages/studio/src/pages/ui-kit/overlays.tsx
2
+ import { useState } from "react";
3
+ import { Button } from "../../ui/Button";
4
+ import { ConfirmDialog } from "../../ui/ConfirmDialog";
5
+ import { Tooltip, TooltipProvider } from "../../ui/Tooltip";
6
+ import { Columns, Specimen, Topic } from "./topic";
7
+ import { UI_KIT_TOPICS } from "./topics";
8
+
9
+ const topic = UI_KIT_TOPICS.find((item) => item.slug === "overlays")!;
10
+
11
+ export function OverlaysTopic() {
12
+ const [open, setOpen] = useState(false);
13
+
14
+ return (
15
+ <TooltipProvider>
16
+ <Topic title={topic.title} lede={topic.lede}>
17
+ <Columns>
18
+ <Specimen label="ConfirmDialog">
19
+ <Button onClick={() => setOpen(true)}>Open dialog</Button>
20
+ <ConfirmDialog
21
+ open={open}
22
+ onOpenChange={setOpen}
23
+ title="Delete this sample?"
24
+ confirmLabel="Delete"
25
+ cancelLabel="Cancel"
26
+ destructive
27
+ onConfirm={() => setOpen(false)}
28
+ />
29
+ </Specimen>
30
+ <Specimen label="Tooltip">
31
+ <Tooltip label="Sample tooltip">
32
+ <Button variant="secondary">Hover</Button>
33
+ </Tooltip>
34
+ </Specimen>
35
+ </Columns>
36
+ </Topic>
37
+ </TooltipProvider>
38
+ );
39
+ }
@@ -0,0 +1,9 @@
1
+ // packages/studio/src/pages/ui-kit/path.ts
2
+ import { isStudioDev } from "../../studio-config";
3
+
4
+ /** Dev-only catalog: `/__ui` or `/__ui/` plus one segment. */
5
+ export function isUiKitPath(pathname: string) {
6
+ if (!isStudioDev()) return false;
7
+ const path = pathname.replace(/\/$/, "") || "/";
8
+ return path === "/__ui" || /^\/__ui\/[^/]+$/.test(path);
9
+ }
@@ -0,0 +1,32 @@
1
+ // packages/studio/src/pages/ui-kit/sections.tsx
2
+ import { BackLink } from "../../ui/BackLink";
3
+ import { Button } from "../../ui/Button";
4
+ import { PageHeader, Panel, Stack } from "../../ui/Page";
5
+ import { Text } from "../../ui/Text";
6
+ import { Specimen, Topic } from "./topic";
7
+ import { UI_KIT_TOPICS } from "./topics";
8
+
9
+ const topic = UI_KIT_TOPICS.find((item) => item.slug === "sections")!;
10
+
11
+ export function SectionsTopic() {
12
+ return (
13
+ <Topic title={topic.title} lede={topic.lede}>
14
+ <Specimen label="PageHeader" block>
15
+ <PageHeader
16
+ kicker={<BackLink to="/__ui/sections">Sections</BackLink>}
17
+ title="Documents"
18
+ description="Sample page header with action."
19
+ action={<Button>New</Button>}
20
+ />
21
+ </Specimen>
22
+ <Specimen label="Panel" block>
23
+ <Panel>
24
+ <Stack>
25
+ <Text>First line</Text>
26
+ <Text>Second line</Text>
27
+ </Stack>
28
+ </Panel>
29
+ </Specimen>
30
+ </Topic>
31
+ );
32
+ }