@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
@@ -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();
@@ -66,9 +77,43 @@ export function MediaLightbox({
66
77
  aria-label={item.filename}
67
78
  >
68
79
  <div {...stylex.props(styles.stage)} onClick={onClose}>
69
- <div {...stylex.props(styles.stageTools)} onClick={(event) => event.stopPropagation()}>
80
+ <button
81
+ type="button"
82
+ aria-label="Previous image"
83
+ disabled={index <= 0}
84
+ {...stylex.props(styles.nav, styles.navPrev)}
85
+ onClick={(event) => {
86
+ event.stopPropagation();
87
+ onPrev();
88
+ }}
89
+ >
90
+ ‹
91
+ </button>
92
+ <div {...stylex.props(styles.frame)} onClick={(event) => event.stopPropagation()}>
93
+ <StageImage
94
+ key={item.id}
95
+ thumb={item.thumbUrl}
96
+ full={full}
97
+ alt={item.alt?.trim() || item.filename}
98
+ />
99
+ </div>
100
+ <button
101
+ type="button"
102
+ aria-label="Next image"
103
+ disabled={index >= count - 1}
104
+ {...stylex.props(styles.nav, styles.navNext)}
105
+ onClick={(event) => {
106
+ event.stopPropagation();
107
+ onNext();
108
+ }}
109
+ >
110
+ ›
111
+ </button>
112
+ </div>
113
+ <aside {...stylex.props(styles.sidebar)}>
114
+ <div {...stylex.props(styles.sidebarTools)}>
70
115
  <Menu.Root>
71
- <Menu.Trigger aria-label="Image actions" className={cx(styles.stageTool)}>
116
+ <Menu.Trigger aria-label="Image actions" className={cx(styles.sidebarTool)}>
72
117
  <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
73
118
  <circle cx="3" cy="7" r="1.15" fill="currentColor" />
74
119
  <circle cx="7" cy="7" r="1.15" fill="currentColor" />
@@ -109,12 +154,7 @@ export function MediaLightbox({
109
154
  </Menu.Positioner>
110
155
  </Menu.Portal>
111
156
  </Menu.Root>
112
- <button
113
- type="button"
114
- aria-label="Close"
115
- {...stylex.props(styles.stageTool)}
116
- onClick={onClose}
117
- >
157
+ <button type="button" aria-label="Close" {...stylex.props(styles.sidebarTool)} onClick={onClose}>
118
158
  <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
119
159
  <path
120
160
  d="M3.5 3.5l7 7M10.5 3.5l-7 7"
@@ -126,81 +166,60 @@ export function MediaLightbox({
126
166
  </svg>
127
167
  </button>
128
168
  </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
169
+ <div {...stylex.props(styles.sidebarBody)}>
170
+ <div {...stylex.props(styles.filename)}>
171
+ <Text as="h2" size="lg" weight="medium">
172
+ {item.filename}
173
173
  </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
174
  </div>
203
- <p {...stylex.props(styles.url)}>{item.url}</p>
175
+ <dl {...stylex.props(styles.facts)}>
176
+ <Fact label="Dimensions" value={dimensions(item)} />
177
+ <Fact label="Size" value={item.byteSize === undefined ? "—" : formatBytes(item.byteSize)} />
178
+ <Fact label="Type" value={kindLabel(item.contentType)} />
179
+ <Fact label="Added" value={addedLabel(item.createdAt)} />
180
+ </dl>
181
+ <MediaText
182
+ key={item.id}
183
+ item={item}
184
+ canEdit={canEdit}
185
+ onDescribe={onDescribe}
186
+ />
187
+ <div {...stylex.props(styles.urlBlock)}>
188
+ <div {...stylex.props(styles.urlHead)}>
189
+ <Text size="xs" tone="muted">
190
+ Original URL
191
+ </Text>
192
+ <button
193
+ type="button"
194
+ aria-label={copied ? "Copied" : "Copy URL"}
195
+ {...stylex.props(styles.copy)}
196
+ onClick={() => {
197
+ void navigator.clipboard.writeText(item.url).then(() => setCopied(true));
198
+ }}
199
+ >
200
+ <svg width="14" height="14" viewBox="0 0 14 14" aria-hidden="true">
201
+ <rect
202
+ x="4.75"
203
+ y="4.75"
204
+ width="7"
205
+ height="7"
206
+ rx="1.25"
207
+ fill="none"
208
+ stroke="currentColor"
209
+ strokeWidth="1.5"
210
+ />
211
+ <path
212
+ 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"
213
+ fill="none"
214
+ stroke="currentColor"
215
+ strokeWidth="1.5"
216
+ />
217
+ </svg>
218
+ {copied ? "Copied" : "Copy"}
219
+ </button>
220
+ </div>
221
+ <p {...stylex.props(styles.url)}>{item.url}</p>
222
+ </div>
204
223
  </div>
205
224
  </aside>
206
225
  </div>
@@ -222,6 +241,71 @@ function StageImage({ thumb, full, alt }: { thumb: string; full: string; alt: st
222
241
  );
223
242
  }
224
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
+
225
309
  function Fact({ label, value }: { label: string; value: string }) {
226
310
  return (
227
311
  <div {...stylex.props(styles.fact)}>
@@ -324,31 +408,35 @@ const styles = stylex.create({
324
408
  cursor: "default",
325
409
  },
326
410
  },
327
- stageTools: {
328
- position: "absolute",
329
- top: space.xl,
330
- right: space.xl,
331
- zIndex: 2,
411
+ sidebarTools: {
332
412
  display: "flex",
333
413
  alignItems: "center",
334
- gap: space.sm,
414
+ justifyContent: "flex-end",
415
+ gap: space.xs,
335
416
  },
336
- stageTool: {
417
+ sidebarTool: {
337
418
  width: "32px",
338
419
  height: "32px",
339
420
  display: "flex",
340
421
  alignItems: "center",
341
422
  justifyContent: "center",
342
423
  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",
424
+ borderWidth: 0,
425
+ borderRadius: radius.md,
426
+ backgroundColor: "transparent",
427
+ color: colors.textMuted,
349
428
  cursor: "pointer",
350
429
  [hover]: {
351
- backgroundColor: "rgba(255, 255, 255, 0.16)",
430
+ ":hover": {
431
+ backgroundColor: colors.bgHover,
432
+ color: colors.text,
433
+ },
434
+ },
435
+ ":focus-visible": {
436
+ outlineWidth: 2,
437
+ outlineStyle: "solid",
438
+ outlineColor: colors.focus,
439
+ outlineOffset: 1,
352
440
  },
353
441
  },
354
442
  menu: {
@@ -371,7 +459,7 @@ const styles = stylex.create({
371
459
  flexShrink: 0,
372
460
  display: "flex",
373
461
  flexDirection: "column",
374
- justifyContent: "flex-end",
462
+ justifyContent: "space-between",
375
463
  gap: space.xl,
376
464
  padding: space.xl,
377
465
  backgroundColor: colors.bgSurface,
@@ -388,6 +476,12 @@ const styles = stylex.create({
388
476
  borderTopColor: colors.border,
389
477
  },
390
478
  },
479
+ sidebarBody: {
480
+ display: "flex",
481
+ flexDirection: "column",
482
+ gap: space.xl,
483
+ minWidth: 0,
484
+ },
391
485
  filename: {
392
486
  minWidth: 0,
393
487
  overflowWrap: "anywhere",
@@ -398,6 +492,11 @@ const styles = stylex.create({
398
492
  gap: space.lg,
399
493
  margin: 0,
400
494
  },
495
+ textFields: {
496
+ display: "flex",
497
+ flexDirection: "column",
498
+ gap: space.lg,
499
+ },
401
500
  fact: {
402
501
  display: "flex",
403
502
  flexDirection: "column",
@@ -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",