@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { IconDownload, IconPlus, IconX } from "@tabler/icons-react";
|
|
2
|
+
import { useState } from "react";
|
|
2
3
|
|
|
3
4
|
import { Button } from "@/components/ui/button";
|
|
4
5
|
import { Input } from "@/components/ui/input";
|
|
@@ -12,9 +13,10 @@ import {
|
|
|
12
13
|
} from "@/components/ui/select";
|
|
13
14
|
import { cn } from "@/lib/utils";
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
export type ExportFormat = "png" | "jpg" | "svg" | "pdf" | "webp";
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
/** Scale preset value — "custom" means the user typed a freeform multiplier */
|
|
19
|
+
export type ExportScale = "0.5" | "1" | "2" | "3" | "4" | "custom";
|
|
18
20
|
|
|
19
21
|
export interface ExportSettingsValue {
|
|
20
22
|
scale: number;
|
|
@@ -28,6 +30,8 @@ export interface ExportSettingsPanelLabels {
|
|
|
28
30
|
format: string;
|
|
29
31
|
suffix: string;
|
|
30
32
|
export: string;
|
|
33
|
+
addExport: string;
|
|
34
|
+
removeExport: string;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
export interface ExportSettingsPanelProps {
|
|
@@ -47,9 +51,100 @@ const DEFAULT_LABELS: ExportSettingsPanelLabels = {
|
|
|
47
51
|
format: "Format", // i18n-ignore fallback component label
|
|
48
52
|
suffix: "Suffix", // i18n-ignore fallback component label
|
|
49
53
|
export: "Export", // i18n-ignore fallback component label
|
|
54
|
+
addExport: "Add export", // i18n-ignore fallback component label
|
|
55
|
+
removeExport: "Remove export", // i18n-ignore fallback component label
|
|
50
56
|
};
|
|
51
57
|
|
|
52
|
-
const DEFAULT_FORMATS: ExportFormat[] = ["png", "jpg", "svg", "pdf"];
|
|
58
|
+
const DEFAULT_FORMATS: ExportFormat[] = ["png", "jpg", "svg", "pdf", "webp"];
|
|
59
|
+
|
|
60
|
+
/** Preset scale options shown in the scale dropdown — matches Figma's presets */
|
|
61
|
+
const SCALE_PRESETS: { label: string; value: ExportScale }[] = [
|
|
62
|
+
{ label: "0.5x", value: "0.5" },
|
|
63
|
+
{ label: "1x", value: "1" },
|
|
64
|
+
{ label: "2x", value: "2" },
|
|
65
|
+
{ label: "3x", value: "3" },
|
|
66
|
+
{ label: "4x", value: "4" },
|
|
67
|
+
{ label: "Custom…", value: "custom" }, // i18n-ignore fixed scale preset label
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Map a numeric scale to the nearest preset key, or "custom" if it doesn't
|
|
72
|
+
* match any of the five standard multipliers.
|
|
73
|
+
*/
|
|
74
|
+
function scaleToPreset(scale: number): ExportScale {
|
|
75
|
+
const hit = SCALE_PRESETS.find(
|
|
76
|
+
(p) => p.value !== "custom" && Number(p.value) === scale,
|
|
77
|
+
);
|
|
78
|
+
return hit ? hit.value : "custom";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A single in-progress export row (multi-row internal state) */
|
|
82
|
+
interface ExportRow {
|
|
83
|
+
id: number;
|
|
84
|
+
scale: number;
|
|
85
|
+
format: ExportFormat;
|
|
86
|
+
suffix: string;
|
|
87
|
+
/** Whether the user has switched to freeform scale entry */
|
|
88
|
+
customScale: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let _nextId = 1;
|
|
92
|
+
function nextId() {
|
|
93
|
+
return _nextId++;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function rowFromValue(v: ExportSettingsValue): ExportRow {
|
|
97
|
+
return {
|
|
98
|
+
id: nextId(),
|
|
99
|
+
scale: v.scale,
|
|
100
|
+
format: v.format,
|
|
101
|
+
suffix: v.suffix,
|
|
102
|
+
customScale: scaleToPreset(v.scale) === "custom",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Scale dropdown — shows Figma's 0.5x/1x/2x/3x/4x presets + Custom */
|
|
107
|
+
function ScaleSelect({
|
|
108
|
+
scale,
|
|
109
|
+
customScale,
|
|
110
|
+
disabled,
|
|
111
|
+
onPresetChange,
|
|
112
|
+
}: {
|
|
113
|
+
scale: number;
|
|
114
|
+
customScale: boolean;
|
|
115
|
+
disabled: boolean;
|
|
116
|
+
onPresetChange: (preset: ExportScale, raw: number) => void;
|
|
117
|
+
}) {
|
|
118
|
+
const preset = customScale ? "custom" : scaleToPreset(scale);
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Select
|
|
122
|
+
value={preset}
|
|
123
|
+
disabled={disabled}
|
|
124
|
+
onValueChange={(v) => {
|
|
125
|
+
const p = v as ExportScale;
|
|
126
|
+
if (p === "custom") {
|
|
127
|
+
onPresetChange("custom", scale);
|
|
128
|
+
} else {
|
|
129
|
+
onPresetChange(p, Number(p));
|
|
130
|
+
}
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
<SelectTrigger className="h-6 w-14 shrink-0 px-1.5 text-[11px]">
|
|
134
|
+
<SelectValue />
|
|
135
|
+
</SelectTrigger>
|
|
136
|
+
<SelectContent>
|
|
137
|
+
<SelectGroup>
|
|
138
|
+
{SCALE_PRESETS.map((p) => (
|
|
139
|
+
<SelectItem key={p.value} value={p.value} className="text-[11px]">
|
|
140
|
+
{p.label}
|
|
141
|
+
</SelectItem>
|
|
142
|
+
))}
|
|
143
|
+
</SelectGroup>
|
|
144
|
+
</SelectContent>
|
|
145
|
+
</Select>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
53
148
|
|
|
54
149
|
export function ExportSettingsPanel({
|
|
55
150
|
value,
|
|
@@ -64,6 +159,83 @@ export function ExportSettingsPanel({
|
|
|
64
159
|
const copy = { ...DEFAULT_LABELS, ...labels };
|
|
65
160
|
const isDisabled = disabled || exporting;
|
|
66
161
|
|
|
162
|
+
// Multi-row internal state — primary row is kept in sync with the `value` prop
|
|
163
|
+
const [rows, setRows] = useState<ExportRow[]>(() => [rowFromValue(value)]);
|
|
164
|
+
|
|
165
|
+
// Keep primary row in sync when the controlled value prop changes externally
|
|
166
|
+
const primaryValue = rows[0];
|
|
167
|
+
const needsSync =
|
|
168
|
+
!primaryValue.customScale &&
|
|
169
|
+
(primaryValue.scale !== value.scale ||
|
|
170
|
+
primaryValue.format !== value.format ||
|
|
171
|
+
primaryValue.suffix !== value.suffix);
|
|
172
|
+
|
|
173
|
+
if (needsSync) {
|
|
174
|
+
setRows((prev) => [
|
|
175
|
+
{
|
|
176
|
+
...prev[0],
|
|
177
|
+
scale: value.scale,
|
|
178
|
+
format: value.format,
|
|
179
|
+
suffix: value.suffix,
|
|
180
|
+
},
|
|
181
|
+
...prev.slice(1),
|
|
182
|
+
]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function patchRow(id: number, patch: Partial<ExportRow>) {
|
|
186
|
+
setRows((prev) => prev.map((r) => (r.id === id ? { ...r, ...patch } : r)));
|
|
187
|
+
// Report primary row changes to the parent via `onChange`
|
|
188
|
+
if (id === rows[0]?.id) {
|
|
189
|
+
const next = { ...rows[0], ...patch };
|
|
190
|
+
const parent: Partial<ExportSettingsValue> = {};
|
|
191
|
+
if ("scale" in patch) parent.scale = next.scale;
|
|
192
|
+
if ("format" in patch) parent.format = next.format as ExportFormat;
|
|
193
|
+
if ("suffix" in patch) parent.suffix = next.suffix;
|
|
194
|
+
if (Object.keys(parent).length > 0) onChange(parent);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function addRow() {
|
|
199
|
+
setRows((prev) => [
|
|
200
|
+
...prev,
|
|
201
|
+
{
|
|
202
|
+
id: nextId(),
|
|
203
|
+
scale: 1,
|
|
204
|
+
format: "png" as ExportFormat,
|
|
205
|
+
suffix: "",
|
|
206
|
+
customScale: false,
|
|
207
|
+
},
|
|
208
|
+
]);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function removeRow(id: number) {
|
|
212
|
+
setRows((prev) => {
|
|
213
|
+
// Must keep at least one row
|
|
214
|
+
if (prev.length <= 1) return prev;
|
|
215
|
+
const next = prev.filter((r) => r.id !== id);
|
|
216
|
+
// If the primary (first) row was removed, the new first row becomes primary
|
|
217
|
+
if (prev[0]?.id === id && next[0]) {
|
|
218
|
+
onChange({
|
|
219
|
+
scale: next[0].scale,
|
|
220
|
+
format: next[0].format,
|
|
221
|
+
suffix: next[0].suffix,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return next;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function handleExport() {
|
|
229
|
+
// Pass the primary row's settings to onExport; rows array is internal
|
|
230
|
+
const primary = rows[0];
|
|
231
|
+
if (!primary) return;
|
|
232
|
+
onExport({
|
|
233
|
+
scale: primary.scale,
|
|
234
|
+
format: primary.format,
|
|
235
|
+
suffix: primary.suffix,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
67
239
|
return (
|
|
68
240
|
<div className={cn("space-y-1.5", className)}>
|
|
69
241
|
{/* Section header: title left, "+" right — matches Figma export header */}
|
|
@@ -73,89 +245,35 @@ export function ExportSettingsPanel({
|
|
|
73
245
|
</span>
|
|
74
246
|
<button
|
|
75
247
|
type="button"
|
|
76
|
-
aria-label={copy.
|
|
248
|
+
aria-label={copy.addExport}
|
|
77
249
|
disabled={isDisabled}
|
|
78
250
|
className="flex size-6 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-50"
|
|
79
|
-
onClick={
|
|
80
|
-
/* single-value interface: noop — the setting already exists */
|
|
81
|
-
}}
|
|
251
|
+
onClick={addRow}
|
|
82
252
|
>
|
|
83
253
|
<IconPlus className="size-3.5" />
|
|
84
254
|
</button>
|
|
85
255
|
</div>
|
|
86
256
|
|
|
87
|
-
{/* Export
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
precision={2}
|
|
99
|
-
disabled={isDisabled}
|
|
100
|
-
inputClassName="h-6 w-12 text-[11px]"
|
|
101
|
-
className="shrink-0"
|
|
102
|
-
/>
|
|
103
|
-
|
|
104
|
-
{/* Format dropdown */}
|
|
105
|
-
<Select
|
|
106
|
-
value={value.format}
|
|
107
|
-
disabled={isDisabled}
|
|
108
|
-
onValueChange={(format) =>
|
|
109
|
-
onChange({ format: format as ExportFormat })
|
|
110
|
-
}
|
|
111
|
-
>
|
|
112
|
-
<SelectTrigger className="h-6 min-w-0 flex-1 px-1.5 text-[11px]">
|
|
113
|
-
<SelectValue />
|
|
114
|
-
</SelectTrigger>
|
|
115
|
-
<SelectContent>
|
|
116
|
-
<SelectGroup>
|
|
117
|
-
{formats.map((format) => (
|
|
118
|
-
<SelectItem
|
|
119
|
-
key={format}
|
|
120
|
-
value={format}
|
|
121
|
-
className="text-[11px] uppercase"
|
|
122
|
-
>
|
|
123
|
-
{format.toUpperCase()}
|
|
124
|
-
</SelectItem>
|
|
125
|
-
))}
|
|
126
|
-
</SelectGroup>
|
|
127
|
-
</SelectContent>
|
|
128
|
-
</Select>
|
|
129
|
-
|
|
130
|
-
{/* Suffix input */}
|
|
131
|
-
<Input
|
|
132
|
-
value={value.suffix}
|
|
133
|
-
disabled={isDisabled}
|
|
134
|
-
onChange={(event) => onChange({ suffix: event.target.value })}
|
|
135
|
-
placeholder="@2x"
|
|
136
|
-
className="h-6 min-w-0 flex-1 px-1.5 text-[11px]"
|
|
257
|
+
{/* Export rows: [scale ▾] [format ▾] [suffix] [×] — Figma compact inline layout */}
|
|
258
|
+
{rows.map((row) => (
|
|
259
|
+
<ExportRow
|
|
260
|
+
key={row.id}
|
|
261
|
+
row={row}
|
|
262
|
+
formats={formats}
|
|
263
|
+
labels={copy}
|
|
264
|
+
isDisabled={isDisabled}
|
|
265
|
+
canRemove={rows.length > 1}
|
|
266
|
+
onPatchRow={patchRow}
|
|
267
|
+
onRemoveRow={removeRow}
|
|
137
268
|
/>
|
|
138
|
-
|
|
139
|
-
{/* Remove row button — matches Figma's × on each export entry */}
|
|
140
|
-
<button
|
|
141
|
-
type="button"
|
|
142
|
-
aria-label={copy.export}
|
|
143
|
-
disabled={isDisabled}
|
|
144
|
-
className="flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-50"
|
|
145
|
-
onClick={() => {
|
|
146
|
-
/* single-value interface: noop */
|
|
147
|
-
}}
|
|
148
|
-
>
|
|
149
|
-
<IconX className="size-3" />
|
|
150
|
-
</button>
|
|
151
|
-
</div>
|
|
269
|
+
))}
|
|
152
270
|
|
|
153
271
|
{/* Export button — full width at bottom, Figma style */}
|
|
154
272
|
<Button
|
|
155
273
|
type="button"
|
|
156
274
|
variant="outline"
|
|
157
275
|
disabled={isDisabled}
|
|
158
|
-
onClick={
|
|
276
|
+
onClick={handleExport}
|
|
159
277
|
className="h-6 w-full px-2 text-[11px]"
|
|
160
278
|
>
|
|
161
279
|
<IconDownload className="size-3.5" />
|
|
@@ -164,3 +282,114 @@ export function ExportSettingsPanel({
|
|
|
164
282
|
</div>
|
|
165
283
|
);
|
|
166
284
|
}
|
|
285
|
+
|
|
286
|
+
/** A single export row: [scale ▾ or custom input] [format ▾] [suffix] [×] */
|
|
287
|
+
function ExportRow({
|
|
288
|
+
row,
|
|
289
|
+
formats,
|
|
290
|
+
labels,
|
|
291
|
+
isDisabled,
|
|
292
|
+
canRemove,
|
|
293
|
+
onPatchRow,
|
|
294
|
+
onRemoveRow,
|
|
295
|
+
}: {
|
|
296
|
+
row: ExportRow;
|
|
297
|
+
formats: ExportFormat[];
|
|
298
|
+
labels: ExportSettingsPanelLabels;
|
|
299
|
+
isDisabled: boolean;
|
|
300
|
+
canRemove: boolean;
|
|
301
|
+
onPatchRow: (id: number, patch: Partial<ExportRow>) => void;
|
|
302
|
+
onRemoveRow: (id: number) => void;
|
|
303
|
+
}) {
|
|
304
|
+
return (
|
|
305
|
+
<div className="flex items-center gap-1">
|
|
306
|
+
{/* Scale — dropdown (presets) or freeform input when "Custom…" selected */}
|
|
307
|
+
{row.customScale ? (
|
|
308
|
+
<Input
|
|
309
|
+
type="number"
|
|
310
|
+
value={row.scale}
|
|
311
|
+
disabled={isDisabled}
|
|
312
|
+
min={0.01}
|
|
313
|
+
max={100}
|
|
314
|
+
step={0.5}
|
|
315
|
+
onChange={(e) => {
|
|
316
|
+
const parsed = parseFloat(e.target.value);
|
|
317
|
+
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
318
|
+
onPatchRow(row.id, { scale: parsed });
|
|
319
|
+
}
|
|
320
|
+
}}
|
|
321
|
+
onBlur={() => {
|
|
322
|
+
// Switch back to preset dropdown if the typed value matches a preset
|
|
323
|
+
if (scaleToPreset(row.scale) !== "custom") {
|
|
324
|
+
onPatchRow(row.id, { customScale: false });
|
|
325
|
+
}
|
|
326
|
+
}}
|
|
327
|
+
className="h-6 w-14 shrink-0 px-1.5 text-[11px] tabular-nums"
|
|
328
|
+
aria-label="Scale"
|
|
329
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
330
|
+
autoFocus
|
|
331
|
+
/>
|
|
332
|
+
) : (
|
|
333
|
+
<ScaleSelect
|
|
334
|
+
scale={row.scale}
|
|
335
|
+
customScale={false}
|
|
336
|
+
disabled={isDisabled}
|
|
337
|
+
onPresetChange={(preset, raw) => {
|
|
338
|
+
if (preset === "custom") {
|
|
339
|
+
onPatchRow(row.id, { customScale: true });
|
|
340
|
+
} else {
|
|
341
|
+
onPatchRow(row.id, { scale: raw, customScale: false });
|
|
342
|
+
}
|
|
343
|
+
}}
|
|
344
|
+
/>
|
|
345
|
+
)}
|
|
346
|
+
|
|
347
|
+
{/* Format dropdown — PNG / JPG / SVG / PDF / WEBP */}
|
|
348
|
+
<Select
|
|
349
|
+
value={row.format}
|
|
350
|
+
disabled={isDisabled}
|
|
351
|
+
onValueChange={(format) =>
|
|
352
|
+
onPatchRow(row.id, { format: format as ExportFormat })
|
|
353
|
+
}
|
|
354
|
+
>
|
|
355
|
+
<SelectTrigger className="h-6 min-w-0 flex-1 px-1.5 text-[11px]">
|
|
356
|
+
<SelectValue />
|
|
357
|
+
</SelectTrigger>
|
|
358
|
+
<SelectContent>
|
|
359
|
+
<SelectGroup>
|
|
360
|
+
{formats.map((format) => (
|
|
361
|
+
<SelectItem
|
|
362
|
+
key={format}
|
|
363
|
+
value={format}
|
|
364
|
+
className="text-[11px] uppercase"
|
|
365
|
+
>
|
|
366
|
+
{format.toUpperCase()}
|
|
367
|
+
</SelectItem>
|
|
368
|
+
))}
|
|
369
|
+
</SelectGroup>
|
|
370
|
+
</SelectContent>
|
|
371
|
+
</Select>
|
|
372
|
+
|
|
373
|
+
{/* Suffix input — e.g. "@2x", "-mobile" */}
|
|
374
|
+
<Input
|
|
375
|
+
value={row.suffix}
|
|
376
|
+
disabled={isDisabled}
|
|
377
|
+
onChange={(e) => onPatchRow(row.id, { suffix: e.target.value })}
|
|
378
|
+
placeholder={labels.suffix}
|
|
379
|
+
className="h-6 min-w-0 flex-1 px-1.5 text-[11px]"
|
|
380
|
+
aria-label={labels.suffix}
|
|
381
|
+
/>
|
|
382
|
+
|
|
383
|
+
{/* Remove row button — matches Figma's × on each export entry */}
|
|
384
|
+
<button
|
|
385
|
+
type="button"
|
|
386
|
+
aria-label={labels.removeExport}
|
|
387
|
+
disabled={isDisabled || !canRemove}
|
|
388
|
+
className="flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-50"
|
|
389
|
+
onClick={() => onRemoveRow(row.id)}
|
|
390
|
+
>
|
|
391
|
+
<IconX className="size-3" />
|
|
392
|
+
</button>
|
|
393
|
+
</div>
|
|
394
|
+
);
|
|
395
|
+
}
|