@agent-native/core 0.137.1 → 0.137.2
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.
- package/corpus/README.md +1 -1
- package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +258 -0
- package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +130 -0
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +25 -337
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +73 -53
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +804 -0
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +101 -45
- package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +7 -4
- package/corpus/templates/slides/app/components/editor/bullet-editing.ts +11 -3
- package/corpus/templates/slides/app/components/editor/commit-active-edit.ts +21 -0
- package/corpus/templates/slides/app/components/editor/list-editing.ts +219 -0
- package/corpus/templates/slides/app/components/editor/selection-overlay-measurement.ts +0 -3
- package/corpus/templates/slides/app/components/editor/slide-style.ts +199 -0
- package/corpus/templates/slides/app/global.css +14 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +8 -1
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +48 -22
- package/corpus/templates/slides/changelog/2026-08-01-add-slide-moved-to-the-toolbar-and-the-slide-rail-is-now-mor.md +9 -0
- package/corpus/templates/slides/changelog/2026-08-01-add-slide-undo-redo-and-the-text-tool-now-lead-the-slide-too.md +9 -0
- package/corpus/templates/slides/changelog/2026-08-01-slide-styling-now-appears-in-a-contextual-toolbar-above-the-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-01-the-slide-style-side-panel-is-retired-all-styling-now-lives-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-03-the-slide-toolbar-is-denser-swatch-only-colors-dropdowns-for.md +10 -0
- package/corpus/templates/slides/changelog/2026-08-04-bullet-and-numbered-list-buttons-in-the-slide-toolbar-conver.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-04-italic-and-underline-are-now-one-click-away-in-the-slide-too.md +6 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +2 -2
- package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +0 -763
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
2
|
+
import {
|
|
3
|
+
VisualColorPicker,
|
|
4
|
+
VisualControlRow,
|
|
5
|
+
VisualScrubInput,
|
|
6
|
+
VisualSegmentedControl,
|
|
7
|
+
} from "@agent-native/toolkit/design-tweaks";
|
|
8
|
+
import type { DesignSystemData } from "@shared/api";
|
|
9
|
+
import {
|
|
10
|
+
IconAlignCenter,
|
|
11
|
+
IconAlignJustified,
|
|
12
|
+
IconAlignLeft,
|
|
13
|
+
IconAlignRight,
|
|
14
|
+
IconAngle,
|
|
15
|
+
IconArrowAutofitHeight,
|
|
16
|
+
IconArrowAutofitWidth,
|
|
17
|
+
IconBorderRadius,
|
|
18
|
+
IconBorderStyle,
|
|
19
|
+
IconBoxPadding,
|
|
20
|
+
IconDots,
|
|
21
|
+
IconGridDots,
|
|
22
|
+
IconItalic,
|
|
23
|
+
IconLayoutAlignLeft,
|
|
24
|
+
IconLetterCase,
|
|
25
|
+
IconList,
|
|
26
|
+
IconListNumbers,
|
|
27
|
+
IconSpacingHorizontal,
|
|
28
|
+
IconSpacingVertical,
|
|
29
|
+
IconStackBack,
|
|
30
|
+
IconStackFront,
|
|
31
|
+
IconUnderline,
|
|
32
|
+
} from "@tabler/icons-react";
|
|
33
|
+
import type { ReactNode } from "react";
|
|
34
|
+
|
|
35
|
+
import { Button } from "@/components/ui/button";
|
|
36
|
+
import {
|
|
37
|
+
DropdownMenu,
|
|
38
|
+
DropdownMenuContent,
|
|
39
|
+
DropdownMenuItem,
|
|
40
|
+
DropdownMenuTrigger,
|
|
41
|
+
} from "@/components/ui/dropdown-menu";
|
|
42
|
+
import {
|
|
43
|
+
Popover,
|
|
44
|
+
PopoverContent,
|
|
45
|
+
PopoverTrigger,
|
|
46
|
+
} from "@/components/ui/popover";
|
|
47
|
+
import {
|
|
48
|
+
Tooltip,
|
|
49
|
+
TooltipContent,
|
|
50
|
+
TooltipTrigger,
|
|
51
|
+
} from "@/components/ui/tooltip";
|
|
52
|
+
import { cn, shortcutLabel } from "@/lib/utils";
|
|
53
|
+
|
|
54
|
+
import type { SlideListKind } from "./list-editing";
|
|
55
|
+
import {
|
|
56
|
+
backgroundCssValue,
|
|
57
|
+
formatValue,
|
|
58
|
+
horizontalAlignPatch,
|
|
59
|
+
resolveHorizontalAlignment,
|
|
60
|
+
resolveVerticalAlignment,
|
|
61
|
+
rotationTransform,
|
|
62
|
+
tokenPalette,
|
|
63
|
+
verticalAlignPatch,
|
|
64
|
+
type SlideStylePatch,
|
|
65
|
+
type SlideStyleSnapshot,
|
|
66
|
+
} from "./slide-style";
|
|
67
|
+
|
|
68
|
+
const TOOLBAR_DIVIDER = "mx-1 h-4 w-px shrink-0 bg-border";
|
|
69
|
+
const SCRUB_CLASS = "w-24 shrink-0";
|
|
70
|
+
const SIZE_SCRUB_CLASS = "w-28 shrink-0 gap-0.5";
|
|
71
|
+
const MENU_BUTTON_CLASS =
|
|
72
|
+
"size-7 shrink-0 cursor-pointer text-muted-foreground hover:text-foreground";
|
|
73
|
+
const TOGGLE_ACTIVE_CLASS = "bg-accent text-foreground";
|
|
74
|
+
const MENU_TRIGGER_BASE =
|
|
75
|
+
"flex h-7 shrink-0 cursor-pointer items-center gap-1 rounded-md px-1.5 text-[11px] font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
|
|
76
|
+
const VALUE_MENU_CLASS = `${MENU_TRIGGER_BASE} min-w-14`;
|
|
77
|
+
const ICON_MENU_CLASS = `${MENU_TRIGGER_BASE} text-muted-foreground hover:text-foreground`;
|
|
78
|
+
const CARET_CLASS =
|
|
79
|
+
"size-0 shrink-0 border-x-[4px] border-t-[5px] border-x-transparent border-t-muted-foreground/70";
|
|
80
|
+
|
|
81
|
+
type Translate = ReturnType<typeof useT>;
|
|
82
|
+
|
|
83
|
+
function fontWeightOptions(t: Translate) {
|
|
84
|
+
return [
|
|
85
|
+
{ label: t("styleInspector.regular"), value: "400" },
|
|
86
|
+
{ label: t("styleInspector.medium"), value: "500" },
|
|
87
|
+
{ label: t("styleInspector.semi"), value: "600" },
|
|
88
|
+
{ label: t("styleInspector.bold"), value: "700" },
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function weightLabel(fontWeight: string, t: Translate) {
|
|
93
|
+
const match = fontWeightOptions(t).find(
|
|
94
|
+
(option) => option.value === fontWeight,
|
|
95
|
+
);
|
|
96
|
+
return match ? match.label : fontWeight;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function textAlignOptions(t: Translate) {
|
|
100
|
+
return [
|
|
101
|
+
{ label: t("styleInspector.left"), value: "left", icon: IconAlignLeft },
|
|
102
|
+
{
|
|
103
|
+
label: t("styleInspector.center"),
|
|
104
|
+
value: "center",
|
|
105
|
+
icon: IconAlignCenter,
|
|
106
|
+
},
|
|
107
|
+
{ label: t("styleInspector.right"), value: "right", icon: IconAlignRight },
|
|
108
|
+
{
|
|
109
|
+
label: t("styleInspector.justify"),
|
|
110
|
+
value: "justify",
|
|
111
|
+
icon: IconAlignJustified,
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function alignIcon(textAlign: string) {
|
|
117
|
+
if (textAlign === "center") return IconAlignCenter;
|
|
118
|
+
if (textAlign === "right") return IconAlignRight;
|
|
119
|
+
if (textAlign === "justify") return IconAlignJustified;
|
|
120
|
+
return IconAlignLeft;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Horizontal counterpart to the style dock: the same snapshot and patch
|
|
125
|
+
* callback, presented as a row above the canvas so the slide keeps full width.
|
|
126
|
+
* Controls past the first few live in grouped popovers — a flat row overflows
|
|
127
|
+
* once the agent sidebar and slide rail take their share of the width.
|
|
128
|
+
*/
|
|
129
|
+
export function SlideContextToolbar({
|
|
130
|
+
snapshot,
|
|
131
|
+
background,
|
|
132
|
+
designSystem,
|
|
133
|
+
className,
|
|
134
|
+
leading,
|
|
135
|
+
onChange,
|
|
136
|
+
onBackgroundChange,
|
|
137
|
+
onArrange,
|
|
138
|
+
onToggleList,
|
|
139
|
+
}: {
|
|
140
|
+
snapshot: SlideStyleSnapshot | null;
|
|
141
|
+
background: string | undefined;
|
|
142
|
+
designSystem?: DesignSystemData;
|
|
143
|
+
className?: string;
|
|
144
|
+
/** Selection-independent actions pinned to the head of the row. */
|
|
145
|
+
leading?: ReactNode;
|
|
146
|
+
onChange: (patch: SlideStylePatch) => void;
|
|
147
|
+
onBackgroundChange: (background: string) => void;
|
|
148
|
+
onArrange?: (target: "front" | "back") => void;
|
|
149
|
+
onToggleList?: (kind: SlideListKind) => void;
|
|
150
|
+
}) {
|
|
151
|
+
const t = useT();
|
|
152
|
+
const documentColors = tokenPalette(designSystem, t).map(
|
|
153
|
+
(option) => option.value,
|
|
154
|
+
);
|
|
155
|
+
const inlineEditSurfaceProps = {
|
|
156
|
+
"data-slide-inline-edit-surface": "true",
|
|
157
|
+
};
|
|
158
|
+
const mixedTextStyles = snapshot?.mixedTextStyles ?? [];
|
|
159
|
+
// A mixed selection has no single state to reflect, so the toggle reads as
|
|
160
|
+
// off and one click makes the whole selection consistent.
|
|
161
|
+
const isItalic =
|
|
162
|
+
!mixedTextStyles.includes("fontStyle") &&
|
|
163
|
+
(snapshot?.fontStyle ?? "").startsWith("italic");
|
|
164
|
+
// A mixed selection has no single size, so the scrub input reports a step as
|
|
165
|
+
// a relative delta rather than a value. Writing that delta as an absolute
|
|
166
|
+
// size would set the whole selection to a few pixels; step from the block's
|
|
167
|
+
// own size instead, which also makes the selection consistent in one click.
|
|
168
|
+
const sizeFor = (value: number, meta?: { relativeDelta?: number }) => {
|
|
169
|
+
const delta = meta?.relativeDelta;
|
|
170
|
+
if (typeof delta !== "number") return value;
|
|
171
|
+
return Math.min(160, Math.max(8, (snapshot?.fontSize ?? 0) + delta));
|
|
172
|
+
};
|
|
173
|
+
const decorationMixed = mixedTextStyles.includes("textDecoration");
|
|
174
|
+
const isUnderline =
|
|
175
|
+
!decorationMixed && (snapshot?.textDecoration ?? "").includes("underline");
|
|
176
|
+
// Text can carry more than one decoration, and the agent writes
|
|
177
|
+
// line-through even though no control exposes it. Editing the underline
|
|
178
|
+
// token in place keeps the rest; writing a bare "none" would erase them.
|
|
179
|
+
const underlinePatch = () => {
|
|
180
|
+
if (decorationMixed) return "underline";
|
|
181
|
+
const tokens = (snapshot?.textDecoration ?? "")
|
|
182
|
+
.split(/\s+/)
|
|
183
|
+
.filter((token) => token && token !== "none");
|
|
184
|
+
const next = isUnderline
|
|
185
|
+
? tokens.filter((token) => token !== "underline")
|
|
186
|
+
: [...tokens, "underline"];
|
|
187
|
+
return next.length > 0 ? next.join(" ") : "none";
|
|
188
|
+
};
|
|
189
|
+
// Null means the slide uses a background this picker cannot represent (named
|
|
190
|
+
// utility, gradient); surface that as Mixed rather than guessing a hex.
|
|
191
|
+
const slideBackground = backgroundCssValue(background);
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<div
|
|
195
|
+
className={cn(
|
|
196
|
+
"slide-context-toolbar flex h-10 shrink-0 items-center gap-1 overflow-x-auto whitespace-nowrap border-b border-border/70 bg-muted/60 px-2 sm:px-3",
|
|
197
|
+
className,
|
|
198
|
+
)}
|
|
199
|
+
data-slide-context-toolbar="true"
|
|
200
|
+
role="toolbar"
|
|
201
|
+
aria-label={t("styleInspector.title")}
|
|
202
|
+
>
|
|
203
|
+
{leading && (
|
|
204
|
+
<>
|
|
205
|
+
{leading}
|
|
206
|
+
<div className={TOOLBAR_DIVIDER} />
|
|
207
|
+
</>
|
|
208
|
+
)}
|
|
209
|
+
{!snapshot ? (
|
|
210
|
+
<VisualColorPicker
|
|
211
|
+
label={t("styleInspector.slideBackground")}
|
|
212
|
+
value={slideBackground ?? ""}
|
|
213
|
+
mixed={slideBackground === null}
|
|
214
|
+
mixedLabel={t("styleInspector.mixed")}
|
|
215
|
+
documentColors={documentColors}
|
|
216
|
+
variant="swatch"
|
|
217
|
+
contentProps={inlineEditSurfaceProps}
|
|
218
|
+
onChange={onBackgroundChange}
|
|
219
|
+
/>
|
|
220
|
+
) : (
|
|
221
|
+
<>
|
|
222
|
+
{snapshot.isText ? (
|
|
223
|
+
<>
|
|
224
|
+
<VisualScrubInput
|
|
225
|
+
label={t("styleInspector.size")}
|
|
226
|
+
icon={IconLetterCase}
|
|
227
|
+
prefix="icon"
|
|
228
|
+
steppers
|
|
229
|
+
decrementLabel={t("styleInspector.decreaseSize")}
|
|
230
|
+
incrementLabel={t("styleInspector.increaseSize")}
|
|
231
|
+
value={snapshot.fontSize}
|
|
232
|
+
min={8}
|
|
233
|
+
max={160}
|
|
234
|
+
unit="px"
|
|
235
|
+
mixed={mixedTextStyles.includes("fontSize")}
|
|
236
|
+
mixedLabel={t("styleInspector.mixed")}
|
|
237
|
+
className={SIZE_SCRUB_CLASS}
|
|
238
|
+
onChange={(fontSize, meta) =>
|
|
239
|
+
onChange({
|
|
240
|
+
fontSize: `${formatValue(sizeFor(fontSize, meta))}px`,
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
/>
|
|
244
|
+
<div className={TOOLBAR_DIVIDER} />
|
|
245
|
+
<DropdownMenu>
|
|
246
|
+
<Tooltip>
|
|
247
|
+
<TooltipTrigger asChild>
|
|
248
|
+
<DropdownMenuTrigger asChild>
|
|
249
|
+
<button
|
|
250
|
+
type="button"
|
|
251
|
+
className={VALUE_MENU_CLASS}
|
|
252
|
+
aria-label={t("styleInspector.weight")}
|
|
253
|
+
>
|
|
254
|
+
<span className="truncate">
|
|
255
|
+
{mixedTextStyles.includes("fontWeight")
|
|
256
|
+
? t("styleInspector.mixed")
|
|
257
|
+
: weightLabel(snapshot.fontWeight, t)}
|
|
258
|
+
</span>
|
|
259
|
+
<span aria-hidden="true" className={CARET_CLASS} />
|
|
260
|
+
</button>
|
|
261
|
+
</DropdownMenuTrigger>
|
|
262
|
+
</TooltipTrigger>
|
|
263
|
+
<TooltipContent>{t("styleInspector.weight")}</TooltipContent>
|
|
264
|
+
</Tooltip>
|
|
265
|
+
<DropdownMenuContent align="start" className="w-36">
|
|
266
|
+
{fontWeightOptions(t).map((option) => (
|
|
267
|
+
<DropdownMenuItem
|
|
268
|
+
key={option.value}
|
|
269
|
+
onSelect={() => onChange({ fontWeight: option.value })}
|
|
270
|
+
>
|
|
271
|
+
<span style={{ fontWeight: Number(option.value) }}>
|
|
272
|
+
{option.label}
|
|
273
|
+
</span>
|
|
274
|
+
</DropdownMenuItem>
|
|
275
|
+
))}
|
|
276
|
+
</DropdownMenuContent>
|
|
277
|
+
</DropdownMenu>
|
|
278
|
+
|
|
279
|
+
<Tooltip>
|
|
280
|
+
<TooltipTrigger asChild>
|
|
281
|
+
<Button
|
|
282
|
+
type="button"
|
|
283
|
+
variant="ghost"
|
|
284
|
+
size="icon"
|
|
285
|
+
className={cn(
|
|
286
|
+
MENU_BUTTON_CLASS,
|
|
287
|
+
isItalic && TOGGLE_ACTIVE_CLASS,
|
|
288
|
+
)}
|
|
289
|
+
aria-label={t("styleInspector.italic")}
|
|
290
|
+
aria-pressed={isItalic}
|
|
291
|
+
onClick={() =>
|
|
292
|
+
onChange({ fontStyle: isItalic ? "normal" : "italic" })
|
|
293
|
+
}
|
|
294
|
+
>
|
|
295
|
+
<IconItalic className="size-4" />
|
|
296
|
+
</Button>
|
|
297
|
+
</TooltipTrigger>
|
|
298
|
+
<TooltipContent>
|
|
299
|
+
{`${t("styleInspector.italic")} (${shortcutLabel("cmd+i")})`}
|
|
300
|
+
</TooltipContent>
|
|
301
|
+
</Tooltip>
|
|
302
|
+
|
|
303
|
+
<Tooltip>
|
|
304
|
+
<TooltipTrigger asChild>
|
|
305
|
+
<Button
|
|
306
|
+
type="button"
|
|
307
|
+
variant="ghost"
|
|
308
|
+
size="icon"
|
|
309
|
+
className={cn(
|
|
310
|
+
MENU_BUTTON_CLASS,
|
|
311
|
+
isUnderline && TOGGLE_ACTIVE_CLASS,
|
|
312
|
+
)}
|
|
313
|
+
aria-label={t("styleInspector.underline")}
|
|
314
|
+
aria-pressed={isUnderline}
|
|
315
|
+
onClick={() =>
|
|
316
|
+
onChange({ textDecoration: underlinePatch() })
|
|
317
|
+
}
|
|
318
|
+
>
|
|
319
|
+
<IconUnderline className="size-4" />
|
|
320
|
+
</Button>
|
|
321
|
+
</TooltipTrigger>
|
|
322
|
+
<TooltipContent>
|
|
323
|
+
{`${t("styleInspector.underline")} (${shortcutLabel("cmd+u")})`}
|
|
324
|
+
</TooltipContent>
|
|
325
|
+
</Tooltip>
|
|
326
|
+
|
|
327
|
+
<VisualColorPicker
|
|
328
|
+
label={t("styleInspector.textColor")}
|
|
329
|
+
value={snapshot.color}
|
|
330
|
+
documentColors={documentColors}
|
|
331
|
+
mixed={mixedTextStyles.includes("color")}
|
|
332
|
+
mixedLabel={t("styleInspector.mixed")}
|
|
333
|
+
variant="swatch"
|
|
334
|
+
glyph="A"
|
|
335
|
+
contentProps={inlineEditSurfaceProps}
|
|
336
|
+
onChange={(value) => onChange({ color: value })}
|
|
337
|
+
/>
|
|
338
|
+
|
|
339
|
+
<div className={TOOLBAR_DIVIDER} />
|
|
340
|
+
|
|
341
|
+
<DropdownMenu>
|
|
342
|
+
<Tooltip>
|
|
343
|
+
<TooltipTrigger asChild>
|
|
344
|
+
<DropdownMenuTrigger asChild>
|
|
345
|
+
<button
|
|
346
|
+
type="button"
|
|
347
|
+
className={ICON_MENU_CLASS}
|
|
348
|
+
aria-label={t("styleInspector.align")}
|
|
349
|
+
>
|
|
350
|
+
{(() => {
|
|
351
|
+
const Icon = alignIcon(snapshot.textAlign);
|
|
352
|
+
return <Icon className="size-4" />;
|
|
353
|
+
})()}
|
|
354
|
+
<span aria-hidden="true" className={CARET_CLASS} />
|
|
355
|
+
</button>
|
|
356
|
+
</DropdownMenuTrigger>
|
|
357
|
+
</TooltipTrigger>
|
|
358
|
+
<TooltipContent>{t("styleInspector.align")}</TooltipContent>
|
|
359
|
+
</Tooltip>
|
|
360
|
+
<DropdownMenuContent align="start" className="w-36">
|
|
361
|
+
{textAlignOptions(t).map((option) => (
|
|
362
|
+
<DropdownMenuItem
|
|
363
|
+
key={option.value}
|
|
364
|
+
onSelect={() => onChange({ textAlign: option.value })}
|
|
365
|
+
>
|
|
366
|
+
<option.icon className="size-4" />
|
|
367
|
+
{option.label}
|
|
368
|
+
</DropdownMenuItem>
|
|
369
|
+
))}
|
|
370
|
+
</DropdownMenuContent>
|
|
371
|
+
</DropdownMenu>
|
|
372
|
+
|
|
373
|
+
{onToggleList && (
|
|
374
|
+
<>
|
|
375
|
+
<Tooltip>
|
|
376
|
+
<TooltipTrigger asChild>
|
|
377
|
+
<Button
|
|
378
|
+
type="button"
|
|
379
|
+
variant="ghost"
|
|
380
|
+
size="icon"
|
|
381
|
+
className={cn(
|
|
382
|
+
MENU_BUTTON_CLASS,
|
|
383
|
+
snapshot.listKind === "bullet" && TOGGLE_ACTIVE_CLASS,
|
|
384
|
+
)}
|
|
385
|
+
aria-label={t("styleInspector.bulletList")}
|
|
386
|
+
aria-pressed={snapshot.listKind === "bullet"}
|
|
387
|
+
onClick={() => onToggleList("bullet")}
|
|
388
|
+
>
|
|
389
|
+
<IconList className="size-4" />
|
|
390
|
+
</Button>
|
|
391
|
+
</TooltipTrigger>
|
|
392
|
+
<TooltipContent>
|
|
393
|
+
{t("styleInspector.bulletList")}
|
|
394
|
+
</TooltipContent>
|
|
395
|
+
</Tooltip>
|
|
396
|
+
|
|
397
|
+
<Tooltip>
|
|
398
|
+
<TooltipTrigger asChild>
|
|
399
|
+
<Button
|
|
400
|
+
type="button"
|
|
401
|
+
variant="ghost"
|
|
402
|
+
size="icon"
|
|
403
|
+
className={cn(
|
|
404
|
+
MENU_BUTTON_CLASS,
|
|
405
|
+
snapshot.listKind === "ordered" &&
|
|
406
|
+
TOGGLE_ACTIVE_CLASS,
|
|
407
|
+
)}
|
|
408
|
+
aria-label={t("styleInspector.numberedList")}
|
|
409
|
+
aria-pressed={snapshot.listKind === "ordered"}
|
|
410
|
+
onClick={() => onToggleList("ordered")}
|
|
411
|
+
>
|
|
412
|
+
<IconListNumbers className="size-4" />
|
|
413
|
+
</Button>
|
|
414
|
+
</TooltipTrigger>
|
|
415
|
+
<TooltipContent>
|
|
416
|
+
{t("styleInspector.numberedList")}
|
|
417
|
+
</TooltipContent>
|
|
418
|
+
</Tooltip>
|
|
419
|
+
</>
|
|
420
|
+
)}
|
|
421
|
+
</>
|
|
422
|
+
) : (
|
|
423
|
+
<>
|
|
424
|
+
<VisualColorPicker
|
|
425
|
+
label={
|
|
426
|
+
snapshot.isImage
|
|
427
|
+
? t("styleInspector.tint")
|
|
428
|
+
: t("styleInspector.fill")
|
|
429
|
+
}
|
|
430
|
+
value={snapshot.backgroundColor}
|
|
431
|
+
documentColors={documentColors}
|
|
432
|
+
allowTransparent
|
|
433
|
+
variant="swatch"
|
|
434
|
+
contentProps={inlineEditSurfaceProps}
|
|
435
|
+
onChange={(value) => onChange({ backgroundColor: value })}
|
|
436
|
+
/>
|
|
437
|
+
<VisualScrubInput
|
|
438
|
+
label={t("styleInspector.opacity")}
|
|
439
|
+
icon={IconGridDots}
|
|
440
|
+
prefix="icon"
|
|
441
|
+
value={snapshot.opacity}
|
|
442
|
+
min={0}
|
|
443
|
+
max={100}
|
|
444
|
+
step={5}
|
|
445
|
+
unit="%"
|
|
446
|
+
className={SCRUB_CLASS}
|
|
447
|
+
onChange={(opacity) =>
|
|
448
|
+
onChange({ opacity: String(opacity / 100) })
|
|
449
|
+
}
|
|
450
|
+
/>
|
|
451
|
+
<VisualScrubInput
|
|
452
|
+
label={t("styleInspector.cornerRadius")}
|
|
453
|
+
icon={IconBorderRadius}
|
|
454
|
+
prefix="icon"
|
|
455
|
+
value={snapshot.borderRadius}
|
|
456
|
+
min={0}
|
|
457
|
+
max={96}
|
|
458
|
+
unit="px"
|
|
459
|
+
className={SCRUB_CLASS}
|
|
460
|
+
onChange={(radius) =>
|
|
461
|
+
onChange({ borderRadius: `${formatValue(radius)}px` })
|
|
462
|
+
}
|
|
463
|
+
/>
|
|
464
|
+
|
|
465
|
+
<div className={TOOLBAR_DIVIDER} />
|
|
466
|
+
<VisualScrubInput
|
|
467
|
+
label={t("styleInspector.strokeWeight")}
|
|
468
|
+
icon={IconBorderStyle}
|
|
469
|
+
prefix="icon"
|
|
470
|
+
value={snapshot.borderWidth}
|
|
471
|
+
min={0}
|
|
472
|
+
max={16}
|
|
473
|
+
unit="px"
|
|
474
|
+
className={SCRUB_CLASS}
|
|
475
|
+
onChange={(width) =>
|
|
476
|
+
onChange({ borderWidth: `${formatValue(width)}px` })
|
|
477
|
+
}
|
|
478
|
+
/>
|
|
479
|
+
<VisualColorPicker
|
|
480
|
+
label={t("styleInspector.strokeColor")}
|
|
481
|
+
value={snapshot.borderColor}
|
|
482
|
+
documentColors={documentColors}
|
|
483
|
+
variant="swatch"
|
|
484
|
+
contentProps={inlineEditSurfaceProps}
|
|
485
|
+
onChange={(value) => onChange({ borderColor: value })}
|
|
486
|
+
/>
|
|
487
|
+
</>
|
|
488
|
+
)}
|
|
489
|
+
|
|
490
|
+
<div className={TOOLBAR_DIVIDER} />
|
|
491
|
+
|
|
492
|
+
{snapshot.isAbsolute && (
|
|
493
|
+
<Popover>
|
|
494
|
+
<Tooltip>
|
|
495
|
+
<TooltipTrigger asChild>
|
|
496
|
+
<PopoverTrigger asChild>
|
|
497
|
+
<Button
|
|
498
|
+
type="button"
|
|
499
|
+
variant="ghost"
|
|
500
|
+
size="icon"
|
|
501
|
+
className={MENU_BUTTON_CLASS}
|
|
502
|
+
aria-label={t("styleInspector.position")}
|
|
503
|
+
>
|
|
504
|
+
<IconLayoutAlignLeft className="size-3.5" />
|
|
505
|
+
</Button>
|
|
506
|
+
</PopoverTrigger>
|
|
507
|
+
</TooltipTrigger>
|
|
508
|
+
<TooltipContent>{t("styleInspector.position")}</TooltipContent>
|
|
509
|
+
</Tooltip>
|
|
510
|
+
<PopoverContent
|
|
511
|
+
align="start"
|
|
512
|
+
className="w-60 space-y-2 p-2"
|
|
513
|
+
{...inlineEditSurfaceProps}
|
|
514
|
+
>
|
|
515
|
+
{snapshot.isAbsolute && (
|
|
516
|
+
<>
|
|
517
|
+
<VisualControlRow label={t("styleInspector.horizontal")}>
|
|
518
|
+
<VisualSegmentedControl
|
|
519
|
+
value={resolveHorizontalAlignment(snapshot)}
|
|
520
|
+
onChange={(alignment) =>
|
|
521
|
+
onChange(horizontalAlignPatch(snapshot, alignment))
|
|
522
|
+
}
|
|
523
|
+
className="slides-inspector-segment"
|
|
524
|
+
options={[
|
|
525
|
+
{ label: t("styleInspector.left"), value: "left" },
|
|
526
|
+
{
|
|
527
|
+
label: t("styleInspector.center"),
|
|
528
|
+
value: "center",
|
|
529
|
+
},
|
|
530
|
+
{ label: t("styleInspector.right"), value: "right" },
|
|
531
|
+
]}
|
|
532
|
+
/>
|
|
533
|
+
</VisualControlRow>
|
|
534
|
+
<VisualControlRow label={t("styleInspector.vertical")}>
|
|
535
|
+
<VisualSegmentedControl
|
|
536
|
+
value={resolveVerticalAlignment(snapshot)}
|
|
537
|
+
onChange={(alignment) =>
|
|
538
|
+
onChange(verticalAlignPatch(snapshot, alignment))
|
|
539
|
+
}
|
|
540
|
+
className="slides-inspector-segment"
|
|
541
|
+
options={[
|
|
542
|
+
{ label: t("styleInspector.top"), value: "top" },
|
|
543
|
+
{
|
|
544
|
+
label: t("styleInspector.middle"),
|
|
545
|
+
value: "middle",
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
label: t("styleInspector.bottom"),
|
|
549
|
+
value: "bottom",
|
|
550
|
+
},
|
|
551
|
+
]}
|
|
552
|
+
/>
|
|
553
|
+
</VisualControlRow>
|
|
554
|
+
</>
|
|
555
|
+
)}
|
|
556
|
+
</PopoverContent>
|
|
557
|
+
</Popover>
|
|
558
|
+
)}
|
|
559
|
+
|
|
560
|
+
{snapshot.isAbsolute && onArrange && (
|
|
561
|
+
<>
|
|
562
|
+
<Tooltip>
|
|
563
|
+
<TooltipTrigger asChild>
|
|
564
|
+
<Button
|
|
565
|
+
type="button"
|
|
566
|
+
variant="ghost"
|
|
567
|
+
size="icon"
|
|
568
|
+
className={MENU_BUTTON_CLASS}
|
|
569
|
+
onClick={() => onArrange("back")}
|
|
570
|
+
aria-label={t("styleInspector.sendToBack")}
|
|
571
|
+
>
|
|
572
|
+
<IconStackBack className="size-3.5" />
|
|
573
|
+
</Button>
|
|
574
|
+
</TooltipTrigger>
|
|
575
|
+
<TooltipContent>
|
|
576
|
+
{t("styleInspector.sendToBack")}
|
|
577
|
+
</TooltipContent>
|
|
578
|
+
</Tooltip>
|
|
579
|
+
<Tooltip>
|
|
580
|
+
<TooltipTrigger asChild>
|
|
581
|
+
<Button
|
|
582
|
+
type="button"
|
|
583
|
+
variant="ghost"
|
|
584
|
+
size="icon"
|
|
585
|
+
className={MENU_BUTTON_CLASS}
|
|
586
|
+
onClick={() => onArrange("front")}
|
|
587
|
+
aria-label={t("styleInspector.bringToFront")}
|
|
588
|
+
>
|
|
589
|
+
<IconStackFront className="size-3.5" />
|
|
590
|
+
</Button>
|
|
591
|
+
</TooltipTrigger>
|
|
592
|
+
<TooltipContent>
|
|
593
|
+
{t("styleInspector.bringToFront")}
|
|
594
|
+
</TooltipContent>
|
|
595
|
+
</Tooltip>
|
|
596
|
+
</>
|
|
597
|
+
)}
|
|
598
|
+
|
|
599
|
+
<Popover>
|
|
600
|
+
<Tooltip>
|
|
601
|
+
<TooltipTrigger asChild>
|
|
602
|
+
<PopoverTrigger asChild>
|
|
603
|
+
<Button
|
|
604
|
+
type="button"
|
|
605
|
+
variant="ghost"
|
|
606
|
+
size="icon"
|
|
607
|
+
className={MENU_BUTTON_CLASS}
|
|
608
|
+
aria-label={t("styleInspector.controls")}
|
|
609
|
+
>
|
|
610
|
+
<IconDots className="size-3.5" />
|
|
611
|
+
</Button>
|
|
612
|
+
</PopoverTrigger>
|
|
613
|
+
</TooltipTrigger>
|
|
614
|
+
<TooltipContent>{t("styleInspector.controls")}</TooltipContent>
|
|
615
|
+
</Tooltip>
|
|
616
|
+
<PopoverContent
|
|
617
|
+
align="end"
|
|
618
|
+
className="w-64 space-y-3 p-2"
|
|
619
|
+
{...inlineEditSurfaceProps}
|
|
620
|
+
>
|
|
621
|
+
<div className="grid grid-cols-2 gap-2">
|
|
622
|
+
<VisualScrubInput
|
|
623
|
+
label={t("styleInspector.width")}
|
|
624
|
+
icon={IconArrowAutofitWidth}
|
|
625
|
+
prefix="icon"
|
|
626
|
+
value={snapshot.width}
|
|
627
|
+
min={0}
|
|
628
|
+
unit="px"
|
|
629
|
+
onChange={(width) =>
|
|
630
|
+
onChange({ width: `${formatValue(width)}px` })
|
|
631
|
+
}
|
|
632
|
+
/>
|
|
633
|
+
<VisualScrubInput
|
|
634
|
+
label={t("styleInspector.height")}
|
|
635
|
+
icon={IconArrowAutofitHeight}
|
|
636
|
+
prefix="icon"
|
|
637
|
+
value={snapshot.height}
|
|
638
|
+
min={0}
|
|
639
|
+
unit="px"
|
|
640
|
+
onChange={(height) =>
|
|
641
|
+
onChange({ height: `${formatValue(height)}px` })
|
|
642
|
+
}
|
|
643
|
+
/>
|
|
644
|
+
</div>
|
|
645
|
+
|
|
646
|
+
{snapshot.isAbsolute && (
|
|
647
|
+
<>
|
|
648
|
+
<div className="grid grid-cols-2 gap-2">
|
|
649
|
+
<VisualScrubInput
|
|
650
|
+
label={t("styleInspector.x")}
|
|
651
|
+
icon={null}
|
|
652
|
+
labelClassName="w-8 justify-center"
|
|
653
|
+
value={snapshot.x}
|
|
654
|
+
unit="px"
|
|
655
|
+
onChange={(x) =>
|
|
656
|
+
onChange({ left: `${formatValue(x)}px` })
|
|
657
|
+
}
|
|
658
|
+
/>
|
|
659
|
+
<VisualScrubInput
|
|
660
|
+
label={t("styleInspector.y")}
|
|
661
|
+
icon={null}
|
|
662
|
+
labelClassName="w-8 justify-center"
|
|
663
|
+
value={snapshot.y}
|
|
664
|
+
unit="px"
|
|
665
|
+
onChange={(y) => onChange({ top: `${formatValue(y)}px` })}
|
|
666
|
+
/>
|
|
667
|
+
</div>
|
|
668
|
+
<VisualScrubInput
|
|
669
|
+
label={t("styleInspector.rotation")}
|
|
670
|
+
icon={IconAngle}
|
|
671
|
+
prefix="icon"
|
|
672
|
+
value={snapshot.rotation}
|
|
673
|
+
min={-360}
|
|
674
|
+
max={360}
|
|
675
|
+
unit="°"
|
|
676
|
+
onChange={(rotation) =>
|
|
677
|
+
onChange({ transform: rotationTransform(rotation) })
|
|
678
|
+
}
|
|
679
|
+
/>
|
|
680
|
+
</>
|
|
681
|
+
)}
|
|
682
|
+
|
|
683
|
+
{snapshot.isText && (
|
|
684
|
+
<>
|
|
685
|
+
<div className="grid grid-cols-2 gap-2">
|
|
686
|
+
<VisualScrubInput
|
|
687
|
+
label={t("styleInspector.opacity")}
|
|
688
|
+
icon={IconGridDots}
|
|
689
|
+
prefix="icon"
|
|
690
|
+
value={snapshot.opacity}
|
|
691
|
+
min={0}
|
|
692
|
+
max={100}
|
|
693
|
+
step={5}
|
|
694
|
+
unit="%"
|
|
695
|
+
onChange={(opacity) =>
|
|
696
|
+
onChange({ opacity: String(opacity / 100) })
|
|
697
|
+
}
|
|
698
|
+
/>
|
|
699
|
+
<VisualScrubInput
|
|
700
|
+
label={t("styleInspector.cornerRadius")}
|
|
701
|
+
icon={IconBorderRadius}
|
|
702
|
+
prefix="icon"
|
|
703
|
+
value={snapshot.borderRadius}
|
|
704
|
+
min={0}
|
|
705
|
+
max={96}
|
|
706
|
+
unit="px"
|
|
707
|
+
onChange={(radius) =>
|
|
708
|
+
onChange({ borderRadius: `${formatValue(radius)}px` })
|
|
709
|
+
}
|
|
710
|
+
/>
|
|
711
|
+
</div>
|
|
712
|
+
<VisualScrubInput
|
|
713
|
+
label={t("styleInspector.strokeWeight")}
|
|
714
|
+
icon={IconBorderStyle}
|
|
715
|
+
prefix="icon"
|
|
716
|
+
value={snapshot.borderWidth}
|
|
717
|
+
min={0}
|
|
718
|
+
max={16}
|
|
719
|
+
unit="px"
|
|
720
|
+
onChange={(width) =>
|
|
721
|
+
onChange({ borderWidth: `${formatValue(width)}px` })
|
|
722
|
+
}
|
|
723
|
+
/>
|
|
724
|
+
<VisualControlRow label={t("styleInspector.strokeColor")}>
|
|
725
|
+
<VisualColorPicker
|
|
726
|
+
label={t("styleInspector.strokeColor")}
|
|
727
|
+
value={snapshot.borderColor}
|
|
728
|
+
documentColors={documentColors}
|
|
729
|
+
variant="filled"
|
|
730
|
+
className="rounded-sm"
|
|
731
|
+
contentProps={inlineEditSurfaceProps}
|
|
732
|
+
onChange={(value) => onChange({ borderColor: value })}
|
|
733
|
+
/>
|
|
734
|
+
</VisualControlRow>
|
|
735
|
+
<VisualScrubInput
|
|
736
|
+
label={t("styleInspector.line")}
|
|
737
|
+
icon={IconArrowAutofitHeight}
|
|
738
|
+
prefix="icon"
|
|
739
|
+
value={snapshot.lineHeight}
|
|
740
|
+
min={0.8}
|
|
741
|
+
max={3}
|
|
742
|
+
step={0.05}
|
|
743
|
+
onChange={(lineHeight) =>
|
|
744
|
+
onChange({ lineHeight: formatValue(lineHeight) })
|
|
745
|
+
}
|
|
746
|
+
/>
|
|
747
|
+
<VisualControlRow label={t("styleInspector.fill")}>
|
|
748
|
+
<VisualColorPicker
|
|
749
|
+
label={t("styleInspector.fill")}
|
|
750
|
+
value={snapshot.backgroundColor}
|
|
751
|
+
documentColors={documentColors}
|
|
752
|
+
allowTransparent
|
|
753
|
+
variant="filled"
|
|
754
|
+
className="rounded-sm"
|
|
755
|
+
contentProps={inlineEditSurfaceProps}
|
|
756
|
+
onChange={(value) => onChange({ backgroundColor: value })}
|
|
757
|
+
/>
|
|
758
|
+
</VisualControlRow>
|
|
759
|
+
</>
|
|
760
|
+
)}
|
|
761
|
+
|
|
762
|
+
{!snapshot.isImage && (
|
|
763
|
+
<div className="grid grid-cols-2 gap-2">
|
|
764
|
+
<VisualScrubInput
|
|
765
|
+
label={t("styleInspector.horizontal")}
|
|
766
|
+
icon={IconSpacingHorizontal}
|
|
767
|
+
prefix="icon"
|
|
768
|
+
value={snapshot.paddingX}
|
|
769
|
+
min={0}
|
|
770
|
+
max={120}
|
|
771
|
+
step={2}
|
|
772
|
+
unit="px"
|
|
773
|
+
onChange={(padding) =>
|
|
774
|
+
onChange({
|
|
775
|
+
paddingLeft: `${formatValue(padding)}px`,
|
|
776
|
+
paddingRight: `${formatValue(padding)}px`,
|
|
777
|
+
})
|
|
778
|
+
}
|
|
779
|
+
/>
|
|
780
|
+
<VisualScrubInput
|
|
781
|
+
label={t("styleInspector.vertical")}
|
|
782
|
+
icon={IconSpacingVertical}
|
|
783
|
+
prefix="icon"
|
|
784
|
+
value={snapshot.paddingY}
|
|
785
|
+
min={0}
|
|
786
|
+
max={120}
|
|
787
|
+
step={2}
|
|
788
|
+
unit="px"
|
|
789
|
+
onChange={(padding) =>
|
|
790
|
+
onChange({
|
|
791
|
+
paddingTop: `${formatValue(padding)}px`,
|
|
792
|
+
paddingBottom: `${formatValue(padding)}px`,
|
|
793
|
+
})
|
|
794
|
+
}
|
|
795
|
+
/>
|
|
796
|
+
</div>
|
|
797
|
+
)}
|
|
798
|
+
</PopoverContent>
|
|
799
|
+
</Popover>
|
|
800
|
+
</>
|
|
801
|
+
)}
|
|
802
|
+
</div>
|
|
803
|
+
);
|
|
804
|
+
}
|