@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.
Files changed (34) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +258 -0
  3. package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +130 -0
  4. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +25 -337
  5. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +73 -53
  6. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +804 -0
  7. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +101 -45
  8. package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +7 -4
  9. package/corpus/templates/slides/app/components/editor/bullet-editing.ts +11 -3
  10. package/corpus/templates/slides/app/components/editor/commit-active-edit.ts +21 -0
  11. package/corpus/templates/slides/app/components/editor/list-editing.ts +219 -0
  12. package/corpus/templates/slides/app/components/editor/selection-overlay-measurement.ts +0 -3
  13. package/corpus/templates/slides/app/components/editor/slide-style.ts +199 -0
  14. package/corpus/templates/slides/app/global.css +14 -3
  15. package/corpus/templates/slides/app/i18n/en-US.ts +8 -1
  16. package/corpus/templates/slides/app/pages/DeckEditor.tsx +48 -22
  17. package/corpus/templates/slides/changelog/2026-08-01-add-slide-moved-to-the-toolbar-and-the-slide-rail-is-now-mor.md +9 -0
  18. package/corpus/templates/slides/changelog/2026-08-01-add-slide-undo-redo-and-the-text-tool-now-lead-the-slide-too.md +9 -0
  19. package/corpus/templates/slides/changelog/2026-08-01-slide-styling-now-appears-in-a-contextual-toolbar-above-the-.md +6 -0
  20. package/corpus/templates/slides/changelog/2026-08-01-the-slide-style-side-panel-is-retired-all-styling-now-lives-.md +6 -0
  21. package/corpus/templates/slides/changelog/2026-08-03-the-slide-toolbar-is-denser-swatch-only-colors-dropdowns-for.md +10 -0
  22. package/corpus/templates/slides/changelog/2026-08-04-bullet-and-numbered-list-buttons-in-the-slide-toolbar-conver.md +6 -0
  23. package/corpus/templates/slides/changelog/2026-08-04-italic-and-underline-are-now-one-click-away-in-the-slide-too.md +6 -0
  24. package/dist/collab/awareness.d.ts +2 -2
  25. package/dist/collab/struct-routes.d.ts +1 -1
  26. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  27. package/dist/observability/routes.d.ts +3 -3
  28. package/dist/progress/routes.d.ts +1 -1
  29. package/dist/resources/handlers.d.ts +1 -1
  30. package/dist/secrets/routes.d.ts +3 -3
  31. package/dist/server/realtime-token.d.ts +1 -1
  32. package/dist/server/transcribe-voice.d.ts +1 -1
  33. package/package.json +2 -2
  34. package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +0 -763
@@ -1,763 +0,0 @@
1
- import { useT } from "@agent-native/core/client/i18n";
2
- import {
3
- VisualColorPicker,
4
- VisualControlRow,
5
- VisualInspectorPanel,
6
- VisualInspectorSection,
7
- VisualScrubInput,
8
- VisualSegmentedControl,
9
- } from "@agent-native/toolkit/design-tweaks";
10
- import type { DesignSystemData } from "@shared/api";
11
- import {
12
- IconAngle,
13
- IconArrowAutofitHeight,
14
- IconArrowAutofitWidth,
15
- IconBorderRadius,
16
- IconBorderStyle,
17
- IconBoxPadding,
18
- IconDroplet,
19
- IconGridDots,
20
- IconLetterCase,
21
- IconRuler2,
22
- IconSpacingHorizontal,
23
- IconSpacingVertical,
24
- IconStackBack,
25
- IconStackFront,
26
- IconX,
27
- } from "@tabler/icons-react";
28
-
29
- import { Button } from "@/components/ui/button";
30
- import { cn } from "@/lib/utils";
31
-
32
- import type { InlineTextStyleKey } from "./rich-text-selection";
33
-
34
- export interface SlideStyleSnapshot {
35
- /** Omitted snapshots are existing object snapshots for backward compatibility. */
36
- mode?: "object";
37
- selector: string;
38
- label: string;
39
- tagName: string;
40
- textPreview: string;
41
- isText: boolean;
42
- isImage: boolean;
43
- isAbsolute: boolean;
44
- x: number;
45
- y: number;
46
- width: number;
47
- height: number;
48
- rotation: number;
49
- slideWidth: number;
50
- slideHeight: number;
51
- color: string;
52
- backgroundColor: string;
53
- fontSize: number;
54
- fontWeight: string;
55
- lineHeight: number;
56
- textAlign: string;
57
- opacity: number;
58
- borderRadius: number;
59
- borderWidth: number;
60
- borderColor: string;
61
- paddingX: number;
62
- paddingY: number;
63
- zIndex: number;
64
- textStyleScope?: "block" | "selection";
65
- mixedTextStyles?: InlineTextStyleKey[];
66
- }
67
-
68
- /**
69
- * Slide backgrounds are a surface, rather than selectable canvas objects.
70
- * The editor passes this mode while its Style panel is open without an object
71
- * selection, so the inspector exposes only background-safe controls.
72
- */
73
- export interface SlideBackgroundStyleSnapshot {
74
- mode: "background";
75
- backgroundColor: string;
76
- }
77
-
78
- export type SlideStyleInspectorSnapshot =
79
- | SlideStyleSnapshot
80
- | SlideBackgroundStyleSnapshot;
81
-
82
- export type SlideStylePatch = Partial<{
83
- color: string;
84
- backgroundColor: string;
85
- fontSize: string;
86
- fontWeight: string;
87
- lineHeight: string;
88
- textAlign: string;
89
- opacity: string;
90
- borderRadius: string;
91
- borderWidth: string;
92
- borderColor: string;
93
- paddingLeft: string;
94
- paddingRight: string;
95
- paddingTop: string;
96
- paddingBottom: string;
97
- left: string;
98
- top: string;
99
- width: string;
100
- height: string;
101
- transform: string;
102
- zIndex: string;
103
- }>;
104
-
105
- export type SlideBackgroundStylePatch = Required<
106
- Pick<SlideStylePatch, "backgroundColor">
107
- >;
108
-
109
- function tokenPalette(
110
- designSystem: DesignSystemData | undefined,
111
- t: (key: string) => string,
112
- ) {
113
- const colors = designSystem?.colors;
114
- const base = colors
115
- ? [
116
- {
117
- label: t("styleInspector.primary"),
118
- value: colors.primary,
119
- color: colors.primary,
120
- },
121
- {
122
- label: t("styleInspector.secondary"),
123
- value: colors.secondary,
124
- color: colors.secondary,
125
- },
126
- {
127
- label: t("styleInspector.accent"),
128
- value: colors.accent,
129
- color: colors.accent,
130
- },
131
- {
132
- label: t("styleInspector.surface"),
133
- value: colors.surface,
134
- color: colors.surface,
135
- },
136
- {
137
- label: t("styleInspector.background"),
138
- value: colors.background,
139
- color: colors.background,
140
- },
141
- {
142
- label: t("styleInspector.text"),
143
- value: colors.text,
144
- color: colors.text,
145
- },
146
- {
147
- label: t("styleInspector.muted"),
148
- value: colors.textMuted,
149
- color: colors.textMuted,
150
- },
151
- ]
152
- : [];
153
-
154
- return [
155
- ...base,
156
- { label: t("styleInspector.white"), value: "#ffffff", color: "#ffffff" },
157
- { label: t("styleInspector.black"), value: "#000000", color: "#000000" },
158
- { label: t("styleInspector.slate"), value: "#1f2937", color: "#1f2937" },
159
- { label: t("styleInspector.blue"), value: "#609ff8", color: "#609ff8" },
160
- { label: t("styleInspector.cyan"), value: "#22d3ee", color: "#22d3ee" },
161
- {
162
- label: t("styleInspector.emerald"),
163
- value: "#34d399",
164
- color: "#34d399",
165
- },
166
- { label: t("styleInspector.amber"), value: "#fbbf24", color: "#fbbf24" },
167
- { label: t("styleInspector.rose"), value: "#fb7185", color: "#fb7185" },
168
- ];
169
- }
170
-
171
- // `slide.background` holds either a raw CSS value or a Tailwind arbitrary
172
- // class (`bg-[#000000]`), which SlideRenderer applies as a class rather than
173
- // an inline style. The picker only speaks CSS colors, so unwrap the arbitrary
174
- // form and report anything else (named utilities, gradients) as unreadable
175
- // rather than guessing a hex the slide is not actually using.
176
- function backgroundCssValue(background: string | undefined): string | null {
177
- // SlideRenderer's own fallback when the field is unset.
178
- if (!background) return "#000000";
179
- const arbitrary = background.match(/^bg-\[(.+)\]$/);
180
- if (arbitrary) return arbitrary[1].replace(/_/g, " ");
181
- return background.startsWith("bg-") ? null : background;
182
- }
183
-
184
- /**
185
- * Rendered in the style dock when no element is selected — otherwise the
186
- * slide's own background is unreachable from the editor UI.
187
- */
188
- export function SlideBackgroundInspector({
189
- background,
190
- designSystem,
191
- className,
192
- onChange,
193
- onClose,
194
- }: {
195
- background: string | undefined;
196
- designSystem?: DesignSystemData;
197
- className?: string;
198
- onChange: (background: string) => void;
199
- onClose?: () => void;
200
- }) {
201
- const t = useT();
202
- const documentColors = tokenPalette(designSystem, t).map(
203
- (option) => option.value,
204
- );
205
- const solid = backgroundCssValue(background);
206
-
207
- return (
208
- <VisualInspectorPanel
209
- title={t("styleInspector.title")}
210
- subtitle={t("styleInspector.slide")}
211
- className={className}
212
- headerAction={
213
- onClose ? (
214
- <Button
215
- type="button"
216
- variant="ghost"
217
- size="icon"
218
- className="size-7 cursor-pointer text-muted-foreground hover:text-foreground"
219
- onClick={onClose}
220
- aria-label={t("styleInspector.close")}
221
- >
222
- <IconX className="size-3.5" />
223
- </Button>
224
- ) : undefined
225
- }
226
- >
227
- <VisualInspectorSection
228
- title={
229
- <span className="inline-flex items-center gap-1.5">
230
- <IconDroplet className="size-3" />
231
- {t("styleInspector.slideBackground")}
232
- </span>
233
- }
234
- >
235
- <VisualControlRow label={t("styleInspector.background")}>
236
- <VisualColorPicker
237
- label={t("styleInspector.slideBackground")}
238
- value={solid ?? "#000000"}
239
- mixed={solid === null}
240
- mixedLabel={t("styleInspector.mixed")}
241
- documentColors={documentColors}
242
- onChange={onChange}
243
- />
244
- </VisualControlRow>
245
- </VisualInspectorSection>
246
- </VisualInspectorPanel>
247
- );
248
- }
249
-
250
- function formatValue(value: number) {
251
- return Number.isInteger(value)
252
- ? String(value)
253
- : String(Number(value.toFixed(2)));
254
- }
255
-
256
- function rotationTransform(rotation: number) {
257
- return `rotate(${formatValue(rotation)}deg)`;
258
- }
259
-
260
- export function SlideStyleInspector({
261
- snapshot,
262
- designSystem,
263
- className,
264
- onChange,
265
- onArrange,
266
- onClose,
267
- }: {
268
- snapshot: SlideStyleInspectorSnapshot;
269
- designSystem?: DesignSystemData;
270
- className?: string;
271
- onChange: (patch: SlideStylePatch) => void;
272
- /** Bring-to-front / send-to-back for the selected freeform object. Omitted
273
- * (or the snapshot not being a freeform object) hides the Arrange row. */
274
- onArrange?: (target: "front" | "back") => void;
275
- onClose: () => void;
276
- }) {
277
- const t = useT();
278
- const palette = tokenPalette(designSystem, t);
279
- const documentColors = palette.map((option) => option.value);
280
- const inlineEditSurfaceProps = {
281
- "data-slide-inline-edit-surface": "true",
282
- };
283
- const inspectorClassName = cn(
284
- "slide-style-inspector h-full w-full rounded-none border-0 bg-transparent text-foreground shadow-none backdrop-blur-none",
285
- className,
286
- );
287
- const headerAction = (
288
- <Button
289
- type="button"
290
- variant="ghost"
291
- size="icon"
292
- className="size-7 shrink-0 text-muted-foreground hover:text-foreground"
293
- onClick={onClose}
294
- aria-label={t("styleInspector.close")}
295
- >
296
- <IconX className="size-3.5" />
297
- </Button>
298
- );
299
-
300
- if (snapshot.mode === "background") {
301
- return (
302
- <VisualInspectorPanel
303
- title={t("styleInspector.title")}
304
- subtitle={t("styleInspector.background")}
305
- className={inspectorClassName}
306
- headerAction={headerAction}
307
- >
308
- <VisualInspectorSection
309
- title={
310
- <span className="inline-flex items-center gap-1.5">
311
- <IconDroplet className="size-3.5" />
312
- {t("styleInspector.fill")}
313
- </span>
314
- }
315
- className="slides-inspector-section"
316
- >
317
- <VisualControlRow
318
- label={t("styleInspector.color")}
319
- className="slides-inspector-control"
320
- >
321
- <VisualColorPicker
322
- label={t("styleInspector.fill")}
323
- value={snapshot.backgroundColor}
324
- documentColors={documentColors}
325
- allowTransparent
326
- variant="filled"
327
- className="rounded-sm bg-[var(--slides-inspector-control-background)] hover:bg-[var(--slides-inspector-control-background)]"
328
- contentProps={inlineEditSurfaceProps}
329
- onChange={(value) => onChange({ backgroundColor: value })}
330
- />
331
- </VisualControlRow>
332
- </VisualInspectorSection>
333
- </VisualInspectorPanel>
334
- );
335
- }
336
-
337
- const mixedTextStyles = snapshot.mixedTextStyles ?? [];
338
- const targetLabel =
339
- snapshot.textPreview || snapshot.label || snapshot.tagName.toUpperCase();
340
- const horizontalAlignment =
341
- snapshot.x <= 0
342
- ? "left"
343
- : Math.abs(snapshot.x - (snapshot.slideWidth - snapshot.width) / 2) < 1
344
- ? "center"
345
- : "right";
346
- const verticalAlignment =
347
- snapshot.y <= 0
348
- ? "top"
349
- : Math.abs(snapshot.y - (snapshot.slideHeight - snapshot.height) / 2) < 1
350
- ? "middle"
351
- : "bottom";
352
-
353
- const alignHorizontal = (alignment: string) => {
354
- const available = Math.max(0, snapshot.slideWidth - snapshot.width);
355
- const x =
356
- alignment === "left"
357
- ? 0
358
- : alignment === "center"
359
- ? available / 2
360
- : available;
361
- onChange({ left: `${formatValue(x)}px` });
362
- };
363
- const alignVertical = (alignment: string) => {
364
- const available = Math.max(0, snapshot.slideHeight - snapshot.height);
365
- const y =
366
- alignment === "top"
367
- ? 0
368
- : alignment === "middle"
369
- ? available / 2
370
- : available;
371
- onChange({ top: `${formatValue(y)}px` });
372
- };
373
-
374
- return (
375
- <VisualInspectorPanel
376
- title={t("styleInspector.title")}
377
- subtitle={targetLabel}
378
- className={inspectorClassName}
379
- headerAction={headerAction}
380
- >
381
- {snapshot.isAbsolute ? (
382
- <VisualInspectorSection
383
- title={
384
- <span className="inline-flex items-center gap-1.5">
385
- <IconSpacingHorizontal className="size-3.5" />
386
- {t("styleInspector.position")}
387
- </span>
388
- }
389
- className="slides-inspector-section"
390
- >
391
- <VisualControlRow
392
- label={t("styleInspector.horizontal")}
393
- className="slides-inspector-control"
394
- >
395
- <VisualSegmentedControl
396
- value={horizontalAlignment}
397
- onChange={alignHorizontal}
398
- className="slides-inspector-segment"
399
- options={[
400
- { label: t("styleInspector.left"), value: "left" },
401
- { label: t("styleInspector.center"), value: "center" },
402
- { label: t("styleInspector.right"), value: "right" },
403
- ]}
404
- />
405
- </VisualControlRow>
406
- <VisualControlRow
407
- label={t("styleInspector.vertical")}
408
- className="slides-inspector-control"
409
- >
410
- <VisualSegmentedControl
411
- value={verticalAlignment}
412
- onChange={alignVertical}
413
- className="slides-inspector-segment"
414
- options={[
415
- { label: t("styleInspector.top"), value: "top" },
416
- { label: t("styleInspector.middle"), value: "middle" },
417
- { label: t("styleInspector.bottom"), value: "bottom" },
418
- ]}
419
- />
420
- </VisualControlRow>
421
- <div className="grid grid-cols-2 gap-2">
422
- <VisualScrubInput
423
- label={t("styleInspector.x")}
424
- icon={null}
425
- labelClassName="w-8 justify-center"
426
- value={snapshot.x}
427
- unit="px"
428
- onChange={(x) => onChange({ left: `${formatValue(x)}px` })}
429
- />
430
- <VisualScrubInput
431
- label={t("styleInspector.y")}
432
- icon={null}
433
- labelClassName="w-8 justify-center"
434
- value={snapshot.y}
435
- unit="px"
436
- onChange={(y) => onChange({ top: `${formatValue(y)}px` })}
437
- />
438
- </div>
439
- <VisualScrubInput
440
- label={t("styleInspector.rotation")}
441
- icon={IconAngle}
442
- prefix="icon"
443
- value={snapshot.rotation}
444
- min={-360}
445
- max={360}
446
- unit="°"
447
- onChange={(rotation) =>
448
- onChange({ transform: rotationTransform(rotation) })
449
- }
450
- />
451
- </VisualInspectorSection>
452
- ) : null}
453
-
454
- {snapshot.isAbsolute && onArrange ? (
455
- <VisualInspectorSection
456
- title={
457
- <span className="inline-flex items-center gap-1.5">
458
- <IconStackFront className="size-3.5" />
459
- {t("styleInspector.arrange")}
460
- </span>
461
- }
462
- className="slides-inspector-section"
463
- >
464
- <VisualControlRow
465
- label={t("styleInspector.order")}
466
- className="slides-inspector-control"
467
- >
468
- <div className="flex items-center gap-1">
469
- <Button
470
- type="button"
471
- variant="ghost"
472
- size="icon"
473
- className="size-7 cursor-pointer rounded-sm bg-[var(--slides-inspector-control-background)] text-muted-foreground hover:bg-[var(--slides-inspector-control-background)] hover:text-foreground"
474
- onClick={() => onArrange("back")}
475
- aria-label={t("styleInspector.sendToBack")}
476
- >
477
- <IconStackBack className="size-3.5" />
478
- </Button>
479
- <Button
480
- type="button"
481
- variant="ghost"
482
- size="icon"
483
- className="size-7 cursor-pointer rounded-sm bg-[var(--slides-inspector-control-background)] text-muted-foreground hover:bg-[var(--slides-inspector-control-background)] hover:text-foreground"
484
- onClick={() => onArrange("front")}
485
- aria-label={t("styleInspector.bringToFront")}
486
- >
487
- <IconStackFront className="size-3.5" />
488
- </Button>
489
- </div>
490
- </VisualControlRow>
491
- </VisualInspectorSection>
492
- ) : null}
493
-
494
- <VisualInspectorSection
495
- title={
496
- <span className="inline-flex items-center gap-1.5">
497
- <IconRuler2 className="size-3.5" />
498
- {t("styleInspector.layoutDimensions")}
499
- </span>
500
- }
501
- className="slides-inspector-section"
502
- >
503
- <div className="grid grid-cols-2 gap-2">
504
- <VisualScrubInput
505
- label={t("styleInspector.width")}
506
- icon={IconArrowAutofitWidth}
507
- prefix="icon"
508
- value={snapshot.width}
509
- min={0}
510
- unit="px"
511
- onChange={(width) => onChange({ width: `${formatValue(width)}px` })}
512
- />
513
- <VisualScrubInput
514
- label={t("styleInspector.height")}
515
- icon={IconArrowAutofitHeight}
516
- prefix="icon"
517
- value={snapshot.height}
518
- min={0}
519
- unit="px"
520
- onChange={(height) =>
521
- onChange({ height: `${formatValue(height)}px` })
522
- }
523
- />
524
- </div>
525
- </VisualInspectorSection>
526
-
527
- <VisualInspectorSection
528
- title={
529
- <span className="inline-flex items-center gap-1.5">
530
- <IconBorderRadius className="size-3.5" />
531
- {t("styleInspector.appearance")}
532
- </span>
533
- }
534
- className="slides-inspector-section"
535
- >
536
- <div className="grid grid-cols-2 gap-2">
537
- <VisualScrubInput
538
- label={t("styleInspector.opacity")}
539
- icon={IconGridDots}
540
- prefix="icon"
541
- value={snapshot.opacity}
542
- min={0}
543
- max={100}
544
- step={5}
545
- unit="%"
546
- onChange={(opacity) => onChange({ opacity: String(opacity / 100) })}
547
- />
548
- <VisualScrubInput
549
- label={t("styleInspector.cornerRadius")}
550
- icon={IconBorderRadius}
551
- prefix="icon"
552
- value={snapshot.borderRadius}
553
- min={0}
554
- max={96}
555
- unit="px"
556
- onChange={(radius) =>
557
- onChange({ borderRadius: `${formatValue(radius)}px` })
558
- }
559
- />
560
- </div>
561
- </VisualInspectorSection>
562
-
563
- <VisualInspectorSection
564
- title={
565
- <span className="inline-flex items-center gap-1.5">
566
- <IconDroplet className="size-3.5" />
567
- {snapshot.isImage
568
- ? t("styleInspector.tint")
569
- : t("styleInspector.fill")}
570
- </span>
571
- }
572
- className="slides-inspector-section"
573
- >
574
- <VisualControlRow
575
- label={t("styleInspector.color")}
576
- className="slides-inspector-control"
577
- >
578
- <VisualColorPicker
579
- label={
580
- snapshot.isImage
581
- ? t("styleInspector.tint")
582
- : t("styleInspector.fill")
583
- }
584
- value={snapshot.backgroundColor}
585
- documentColors={documentColors}
586
- allowTransparent
587
- variant="filled"
588
- className="rounded-sm bg-[var(--slides-inspector-control-background)] hover:bg-[var(--slides-inspector-control-background)]"
589
- contentProps={inlineEditSurfaceProps}
590
- onChange={(value) => onChange({ backgroundColor: value })}
591
- />
592
- </VisualControlRow>
593
- </VisualInspectorSection>
594
-
595
- <VisualInspectorSection
596
- title={
597
- <span className="inline-flex items-center gap-1.5">
598
- <IconBorderRadius className="size-3.5" />
599
- {t("styleInspector.stroke")}
600
- </span>
601
- }
602
- className="slides-inspector-section"
603
- >
604
- <VisualScrubInput
605
- label={t("styleInspector.strokeWeight")}
606
- icon={IconBorderStyle}
607
- prefix="icon"
608
- value={snapshot.borderWidth}
609
- min={0}
610
- max={16}
611
- unit="px"
612
- onChange={(width) =>
613
- onChange({ borderWidth: `${formatValue(width)}px` })
614
- }
615
- />
616
- <VisualControlRow
617
- label={t("styleInspector.strokeColor")}
618
- className="slides-inspector-control"
619
- >
620
- <VisualColorPicker
621
- label={t("styleInspector.strokeColor")}
622
- value={snapshot.borderColor}
623
- documentColors={documentColors}
624
- variant="filled"
625
- className="rounded-sm bg-[var(--slides-inspector-control-background)] hover:bg-[var(--slides-inspector-control-background)]"
626
- contentProps={inlineEditSurfaceProps}
627
- onChange={(value) => onChange({ borderColor: value })}
628
- />
629
- </VisualControlRow>
630
- </VisualInspectorSection>
631
-
632
- {snapshot.isText ? (
633
- <VisualInspectorSection
634
- title={
635
- <span className="inline-flex items-center gap-1.5">
636
- <IconLetterCase className="size-3.5" />
637
- {t("styleInspector.typography")}
638
- </span>
639
- }
640
- className="slides-inspector-section"
641
- >
642
- <VisualControlRow
643
- label={t("styleInspector.color")}
644
- className="slides-inspector-control"
645
- >
646
- <VisualColorPicker
647
- label={t("styleInspector.textColor")}
648
- value={snapshot.color}
649
- documentColors={documentColors}
650
- mixed={mixedTextStyles.includes("color")}
651
- mixedLabel={t("styleInspector.mixed")}
652
- variant="filled"
653
- className="rounded-sm bg-[var(--slides-inspector-control-background)] hover:bg-[var(--slides-inspector-control-background)]"
654
- contentProps={inlineEditSurfaceProps}
655
- onChange={(value) => onChange({ color: value })}
656
- />
657
- </VisualControlRow>
658
- <div className="grid grid-cols-2 gap-2">
659
- <VisualScrubInput
660
- label={t("styleInspector.size")}
661
- icon={IconLetterCase}
662
- prefix="icon"
663
- value={snapshot.fontSize}
664
- min={8}
665
- max={160}
666
- unit="px"
667
- mixed={mixedTextStyles.includes("fontSize")}
668
- mixedLabel={t("styleInspector.mixed")}
669
- onChange={(fontSize) =>
670
- onChange({ fontSize: `${formatValue(fontSize)}px` })
671
- }
672
- />
673
- <VisualScrubInput
674
- label={t("styleInspector.line")}
675
- icon={IconArrowAutofitHeight}
676
- prefix="icon"
677
- value={snapshot.lineHeight}
678
- min={0.8}
679
- max={3}
680
- step={0.05}
681
- onChange={(lineHeight) =>
682
- onChange({ lineHeight: formatValue(lineHeight) })
683
- }
684
- />
685
- </div>
686
- <VisualSegmentedControl
687
- value={
688
- mixedTextStyles.includes("fontWeight")
689
- ? null
690
- : snapshot.fontWeight
691
- }
692
- onChange={(fontWeight) => onChange({ fontWeight })}
693
- className="slides-inspector-segment"
694
- options={[
695
- { label: t("styleInspector.regular"), value: "400" },
696
- { label: t("styleInspector.medium"), value: "500" },
697
- { label: t("styleInspector.semi"), value: "600" },
698
- { label: t("styleInspector.bold"), value: "700" },
699
- ]}
700
- />
701
- <VisualSegmentedControl
702
- value={snapshot.textAlign}
703
- onChange={(textAlign) => onChange({ textAlign })}
704
- className="slides-inspector-segment"
705
- options={[
706
- { label: t("styleInspector.left"), value: "left" },
707
- { label: t("styleInspector.center"), value: "center" },
708
- { label: t("styleInspector.right"), value: "right" },
709
- { label: t("styleInspector.justify"), value: "justify" },
710
- ]}
711
- />
712
- </VisualInspectorSection>
713
- ) : null}
714
-
715
- {!snapshot.isImage ? (
716
- <VisualInspectorSection
717
- title={
718
- <span className="inline-flex items-center gap-1.5">
719
- <IconBoxPadding className="size-3.5" />
720
- {t("styleInspector.spacing")}
721
- </span>
722
- }
723
- className="slides-inspector-section"
724
- >
725
- <div className="grid grid-cols-2 gap-2">
726
- <VisualScrubInput
727
- label={t("styleInspector.horizontal")}
728
- icon={IconSpacingHorizontal}
729
- prefix="icon"
730
- value={snapshot.paddingX}
731
- min={0}
732
- max={120}
733
- step={2}
734
- unit="px"
735
- onChange={(padding) =>
736
- onChange({
737
- paddingLeft: `${formatValue(padding)}px`,
738
- paddingRight: `${formatValue(padding)}px`,
739
- })
740
- }
741
- />
742
- <VisualScrubInput
743
- label={t("styleInspector.vertical")}
744
- icon={IconSpacingVertical}
745
- prefix="icon"
746
- value={snapshot.paddingY}
747
- min={0}
748
- max={120}
749
- step={2}
750
- unit="px"
751
- onChange={(padding) =>
752
- onChange({
753
- paddingTop: `${formatValue(padding)}px`,
754
- paddingBottom: `${formatValue(padding)}px`,
755
- })
756
- }
757
- />
758
- </div>
759
- </VisualInspectorSection>
760
- ) : null}
761
- </VisualInspectorPanel>
762
- );
763
- }