@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,17 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
IconArrowsDiagonal,
|
|
4
|
-
IconLink,
|
|
5
|
-
IconUnlink,
|
|
6
|
-
} from "@tabler/icons-react";
|
|
7
|
-
import type { ReactNode } from "react";
|
|
1
|
+
import { IconArrowBackUp } from "@tabler/icons-react";
|
|
2
|
+
import { type ReactNode, useState } from "react";
|
|
8
3
|
|
|
9
4
|
import { Button } from "@/components/ui/button";
|
|
10
5
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
11
6
|
import {
|
|
12
7
|
DropdownMenu,
|
|
8
|
+
DropdownMenuCheckboxItem,
|
|
13
9
|
DropdownMenuContent,
|
|
14
10
|
DropdownMenuItem,
|
|
11
|
+
DropdownMenuSeparator,
|
|
15
12
|
DropdownMenuTrigger,
|
|
16
13
|
} from "@/components/ui/dropdown-menu";
|
|
17
14
|
import {
|
|
@@ -22,20 +19,26 @@ import {
|
|
|
22
19
|
} from "@/components/ui/tooltip";
|
|
23
20
|
import { cn } from "@/lib/utils";
|
|
24
21
|
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
import type {
|
|
23
|
+
AlignmentHorizontal,
|
|
24
|
+
AlignmentMatrixValue,
|
|
25
|
+
AlignmentVertical,
|
|
26
|
+
DistributionAxis,
|
|
29
27
|
} from "./AlignmentMatrix";
|
|
30
28
|
import {
|
|
31
29
|
IconFlowGrid,
|
|
32
30
|
IconFlowHorizontal,
|
|
33
31
|
IconFlowVertical,
|
|
34
|
-
IconFlowWrap,
|
|
35
32
|
IconGap,
|
|
36
|
-
IconLayoutSettings,
|
|
37
33
|
IconPaddingHorizontal,
|
|
38
34
|
IconPaddingVertical,
|
|
35
|
+
IconSizingFill,
|
|
36
|
+
IconSizingFixed,
|
|
37
|
+
IconSizingHug,
|
|
38
|
+
IconSizingMax,
|
|
39
|
+
IconSizingMin,
|
|
40
|
+
IconSizingRemove,
|
|
41
|
+
IconSizingVariable,
|
|
39
42
|
} from "./figma-icons";
|
|
40
43
|
import { ScrubInput } from "./ScrubInput";
|
|
41
44
|
|
|
@@ -44,6 +47,12 @@ export type AutoLayoutWrap = "nowrap" | "wrap";
|
|
|
44
47
|
export type AutoLayoutSizing = "hug" | "fill" | "fixed";
|
|
45
48
|
export type AutoLayoutSizingAxis = "horizontal" | "vertical";
|
|
46
49
|
|
|
50
|
+
/**
|
|
51
|
+
* The active flow option. "normal" represents block / normal-flow layout for
|
|
52
|
+
* non-flex containers; the other four map to flex direction + wrap state.
|
|
53
|
+
*/
|
|
54
|
+
export type AutoLayoutFlow = "normal" | "vertical" | "horizontal" | "grid";
|
|
55
|
+
|
|
47
56
|
export interface AutoLayoutPadding {
|
|
48
57
|
top: number;
|
|
49
58
|
right: number;
|
|
@@ -67,6 +76,22 @@ export interface AutoLayoutMatrixValue {
|
|
|
67
76
|
horizontal: AutoLayoutSizing;
|
|
68
77
|
vertical: AutoLayoutSizing;
|
|
69
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* Currently-set min/max constraints per axis, in px. `null` means the
|
|
81
|
+
* constraint is not set (Figma shows the "Add min/max…" menu item instead of
|
|
82
|
+
* a sub-row). Optional so existing callers are unaffected.
|
|
83
|
+
*/
|
|
84
|
+
childMinMax?: {
|
|
85
|
+
horizontal?: { min?: number | null; max?: number | null };
|
|
86
|
+
vertical?: { min?: number | null; max?: number | null };
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Optional layout-mode hint. When "block" the control renders the
|
|
90
|
+
* normal-flow (non-flex) state — the first flow icon is active and gap is
|
|
91
|
+
* treated as disabled. Defaults to flex when omitted so existing callers are
|
|
92
|
+
* unaffected.
|
|
93
|
+
*/
|
|
94
|
+
display?: "flex" | "block";
|
|
70
95
|
}
|
|
71
96
|
|
|
72
97
|
export interface AutoLayoutMatrixLabels {
|
|
@@ -90,6 +115,20 @@ export interface AutoLayoutMatrixLabels {
|
|
|
90
115
|
fill: string;
|
|
91
116
|
fixed: string;
|
|
92
117
|
clipContent: string;
|
|
118
|
+
fixedWidth: string;
|
|
119
|
+
fixedHeight: string;
|
|
120
|
+
hugContents: string;
|
|
121
|
+
fillContainer: string;
|
|
122
|
+
addMinWidth: string;
|
|
123
|
+
addMaxWidth: string;
|
|
124
|
+
addMinHeight: string;
|
|
125
|
+
addMaxHeight: string;
|
|
126
|
+
minWidth: string;
|
|
127
|
+
maxWidth: string;
|
|
128
|
+
minHeight: string;
|
|
129
|
+
maxHeight: string;
|
|
130
|
+
applyVariable: string;
|
|
131
|
+
removeConstraint: string;
|
|
93
132
|
}
|
|
94
133
|
|
|
95
134
|
export interface AutoLayoutMatrixProps {
|
|
@@ -106,6 +145,29 @@ export interface AutoLayoutMatrixProps {
|
|
|
106
145
|
axis: AutoLayoutSizingAxis,
|
|
107
146
|
sizing: AutoLayoutSizing,
|
|
108
147
|
) => void;
|
|
148
|
+
/**
|
|
149
|
+
* Set or clear a min/max constraint on an axis. `value === null` clears it.
|
|
150
|
+
* Optional — when omitted the "Add min/max…" rows and constraint sub-rows are
|
|
151
|
+
* hidden, so existing callers are unaffected.
|
|
152
|
+
*/
|
|
153
|
+
onChildMinMaxChange?: (
|
|
154
|
+
axis: AutoLayoutSizingAxis,
|
|
155
|
+
kind: "min" | "max",
|
|
156
|
+
value: number | null,
|
|
157
|
+
) => void;
|
|
158
|
+
/**
|
|
159
|
+
* Invoked when the user picks "Apply variable…". Optional — when omitted the
|
|
160
|
+
* variable row is still shown but disabled (placeholder), matching Figma when
|
|
161
|
+
* no variable collections exist.
|
|
162
|
+
*/
|
|
163
|
+
onApplyVariable?: (axis: AutoLayoutSizingAxis) => void;
|
|
164
|
+
/**
|
|
165
|
+
* Emitted when the user picks a flow that changes the layout mode between
|
|
166
|
+
* normal-flow (block) and flex. Pass this so selecting the first ("normal")
|
|
167
|
+
* flow icon can turn auto layout off, and selecting a flex flow can turn it
|
|
168
|
+
* on. Optional — when omitted the control still works in pure-flex mode.
|
|
169
|
+
*/
|
|
170
|
+
onDisplayChange?: (display: "flex" | "block") => void;
|
|
109
171
|
availableChildSizing?: Partial<
|
|
110
172
|
Record<AutoLayoutSizingAxis, AutoLayoutSizing[]>
|
|
111
173
|
>;
|
|
@@ -114,7 +176,8 @@ export interface AutoLayoutMatrixProps {
|
|
|
114
176
|
className?: string;
|
|
115
177
|
}
|
|
116
178
|
|
|
117
|
-
|
|
179
|
+
/** Default English labels for the auto-layout matrix and SizingField. */
|
|
180
|
+
export const DEFAULT_AUTO_LAYOUT_LABELS: AutoLayoutMatrixLabels = {
|
|
118
181
|
title: "Auto layout", // i18n-ignore fallback component label
|
|
119
182
|
alignment: "Alignment", // i18n-ignore fallback component label
|
|
120
183
|
direction: "Direction", // i18n-ignore fallback component label
|
|
@@ -135,17 +198,29 @@ const DEFAULT_LABELS: AutoLayoutMatrixLabels = {
|
|
|
135
198
|
fill: "Fill", // i18n-ignore fallback component label
|
|
136
199
|
fixed: "Fixed", // i18n-ignore fallback component label
|
|
137
200
|
clipContent: "Clip content", // i18n-ignore fallback component label
|
|
201
|
+
fixedWidth: "Fixed width", // i18n-ignore fallback component label
|
|
202
|
+
fixedHeight: "Fixed height", // i18n-ignore fallback component label
|
|
203
|
+
hugContents: "Hug contents", // i18n-ignore fallback component label
|
|
204
|
+
fillContainer: "Fill container", // i18n-ignore fallback component label
|
|
205
|
+
addMinWidth: "Add min width…", // i18n-ignore fallback component label
|
|
206
|
+
addMaxWidth: "Add max width…", // i18n-ignore fallback component label
|
|
207
|
+
addMinHeight: "Add min height…", // i18n-ignore fallback component label
|
|
208
|
+
addMaxHeight: "Add max height…", // i18n-ignore fallback component label
|
|
209
|
+
minWidth: "Min width", // i18n-ignore fallback component label
|
|
210
|
+
maxWidth: "Max width", // i18n-ignore fallback component label
|
|
211
|
+
minHeight: "Min height", // i18n-ignore fallback component label
|
|
212
|
+
maxHeight: "Max height", // i18n-ignore fallback component label
|
|
213
|
+
applyVariable: "Apply variable…", // i18n-ignore fallback component label
|
|
214
|
+
removeConstraint: "Remove", // i18n-ignore fallback component label
|
|
138
215
|
};
|
|
139
216
|
|
|
140
217
|
const SIZING_OPTIONS: AutoLayoutSizing[] = ["hug", "fill", "fixed"];
|
|
141
218
|
|
|
142
|
-
/** Derive the
|
|
143
|
-
function getFlowOption(
|
|
144
|
-
|
|
145
|
-
wrap
|
|
146
|
-
|
|
147
|
-
if (wrap === "wrap") return "wrap";
|
|
148
|
-
if (direction === "vertical") return "vertical";
|
|
219
|
+
/** Derive the active flow option from display + direction + wrap state. */
|
|
220
|
+
function getFlowOption(value: AutoLayoutMatrixValue): AutoLayoutFlow {
|
|
221
|
+
if (value.display === "block") return "normal";
|
|
222
|
+
if (value.wrap === "wrap") return "grid";
|
|
223
|
+
if (value.direction === "vertical") return "vertical";
|
|
149
224
|
return "horizontal";
|
|
150
225
|
}
|
|
151
226
|
|
|
@@ -160,12 +235,15 @@ export function AutoLayoutMatrix({
|
|
|
160
235
|
onClipContentChange,
|
|
161
236
|
onDistribute,
|
|
162
237
|
onChildSizingChange,
|
|
238
|
+
onChildMinMaxChange,
|
|
239
|
+
onApplyVariable,
|
|
240
|
+
onDisplayChange,
|
|
163
241
|
availableChildSizing,
|
|
164
242
|
labels,
|
|
165
243
|
disabled = false,
|
|
166
244
|
className,
|
|
167
245
|
}: AutoLayoutMatrixProps) {
|
|
168
|
-
const copy = { ...
|
|
246
|
+
const copy = { ...DEFAULT_AUTO_LAYOUT_LABELS, ...labels };
|
|
169
247
|
|
|
170
248
|
const horizontalPaddingValue = Math.round(
|
|
171
249
|
(value.padding.left + value.padding.right) / 2,
|
|
@@ -174,67 +252,75 @@ export function AutoLayoutMatrix({
|
|
|
174
252
|
(value.padding.top + value.padding.bottom) / 2,
|
|
175
253
|
);
|
|
176
254
|
|
|
177
|
-
const activeFlow = getFlowOption(value
|
|
255
|
+
const activeFlow = getFlowOption(value);
|
|
256
|
+
const isBlock = activeFlow === "normal";
|
|
257
|
+
|
|
258
|
+
/** Apply a flow choice, coordinating display + direction + wrap. */
|
|
259
|
+
const selectFlow = (flow: AutoLayoutFlow) => {
|
|
260
|
+
if (flow === "normal") {
|
|
261
|
+
onDisplayChange?.("block");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
// Any flex flow turns auto layout on.
|
|
265
|
+
onDisplayChange?.("flex");
|
|
266
|
+
if (flow === "vertical") {
|
|
267
|
+
onDirectionChange("vertical");
|
|
268
|
+
onWrapChange("nowrap");
|
|
269
|
+
} else if (flow === "horizontal") {
|
|
270
|
+
onDirectionChange("horizontal");
|
|
271
|
+
onWrapChange("nowrap");
|
|
272
|
+
} else {
|
|
273
|
+
// grid → horizontal direction with wrap
|
|
274
|
+
onDirectionChange("horizontal");
|
|
275
|
+
onWrapChange("wrap");
|
|
276
|
+
}
|
|
277
|
+
};
|
|
178
278
|
|
|
179
279
|
return (
|
|
180
280
|
<TooltipProvider delayDuration={250}>
|
|
181
|
-
<div className={cn("space-y-
|
|
281
|
+
<div className={cn("space-y-3", className)}>
|
|
182
282
|
{/* ── Flow ── */}
|
|
183
283
|
<div className="space-y-1.5">
|
|
184
|
-
<
|
|
284
|
+
<ControlLabel>
|
|
185
285
|
{"Flow" /* i18n-ignore Figma inspector label */}
|
|
186
|
-
</
|
|
286
|
+
</ControlLabel>
|
|
187
287
|
<div className="flex items-center gap-1.5">
|
|
188
|
-
{/* 4-segment flow bar:
|
|
189
|
-
<div className="flex h-
|
|
288
|
+
{/* 4-segment flow bar: normal / vertical / horizontal / grid */}
|
|
289
|
+
<div className="flex h-7 flex-1 items-center gap-0.5 rounded-md bg-[var(--design-editor-control-bg)] p-0.5">
|
|
190
290
|
<FlowButton
|
|
191
|
-
label={
|
|
192
|
-
active={activeFlow === "
|
|
291
|
+
label={"Normal flow" /* i18n-ignore Figma inspector label */}
|
|
292
|
+
active={activeFlow === "normal"}
|
|
193
293
|
disabled={disabled}
|
|
194
|
-
onClick={() =>
|
|
195
|
-
onDirectionChange("horizontal");
|
|
196
|
-
onWrapChange("nowrap");
|
|
197
|
-
}}
|
|
198
|
-
position="first"
|
|
294
|
+
onClick={() => selectFlow("normal")}
|
|
199
295
|
>
|
|
200
|
-
<
|
|
296
|
+
<IconFlowNormal />
|
|
201
297
|
</FlowButton>
|
|
202
298
|
<FlowButton
|
|
203
299
|
label={copy.vertical}
|
|
204
300
|
active={activeFlow === "vertical"}
|
|
205
301
|
disabled={disabled}
|
|
206
|
-
onClick={() =>
|
|
207
|
-
onDirectionChange("vertical");
|
|
208
|
-
onWrapChange("nowrap");
|
|
209
|
-
}}
|
|
302
|
+
onClick={() => selectFlow("vertical")}
|
|
210
303
|
>
|
|
211
304
|
<IconFlowVertical className="size-3.5" />
|
|
212
305
|
</FlowButton>
|
|
213
306
|
<FlowButton
|
|
214
|
-
label={copy.
|
|
215
|
-
active={activeFlow === "
|
|
307
|
+
label={copy.horizontal}
|
|
308
|
+
active={activeFlow === "horizontal"}
|
|
216
309
|
disabled={disabled}
|
|
217
|
-
onClick={() =>
|
|
218
|
-
onDirectionChange("horizontal");
|
|
219
|
-
onWrapChange("wrap");
|
|
220
|
-
}}
|
|
310
|
+
onClick={() => selectFlow("horizontal")}
|
|
221
311
|
>
|
|
222
|
-
<
|
|
312
|
+
<IconFlowHorizontal className="size-3.5" />
|
|
223
313
|
</FlowButton>
|
|
224
314
|
<FlowButton
|
|
225
315
|
label={"Grid" /* i18n-ignore Figma inspector label */}
|
|
226
316
|
active={activeFlow === "grid"}
|
|
227
317
|
disabled={disabled}
|
|
228
|
-
onClick={() =>
|
|
229
|
-
onDirectionChange("horizontal");
|
|
230
|
-
onWrapChange("wrap");
|
|
231
|
-
}}
|
|
232
|
-
position="last"
|
|
318
|
+
onClick={() => selectFlow("grid")}
|
|
233
319
|
>
|
|
234
320
|
<IconFlowGrid className="size-3.5" />
|
|
235
321
|
</FlowButton>
|
|
236
322
|
</div>
|
|
237
|
-
{/* Reset
|
|
323
|
+
{/* Reset / reverse-flow button */}
|
|
238
324
|
<Tooltip>
|
|
239
325
|
<TooltipTrigger asChild>
|
|
240
326
|
<Button
|
|
@@ -245,13 +331,10 @@ export function AutoLayoutMatrix({
|
|
|
245
331
|
aria-label={
|
|
246
332
|
"Reset auto layout flow" /* i18n-ignore inspector tooltip */
|
|
247
333
|
}
|
|
248
|
-
onClick={() =>
|
|
249
|
-
|
|
250
|
-
onWrapChange("nowrap");
|
|
251
|
-
}}
|
|
252
|
-
className="size-6 shrink-0 rounded-md text-muted-foreground hover:bg-[var(--design-editor-control-bg)] hover:text-foreground"
|
|
334
|
+
onClick={() => selectFlow("horizontal")}
|
|
335
|
+
className="size-7 shrink-0 rounded-md text-muted-foreground hover:bg-[var(--design-editor-control-bg)] hover:text-foreground"
|
|
253
336
|
>
|
|
254
|
-
<IconArrowBackUp className="size-
|
|
337
|
+
<IconArrowBackUp className="size-4" />
|
|
255
338
|
</Button>
|
|
256
339
|
</TooltipTrigger>
|
|
257
340
|
<TooltipContent>
|
|
@@ -263,27 +346,41 @@ export function AutoLayoutMatrix({
|
|
|
263
346
|
|
|
264
347
|
{/* ── Resizing ── */}
|
|
265
348
|
<div className="space-y-1.5">
|
|
266
|
-
<
|
|
349
|
+
<ControlLabel>
|
|
267
350
|
{"Resizing" /* i18n-ignore Figma inspector label */}
|
|
268
|
-
</
|
|
269
|
-
<div className="grid grid-cols-[
|
|
351
|
+
</ControlLabel>
|
|
352
|
+
<div className="grid grid-cols-[1fr_1fr_auto] items-start gap-1.5">
|
|
270
353
|
<SizingField
|
|
271
354
|
axis="W"
|
|
355
|
+
sizingAxis="horizontal"
|
|
272
356
|
value={value.childSizing.horizontal}
|
|
273
357
|
resolvedSize={value.resolvedSize?.horizontal}
|
|
274
|
-
|
|
358
|
+
minMax={value.childMinMax?.horizontal}
|
|
359
|
+
options={resolveSizingOptions(
|
|
360
|
+
availableChildSizing?.horizontal,
|
|
361
|
+
value.childSizing.horizontal,
|
|
362
|
+
)}
|
|
275
363
|
labels={copy}
|
|
276
364
|
disabled={disabled}
|
|
277
365
|
onChange={(next) => onChildSizingChange("horizontal", next)}
|
|
366
|
+
onMinMaxChange={onChildMinMaxChange}
|
|
367
|
+
onApplyVariable={onApplyVariable}
|
|
278
368
|
/>
|
|
279
369
|
<SizingField
|
|
280
370
|
axis="H"
|
|
371
|
+
sizingAxis="vertical"
|
|
281
372
|
value={value.childSizing.vertical}
|
|
282
373
|
resolvedSize={value.resolvedSize?.vertical}
|
|
283
|
-
|
|
374
|
+
minMax={value.childMinMax?.vertical}
|
|
375
|
+
options={resolveSizingOptions(
|
|
376
|
+
availableChildSizing?.vertical,
|
|
377
|
+
value.childSizing.vertical,
|
|
378
|
+
)}
|
|
284
379
|
labels={copy}
|
|
285
380
|
disabled={disabled}
|
|
286
381
|
onChange={(next) => onChildSizingChange("vertical", next)}
|
|
382
|
+
onMinMaxChange={onChildMinMaxChange}
|
|
383
|
+
onApplyVariable={onApplyVariable}
|
|
287
384
|
/>
|
|
288
385
|
{/* Resize-to-fit icon button */}
|
|
289
386
|
<Tooltip>
|
|
@@ -300,9 +397,9 @@ export function AutoLayoutMatrix({
|
|
|
300
397
|
onChildSizingChange("horizontal", "hug");
|
|
301
398
|
onChildSizingChange("vertical", "hug");
|
|
302
399
|
}}
|
|
303
|
-
className="size-
|
|
400
|
+
className="size-7 rounded-md text-muted-foreground hover:bg-[var(--design-editor-control-bg)] hover:text-foreground"
|
|
304
401
|
>
|
|
305
|
-
<
|
|
402
|
+
<IconResizeToFitMini />
|
|
306
403
|
</Button>
|
|
307
404
|
</TooltipTrigger>
|
|
308
405
|
<TooltipContent>
|
|
@@ -312,223 +409,124 @@ export function AutoLayoutMatrix({
|
|
|
312
409
|
</div>
|
|
313
410
|
</div>
|
|
314
411
|
|
|
315
|
-
{/* ── Alignment + Gap (
|
|
316
|
-
<div className="grid grid-cols-[
|
|
317
|
-
{/* Left: Alignment label + 3×3 matrix */}
|
|
412
|
+
{/* ── Alignment + Gap (single label row) ── */}
|
|
413
|
+
<div className="grid grid-cols-[78px_1fr] items-start gap-3">
|
|
414
|
+
{/* Left: Alignment label + compact 3×3 matrix (no border box) */}
|
|
318
415
|
<div className="space-y-1.5">
|
|
319
|
-
<
|
|
416
|
+
<ControlLabel>
|
|
320
417
|
{"Alignment" /* i18n-ignore Figma inspector label */}
|
|
321
|
-
</
|
|
322
|
-
<
|
|
418
|
+
</ControlLabel>
|
|
419
|
+
<CompactAlignmentMatrix
|
|
323
420
|
value={value.alignment}
|
|
324
421
|
onChange={onAlignmentChange}
|
|
422
|
+
direction={value.direction}
|
|
423
|
+
disabled={disabled || isBlock}
|
|
325
424
|
onDistribute={onDistribute}
|
|
326
|
-
disabled={disabled}
|
|
327
425
|
/>
|
|
328
426
|
</div>
|
|
329
427
|
|
|
330
|
-
{/* Right: Gap label +
|
|
428
|
+
{/* Right: Gap label + [icon] value [▾] + sliders */}
|
|
331
429
|
<div className="space-y-1.5">
|
|
332
|
-
<
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
{
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
ariaLabel={copy.gap}
|
|
341
|
-
value={value.gap}
|
|
342
|
-
onChange={(next) => onGapChange(next)}
|
|
343
|
-
icon={IconGap}
|
|
344
|
-
unit="px"
|
|
345
|
-
min={0}
|
|
346
|
-
step={1}
|
|
347
|
-
precision={1}
|
|
348
|
-
disabled={disabled}
|
|
349
|
-
labelClassName="w-5 shrink-0 [&>span]:hidden"
|
|
350
|
-
inputClassName="h-6 text-[11px]"
|
|
351
|
-
/>
|
|
352
|
-
</div>
|
|
353
|
-
{/* Gap options / layout settings button */}
|
|
354
|
-
<Tooltip>
|
|
355
|
-
<TooltipTrigger asChild>
|
|
356
|
-
<Button
|
|
357
|
-
type="button"
|
|
358
|
-
variant="ghost"
|
|
359
|
-
size="icon"
|
|
360
|
-
disabled={disabled}
|
|
361
|
-
aria-label={
|
|
362
|
-
"Layout settings" /* i18n-ignore inspector tooltip */
|
|
363
|
-
}
|
|
364
|
-
className="size-6 shrink-0 rounded-md text-muted-foreground hover:bg-[var(--design-editor-control-bg)] hover:text-foreground"
|
|
365
|
-
>
|
|
366
|
-
<IconLayoutSettings className="size-3.5" />
|
|
367
|
-
</Button>
|
|
368
|
-
</TooltipTrigger>
|
|
369
|
-
<TooltipContent>
|
|
370
|
-
{"Layout settings" /* i18n-ignore inspector tooltip */}
|
|
371
|
-
</TooltipContent>
|
|
372
|
-
</Tooltip>
|
|
373
|
-
</div>
|
|
430
|
+
<ControlLabel>{copy.gap}</ControlLabel>
|
|
431
|
+
<GapField
|
|
432
|
+
value={value.gap}
|
|
433
|
+
onGapChange={onGapChange}
|
|
434
|
+
onDistribute={onDistribute}
|
|
435
|
+
label={copy.gap}
|
|
436
|
+
disabled={disabled || isBlock}
|
|
437
|
+
/>
|
|
374
438
|
</div>
|
|
375
439
|
</div>
|
|
376
440
|
|
|
377
441
|
{/* ── Padding ── */}
|
|
378
442
|
<div className="space-y-1.5">
|
|
379
|
-
<
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
value.paddingLinked ? value.padding.top : horizontalPaddingValue
|
|
389
|
-
}
|
|
390
|
-
onChange={(next) => {
|
|
391
|
-
if (value.paddingLinked) {
|
|
443
|
+
<ControlLabel>{copy.padding}</ControlLabel>
|
|
444
|
+
{value.paddingLinked ? (
|
|
445
|
+
/* Default linked state: 2 compact fields + link toggle */
|
|
446
|
+
<div className="grid grid-cols-[1fr_1fr_auto] items-center gap-1.5">
|
|
447
|
+
<PaddingField
|
|
448
|
+
icon={IconPaddingHorizontal}
|
|
449
|
+
ariaLabel={copy.paddingLeft + " / " + copy.paddingRight}
|
|
450
|
+
value={horizontalPaddingValue}
|
|
451
|
+
onChange={(next) =>
|
|
392
452
|
onPaddingChange({
|
|
393
|
-
top:
|
|
394
|
-
|
|
395
|
-
bottom: next,
|
|
453
|
+
top: value.padding.top,
|
|
454
|
+
bottom: value.padding.bottom,
|
|
396
455
|
left: next,
|
|
397
|
-
});
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
onPaddingChange({
|
|
401
|
-
...value.padding,
|
|
402
|
-
left: next,
|
|
403
|
-
right: next,
|
|
404
|
-
});
|
|
405
|
-
}}
|
|
406
|
-
icon={IconPaddingHorizontal}
|
|
407
|
-
unit="px"
|
|
408
|
-
min={0}
|
|
409
|
-
step={1}
|
|
410
|
-
precision={1}
|
|
411
|
-
disabled={disabled}
|
|
412
|
-
labelClassName="w-5 shrink-0 [&>span]:hidden"
|
|
413
|
-
inputClassName="h-6 text-[11px]"
|
|
414
|
-
/>
|
|
415
|
-
{/* Vertical padding (top + bottom) */}
|
|
416
|
-
<ScrubInput
|
|
417
|
-
label={copy.padding}
|
|
418
|
-
ariaLabel={copy.paddingTop + " / " + copy.paddingBottom}
|
|
419
|
-
value={
|
|
420
|
-
value.paddingLinked ? value.padding.top : verticalPaddingValue
|
|
421
|
-
}
|
|
422
|
-
onChange={(next) => {
|
|
423
|
-
if (value.paddingLinked) {
|
|
424
|
-
onPaddingChange({
|
|
425
|
-
top: next,
|
|
426
456
|
right: next,
|
|
427
|
-
|
|
428
|
-
left: next,
|
|
429
|
-
});
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
onPaddingChange({
|
|
433
|
-
...value.padding,
|
|
434
|
-
top: next,
|
|
435
|
-
bottom: next,
|
|
436
|
-
});
|
|
437
|
-
}}
|
|
438
|
-
icon={IconPaddingVertical}
|
|
439
|
-
unit="px"
|
|
440
|
-
min={0}
|
|
441
|
-
step={1}
|
|
442
|
-
precision={1}
|
|
443
|
-
disabled={disabled}
|
|
444
|
-
labelClassName="w-5 shrink-0 [&>span]:hidden"
|
|
445
|
-
inputClassName="h-6 text-[11px]"
|
|
446
|
-
/>
|
|
447
|
-
{/* Link/unlink independent padding */}
|
|
448
|
-
<Tooltip>
|
|
449
|
-
<TooltipTrigger asChild>
|
|
450
|
-
<Button
|
|
451
|
-
type="button"
|
|
452
|
-
variant="ghost"
|
|
453
|
-
size="icon"
|
|
454
|
-
disabled={disabled}
|
|
455
|
-
aria-label={
|
|
456
|
-
value.paddingLinked ? copy.unlinkPadding : copy.linkPadding
|
|
457
|
-
}
|
|
458
|
-
onClick={() => onPaddingLinkedChange(!value.paddingLinked)}
|
|
459
|
-
className="size-6 rounded-md text-muted-foreground hover:bg-[var(--design-editor-control-bg)] hover:text-foreground"
|
|
460
|
-
>
|
|
461
|
-
{value.paddingLinked ? (
|
|
462
|
-
<IconLink className="size-3.5" />
|
|
463
|
-
) : (
|
|
464
|
-
<IconUnlink className="size-3.5" />
|
|
465
|
-
)}
|
|
466
|
-
</Button>
|
|
467
|
-
</TooltipTrigger>
|
|
468
|
-
<TooltipContent>
|
|
469
|
-
{value.paddingLinked ? copy.unlinkPadding : copy.linkPadding}
|
|
470
|
-
</TooltipContent>
|
|
471
|
-
</Tooltip>
|
|
472
|
-
</div>
|
|
473
|
-
|
|
474
|
-
{/* Expanded 4-field padding when unlinked */}
|
|
475
|
-
{!value.paddingLinked && (
|
|
476
|
-
<div className="mt-1 grid grid-cols-2 gap-1.5">
|
|
477
|
-
<ScrubInput
|
|
478
|
-
label={copy.paddingTop}
|
|
479
|
-
value={value.padding.top}
|
|
480
|
-
onChange={(next) =>
|
|
481
|
-
onPaddingChange({ ...value.padding, top: next })
|
|
457
|
+
})
|
|
482
458
|
}
|
|
483
|
-
unit="px"
|
|
484
|
-
min={0}
|
|
485
|
-
step={1}
|
|
486
|
-
precision={1}
|
|
487
459
|
disabled={disabled}
|
|
488
|
-
labelClassName="w-8"
|
|
489
|
-
inputClassName="h-6 text-[11px]"
|
|
490
460
|
/>
|
|
491
|
-
<
|
|
492
|
-
|
|
493
|
-
|
|
461
|
+
<PaddingField
|
|
462
|
+
icon={IconPaddingVertical}
|
|
463
|
+
ariaLabel={copy.paddingTop + " / " + copy.paddingBottom}
|
|
464
|
+
value={verticalPaddingValue}
|
|
494
465
|
onChange={(next) =>
|
|
495
|
-
onPaddingChange({
|
|
466
|
+
onPaddingChange({
|
|
467
|
+
top: next,
|
|
468
|
+
bottom: next,
|
|
469
|
+
left: value.padding.left,
|
|
470
|
+
right: value.padding.right,
|
|
471
|
+
})
|
|
496
472
|
}
|
|
497
|
-
unit="px"
|
|
498
|
-
min={0}
|
|
499
|
-
step={1}
|
|
500
|
-
precision={1}
|
|
501
473
|
disabled={disabled}
|
|
502
|
-
labelClassName="w-8"
|
|
503
|
-
inputClassName="h-6 text-[11px]"
|
|
504
474
|
/>
|
|
505
|
-
<
|
|
506
|
-
|
|
507
|
-
value={value.padding.bottom}
|
|
508
|
-
onChange={(next) =>
|
|
509
|
-
onPaddingChange({ ...value.padding, bottom: next })
|
|
510
|
-
}
|
|
511
|
-
unit="px"
|
|
512
|
-
min={0}
|
|
513
|
-
step={1}
|
|
514
|
-
precision={1}
|
|
475
|
+
<PaddingLinkButton
|
|
476
|
+
linked
|
|
515
477
|
disabled={disabled}
|
|
516
|
-
|
|
517
|
-
|
|
478
|
+
linkLabel={copy.linkPadding}
|
|
479
|
+
unlinkLabel={copy.unlinkPadding}
|
|
480
|
+
onToggle={() => onPaddingLinkedChange(false)}
|
|
518
481
|
/>
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
482
|
+
</div>
|
|
483
|
+
) : (
|
|
484
|
+
/* Unlinked state: expand to 4 separate T / R / B / L fields */
|
|
485
|
+
<div className="grid grid-cols-[1fr_1fr_auto] items-center gap-1.5">
|
|
486
|
+
<div className="col-span-2 grid grid-cols-2 gap-1.5">
|
|
487
|
+
<PaddingField
|
|
488
|
+
icon={IconPaddingTopMini}
|
|
489
|
+
ariaLabel={copy.paddingTop}
|
|
490
|
+
value={value.padding.top}
|
|
491
|
+
onChange={(next) =>
|
|
492
|
+
onPaddingChange({ ...value.padding, top: next })
|
|
493
|
+
}
|
|
494
|
+
disabled={disabled}
|
|
495
|
+
/>
|
|
496
|
+
<PaddingField
|
|
497
|
+
icon={IconPaddingRightMini}
|
|
498
|
+
ariaLabel={copy.paddingRight}
|
|
499
|
+
value={value.padding.right}
|
|
500
|
+
onChange={(next) =>
|
|
501
|
+
onPaddingChange({ ...value.padding, right: next })
|
|
502
|
+
}
|
|
503
|
+
disabled={disabled}
|
|
504
|
+
/>
|
|
505
|
+
<PaddingField
|
|
506
|
+
icon={IconPaddingBottomMini}
|
|
507
|
+
ariaLabel={copy.paddingBottom}
|
|
508
|
+
value={value.padding.bottom}
|
|
509
|
+
onChange={(next) =>
|
|
510
|
+
onPaddingChange({ ...value.padding, bottom: next })
|
|
511
|
+
}
|
|
512
|
+
disabled={disabled}
|
|
513
|
+
/>
|
|
514
|
+
<PaddingField
|
|
515
|
+
icon={IconPaddingLeftMini}
|
|
516
|
+
ariaLabel={copy.paddingLeft}
|
|
517
|
+
value={value.padding.left}
|
|
518
|
+
onChange={(next) =>
|
|
519
|
+
onPaddingChange({ ...value.padding, left: next })
|
|
520
|
+
}
|
|
521
|
+
disabled={disabled}
|
|
522
|
+
/>
|
|
523
|
+
</div>
|
|
524
|
+
<PaddingLinkButton
|
|
525
|
+
linked={false}
|
|
529
526
|
disabled={disabled}
|
|
530
|
-
|
|
531
|
-
|
|
527
|
+
linkLabel={copy.linkPadding}
|
|
528
|
+
unlinkLabel={copy.unlinkPadding}
|
|
529
|
+
onToggle={() => onPaddingLinkedChange(true)}
|
|
532
530
|
/>
|
|
533
531
|
</div>
|
|
534
532
|
)}
|
|
@@ -542,7 +540,7 @@ export function AutoLayoutMatrix({
|
|
|
542
540
|
onCheckedChange={(checked) =>
|
|
543
541
|
onClipContentChange?.(checked === true)
|
|
544
542
|
}
|
|
545
|
-
className="size-
|
|
543
|
+
className="size-3.5 rounded-[3px]"
|
|
546
544
|
/>
|
|
547
545
|
<span>{copy.clipContent}</span>
|
|
548
546
|
</label>
|
|
@@ -551,10 +549,238 @@ export function AutoLayoutMatrix({
|
|
|
551
549
|
);
|
|
552
550
|
}
|
|
553
551
|
|
|
552
|
+
// ─────────────────────────────────────────────────
|
|
553
|
+
// Helpers
|
|
554
|
+
// ─────────────────────────────────────────────────
|
|
555
|
+
|
|
556
|
+
/** Keep the current value selectable even if it's not in the available list. */
|
|
557
|
+
function resolveSizingOptions(
|
|
558
|
+
available: AutoLayoutSizing[] | undefined,
|
|
559
|
+
current: AutoLayoutSizing,
|
|
560
|
+
): AutoLayoutSizing[] {
|
|
561
|
+
if (!available) return SIZING_OPTIONS;
|
|
562
|
+
return available.includes(current) ? available : [...available, current];
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const ALIGNMENT_CELLS: Array<{
|
|
566
|
+
horizontal: AlignmentHorizontal;
|
|
567
|
+
vertical: AlignmentVertical;
|
|
568
|
+
}> = [
|
|
569
|
+
{ horizontal: "left", vertical: "top" },
|
|
570
|
+
{ horizontal: "center", vertical: "top" },
|
|
571
|
+
{ horizontal: "right", vertical: "top" },
|
|
572
|
+
{ horizontal: "left", vertical: "middle" },
|
|
573
|
+
{ horizontal: "center", vertical: "middle" },
|
|
574
|
+
{ horizontal: "right", vertical: "middle" },
|
|
575
|
+
{ horizontal: "left", vertical: "bottom" },
|
|
576
|
+
{ horizontal: "center", vertical: "bottom" },
|
|
577
|
+
{ horizontal: "right", vertical: "bottom" },
|
|
578
|
+
];
|
|
579
|
+
|
|
554
580
|
// ─────────────────────────────────────────────────
|
|
555
581
|
// Sub-components
|
|
556
582
|
// ─────────────────────────────────────────────────
|
|
557
583
|
|
|
584
|
+
/**
|
|
585
|
+
* Compact 3×3 alignment grid (no border box). Inactive cells show a faint dot;
|
|
586
|
+
* the active cell shows accent bars oriented by flow — horizontal bars for a
|
|
587
|
+
* vertical flow, vertical bars for a horizontal flow (Figma convention).
|
|
588
|
+
* When `onDistribute` is provided, two distribute buttons (H + V) are rendered
|
|
589
|
+
* below the grid, matching Figma's inspector layout.
|
|
590
|
+
*/
|
|
591
|
+
function CompactAlignmentMatrix({
|
|
592
|
+
value,
|
|
593
|
+
onChange,
|
|
594
|
+
direction,
|
|
595
|
+
disabled,
|
|
596
|
+
onDistribute,
|
|
597
|
+
}: {
|
|
598
|
+
value: AlignmentMatrixValue;
|
|
599
|
+
onChange: (value: AlignmentMatrixValue) => void;
|
|
600
|
+
direction: AutoLayoutDirection;
|
|
601
|
+
disabled: boolean;
|
|
602
|
+
onDistribute?: (axis: DistributionAxis) => void;
|
|
603
|
+
}) {
|
|
604
|
+
return (
|
|
605
|
+
<div
|
|
606
|
+
className={cn("space-y-1", disabled && "pointer-events-none opacity-40")}
|
|
607
|
+
>
|
|
608
|
+
<div className={cn("grid w-fit grid-cols-3 rounded-md")}>
|
|
609
|
+
{ALIGNMENT_CELLS.map((cell) => {
|
|
610
|
+
const active =
|
|
611
|
+
cell.horizontal === value.horizontal &&
|
|
612
|
+
cell.vertical === value.vertical;
|
|
613
|
+
return (
|
|
614
|
+
<button
|
|
615
|
+
key={`${cell.horizontal}-${cell.vertical}`}
|
|
616
|
+
type="button"
|
|
617
|
+
aria-label={`${cell.vertical} ${cell.horizontal}`}
|
|
618
|
+
aria-pressed={active}
|
|
619
|
+
disabled={disabled}
|
|
620
|
+
onClick={() =>
|
|
621
|
+
onChange({
|
|
622
|
+
horizontal: cell.horizontal,
|
|
623
|
+
vertical: cell.vertical,
|
|
624
|
+
})
|
|
625
|
+
}
|
|
626
|
+
className={cn(
|
|
627
|
+
"flex size-[22px] items-center justify-center rounded-[3px] transition-colors",
|
|
628
|
+
"hover:bg-[var(--design-editor-control-bg)]",
|
|
629
|
+
)}
|
|
630
|
+
>
|
|
631
|
+
{active ? (
|
|
632
|
+
<AlignmentBars
|
|
633
|
+
horizontal={cell.horizontal}
|
|
634
|
+
vertical={cell.vertical}
|
|
635
|
+
direction={direction}
|
|
636
|
+
/>
|
|
637
|
+
) : (
|
|
638
|
+
<span
|
|
639
|
+
className="block size-[3px] rounded-full bg-current opacity-25"
|
|
640
|
+
aria-hidden="true"
|
|
641
|
+
/>
|
|
642
|
+
)}
|
|
643
|
+
</button>
|
|
644
|
+
);
|
|
645
|
+
})}
|
|
646
|
+
</div>
|
|
647
|
+
{onDistribute != null ? (
|
|
648
|
+
<div className="flex gap-0.5">
|
|
649
|
+
<Tooltip>
|
|
650
|
+
<TooltipTrigger asChild>
|
|
651
|
+
<button
|
|
652
|
+
type="button"
|
|
653
|
+
aria-label={
|
|
654
|
+
"Distribute horizontal spacing" /* i18n-ignore Figma inspector tooltip */
|
|
655
|
+
}
|
|
656
|
+
disabled={disabled}
|
|
657
|
+
onClick={() => onDistribute("horizontal")}
|
|
658
|
+
className={cn(
|
|
659
|
+
"flex size-[22px] items-center justify-center rounded-[3px] transition-colors",
|
|
660
|
+
"text-muted-foreground hover:bg-[var(--design-editor-control-bg)] hover:text-foreground",
|
|
661
|
+
"disabled:pointer-events-none disabled:opacity-40",
|
|
662
|
+
)}
|
|
663
|
+
>
|
|
664
|
+
<IconDistributeH />
|
|
665
|
+
</button>
|
|
666
|
+
</TooltipTrigger>
|
|
667
|
+
<TooltipContent>
|
|
668
|
+
{
|
|
669
|
+
"Distribute horizontal spacing" /* i18n-ignore Figma inspector tooltip */
|
|
670
|
+
}
|
|
671
|
+
</TooltipContent>
|
|
672
|
+
</Tooltip>
|
|
673
|
+
<Tooltip>
|
|
674
|
+
<TooltipTrigger asChild>
|
|
675
|
+
<button
|
|
676
|
+
type="button"
|
|
677
|
+
aria-label={
|
|
678
|
+
"Distribute vertical spacing" /* i18n-ignore Figma inspector tooltip */
|
|
679
|
+
}
|
|
680
|
+
disabled={disabled}
|
|
681
|
+
onClick={() => onDistribute("vertical")}
|
|
682
|
+
className={cn(
|
|
683
|
+
"flex size-[22px] items-center justify-center rounded-[3px] transition-colors",
|
|
684
|
+
"text-muted-foreground hover:bg-[var(--design-editor-control-bg)] hover:text-foreground",
|
|
685
|
+
"disabled:pointer-events-none disabled:opacity-40",
|
|
686
|
+
)}
|
|
687
|
+
>
|
|
688
|
+
<IconDistributeV />
|
|
689
|
+
</button>
|
|
690
|
+
</TooltipTrigger>
|
|
691
|
+
<TooltipContent>
|
|
692
|
+
{
|
|
693
|
+
"Distribute vertical spacing" /* i18n-ignore Figma inspector tooltip */
|
|
694
|
+
}
|
|
695
|
+
</TooltipContent>
|
|
696
|
+
</Tooltip>
|
|
697
|
+
</div>
|
|
698
|
+
) : null}
|
|
699
|
+
</div>
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** Accent bars showing items packed toward the active edge, oriented by flow. */
|
|
704
|
+
function AlignmentBars({
|
|
705
|
+
horizontal,
|
|
706
|
+
vertical,
|
|
707
|
+
direction,
|
|
708
|
+
}: {
|
|
709
|
+
horizontal: AlignmentHorizontal;
|
|
710
|
+
vertical: AlignmentVertical;
|
|
711
|
+
direction: AutoLayoutDirection;
|
|
712
|
+
}) {
|
|
713
|
+
const accent = "var(--design-editor-accent-color, #18a0fb)";
|
|
714
|
+
// Vertical flow → children stack vertically → render horizontal bars.
|
|
715
|
+
// Horizontal flow → children sit in a row → render vertical bars.
|
|
716
|
+
const stack = direction === "vertical";
|
|
717
|
+
const box = 14;
|
|
718
|
+
const pad = 2.5;
|
|
719
|
+
const thickness = 2;
|
|
720
|
+
const length = 5;
|
|
721
|
+
const shortLength = 3.5;
|
|
722
|
+
const gap = 1.5;
|
|
723
|
+
|
|
724
|
+
// Position the group of two bars toward the active edge.
|
|
725
|
+
const total = stack
|
|
726
|
+
? thickness * 2 + gap // height when bars are horizontal
|
|
727
|
+
: thickness * 2 + gap; // width when bars are vertical
|
|
728
|
+
|
|
729
|
+
const along = (h: AlignmentHorizontal | AlignmentVertical, size: number) => {
|
|
730
|
+
if (h === "left" || h === "top") return pad;
|
|
731
|
+
if (h === "right" || h === "bottom") return box - pad - size;
|
|
732
|
+
return (box - size) / 2;
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
const bars = [length, shortLength];
|
|
736
|
+
|
|
737
|
+
return (
|
|
738
|
+
<svg viewBox="0 0 14 14" width={14} height={14} aria-hidden="true">
|
|
739
|
+
{bars.map((len, i) => {
|
|
740
|
+
if (stack) {
|
|
741
|
+
// Horizontal bars: vary X by horizontal align, stack along Y.
|
|
742
|
+
const y = along(vertical, total) + i * (thickness + gap);
|
|
743
|
+
const x = along(horizontal, len);
|
|
744
|
+
return (
|
|
745
|
+
<rect
|
|
746
|
+
key={i}
|
|
747
|
+
x={x}
|
|
748
|
+
y={y}
|
|
749
|
+
width={len}
|
|
750
|
+
height={thickness}
|
|
751
|
+
rx={1}
|
|
752
|
+
fill={accent}
|
|
753
|
+
/>
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
// Vertical bars: vary Y by vertical align, distribute along X.
|
|
757
|
+
const x = along(horizontal, total) + i * (thickness + gap);
|
|
758
|
+
const y = along(vertical, len);
|
|
759
|
+
return (
|
|
760
|
+
<rect
|
|
761
|
+
key={i}
|
|
762
|
+
x={x}
|
|
763
|
+
y={y}
|
|
764
|
+
width={thickness}
|
|
765
|
+
height={len}
|
|
766
|
+
rx={1}
|
|
767
|
+
fill={accent}
|
|
768
|
+
/>
|
|
769
|
+
);
|
|
770
|
+
})}
|
|
771
|
+
</svg>
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/** Small uppercase muted section label, 11px. */
|
|
776
|
+
function ControlLabel({ children }: { children: ReactNode }) {
|
|
777
|
+
return (
|
|
778
|
+
<span className="block text-[11px] font-medium text-muted-foreground">
|
|
779
|
+
{children}
|
|
780
|
+
</span>
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
|
|
558
784
|
/** A single segment in the flow segmented control. */
|
|
559
785
|
function FlowButton({
|
|
560
786
|
label,
|
|
@@ -562,14 +788,12 @@ function FlowButton({
|
|
|
562
788
|
disabled,
|
|
563
789
|
onClick,
|
|
564
790
|
children,
|
|
565
|
-
position,
|
|
566
791
|
}: {
|
|
567
792
|
label: string;
|
|
568
793
|
active: boolean;
|
|
569
794
|
disabled: boolean;
|
|
570
795
|
onClick: () => void;
|
|
571
796
|
children: ReactNode;
|
|
572
|
-
position?: "first" | "last";
|
|
573
797
|
}) {
|
|
574
798
|
return (
|
|
575
799
|
<Tooltip>
|
|
@@ -581,16 +805,15 @@ function FlowButton({
|
|
|
581
805
|
disabled={disabled}
|
|
582
806
|
onClick={onClick}
|
|
583
807
|
className={cn(
|
|
584
|
-
"flex flex-1 items-center justify-center transition-colors",
|
|
585
|
-
"
|
|
586
|
-
"text-muted-foreground hover:bg-[var(--design-editor-panel-raised-bg)] hover:text-foreground",
|
|
808
|
+
"flex h-6 flex-1 items-center justify-center rounded-[5px] transition-colors",
|
|
809
|
+
"text-muted-foreground hover:text-foreground",
|
|
587
810
|
"disabled:pointer-events-none disabled:opacity-40",
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
811
|
+
active
|
|
812
|
+
? [
|
|
813
|
+
"bg-[var(--design-editor-panel-bg)] text-foreground",
|
|
814
|
+
"shadow-[0_0_0_1px_var(--design-editor-control-border,hsl(var(--border))),0_1px_2px_rgba(0,0,0,0.25)]",
|
|
815
|
+
]
|
|
816
|
+
: "hover:bg-[var(--design-editor-panel-raised-bg)]",
|
|
594
817
|
)}
|
|
595
818
|
>
|
|
596
819
|
{children}
|
|
@@ -601,99 +824,766 @@ function FlowButton({
|
|
|
601
824
|
);
|
|
602
825
|
}
|
|
603
826
|
|
|
604
|
-
/**
|
|
605
|
-
|
|
606
|
-
|
|
827
|
+
/**
|
|
828
|
+
* Gap field: [icon] value [▾] with a sliders icon to the right.
|
|
829
|
+
* The chevron dropdown offers Fixed (numeric gap) vs Auto (space-between).
|
|
830
|
+
*/
|
|
831
|
+
function GapField({
|
|
607
832
|
value,
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
833
|
+
onGapChange,
|
|
834
|
+
onDistribute,
|
|
835
|
+
label,
|
|
611
836
|
disabled,
|
|
837
|
+
}: {
|
|
838
|
+
value: number;
|
|
839
|
+
onGapChange: (gap: number) => void;
|
|
840
|
+
onDistribute?: (axis: DistributionAxis) => void;
|
|
841
|
+
label: string;
|
|
842
|
+
disabled: boolean;
|
|
843
|
+
}) {
|
|
844
|
+
return (
|
|
845
|
+
<div className="flex items-center gap-1.5">
|
|
846
|
+
{/* [gap-icon] value [▾] in one control surface */}
|
|
847
|
+
<div
|
|
848
|
+
className={cn(
|
|
849
|
+
"flex h-7 min-w-0 flex-1 items-center rounded-md bg-[var(--design-editor-control-bg)] pl-1.5",
|
|
850
|
+
disabled && "opacity-40",
|
|
851
|
+
)}
|
|
852
|
+
>
|
|
853
|
+
<span className="flex shrink-0 items-center text-muted-foreground">
|
|
854
|
+
<IconGap className="size-3.5" />
|
|
855
|
+
</span>
|
|
856
|
+
<ScrubInput
|
|
857
|
+
label={label}
|
|
858
|
+
ariaLabel={label}
|
|
859
|
+
value={value}
|
|
860
|
+
onChange={(next) => onGapChange(next)}
|
|
861
|
+
unit="px"
|
|
862
|
+
min={0}
|
|
863
|
+
step={1}
|
|
864
|
+
precision={1}
|
|
865
|
+
disabled={disabled}
|
|
866
|
+
className="min-w-0 flex-1 gap-0"
|
|
867
|
+
labelClassName="hidden"
|
|
868
|
+
inputClassName="h-6 border-0 bg-transparent px-1 text-[11px] shadow-none focus-visible:ring-0"
|
|
869
|
+
/>
|
|
870
|
+
{onDistribute != null ? (
|
|
871
|
+
<DropdownMenu>
|
|
872
|
+
<Tooltip>
|
|
873
|
+
<TooltipTrigger asChild>
|
|
874
|
+
<DropdownMenuTrigger asChild>
|
|
875
|
+
<button
|
|
876
|
+
type="button"
|
|
877
|
+
aria-label={"Gap mode" /* i18n-ignore inspector tooltip */}
|
|
878
|
+
disabled={disabled}
|
|
879
|
+
className={cn(
|
|
880
|
+
"flex h-7 w-6 shrink-0 items-center justify-center rounded-r-md",
|
|
881
|
+
"text-muted-foreground hover:text-foreground",
|
|
882
|
+
"disabled:pointer-events-none disabled:opacity-40",
|
|
883
|
+
"focus:outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color,hsl(var(--primary)))]",
|
|
884
|
+
)}
|
|
885
|
+
>
|
|
886
|
+
<ChevronDownMini />
|
|
887
|
+
</button>
|
|
888
|
+
</DropdownMenuTrigger>
|
|
889
|
+
</TooltipTrigger>
|
|
890
|
+
<TooltipContent>
|
|
891
|
+
{"Gap mode" /* i18n-ignore inspector tooltip */}
|
|
892
|
+
</TooltipContent>
|
|
893
|
+
</Tooltip>
|
|
894
|
+
<DropdownMenuContent
|
|
895
|
+
align="end"
|
|
896
|
+
className="min-w-[110px] text-[12px]"
|
|
897
|
+
sideOffset={4}
|
|
898
|
+
>
|
|
899
|
+
<DropdownMenuCheckboxItem
|
|
900
|
+
checked
|
|
901
|
+
className="text-[12px]"
|
|
902
|
+
onSelect={() => {
|
|
903
|
+
/* Fixed gap: keep current numeric value (already numeric). */
|
|
904
|
+
}}
|
|
905
|
+
>
|
|
906
|
+
{"Fixed" /* i18n-ignore Figma gap mode label */}
|
|
907
|
+
</DropdownMenuCheckboxItem>
|
|
908
|
+
<DropdownMenuCheckboxItem
|
|
909
|
+
checked={false}
|
|
910
|
+
className="text-[12px]"
|
|
911
|
+
onSelect={() => onDistribute("horizontal")}
|
|
912
|
+
>
|
|
913
|
+
{"Auto" /* i18n-ignore Figma gap mode label */}
|
|
914
|
+
</DropdownMenuCheckboxItem>
|
|
915
|
+
</DropdownMenuContent>
|
|
916
|
+
</DropdownMenu>
|
|
917
|
+
) : null}
|
|
918
|
+
</div>
|
|
919
|
+
{/* Sliders / advanced spacing icon (Figma's tune control) */}
|
|
920
|
+
<Tooltip>
|
|
921
|
+
<TooltipTrigger asChild>
|
|
922
|
+
<Button
|
|
923
|
+
type="button"
|
|
924
|
+
variant="ghost"
|
|
925
|
+
size="icon"
|
|
926
|
+
disabled={disabled}
|
|
927
|
+
aria-label={
|
|
928
|
+
"Advanced gap settings" /* i18n-ignore inspector tooltip */
|
|
929
|
+
}
|
|
930
|
+
onClick={() => onDistribute?.("horizontal")}
|
|
931
|
+
className="size-7 shrink-0 rounded-md text-muted-foreground hover:bg-[var(--design-editor-control-bg)] hover:text-foreground"
|
|
932
|
+
>
|
|
933
|
+
<IconSlidersMini />
|
|
934
|
+
</Button>
|
|
935
|
+
</TooltipTrigger>
|
|
936
|
+
<TooltipContent>
|
|
937
|
+
{"Advanced gap settings" /* i18n-ignore inspector tooltip */}
|
|
938
|
+
</TooltipContent>
|
|
939
|
+
</Tooltip>
|
|
940
|
+
</div>
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/** A compact padding field: [icon] value. */
|
|
945
|
+
function PaddingField({
|
|
946
|
+
icon: Icon,
|
|
947
|
+
ariaLabel,
|
|
948
|
+
value,
|
|
612
949
|
onChange,
|
|
950
|
+
disabled,
|
|
951
|
+
}: {
|
|
952
|
+
icon: (props: { className?: string }) => ReactNode;
|
|
953
|
+
ariaLabel: string;
|
|
954
|
+
value: number;
|
|
955
|
+
onChange: (value: number) => void;
|
|
956
|
+
disabled: boolean;
|
|
957
|
+
}) {
|
|
958
|
+
return (
|
|
959
|
+
<div
|
|
960
|
+
className={cn(
|
|
961
|
+
"flex h-7 min-w-0 items-center rounded-md bg-[var(--design-editor-control-bg)] pl-1.5",
|
|
962
|
+
disabled && "opacity-40",
|
|
963
|
+
)}
|
|
964
|
+
>
|
|
965
|
+
<span className="flex shrink-0 items-center text-muted-foreground">
|
|
966
|
+
<Icon className="size-3.5" />
|
|
967
|
+
</span>
|
|
968
|
+
<ScrubInput
|
|
969
|
+
label={ariaLabel}
|
|
970
|
+
ariaLabel={ariaLabel}
|
|
971
|
+
value={value}
|
|
972
|
+
onChange={(next) => onChange(next)}
|
|
973
|
+
unit="px"
|
|
974
|
+
min={0}
|
|
975
|
+
step={1}
|
|
976
|
+
precision={1}
|
|
977
|
+
disabled={disabled}
|
|
978
|
+
className="min-w-0 flex-1 gap-0"
|
|
979
|
+
labelClassName="hidden"
|
|
980
|
+
inputClassName="h-6 border-0 bg-transparent px-1 text-[11px] shadow-none focus-visible:ring-0"
|
|
981
|
+
/>
|
|
982
|
+
</div>
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/** Link / unlink padding toggle button. */
|
|
987
|
+
function PaddingLinkButton({
|
|
988
|
+
linked,
|
|
989
|
+
disabled,
|
|
990
|
+
linkLabel,
|
|
991
|
+
unlinkLabel,
|
|
992
|
+
onToggle,
|
|
613
993
|
}: {
|
|
994
|
+
linked: boolean;
|
|
995
|
+
disabled: boolean;
|
|
996
|
+
linkLabel: string;
|
|
997
|
+
unlinkLabel: string;
|
|
998
|
+
onToggle: () => void;
|
|
999
|
+
}) {
|
|
1000
|
+
return (
|
|
1001
|
+
<Tooltip>
|
|
1002
|
+
<TooltipTrigger asChild>
|
|
1003
|
+
<Button
|
|
1004
|
+
type="button"
|
|
1005
|
+
variant="ghost"
|
|
1006
|
+
size="icon"
|
|
1007
|
+
disabled={disabled}
|
|
1008
|
+
aria-label={linked ? unlinkLabel : linkLabel}
|
|
1009
|
+
onClick={onToggle}
|
|
1010
|
+
className={cn(
|
|
1011
|
+
"size-7 shrink-0 rounded-md hover:bg-[var(--design-editor-control-bg)]",
|
|
1012
|
+
linked
|
|
1013
|
+
? "text-[var(--design-editor-accent-color,hsl(var(--primary)))] hover:text-[var(--design-editor-accent-color,hsl(var(--primary)))]"
|
|
1014
|
+
: "text-muted-foreground hover:text-foreground",
|
|
1015
|
+
)}
|
|
1016
|
+
>
|
|
1017
|
+
{linked ? <IconPaddingLinked /> : <IconPaddingUnlinked />}
|
|
1018
|
+
</Button>
|
|
1019
|
+
</TooltipTrigger>
|
|
1020
|
+
<TooltipContent>{linked ? unlinkLabel : linkLabel}</TooltipContent>
|
|
1021
|
+
</Tooltip>
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
interface SizingFieldMinMax {
|
|
1026
|
+
min?: number | null;
|
|
1027
|
+
max?: number | null;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
export interface SizingFieldProps {
|
|
1031
|
+
/** Display letter ("W" / "H"). */
|
|
614
1032
|
axis: string;
|
|
1033
|
+
/** Logical axis used by min/max + variable callbacks. */
|
|
1034
|
+
sizingAxis: AutoLayoutSizingAxis;
|
|
615
1035
|
value: AutoLayoutSizing;
|
|
616
1036
|
resolvedSize?: number;
|
|
1037
|
+
/** Currently-set min/max constraints (px). */
|
|
1038
|
+
minMax?: SizingFieldMinMax;
|
|
617
1039
|
options?: AutoLayoutSizing[];
|
|
618
|
-
|
|
1040
|
+
/** Optional label overrides; English defaults are used for any omitted key. */
|
|
1041
|
+
labels?: Partial<AutoLayoutMatrixLabels>;
|
|
619
1042
|
disabled: boolean;
|
|
620
1043
|
onChange: (value: AutoLayoutSizing) => void;
|
|
1044
|
+
onMinMaxChange?: (
|
|
1045
|
+
axis: AutoLayoutSizingAxis,
|
|
1046
|
+
kind: "min" | "max",
|
|
1047
|
+
value: number | null,
|
|
1048
|
+
) => void;
|
|
1049
|
+
onApplyVariable?: (axis: AutoLayoutSizingAxis) => void;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* Figma-style sizing field. Trigger renders `[axis | value | mode ▾]`; the menu
|
|
1054
|
+
* is the full Figma resizing dropdown:
|
|
1055
|
+
* Fixed · Hug contents · Fill container
|
|
1056
|
+
* ──────────────────────
|
|
1057
|
+
* Add min … · Add max …
|
|
1058
|
+
* ──────────────────────
|
|
1059
|
+
* Apply variable …
|
|
1060
|
+
* "Add min/max" reveals an inline number input; set constraints render as a
|
|
1061
|
+
* small sub-row below the trigger with a remove (×). Hug/Fill rows are gated by
|
|
1062
|
+
* the `options` list (per-axis availability); Fixed is always present.
|
|
1063
|
+
*/
|
|
1064
|
+
export function SizingField({
|
|
1065
|
+
axis,
|
|
1066
|
+
sizingAxis,
|
|
1067
|
+
value,
|
|
1068
|
+
resolvedSize,
|
|
1069
|
+
minMax,
|
|
1070
|
+
options = SIZING_OPTIONS,
|
|
1071
|
+
labels: labelOverrides,
|
|
1072
|
+
disabled,
|
|
1073
|
+
onChange,
|
|
1074
|
+
onMinMaxChange,
|
|
1075
|
+
onApplyVariable,
|
|
1076
|
+
}: SizingFieldProps) {
|
|
1077
|
+
const labels = { ...DEFAULT_AUTO_LAYOUT_LABELS, ...labelOverrides };
|
|
1078
|
+
const isWidth = sizingAxis === "horizontal";
|
|
1079
|
+
// Which inline "add" editor is currently open (none by default).
|
|
1080
|
+
const [editing, setEditing] = useState<null | "min" | "max">(null);
|
|
1081
|
+
|
|
1082
|
+
const minValue = minMax?.min ?? null;
|
|
1083
|
+
const maxValue = minMax?.max ?? null;
|
|
1084
|
+
const hasMin = minValue != null;
|
|
1085
|
+
const hasMax = maxValue != null;
|
|
1086
|
+
|
|
1087
|
+
const canHug = options.includes("hug");
|
|
1088
|
+
const canFill = options.includes("fill");
|
|
1089
|
+
|
|
1090
|
+
// Figma rule: when Fixed, show ONLY the numeric value + chevron (no word).
|
|
1091
|
+
// When Hug / Fill, show value + the mode word.
|
|
1092
|
+
const showWord = value !== "fixed";
|
|
1093
|
+
|
|
1094
|
+
const addMinLabel = isWidth ? labels.addMinWidth : labels.addMinHeight;
|
|
1095
|
+
const addMaxLabel = isWidth ? labels.addMaxWidth : labels.addMaxHeight;
|
|
1096
|
+
const minLabel = isWidth ? labels.minWidth : labels.minHeight;
|
|
1097
|
+
const maxLabel = isWidth ? labels.maxWidth : labels.maxHeight;
|
|
1098
|
+
|
|
1099
|
+
const openEditor = (kind: "min" | "max") => {
|
|
1100
|
+
// Seed a sensible default when first adding the constraint.
|
|
1101
|
+
const seed = Math.max(0, Math.round(resolvedSize ?? 0));
|
|
1102
|
+
onMinMaxChange?.(sizingAxis, kind, kind === "min" ? seed : seed || 1);
|
|
1103
|
+
setEditing(kind);
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
return (
|
|
1107
|
+
<div className="flex min-w-0 flex-col gap-1">
|
|
1108
|
+
<DropdownMenu>
|
|
1109
|
+
<Tooltip>
|
|
1110
|
+
<TooltipTrigger asChild>
|
|
1111
|
+
<DropdownMenuTrigger asChild>
|
|
1112
|
+
<button
|
|
1113
|
+
type="button"
|
|
1114
|
+
aria-label={`${axis} ${Math.round(resolvedSize ?? 0)} ${labels[value]}`}
|
|
1115
|
+
disabled={disabled}
|
|
1116
|
+
className={cn(
|
|
1117
|
+
"flex h-7 w-full items-center gap-1 overflow-hidden rounded-md px-1.5",
|
|
1118
|
+
"bg-[var(--design-editor-control-bg)] text-[11px]",
|
|
1119
|
+
"hover:bg-[var(--design-editor-panel-raised-bg)]",
|
|
1120
|
+
"disabled:pointer-events-none disabled:opacity-40",
|
|
1121
|
+
"focus:outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color,hsl(var(--primary)))]",
|
|
1122
|
+
)}
|
|
1123
|
+
>
|
|
1124
|
+
{/* Axis letter */}
|
|
1125
|
+
<span className="shrink-0 text-muted-foreground">{axis}</span>
|
|
1126
|
+
{/* Resolved size */}
|
|
1127
|
+
<span className="min-w-0 flex-1 truncate text-left tabular-nums text-foreground">
|
|
1128
|
+
{Math.round(resolvedSize ?? 0)}
|
|
1129
|
+
</span>
|
|
1130
|
+
{/* Mode word (Hug/Fill only) */}
|
|
1131
|
+
{showWord ? (
|
|
1132
|
+
<span className="shrink-0 truncate text-muted-foreground">
|
|
1133
|
+
{labels[value]}
|
|
1134
|
+
</span>
|
|
1135
|
+
) : null}
|
|
1136
|
+
{/* Caret */}
|
|
1137
|
+
<span className="flex shrink-0 items-center text-muted-foreground">
|
|
1138
|
+
<ChevronDownMini />
|
|
1139
|
+
</span>
|
|
1140
|
+
</button>
|
|
1141
|
+
</DropdownMenuTrigger>
|
|
1142
|
+
</TooltipTrigger>
|
|
1143
|
+
<TooltipContent>{`${axis} · ${labels[value]}`}</TooltipContent>
|
|
1144
|
+
</Tooltip>
|
|
1145
|
+
<DropdownMenuContent
|
|
1146
|
+
align="start"
|
|
1147
|
+
className="min-w-[180px] text-[12px]"
|
|
1148
|
+
sideOffset={4}
|
|
1149
|
+
>
|
|
1150
|
+
{/* ── Modes ── */}
|
|
1151
|
+
<SizingMenuItem
|
|
1152
|
+
icon={<IconSizingFixed />}
|
|
1153
|
+
label={labels.fixed}
|
|
1154
|
+
active={value === "fixed"}
|
|
1155
|
+
onSelect={() => onChange("fixed")}
|
|
1156
|
+
/>
|
|
1157
|
+
{canHug ? (
|
|
1158
|
+
<SizingMenuItem
|
|
1159
|
+
icon={<IconSizingHug />}
|
|
1160
|
+
label={labels.hugContents}
|
|
1161
|
+
active={value === "hug"}
|
|
1162
|
+
onSelect={() => onChange("hug")}
|
|
1163
|
+
/>
|
|
1164
|
+
) : null}
|
|
1165
|
+
{canFill ? (
|
|
1166
|
+
<SizingMenuItem
|
|
1167
|
+
icon={<IconSizingFill />}
|
|
1168
|
+
label={labels.fillContainer}
|
|
1169
|
+
active={value === "fill"}
|
|
1170
|
+
onSelect={() => onChange("fill")}
|
|
1171
|
+
/>
|
|
1172
|
+
) : null}
|
|
1173
|
+
|
|
1174
|
+
{/* ── Min / Max ── */}
|
|
1175
|
+
{onMinMaxChange ? (
|
|
1176
|
+
<>
|
|
1177
|
+
<DropdownMenuSeparator />
|
|
1178
|
+
<SizingMenuItem
|
|
1179
|
+
icon={<IconSizingMin />}
|
|
1180
|
+
label={addMinLabel}
|
|
1181
|
+
active={hasMin}
|
|
1182
|
+
disabled={hasMin}
|
|
1183
|
+
onSelect={() => openEditor("min")}
|
|
1184
|
+
/>
|
|
1185
|
+
<SizingMenuItem
|
|
1186
|
+
icon={<IconSizingMax />}
|
|
1187
|
+
label={addMaxLabel}
|
|
1188
|
+
active={hasMax}
|
|
1189
|
+
disabled={hasMax}
|
|
1190
|
+
onSelect={() => openEditor("max")}
|
|
1191
|
+
/>
|
|
1192
|
+
</>
|
|
1193
|
+
) : null}
|
|
1194
|
+
|
|
1195
|
+
{/* ── Variable ── */}
|
|
1196
|
+
<DropdownMenuSeparator />
|
|
1197
|
+
<SizingMenuItem
|
|
1198
|
+
icon={<IconSizingVariable />}
|
|
1199
|
+
label={labels.applyVariable}
|
|
1200
|
+
disabled={!onApplyVariable}
|
|
1201
|
+
onSelect={() => onApplyVariable?.(sizingAxis)}
|
|
1202
|
+
/>
|
|
1203
|
+
</DropdownMenuContent>
|
|
1204
|
+
</DropdownMenu>
|
|
1205
|
+
|
|
1206
|
+
{/* ── Constraint sub-rows ── */}
|
|
1207
|
+
{hasMin ? (
|
|
1208
|
+
<ConstraintSubRow
|
|
1209
|
+
label={minLabel}
|
|
1210
|
+
value={minValue ?? 0}
|
|
1211
|
+
disabled={disabled}
|
|
1212
|
+
removeLabel={labels.removeConstraint}
|
|
1213
|
+
onChange={(next) => onMinMaxChange?.(sizingAxis, "min", next)}
|
|
1214
|
+
onRemove={() => {
|
|
1215
|
+
onMinMaxChange?.(sizingAxis, "min", null);
|
|
1216
|
+
setEditing((cur) => (cur === "min" ? null : cur));
|
|
1217
|
+
}}
|
|
1218
|
+
/>
|
|
1219
|
+
) : null}
|
|
1220
|
+
{hasMax ? (
|
|
1221
|
+
<ConstraintSubRow
|
|
1222
|
+
label={maxLabel}
|
|
1223
|
+
value={maxValue ?? 0}
|
|
1224
|
+
disabled={disabled}
|
|
1225
|
+
removeLabel={labels.removeConstraint}
|
|
1226
|
+
onChange={(next) => onMinMaxChange?.(sizingAxis, "max", next)}
|
|
1227
|
+
onRemove={() => {
|
|
1228
|
+
onMinMaxChange?.(sizingAxis, "max", null);
|
|
1229
|
+
setEditing((cur) => (cur === "max" ? null : cur));
|
|
1230
|
+
}}
|
|
1231
|
+
/>
|
|
1232
|
+
) : null}
|
|
1233
|
+
</div>
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/** A single icon + label row in the sizing menu, with an active checkmark. */
|
|
1238
|
+
function SizingMenuItem({
|
|
1239
|
+
icon,
|
|
1240
|
+
label,
|
|
1241
|
+
active = false,
|
|
1242
|
+
disabled = false,
|
|
1243
|
+
onSelect,
|
|
1244
|
+
}: {
|
|
1245
|
+
icon: ReactNode;
|
|
1246
|
+
label: string;
|
|
1247
|
+
active?: boolean;
|
|
1248
|
+
disabled?: boolean;
|
|
1249
|
+
onSelect: () => void;
|
|
621
1250
|
}) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
1251
|
+
return (
|
|
1252
|
+
<DropdownMenuItem
|
|
1253
|
+
disabled={disabled}
|
|
1254
|
+
onSelect={(event) => {
|
|
1255
|
+
// Keep the menu's selection semantics but route through our handler.
|
|
1256
|
+
event.preventDefault();
|
|
1257
|
+
onSelect();
|
|
1258
|
+
}}
|
|
1259
|
+
className="gap-2 pl-2 pr-2 text-[12px]"
|
|
1260
|
+
>
|
|
1261
|
+
<span className="flex size-4 shrink-0 items-center justify-center text-muted-foreground">
|
|
1262
|
+
{icon}
|
|
1263
|
+
</span>
|
|
1264
|
+
<span className="min-w-0 flex-1 truncate">{label}</span>
|
|
1265
|
+
<span className="flex size-3.5 shrink-0 items-center justify-center text-[var(--design-editor-accent-color,hsl(var(--primary)))]">
|
|
1266
|
+
{active ? <CheckMini /> : null}
|
|
1267
|
+
</span>
|
|
1268
|
+
</DropdownMenuItem>
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
626
1271
|
|
|
1272
|
+
/** Inline min/max constraint editor row with a remove (×) affordance. */
|
|
1273
|
+
function ConstraintSubRow({
|
|
1274
|
+
label,
|
|
1275
|
+
value,
|
|
1276
|
+
disabled,
|
|
1277
|
+
removeLabel,
|
|
1278
|
+
onChange,
|
|
1279
|
+
onRemove,
|
|
1280
|
+
}: {
|
|
1281
|
+
label: string;
|
|
1282
|
+
value: number;
|
|
1283
|
+
disabled: boolean;
|
|
1284
|
+
removeLabel: string;
|
|
1285
|
+
onChange: (value: number) => void;
|
|
1286
|
+
onRemove: () => void;
|
|
1287
|
+
}) {
|
|
627
1288
|
return (
|
|
628
|
-
<
|
|
1289
|
+
<div
|
|
1290
|
+
className={cn(
|
|
1291
|
+
"flex h-6 min-w-0 items-center rounded-md bg-[var(--design-editor-control-bg)] pl-1.5",
|
|
1292
|
+
disabled && "opacity-40",
|
|
1293
|
+
)}
|
|
1294
|
+
>
|
|
1295
|
+
<ScrubInput
|
|
1296
|
+
label={label}
|
|
1297
|
+
ariaLabel={label}
|
|
1298
|
+
value={value}
|
|
1299
|
+
onChange={(next) => onChange(Math.max(0, Math.round(next)))}
|
|
1300
|
+
unit="px"
|
|
1301
|
+
min={0}
|
|
1302
|
+
step={1}
|
|
1303
|
+
precision={1}
|
|
1304
|
+
disabled={disabled}
|
|
1305
|
+
className="min-w-0 flex-1 gap-0"
|
|
1306
|
+
labelClassName="hidden"
|
|
1307
|
+
inputClassName="h-5 border-0 bg-transparent px-1 text-[11px] shadow-none focus-visible:ring-0"
|
|
1308
|
+
/>
|
|
629
1309
|
<Tooltip>
|
|
630
1310
|
<TooltipTrigger asChild>
|
|
631
|
-
<
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
className={cn(
|
|
637
|
-
"flex h-6 w-full items-center overflow-hidden rounded-md",
|
|
638
|
-
"bg-[var(--design-editor-control-bg)] text-[11px]",
|
|
639
|
-
"hover:bg-[var(--design-editor-panel-raised-bg)]",
|
|
640
|
-
"disabled:pointer-events-none disabled:opacity-40",
|
|
641
|
-
"focus:outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
|
|
642
|
-
)}
|
|
643
|
-
>
|
|
644
|
-
{/* Axis letter */}
|
|
645
|
-
<span className="flex h-full w-4 shrink-0 items-center justify-center border-r border-[var(--design-editor-control-border,hsl(var(--border)/0.6))] text-muted-foreground">
|
|
646
|
-
{axis}
|
|
647
|
-
</span>
|
|
648
|
-
{/* Resolved size */}
|
|
649
|
-
<span className="min-w-0 flex-1 truncate px-0.5 text-center tabular-nums text-foreground">
|
|
650
|
-
{Math.round(resolvedSize ?? 0)}
|
|
651
|
-
</span>
|
|
652
|
-
{/* Mode word + caret */}
|
|
653
|
-
<span className="flex h-full shrink-0 items-center gap-0 border-l border-[var(--design-editor-control-border,hsl(var(--border)/0.6))] px-0.5 text-muted-foreground">
|
|
654
|
-
{labels[value]}
|
|
655
|
-
<svg
|
|
656
|
-
viewBox="0 0 8 8"
|
|
657
|
-
width={8}
|
|
658
|
-
height={8}
|
|
659
|
-
fill="currentColor"
|
|
660
|
-
aria-hidden="true"
|
|
661
|
-
className="opacity-60"
|
|
662
|
-
>
|
|
663
|
-
<path
|
|
664
|
-
d="M1.5 3 L4 5.5 L6.5 3"
|
|
665
|
-
fill="none"
|
|
666
|
-
stroke="currentColor"
|
|
667
|
-
strokeWidth="1.2"
|
|
668
|
-
strokeLinecap="round"
|
|
669
|
-
strokeLinejoin="round"
|
|
670
|
-
/>
|
|
671
|
-
</svg>
|
|
672
|
-
</span>
|
|
673
|
-
</button>
|
|
674
|
-
</DropdownMenuTrigger>
|
|
675
|
-
</TooltipTrigger>
|
|
676
|
-
<TooltipContent>{`${axis} ${labels[value]}`}</TooltipContent>
|
|
677
|
-
</Tooltip>
|
|
678
|
-
<DropdownMenuContent
|
|
679
|
-
align="start"
|
|
680
|
-
className="min-w-[100px] text-[12px]"
|
|
681
|
-
sideOffset={4}
|
|
682
|
-
>
|
|
683
|
-
{validOptions.map((opt) => (
|
|
684
|
-
<DropdownMenuItem
|
|
685
|
-
key={opt}
|
|
686
|
-
onSelect={() => onChange(opt)}
|
|
1311
|
+
<button
|
|
1312
|
+
type="button"
|
|
1313
|
+
aria-label={removeLabel}
|
|
1314
|
+
disabled={disabled}
|
|
1315
|
+
onClick={onRemove}
|
|
687
1316
|
className={cn(
|
|
688
|
-
"
|
|
689
|
-
|
|
690
|
-
|
|
1317
|
+
"flex h-6 w-5 shrink-0 items-center justify-center rounded-r-md",
|
|
1318
|
+
"text-muted-foreground hover:text-foreground",
|
|
1319
|
+
"disabled:pointer-events-none disabled:opacity-40",
|
|
691
1320
|
)}
|
|
692
1321
|
>
|
|
693
|
-
|
|
694
|
-
</
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1322
|
+
<IconSizingRemove />
|
|
1323
|
+
</button>
|
|
1324
|
+
</TooltipTrigger>
|
|
1325
|
+
<TooltipContent>{removeLabel}</TooltipContent>
|
|
1326
|
+
</Tooltip>
|
|
1327
|
+
</div>
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/** Small check glyph for the active menu row. */
|
|
1332
|
+
function CheckMini() {
|
|
1333
|
+
return (
|
|
1334
|
+
<svg
|
|
1335
|
+
viewBox="0 0 14 14"
|
|
1336
|
+
width={12}
|
|
1337
|
+
height={12}
|
|
1338
|
+
fill="none"
|
|
1339
|
+
aria-hidden="true"
|
|
1340
|
+
>
|
|
1341
|
+
<path
|
|
1342
|
+
d="M2.5 7.5 L5.5 10.5 L11.5 3.5"
|
|
1343
|
+
stroke="currentColor"
|
|
1344
|
+
strokeWidth="1.6"
|
|
1345
|
+
strokeLinecap="round"
|
|
1346
|
+
strokeLinejoin="round"
|
|
1347
|
+
/>
|
|
1348
|
+
</svg>
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// ─────────────────────────────────────────────────
|
|
1353
|
+
// Inline SVG glyphs (Tabler-weight, 24px viewBox)
|
|
1354
|
+
// ─────────────────────────────────────────────────
|
|
1355
|
+
|
|
1356
|
+
/** Minimal downward chevron — matches the Figma caret weight. */
|
|
1357
|
+
function ChevronDownMini() {
|
|
1358
|
+
return (
|
|
1359
|
+
<svg
|
|
1360
|
+
viewBox="0 0 8 8"
|
|
1361
|
+
width={8}
|
|
1362
|
+
height={8}
|
|
1363
|
+
fill="none"
|
|
1364
|
+
aria-hidden="true"
|
|
1365
|
+
className="opacity-70"
|
|
1366
|
+
>
|
|
1367
|
+
<path
|
|
1368
|
+
d="M1.5 3 L4 5.5 L6.5 3"
|
|
1369
|
+
stroke="currentColor"
|
|
1370
|
+
strokeWidth="1.2"
|
|
1371
|
+
strokeLinecap="round"
|
|
1372
|
+
strokeLinejoin="round"
|
|
1373
|
+
/>
|
|
1374
|
+
</svg>
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
/** Normal / free-flow layout glyph — loosely placed small rects. */
|
|
1379
|
+
function IconFlowNormal() {
|
|
1380
|
+
return (
|
|
1381
|
+
<svg
|
|
1382
|
+
viewBox="0 0 24 24"
|
|
1383
|
+
width={14}
|
|
1384
|
+
height={14}
|
|
1385
|
+
fill="currentColor"
|
|
1386
|
+
aria-hidden="true"
|
|
1387
|
+
>
|
|
1388
|
+
<rect x="4" y="4" width="6" height="6" rx="1.5" />
|
|
1389
|
+
<rect x="14" y="6" width="6" height="6" rx="1.5" />
|
|
1390
|
+
<rect x="6" y="14" width="6" height="6" rx="1.5" />
|
|
1391
|
+
</svg>
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/** Resize-to-fit diagonal arrows. */
|
|
1396
|
+
function IconResizeToFitMini() {
|
|
1397
|
+
return (
|
|
1398
|
+
<svg
|
|
1399
|
+
viewBox="0 0 24 24"
|
|
1400
|
+
width={16}
|
|
1401
|
+
height={16}
|
|
1402
|
+
fill="none"
|
|
1403
|
+
stroke="currentColor"
|
|
1404
|
+
strokeWidth="1.6"
|
|
1405
|
+
strokeLinecap="round"
|
|
1406
|
+
strokeLinejoin="round"
|
|
1407
|
+
aria-hidden="true"
|
|
1408
|
+
>
|
|
1409
|
+
<path d="M9 4 H5 a1 1 0 0 0 -1 1 V9" />
|
|
1410
|
+
<path d="M15 4 H19 a1 1 0 0 1 1 1 V9" />
|
|
1411
|
+
<path d="M9 20 H5 a1 1 0 0 1 -1 -1 V15" />
|
|
1412
|
+
<path d="M15 20 H19 a1 1 0 0 0 1 -1 V15" />
|
|
1413
|
+
</svg>
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
/** Sliders / tune icon for advanced gap settings. */
|
|
1418
|
+
function IconSlidersMini() {
|
|
1419
|
+
return (
|
|
1420
|
+
<svg
|
|
1421
|
+
viewBox="0 0 24 24"
|
|
1422
|
+
width={16}
|
|
1423
|
+
height={16}
|
|
1424
|
+
fill="none"
|
|
1425
|
+
stroke="currentColor"
|
|
1426
|
+
strokeWidth="1.6"
|
|
1427
|
+
strokeLinecap="round"
|
|
1428
|
+
strokeLinejoin="round"
|
|
1429
|
+
aria-hidden="true"
|
|
1430
|
+
>
|
|
1431
|
+
<line x1="8" y1="4" x2="8" y2="20" />
|
|
1432
|
+
<line x1="16" y1="4" x2="16" y2="20" />
|
|
1433
|
+
<circle cx="8" cy="9" r="2.2" fill="var(--design-editor-panel-bg)" />
|
|
1434
|
+
<circle cx="16" cy="15" r="2.2" fill="var(--design-editor-panel-bg)" />
|
|
1435
|
+
</svg>
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/** Padding linked (all four sides) glyph. */
|
|
1440
|
+
function IconPaddingLinked() {
|
|
1441
|
+
return (
|
|
1442
|
+
<svg
|
|
1443
|
+
viewBox="0 0 24 24"
|
|
1444
|
+
width={16}
|
|
1445
|
+
height={16}
|
|
1446
|
+
fill="none"
|
|
1447
|
+
stroke="currentColor"
|
|
1448
|
+
strokeWidth="1.6"
|
|
1449
|
+
strokeLinecap="round"
|
|
1450
|
+
strokeLinejoin="round"
|
|
1451
|
+
aria-hidden="true"
|
|
1452
|
+
>
|
|
1453
|
+
<rect x="4" y="4" width="16" height="16" rx="2" />
|
|
1454
|
+
<rect x="9" y="9" width="6" height="6" rx="1" />
|
|
1455
|
+
</svg>
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/** Padding unlinked glyph (dashed inner frame). */
|
|
1460
|
+
function IconPaddingUnlinked() {
|
|
1461
|
+
return (
|
|
1462
|
+
<svg
|
|
1463
|
+
viewBox="0 0 24 24"
|
|
1464
|
+
width={16}
|
|
1465
|
+
height={16}
|
|
1466
|
+
fill="none"
|
|
1467
|
+
stroke="currentColor"
|
|
1468
|
+
strokeWidth="1.6"
|
|
1469
|
+
strokeLinecap="round"
|
|
1470
|
+
strokeLinejoin="round"
|
|
1471
|
+
aria-hidden="true"
|
|
1472
|
+
>
|
|
1473
|
+
<rect x="4" y="4" width="16" height="16" rx="2" strokeDasharray="3 2.5" />
|
|
1474
|
+
<rect x="9" y="9" width="6" height="6" rx="1" />
|
|
1475
|
+
</svg>
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
/** Per-side padding glyphs — frame with one thick edge. */
|
|
1480
|
+
function IconPaddingTopMini({ className }: { className?: string }) {
|
|
1481
|
+
return <PaddingSideGlyph className={className} side="top" />;
|
|
1482
|
+
}
|
|
1483
|
+
function IconPaddingRightMini({ className }: { className?: string }) {
|
|
1484
|
+
return <PaddingSideGlyph className={className} side="right" />;
|
|
1485
|
+
}
|
|
1486
|
+
function IconPaddingBottomMini({ className }: { className?: string }) {
|
|
1487
|
+
return <PaddingSideGlyph className={className} side="bottom" />;
|
|
1488
|
+
}
|
|
1489
|
+
function IconPaddingLeftMini({ className }: { className?: string }) {
|
|
1490
|
+
return <PaddingSideGlyph className={className} side="left" />;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
type PaddingSide = "top" | "right" | "bottom" | "left";
|
|
1494
|
+
|
|
1495
|
+
const PADDING_EDGE: Record<PaddingSide, [number, number, number, number]> = {
|
|
1496
|
+
top: [4, 4, 20, 4],
|
|
1497
|
+
right: [20, 4, 20, 20],
|
|
1498
|
+
bottom: [4, 20, 20, 20],
|
|
1499
|
+
left: [4, 4, 4, 20],
|
|
1500
|
+
};
|
|
1501
|
+
|
|
1502
|
+
function PaddingSideGlyph({
|
|
1503
|
+
side,
|
|
1504
|
+
className,
|
|
1505
|
+
}: {
|
|
1506
|
+
side: PaddingSide;
|
|
1507
|
+
className?: string;
|
|
1508
|
+
}) {
|
|
1509
|
+
const [x1, y1, x2, y2] = PADDING_EDGE[side];
|
|
1510
|
+
return (
|
|
1511
|
+
<svg
|
|
1512
|
+
viewBox="0 0 24 24"
|
|
1513
|
+
className={cn("size-3.5", className)}
|
|
1514
|
+
fill="none"
|
|
1515
|
+
stroke="currentColor"
|
|
1516
|
+
strokeWidth="1.5"
|
|
1517
|
+
strokeLinecap="round"
|
|
1518
|
+
strokeLinejoin="round"
|
|
1519
|
+
aria-hidden="true"
|
|
1520
|
+
>
|
|
1521
|
+
<rect x="4" y="4" width="16" height="16" rx="2" opacity="0.4" />
|
|
1522
|
+
<line x1={x1} y1={y1} x2={x2} y2={y2} strokeWidth="3" />
|
|
1523
|
+
</svg>
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
/** Distribute horizontal spacing (space-between on main/cross axis). */
|
|
1528
|
+
function IconDistributeH() {
|
|
1529
|
+
return (
|
|
1530
|
+
<svg
|
|
1531
|
+
viewBox="0 0 14 14"
|
|
1532
|
+
width={14}
|
|
1533
|
+
height={14}
|
|
1534
|
+
fill="none"
|
|
1535
|
+
stroke="currentColor"
|
|
1536
|
+
strokeWidth="1.2"
|
|
1537
|
+
strokeLinecap="round"
|
|
1538
|
+
aria-hidden="true"
|
|
1539
|
+
>
|
|
1540
|
+
{/* left edge line */}
|
|
1541
|
+
<line x1="1.5" y1="2" x2="1.5" y2="12" />
|
|
1542
|
+
{/* right edge line */}
|
|
1543
|
+
<line x1="12.5" y1="2" x2="12.5" y2="12" />
|
|
1544
|
+
{/* center block */}
|
|
1545
|
+
<rect
|
|
1546
|
+
x="4.5"
|
|
1547
|
+
y="4"
|
|
1548
|
+
width="5"
|
|
1549
|
+
height="6"
|
|
1550
|
+
rx="1"
|
|
1551
|
+
fill="currentColor"
|
|
1552
|
+
stroke="none"
|
|
1553
|
+
opacity="0.5"
|
|
1554
|
+
/>
|
|
1555
|
+
</svg>
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
/** Distribute vertical spacing (space-between on cross/main axis). */
|
|
1560
|
+
function IconDistributeV() {
|
|
1561
|
+
return (
|
|
1562
|
+
<svg
|
|
1563
|
+
viewBox="0 0 14 14"
|
|
1564
|
+
width={14}
|
|
1565
|
+
height={14}
|
|
1566
|
+
fill="none"
|
|
1567
|
+
stroke="currentColor"
|
|
1568
|
+
strokeWidth="1.2"
|
|
1569
|
+
strokeLinecap="round"
|
|
1570
|
+
aria-hidden="true"
|
|
1571
|
+
>
|
|
1572
|
+
{/* top edge line */}
|
|
1573
|
+
<line x1="2" y1="1.5" x2="12" y2="1.5" />
|
|
1574
|
+
{/* bottom edge line */}
|
|
1575
|
+
<line x1="2" y1="12.5" x2="12" y2="12.5" />
|
|
1576
|
+
{/* center block */}
|
|
1577
|
+
<rect
|
|
1578
|
+
x="4"
|
|
1579
|
+
y="4.5"
|
|
1580
|
+
width="6"
|
|
1581
|
+
height="5"
|
|
1582
|
+
rx="1"
|
|
1583
|
+
fill="currentColor"
|
|
1584
|
+
stroke="none"
|
|
1585
|
+
opacity="0.5"
|
|
1586
|
+
/>
|
|
1587
|
+
</svg>
|
|
698
1588
|
);
|
|
699
1589
|
}
|