@backmatter/studio 0.0.7 → 0.0.8

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.
@@ -66,9 +66,38 @@ export function MediaLightbox({
66
66
  aria-label={item.filename}
67
67
  >
68
68
  <div {...stylex.props(styles.stage)} onClick={onClose}>
69
- <div {...stylex.props(styles.stageTools)} onClick={(event) => event.stopPropagation()}>
69
+ <button
70
+ type="button"
71
+ aria-label="Previous image"
72
+ disabled={index <= 0}
73
+ {...stylex.props(styles.nav, styles.navPrev)}
74
+ onClick={(event) => {
75
+ event.stopPropagation();
76
+ onPrev();
77
+ }}
78
+ >
79
+ ‹
80
+ </button>
81
+ <div {...stylex.props(styles.frame)} onClick={(event) => event.stopPropagation()}>
82
+ <StageImage key={item.id} thumb={item.thumbUrl} full={full} alt={item.filename} />
83
+ </div>
84
+ <button
85
+ type="button"
86
+ aria-label="Next image"
87
+ disabled={index >= count - 1}
88
+ {...stylex.props(styles.nav, styles.navNext)}
89
+ onClick={(event) => {
90
+ event.stopPropagation();
91
+ onNext();
92
+ }}
93
+ >
94
+ ›
95
+ </button>
96
+ </div>
97
+ <aside {...stylex.props(styles.sidebar)}>
98
+ <div {...stylex.props(styles.sidebarTools)}>
70
99
  <Menu.Root>
71
- <Menu.Trigger aria-label="Image actions" className={cx(styles.stageTool)}>
100
+ <Menu.Trigger aria-label="Image actions" className={cx(styles.sidebarTool)}>
72
101
  <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
73
102
  <circle cx="3" cy="7" r="1.15" fill="currentColor" />
74
103
  <circle cx="7" cy="7" r="1.15" fill="currentColor" />
@@ -109,12 +138,7 @@ export function MediaLightbox({
109
138
  </Menu.Positioner>
110
139
  </Menu.Portal>
111
140
  </Menu.Root>
112
- <button
113
- type="button"
114
- aria-label="Close"
115
- {...stylex.props(styles.stageTool)}
116
- onClick={onClose}
117
- >
141
+ <button type="button" aria-label="Close" {...stylex.props(styles.sidebarTool)} onClick={onClose}>
118
142
  <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
119
143
  <path
120
144
  d="M3.5 3.5l7 7M10.5 3.5l-7 7"
@@ -126,81 +150,54 @@ export function MediaLightbox({
126
150
  </svg>
127
151
  </button>
128
152
  </div>
129
- <button
130
- type="button"
131
- aria-label="Previous image"
132
- disabled={index <= 0}
133
- {...stylex.props(styles.nav, styles.navPrev)}
134
- onClick={(event) => {
135
- event.stopPropagation();
136
- onPrev();
137
- }}
138
- >
139
- ‹
140
- </button>
141
- <div {...stylex.props(styles.frame)} onClick={(event) => event.stopPropagation()}>
142
- <StageImage key={item.id} thumb={item.thumbUrl} full={full} alt={item.filename} />
143
- </div>
144
- <button
145
- type="button"
146
- aria-label="Next image"
147
- disabled={index >= count - 1}
148
- {...stylex.props(styles.nav, styles.navNext)}
149
- onClick={(event) => {
150
- event.stopPropagation();
151
- onNext();
152
- }}
153
- >
154
- ›
155
- </button>
156
- </div>
157
- <aside {...stylex.props(styles.sidebar)}>
158
- <div {...stylex.props(styles.filename)}>
159
- <Text as="h2" size="lg" weight="medium">
160
- {item.filename}
161
- </Text>
162
- </div>
163
- <dl {...stylex.props(styles.facts)}>
164
- <Fact label="Dimensions" value={dimensions(item)} />
165
- <Fact label="Size" value={item.byteSize === undefined ? "—" : formatBytes(item.byteSize)} />
166
- <Fact label="Type" value={kindLabel(item.contentType)} />
167
- <Fact label="Added" value={addedLabel(item.createdAt)} />
168
- </dl>
169
- <div {...stylex.props(styles.urlBlock)}>
170
- <div {...stylex.props(styles.urlHead)}>
171
- <Text size="xs" tone="muted">
172
- Original URL
153
+ <div {...stylex.props(styles.sidebarBody)}>
154
+ <div {...stylex.props(styles.filename)}>
155
+ <Text as="h2" size="lg" weight="medium">
156
+ {item.filename}
173
157
  </Text>
174
- <button
175
- type="button"
176
- aria-label={copied ? "Copied" : "Copy URL"}
177
- {...stylex.props(styles.copy)}
178
- onClick={() => {
179
- void navigator.clipboard.writeText(item.url).then(() => setCopied(true));
180
- }}
181
- >
182
- <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
183
- <rect
184
- x="4.75"
185
- y="4.75"
186
- width="7"
187
- height="7"
188
- rx="1.25"
189
- fill="none"
190
- stroke="currentColor"
191
- strokeWidth="1.5"
192
- />
193
- <path
194
- d="M9.25 4.75V3.25A1.25 1.25 0 0 0 8 2H3.25A1.25 1.25 0 0 0 2 3.25V8A1.25 1.25 0 0 0 3.25 9.25H4.75"
195
- fill="none"
196
- stroke="currentColor"
197
- strokeWidth="1.5"
198
- />
199
- </svg>
200
- {copied ? "Copied" : "Copy"}
201
- </button>
202
158
  </div>
203
- <p {...stylex.props(styles.url)}>{item.url}</p>
159
+ <dl {...stylex.props(styles.facts)}>
160
+ <Fact label="Dimensions" value={dimensions(item)} />
161
+ <Fact label="Size" value={item.byteSize === undefined ? "—" : formatBytes(item.byteSize)} />
162
+ <Fact label="Type" value={kindLabel(item.contentType)} />
163
+ <Fact label="Added" value={addedLabel(item.createdAt)} />
164
+ </dl>
165
+ <div {...stylex.props(styles.urlBlock)}>
166
+ <div {...stylex.props(styles.urlHead)}>
167
+ <Text size="xs" tone="muted">
168
+ Original URL
169
+ </Text>
170
+ <button
171
+ type="button"
172
+ aria-label={copied ? "Copied" : "Copy URL"}
173
+ {...stylex.props(styles.copy)}
174
+ onClick={() => {
175
+ void navigator.clipboard.writeText(item.url).then(() => setCopied(true));
176
+ }}
177
+ >
178
+ <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
179
+ <rect
180
+ x="4.75"
181
+ y="4.75"
182
+ width="7"
183
+ height="7"
184
+ rx="1.25"
185
+ fill="none"
186
+ stroke="currentColor"
187
+ strokeWidth="1.5"
188
+ />
189
+ <path
190
+ d="M9.25 4.75V3.25A1.25 1.25 0 0 0 8 2H3.25A1.25 1.25 0 0 0 2 3.25V8A1.25 1.25 0 0 0 3.25 9.25H4.75"
191
+ fill="none"
192
+ stroke="currentColor"
193
+ strokeWidth="1.5"
194
+ />
195
+ </svg>
196
+ {copied ? "Copied" : "Copy"}
197
+ </button>
198
+ </div>
199
+ <p {...stylex.props(styles.url)}>{item.url}</p>
200
+ </div>
204
201
  </div>
205
202
  </aside>
206
203
  </div>
@@ -324,31 +321,35 @@ const styles = stylex.create({
324
321
  cursor: "default",
325
322
  },
326
323
  },
327
- stageTools: {
328
- position: "absolute",
329
- top: space.xl,
330
- right: space.xl,
331
- zIndex: 2,
324
+ sidebarTools: {
332
325
  display: "flex",
333
326
  alignItems: "center",
334
- gap: space.sm,
327
+ justifyContent: "flex-end",
328
+ gap: space.xs,
335
329
  },
336
- stageTool: {
330
+ sidebarTool: {
337
331
  width: "32px",
338
332
  height: "32px",
339
333
  display: "flex",
340
334
  alignItems: "center",
341
335
  justifyContent: "center",
342
336
  padding: 0,
343
- borderWidth: 1,
344
- borderStyle: "solid",
345
- borderColor: "rgba(255, 255, 255, 0.16)",
346
- borderRadius: "999px",
347
- backgroundColor: "rgba(0, 0, 0, 0.45)",
348
- color: "#fafafa",
337
+ borderWidth: 0,
338
+ borderRadius: radius.md,
339
+ backgroundColor: "transparent",
340
+ color: colors.textMuted,
349
341
  cursor: "pointer",
350
342
  [hover]: {
351
- backgroundColor: "rgba(255, 255, 255, 0.16)",
343
+ ":hover": {
344
+ backgroundColor: colors.bgHover,
345
+ color: colors.text,
346
+ },
347
+ },
348
+ ":focus-visible": {
349
+ outlineWidth: 2,
350
+ outlineStyle: "solid",
351
+ outlineColor: colors.focus,
352
+ outlineOffset: 1,
352
353
  },
353
354
  },
354
355
  menu: {
@@ -371,7 +372,7 @@ const styles = stylex.create({
371
372
  flexShrink: 0,
372
373
  display: "flex",
373
374
  flexDirection: "column",
374
- justifyContent: "flex-end",
375
+ justifyContent: "space-between",
375
376
  gap: space.xl,
376
377
  padding: space.xl,
377
378
  backgroundColor: colors.bgSurface,
@@ -388,6 +389,12 @@ const styles = stylex.create({
388
389
  borderTopColor: colors.border,
389
390
  },
390
391
  },
392
+ sidebarBody: {
393
+ display: "flex",
394
+ flexDirection: "column",
395
+ gap: space.xl,
396
+ minWidth: 0,
397
+ },
391
398
  filename: {
392
399
  minWidth: 0,
393
400
  overflowWrap: "anywhere",
@@ -1,10 +1,9 @@
1
1
  // packages/studio/src/pages/SelectRepositoriesLink.tsx
2
- import { useEffect, useState } from "react";
2
+ import { useEffect, useState, type ReactNode } from "react";
3
3
  import { fetchGithubRepositoryAccess } from "../api";
4
4
  import { ButtonLink } from "../ui/Button";
5
5
 
6
- /** Opens the GitHub page where this installation's repository access is chosen. */
7
- export function SelectRepositoriesLink({ workspaceId }: { workspaceId: string }) {
6
+ function useRepositoryAccessUrl(workspaceId: string) {
8
7
  const [url, setUrl] = useState<string | null>(null);
9
8
 
10
9
  useEffect(() => {
@@ -21,10 +20,35 @@ export function SelectRepositoriesLink({ workspaceId }: { workspaceId: string })
21
20
  };
22
21
  }, [workspaceId]);
23
22
 
23
+ return url;
24
+ }
25
+
26
+ /** Opens the GitHub page where this installation's repository access is chosen. */
27
+ export function SelectRepositoriesLink({
28
+ workspaceId,
29
+ label = "Select repositories on GitHub",
30
+ }: {
31
+ workspaceId: string;
32
+ label?: string;
33
+ }) {
34
+ const url = useRepositoryAccessUrl(workspaceId);
24
35
  if (!url) return null;
25
36
  return (
26
37
  <ButtonLink href={url} variant="secondary">
27
- Select repositories on GitHub
38
+ {label}
28
39
  </ButtonLink>
29
40
  );
30
41
  }
42
+
43
+ /** Renders nothing until the GitHub App's repository-access URL is known. */
44
+ export function GithubRepositoryAccess({
45
+ workspaceId,
46
+ children,
47
+ }: {
48
+ workspaceId: string;
49
+ children: (url: string) => ReactNode;
50
+ }) {
51
+ const url = useRepositoryAccessUrl(workspaceId);
52
+ if (!url) return null;
53
+ return children(url);
54
+ }
@@ -11,7 +11,6 @@ import { hostEnvSnippet } from "./host-env";
11
11
  import { isStudioDev, publicApiUrl } from "../studio-config";
12
12
  import { revalidateWebhookUrl } from "./preview-url";
13
13
  import { Button } from "../ui/Button";
14
- import { Field } from "../ui/Field";
15
14
  import { Input } from "../ui/Input";
16
15
  import { PageHeader, Panel, Stack } from "../ui/Page";
17
16
  import { Text } from "../ui/Text";
@@ -147,40 +146,33 @@ export function SettingsGeneralPage() {
147
146
  </Text>
148
147
  <form onSubmit={(e) => void onSaveSiteUrl(e)}>
149
148
  <div {...stylex.props(styles.block)}>
150
- <Field
151
- label="Site URL"
152
- hint={
153
- webhookUrl
154
- ? `Preview origin. Pushes POST ${webhookUrl}`
155
- : "Preview origin. A content push POSTs the revalidate route on this origin."
156
- }
157
- >
158
- <Input
159
- type="url"
160
- value={siteUrl}
161
- readOnly={!canEdit}
162
- placeholder="https://www.example.com"
163
- autoComplete="off"
164
- spellCheck={false}
165
- data-lpignore="true"
166
- data-1p-ignore
167
- onChange={(e) => setSiteUrl(e.target.value)}
168
- />
169
- </Field>
149
+ <Input
150
+ type="url"
151
+ aria-label="Site URL"
152
+ value={siteUrl}
153
+ readOnly={!canEdit}
154
+ placeholder="https://www.example.com"
155
+ autoComplete="off"
156
+ spellCheck={false}
157
+ data-lpignore="true"
158
+ data-1p-ignore
159
+ onChange={(e) => setSiteUrl(e.target.value)}
160
+ />
170
161
  </div>
162
+ <p {...stylex.props(styles.caption)}>
163
+ {webhookUrl
164
+ ? `Preview origin. Pushes POST ${webhookUrl}`
165
+ : "Preview origin. A content push POSTs the revalidate route on this origin."}
166
+ </p>
167
+ <StudioVersions versions={workspace.studioVersions} />
171
168
  {canEdit ? (
172
- <div {...stylex.props(styles.block)}>
173
- <Button
174
- type="submit"
175
- variant="secondary"
176
- disabled={busy !== null}
177
- >
169
+ <div {...stylex.props(styles.actions)}>
170
+ <Button type="submit" variant="secondary" disabled={busy !== null}>
178
171
  {busy === "site-url" ? "Saving…" : "Save"}
179
172
  </Button>
180
173
  </div>
181
174
  ) : null}
182
175
  </form>
183
- <StudioVersions versions={workspace.studioVersions} />
184
176
  </Panel>
185
177
  <Panel>
186
178
  <Text as="h2" size="lg" weight="medium">
@@ -370,6 +362,11 @@ const styles = stylex.create({
370
362
  block: {
371
363
  marginTop: space.md,
372
364
  },
365
+ actions: {
366
+ display: "flex",
367
+ justifyContent: "flex-end",
368
+ marginTop: space.lg,
369
+ },
373
370
  idRow: {
374
371
  display: "flex",
375
372
  alignItems: "center",