@agent-native/core 0.80.0 → 0.80.1
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 +2 -2
- package/corpus/core/CHANGELOG.md +8 -0
- package/corpus/core/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/de-DE/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/de-DE/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/es-ES/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/es-ES/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +16 -1
- package/corpus/core/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +14 -1
- package/corpus/core/docs/content/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/template-design.mdx +14 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/application-state/handlers.ts +1 -1
- package/corpus/core/src/brand-kit/index.ts +1 -1
- package/corpus/core/src/cli/skills.ts +18 -0
- package/corpus/core/src/client/AgentPanel.tsx +1 -1
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +1 -1
- package/corpus/core/src/client/extensions/portable-extension.ts +1 -1
- package/corpus/core/src/code-agents/transcript-normalizer.ts +1 -1
- package/corpus/core/src/extensions/path.ts +1 -4
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +1 -1
- package/corpus/core/src/mcp/build-server.ts +1 -1
- package/corpus/core/src/org/handlers.ts +5 -5
- package/corpus/core/src/org/plugin.ts +2 -2
- package/corpus/core/src/scripts/call-agent.ts +1 -1
- package/corpus/core/src/scripts/db/safety.ts +1 -1
- package/corpus/core/src/server/auth.ts +3 -0
- package/corpus/core/src/server/builder-design-systems.ts +308 -0
- package/corpus/core/src/server/core-routes-plugin.ts +29 -3
- package/corpus/core/src/server/index.ts +8 -0
- package/corpus/core/src/styles/agent-native.css +14 -7
- package/corpus/core/src/usage/store.ts +2 -2
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +298 -15
- package/corpus/templates/analytics/actions/get-sql-dashboard.ts +9 -4
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +18 -2
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +12 -2
- package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +66 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +37 -0
- package/corpus/templates/analytics/app/i18n-data.ts +404 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +333 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +2 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +121 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +49 -26
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +137 -36
- package/corpus/templates/analytics/changelog/2026-06-29-analytics-agents-can-now-place-new-charts-into-requested-das.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-analytics-queries-now-report-failed-source-query-errors-inst.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-charts-can-now-be-dropped-between-full-dashboard-rows-to-cre.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-dashboard-email-reports-include-the-full-dashboard-image-wit.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-main-surface-border-reaches-the-top.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-session-replay-now-records-playable-sessions-in-production-a.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-set-up-session-replay-storage-from-settings-connect-builder-.md +6 -0
- package/corpus/templates/analytics/scripts/cleanup-empty-session-recordings.ts +102 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +49 -26
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +81 -20
- package/corpus/templates/analytics/server/lib/replay-storage.ts +79 -0
- package/corpus/templates/analytics/server/lib/s3-upload-provider.ts +255 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +29 -8
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -2
- package/corpus/templates/analytics/server/plugins/onboarding.ts +97 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +24 -0
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +105 -5
- package/corpus/templates/content/actions/set-image-alt-text.ts +1 -1
- package/corpus/templates/content/shared/document-export.ts +1 -1
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -18
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +17 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +32 -5
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +259 -0
- package/corpus/templates/design/actions/update-file.ts +9 -2
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +33 -75
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +72 -9
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +41 -39
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1029 -249
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +450 -101
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +978 -154
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +7 -9
- package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +133 -66
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +1234 -344
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +144 -94
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +301 -72
- package/corpus/templates/design/app/components/design/inspector/FigmaColorPicker.tsx +876 -286
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +601 -0
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +242 -0
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +30 -7
- package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +212 -155
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +423 -0
- package/corpus/templates/design/app/components/design/inspector/figma-icons.tsx +487 -107
- package/corpus/templates/design/app/components/design/inspector/index.ts +27 -0
- package/corpus/templates/design/app/global.css +29 -3
- package/corpus/templates/design/app/i18n/zh-TW.ts +13 -0
- package/corpus/templates/design/app/i18n-data.ts +239 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +1023 -353
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +57 -221
- package/corpus/templates/design/changelog/2026-06-29-canvas-selection-no-longer-flashes-broken-prototype-html-whe.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-command-menu-selections-are-clearer-in-dark-mode.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-element-selection-now-stays-on-the-intended-layer-after-sele.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-full-view-controls-now-sit-above-frames-inst.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-tools-can-draw-shapes-create-screens-and-kee.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-now-creates-anchor-based-bezier-vectors-with-ha.md +6 -0
- package/corpus/templates/design/server/handlers/import-figma-system.ts +38 -85
- package/corpus/templates/design/server/lib/design-snapshot.ts +9 -1
- package/corpus/templates/design/shared/code-layer.ts +209 -9
- package/corpus/templates/design/shared/html-content.ts +21 -0
- package/corpus/templates/design/shared/pen-path.ts +207 -0
- package/corpus/templates/mail/app/lib/utils.ts +2 -2
- package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +9 -10
- package/corpus/templates/slides/AGENTS.md +3 -2
- package/corpus/templates/slides/actions/import-file.ts +26 -17
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +259 -0
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +35 -167
- package/corpus/templates/slides/app/components/design-system/fig-import-response.ts +9 -13
- package/corpus/templates/slides/app/context/DeckContext.tsx +1 -1
- package/corpus/templates/slides/app/i18n/ar-SA.ts +9 -37
- package/corpus/templates/slides/app/i18n/de-DE.ts +10 -25
- package/corpus/templates/slides/app/i18n/en-US.ts +9 -26
- package/corpus/templates/slides/app/i18n/es-ES.ts +10 -28
- package/corpus/templates/slides/app/i18n/fr-FR.ts +10 -28
- package/corpus/templates/slides/app/i18n/hi-IN.ts +9 -26
- package/corpus/templates/slides/app/i18n/ja-JP.ts +10 -22
- package/corpus/templates/slides/app/i18n/ko-KR.ts +9 -23
- package/corpus/templates/slides/app/i18n/pt-BR.ts +10 -28
- package/corpus/templates/slides/app/i18n/zh-CN.ts +9 -22
- package/corpus/templates/slides/app/i18n/zh-TW.ts +9 -22
- package/corpus/templates/slides/server/handlers/import-figma-system.ts +44 -12
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/videos/app/components/NewCompositionPopover.tsx +1 -1
- package/dist/application-state/handlers.js +1 -1
- package/dist/application-state/handlers.js.map +1 -1
- package/dist/brand-kit/index.d.ts +1 -1
- package/dist/brand-kit/index.js +1 -1
- package/dist/brand-kit/index.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +18 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/extensions/portable-extension.d.ts +1 -1
- package/dist/client/extensions/portable-extension.d.ts.map +1 -1
- package/dist/client/extensions/portable-extension.js.map +1 -1
- package/dist/code-agents/transcript-normalizer.js +1 -1
- package/dist/code-agents/transcript-normalizer.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/extensions/path.d.ts +1 -1
- package/dist/extensions/path.d.ts.map +1 -1
- package/dist/extensions/path.js.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +1 -1
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/mcp/build-server.d.ts +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/org/handlers.js +5 -5
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/plugin.js +2 -2
- package/dist/org/plugin.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/scripts/call-agent.js +1 -1
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/scripts/db/safety.js +1 -1
- package/dist/scripts/db/safety.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +3 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-design-systems.d.ts +28 -0
- package/dist/server/builder-design-systems.d.ts.map +1 -0
- package/dist/server/builder-design-systems.js +217 -0
- package/dist/server/builder-design-systems.js.map +1 -0
- package/dist/server/core-routes-plugin.d.ts +1 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +20 -3
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +14 -7
- package/dist/usage/store.js +2 -2
- package/dist/usage/store.js.map +1 -1
- package/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
- package/docs/content/locales/ar-SA/template-design.mdx +14 -1
- package/docs/content/locales/de-DE/skills-guide.mdx +5 -0
- package/docs/content/locales/de-DE/template-design.mdx +15 -1
- package/docs/content/locales/es-ES/skills-guide.mdx +5 -0
- package/docs/content/locales/es-ES/template-design.mdx +15 -1
- package/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
- package/docs/content/locales/fr-FR/template-design.mdx +15 -1
- package/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
- package/docs/content/locales/hi-IN/template-design.mdx +15 -1
- package/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
- package/docs/content/locales/ja-JP/template-design.mdx +16 -1
- package/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
- package/docs/content/locales/ko-KR/template-design.mdx +14 -1
- package/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
- package/docs/content/locales/pt-BR/template-design.mdx +14 -1
- package/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
- package/docs/content/locales/zh-CN/template-design.mdx +14 -1
- package/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
- package/docs/content/locales/zh-TW/template-design.mdx +14 -1
- package/docs/content/skills-guide.mdx +5 -0
- package/docs/content/template-design.mdx +14 -1
- package/package.json +1 -1
- package/corpus/templates/design/actions/import-figma-file.ts +0 -166
- package/corpus/templates/slides/server/lib/fig-design-system.ts +0 -110
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { IconPhotoPlus, IconX } from "@tabler/icons-react";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
|
|
4
|
+
import { Input } from "@/components/ui/input";
|
|
5
|
+
import {
|
|
6
|
+
Select,
|
|
7
|
+
SelectContent,
|
|
8
|
+
SelectItem,
|
|
9
|
+
SelectTrigger,
|
|
10
|
+
SelectValue,
|
|
11
|
+
} from "@/components/ui/select";
|
|
12
|
+
import {
|
|
13
|
+
Tooltip,
|
|
14
|
+
TooltipContent,
|
|
15
|
+
TooltipTrigger,
|
|
16
|
+
} from "@/components/ui/tooltip";
|
|
17
|
+
import { cn } from "@/lib/utils";
|
|
18
|
+
|
|
19
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export type ImageFitMode = "fill" | "fit" | "crop" | "tile";
|
|
22
|
+
|
|
23
|
+
export interface ImageFillValue {
|
|
24
|
+
url: string;
|
|
25
|
+
fit: ImageFitMode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const FIT_MODES: Array<{ mode: ImageFitMode; label: string }> = [
|
|
29
|
+
{ mode: "fill", label: "Fill" }, // i18n-ignore image fit mode
|
|
30
|
+
{ mode: "fit", label: "Fit" }, // i18n-ignore image fit mode
|
|
31
|
+
{ mode: "crop", label: "Crop" }, // i18n-ignore image fit mode
|
|
32
|
+
{ mode: "tile", label: "Tile" }, // i18n-ignore image fit mode
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// ─── CSS serialization ─────────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
const CHECKER_A = "#d4d4d4";
|
|
38
|
+
const CHECKERBOARD_IMAGE = `linear-gradient(45deg, ${CHECKER_A} 25%, transparent 25%), linear-gradient(-45deg, ${CHECKER_A} 25%, transparent 25%), linear-gradient(45deg, transparent 75%, ${CHECKER_A} 75%), linear-gradient(-45deg, transparent 75%, ${CHECKER_A} 75%)`;
|
|
39
|
+
const FIT_MARKER_RE =
|
|
40
|
+
/\/\*\s*agent-native-image-fit:(fill|fit|crop|tile)\s*\*\//i;
|
|
41
|
+
|
|
42
|
+
function imageFitMarker(fit: ImageFitMode): string {
|
|
43
|
+
return `/* agent-native-image-fit:${fit} */`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Build the CSS `background` shorthand for an image fill.
|
|
48
|
+
* Maps Figma's fit semantics onto background-size / background-repeat:
|
|
49
|
+
* - Fill → cover, no-repeat
|
|
50
|
+
* - Fit → contain, no-repeat
|
|
51
|
+
* - Crop → cover, no-repeat (cropped to the box; identical CSS to Fill but
|
|
52
|
+
* kept distinct so the selection round-trips)
|
|
53
|
+
* - Tile → auto, repeat
|
|
54
|
+
*/
|
|
55
|
+
export function imageFillToCss(value: ImageFillValue): string {
|
|
56
|
+
const url = value.url.trim();
|
|
57
|
+
if (!url) return "transparent";
|
|
58
|
+
const safeUrl = url.replace(/["')]/g, encodeURIComponent);
|
|
59
|
+
const image = `url("${safeUrl}")`;
|
|
60
|
+
switch (value.fit) {
|
|
61
|
+
case "fit":
|
|
62
|
+
return `${image} center / contain no-repeat ${imageFitMarker("fit")}`;
|
|
63
|
+
case "tile":
|
|
64
|
+
return `${image} top left / auto repeat ${imageFitMarker("tile")}`;
|
|
65
|
+
case "crop":
|
|
66
|
+
return `${image} center / cover no-repeat ${imageFitMarker("crop")}`;
|
|
67
|
+
case "fill":
|
|
68
|
+
default:
|
|
69
|
+
return `${image} center / cover no-repeat ${imageFitMarker("fill")}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const URL_RE = /url\((['"]?)([^'")]+)\1\)/i;
|
|
74
|
+
|
|
75
|
+
/** Extract the URL + fit mode from a CSS background value, if present. */
|
|
76
|
+
export function parseImageFillCss(value: string): ImageFillValue | null {
|
|
77
|
+
const match = value.match(URL_RE);
|
|
78
|
+
if (!match) return null;
|
|
79
|
+
const url = match[2];
|
|
80
|
+
const marker = value.match(FIT_MARKER_RE)?.[1] as ImageFitMode | undefined;
|
|
81
|
+
if (marker) return { url, fit: marker };
|
|
82
|
+
let fit: ImageFitMode = "fill";
|
|
83
|
+
if (/contain/i.test(value)) fit = "fit";
|
|
84
|
+
else if (/repeat(?!\s+no)/i.test(value) && !/no-repeat/i.test(value))
|
|
85
|
+
fit = "tile";
|
|
86
|
+
else if (/cover/i.test(value)) fit = "fill";
|
|
87
|
+
return { url, fit };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Component ─────────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
export interface ImageFillControlsProps {
|
|
93
|
+
value: ImageFillValue;
|
|
94
|
+
onChange: (value: ImageFillValue) => void;
|
|
95
|
+
disabled?: boolean;
|
|
96
|
+
className?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function ImageFillControls({
|
|
100
|
+
value,
|
|
101
|
+
onChange,
|
|
102
|
+
disabled = false,
|
|
103
|
+
className,
|
|
104
|
+
}: ImageFillControlsProps) {
|
|
105
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
106
|
+
const [urlDraft, setUrlDraft] = useState(value.url);
|
|
107
|
+
|
|
108
|
+
const commitUrl = () => {
|
|
109
|
+
onChange({ ...value, url: urlDraft.trim() });
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handleFilePick = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
113
|
+
const file = event.target.files?.[0];
|
|
114
|
+
if (!file) return;
|
|
115
|
+
const reader = new FileReader();
|
|
116
|
+
reader.onload = () => {
|
|
117
|
+
const dataUrl = typeof reader.result === "string" ? reader.result : "";
|
|
118
|
+
if (dataUrl) {
|
|
119
|
+
setUrlDraft(dataUrl);
|
|
120
|
+
onChange({ ...value, url: dataUrl });
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
reader.readAsDataURL(file);
|
|
124
|
+
// Allow re-selecting the same file later.
|
|
125
|
+
event.target.value = "";
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div className={cn("space-y-1.5 px-3 pt-2 pb-2", className)}>
|
|
130
|
+
{/* ── Preview / drop target ─────────────────────────────────────────── */}
|
|
131
|
+
<div
|
|
132
|
+
className="relative h-24 w-full overflow-hidden rounded-md border border-border/60"
|
|
133
|
+
style={{
|
|
134
|
+
backgroundImage: value.url
|
|
135
|
+
? `url("${value.url}")`
|
|
136
|
+
: CHECKERBOARD_IMAGE,
|
|
137
|
+
backgroundSize: value.url
|
|
138
|
+
? value.fit === "fit"
|
|
139
|
+
? "contain"
|
|
140
|
+
: value.fit === "tile"
|
|
141
|
+
? "auto"
|
|
142
|
+
: "cover"
|
|
143
|
+
: "8px 8px, 8px 8px, 8px 8px, 8px 8px",
|
|
144
|
+
backgroundRepeat: value.fit === "tile" ? "repeat" : "no-repeat",
|
|
145
|
+
backgroundPosition: "center",
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
{!value.url && (
|
|
149
|
+
<div className="absolute inset-0 flex flex-col items-center justify-center gap-1 text-muted-foreground">
|
|
150
|
+
<IconPhotoPlus className="size-5" />
|
|
151
|
+
<span className="text-[10px]">
|
|
152
|
+
{"Upload or paste a URL" /* i18n-ignore */}
|
|
153
|
+
</span>
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
{value.url && (
|
|
157
|
+
<Tooltip>
|
|
158
|
+
<TooltipTrigger asChild>
|
|
159
|
+
<button
|
|
160
|
+
type="button"
|
|
161
|
+
aria-label={"Remove image" /* i18n-ignore */}
|
|
162
|
+
disabled={disabled}
|
|
163
|
+
onClick={() => {
|
|
164
|
+
setUrlDraft("");
|
|
165
|
+
onChange({ ...value, url: "" });
|
|
166
|
+
}}
|
|
167
|
+
className="absolute right-1 top-1 flex size-5 items-center justify-center rounded bg-black/50 text-white hover:bg-black/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
168
|
+
>
|
|
169
|
+
<IconX className="size-3" />
|
|
170
|
+
</button>
|
|
171
|
+
</TooltipTrigger>
|
|
172
|
+
<TooltipContent>{"Remove image" /* i18n-ignore */}</TooltipContent>
|
|
173
|
+
</Tooltip>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
{/* ── URL input + upload ────────────────────────────────────────────── */}
|
|
178
|
+
<div className="flex items-center gap-1">
|
|
179
|
+
<Input
|
|
180
|
+
value={urlDraft}
|
|
181
|
+
disabled={disabled}
|
|
182
|
+
placeholder={"Image URL" /* i18n-ignore */}
|
|
183
|
+
aria-label={"Image URL" /* i18n-ignore */}
|
|
184
|
+
spellCheck={false}
|
|
185
|
+
className="h-6 min-w-0 flex-1 rounded-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-2 text-[11px]"
|
|
186
|
+
onChange={(event) => setUrlDraft(event.target.value)}
|
|
187
|
+
onBlur={commitUrl}
|
|
188
|
+
onKeyDown={(event) => {
|
|
189
|
+
if (event.key === "Enter") {
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
commitUrl();
|
|
192
|
+
event.currentTarget.blur();
|
|
193
|
+
}
|
|
194
|
+
}}
|
|
195
|
+
/>
|
|
196
|
+
<Tooltip>
|
|
197
|
+
<TooltipTrigger asChild>
|
|
198
|
+
<button
|
|
199
|
+
type="button"
|
|
200
|
+
disabled={disabled}
|
|
201
|
+
aria-label={"Upload image" /* i18n-ignore */}
|
|
202
|
+
onClick={() => fileInputRef.current?.click()}
|
|
203
|
+
className={cn(
|
|
204
|
+
"flex size-6 shrink-0 items-center justify-center rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-muted-foreground hover:text-foreground",
|
|
205
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
206
|
+
disabled && "pointer-events-none opacity-40",
|
|
207
|
+
)}
|
|
208
|
+
>
|
|
209
|
+
<IconPhotoPlus className="size-3.5" />
|
|
210
|
+
</button>
|
|
211
|
+
</TooltipTrigger>
|
|
212
|
+
<TooltipContent>{"Upload image" /* i18n-ignore */}</TooltipContent>
|
|
213
|
+
</Tooltip>
|
|
214
|
+
<input
|
|
215
|
+
ref={fileInputRef}
|
|
216
|
+
type="file"
|
|
217
|
+
accept="image/*"
|
|
218
|
+
className="hidden"
|
|
219
|
+
onChange={handleFilePick}
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
{/* ── Fit mode dropdown ─────────────────────────────────────────────── */}
|
|
224
|
+
<Select
|
|
225
|
+
value={value.fit}
|
|
226
|
+
onValueChange={(v) => onChange({ ...value, fit: v as ImageFitMode })}
|
|
227
|
+
disabled={disabled}
|
|
228
|
+
>
|
|
229
|
+
<SelectTrigger className="h-6 w-full rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] px-2 text-[11px] shadow-none focus:ring-0 focus:ring-offset-0 focus-visible:ring-2 focus-visible:ring-ring [&>svg]:size-3 [&>svg]:shrink-0">
|
|
230
|
+
<SelectValue />
|
|
231
|
+
</SelectTrigger>
|
|
232
|
+
<SelectContent className="text-[11px]">
|
|
233
|
+
{FIT_MODES.map(({ mode, label }) => (
|
|
234
|
+
<SelectItem key={mode} value={mode} className="text-[11px]">
|
|
235
|
+
{label}
|
|
236
|
+
</SelectItem>
|
|
237
|
+
))}
|
|
238
|
+
</SelectContent>
|
|
239
|
+
</Select>
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
@@ -73,11 +73,13 @@ export function ScrubInput({
|
|
|
73
73
|
);
|
|
74
74
|
const [focused, setFocused] = useState(false);
|
|
75
75
|
const [dragging, setDragging] = useState(false);
|
|
76
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
76
77
|
const skipNextBlurCommitRef = useRef(false);
|
|
77
78
|
const dragRef = useRef({
|
|
78
79
|
pointerId: -1,
|
|
79
80
|
startX: 0,
|
|
80
81
|
startValue: value,
|
|
82
|
+
hasDragged: false,
|
|
81
83
|
});
|
|
82
84
|
|
|
83
85
|
useEffect(() => {
|
|
@@ -109,7 +111,11 @@ export function ScrubInput({
|
|
|
109
111
|
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
110
112
|
event.preventDefault();
|
|
111
113
|
const direction = event.key === "ArrowUp" ? 1 : -1;
|
|
112
|
-
|
|
114
|
+
// getScrubStepFromEvent handles shiftKey (×10) and altKey (÷10).
|
|
115
|
+
// Cmd (metaKey) mirrors Shift for ×10 — Figma convention on macOS.
|
|
116
|
+
const baseStep = getScrubStepFromEvent(event, step);
|
|
117
|
+
const cmdMultiplier = event.metaKey && !event.shiftKey ? 10 : 1;
|
|
118
|
+
setNextValue(value + direction * baseStep * cmdMultiplier, {
|
|
113
119
|
source: "keyboard",
|
|
114
120
|
});
|
|
115
121
|
return;
|
|
@@ -138,6 +144,7 @@ export function ScrubInput({
|
|
|
138
144
|
pointerId: event.pointerId,
|
|
139
145
|
startX: event.clientX,
|
|
140
146
|
startValue: value,
|
|
147
|
+
hasDragged: false,
|
|
141
148
|
};
|
|
142
149
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
143
150
|
setDragging(true);
|
|
@@ -147,6 +154,7 @@ export function ScrubInput({
|
|
|
147
154
|
if (!dragging || dragRef.current.pointerId !== event.pointerId) return;
|
|
148
155
|
const delta = event.clientX - dragRef.current.startX;
|
|
149
156
|
if (delta === 0) return;
|
|
157
|
+
dragRef.current.hasDragged = true;
|
|
150
158
|
const next =
|
|
151
159
|
dragRef.current.startValue +
|
|
152
160
|
delta *
|
|
@@ -160,7 +168,15 @@ export function ScrubInput({
|
|
|
160
168
|
const endDrag = (event: PointerEvent<HTMLLabelElement>) => {
|
|
161
169
|
if (dragRef.current.pointerId !== event.pointerId) return;
|
|
162
170
|
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
171
|
+
const wasDrag = dragRef.current.hasDragged;
|
|
163
172
|
setDragging(false);
|
|
173
|
+
// If the pointer was released without dragging (a plain click), focus the
|
|
174
|
+
// input so the user can type immediately — mirrors Figma's label click
|
|
175
|
+
// behaviour (the event.preventDefault() in handlePointerDown blocks the
|
|
176
|
+
// native label→input focus transfer).
|
|
177
|
+
if (!wasDrag && !disabled) {
|
|
178
|
+
inputRef.current?.focus();
|
|
179
|
+
}
|
|
164
180
|
};
|
|
165
181
|
|
|
166
182
|
return (
|
|
@@ -174,9 +190,10 @@ export function ScrubInput({
|
|
|
174
190
|
onPointerUp={endDrag}
|
|
175
191
|
onPointerCancel={endDrag}
|
|
176
192
|
className={cn(
|
|
177
|
-
"flex h-6 w-20 shrink-0 cursor-ew-resize select-none items-center gap-1 text-[11px] text-muted-foreground",
|
|
178
|
-
"hover:bg-[var(--design-editor-control-bg)] hover:
|
|
179
|
-
dragging &&
|
|
193
|
+
"flex h-6 w-20 shrink-0 cursor-ew-resize select-none items-center gap-1 rounded-sm text-[11px] text-muted-foreground transition-colors",
|
|
194
|
+
"hover:bg-[var(--design-editor-control-bg)] hover:text-foreground",
|
|
195
|
+
dragging &&
|
|
196
|
+
"bg-[var(--design-editor-control-bg)] text-foreground",
|
|
180
197
|
disabled && "pointer-events-none cursor-not-allowed opacity-50",
|
|
181
198
|
labelClassName,
|
|
182
199
|
)}
|
|
@@ -185,9 +202,10 @@ export function ScrubInput({
|
|
|
185
202
|
<span className="truncate">{label}</span>
|
|
186
203
|
</Label>
|
|
187
204
|
</TooltipTrigger>
|
|
188
|
-
<TooltipContent>{`${label}
|
|
205
|
+
<TooltipContent>{`${label} — drag to scrub · ↑↓ step · Shift ×10 · ⌥ fine`}</TooltipContent>
|
|
189
206
|
</Tooltip>
|
|
190
207
|
<Input
|
|
208
|
+
ref={inputRef}
|
|
191
209
|
id={inputId}
|
|
192
210
|
value={draft}
|
|
193
211
|
disabled={disabled}
|
|
@@ -196,7 +214,7 @@ export function ScrubInput({
|
|
|
196
214
|
aria-label={ariaLabel ?? label}
|
|
197
215
|
onFocus={(event) => {
|
|
198
216
|
setFocused(true);
|
|
199
|
-
event.
|
|
217
|
+
event.currentTarget.select();
|
|
200
218
|
}}
|
|
201
219
|
onBlur={() => {
|
|
202
220
|
setFocused(false);
|
|
@@ -208,7 +226,12 @@ export function ScrubInput({
|
|
|
208
226
|
}}
|
|
209
227
|
onChange={(event) => setDraft(event.target.value)}
|
|
210
228
|
onKeyDown={handleKeyDown}
|
|
211
|
-
className={cn(
|
|
229
|
+
className={cn(
|
|
230
|
+
// Compact Figma-style: h-6, 11px tabular text, ring-1 with no offset.
|
|
231
|
+
"h-6 text-[11px] tabular-nums",
|
|
232
|
+
"focus-visible:ring-1 focus-visible:ring-offset-0",
|
|
233
|
+
inputClassName,
|
|
234
|
+
)}
|
|
212
235
|
/>
|
|
213
236
|
</div>
|
|
214
237
|
);
|