@agent-native/core 0.79.27 → 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 +34 -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 +55 -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 +66 -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 +65 -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 +64 -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 +59 -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 +62 -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 +58 -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 +62 -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 +50 -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 +52 -1
- package/corpus/core/docs/content/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/template-design.mdx +59 -1
- package/corpus/core/package.json +2 -1
- package/corpus/core/src/agent/engine/index.ts +1 -0
- package/corpus/core/src/agent/engine/registry.ts +40 -0
- package/corpus/core/src/agent/production-agent.ts +6 -2
- 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/design-connect.ts +490 -0
- package/corpus/core/src/cli/index.ts +16 -0
- package/corpus/core/src/cli/skills.ts +144 -3
- package/corpus/core/src/client/AgentPanel.tsx +1 -1
- package/corpus/core/src/client/AssistantChat.tsx +43 -7
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +1 -1
- package/corpus/core/src/client/agent-chat.ts +27 -3
- package/corpus/core/src/client/blocks/library/MermaidBlock.tsx +1 -1
- package/corpus/core/src/client/blocks/library/diagram.tsx +2 -2
- package/corpus/core/src/client/blocks/library/wireframe.tsx +1 -1
- package/corpus/core/src/client/chat/message-components.tsx +1 -1
- package/corpus/core/src/client/chat-model-groups.ts +1 -2
- package/corpus/core/src/client/components/LiveCursorOverlay.tsx +33 -66
- package/corpus/core/src/client/composer/TiptapComposer.tsx +99 -24
- package/corpus/core/src/client/composer/useVoiceDictation.ts +429 -260
- package/corpus/core/src/client/extensions/portable-extension.ts +1 -1
- package/corpus/core/src/client/frame-protocol.ts +17 -2
- package/corpus/core/src/client/rich-markdown-editor/RegistryBlockNode.tsx +2 -2
- package/corpus/core/src/client/sharing/ShareButton.tsx +20 -15
- package/corpus/core/src/client/use-agent-engine-configured.ts +80 -28
- 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/server/transcribe-voice.ts +57 -22
- package/corpus/core/src/shared/mcp-embed-headers.ts +23 -1
- package/corpus/core/src/styles/agent-native.css +60 -4
- package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
- package/corpus/core/src/usage/store.ts +2 -2
- package/corpus/core/src/vite/client.ts +2 -0
- package/corpus/core/src/voice/index.ts +18 -0
- package/corpus/core/src/voice/voice-cleanup-prompt.ts +38 -0
- package/corpus/core/src/voice/voice-context.ts +217 -0
- package/corpus/core/src/voice/voice-replacements.ts +75 -0
- package/corpus/templates/analytics/AGENTS.md +4 -3
- 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/list-session-recordings.ts +6 -1
- 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/components/layout/Sidebar.tsx +557 -400
- package/corpus/templates/analytics/app/global.css +0 -4
- package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +66 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +39 -0
- package/corpus/templates/analytics/app/i18n-data.ts +424 -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 +82 -34
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +7 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +144 -36
- package/corpus/templates/analytics/changelog/2026-06-28-ask-tab-now-matches-the-darker-app-background.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-28-dashboard-email-reports-now-use-cleaner-light-mode-snapshots.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-28-sessions-now-show-only-signed-in-recordings-with-playable-re.md +6 -0
- 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/seeds/dashboards/agent-native-templates-first-party.json +3 -3
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +75 -30
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +5 -2
- 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 +53 -12
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -2
- package/corpus/templates/analytics/server/plugins/onboarding.ts +97 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -4
- package/corpus/templates/assets/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
- package/corpus/templates/brain/app/components/layout/Layout.tsx +29 -2
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +131 -42
- package/corpus/templates/brain/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/brain/app/i18n-data.ts +20 -0
- package/corpus/templates/brain/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +29 -1
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +417 -299
- package/corpus/templates/calendar/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/calendar/app/i18n-data.ts +20 -0
- package/corpus/templates/calendar/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +6 -19
- package/corpus/templates/chat/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
- package/corpus/templates/clips/AGENTS.md +4 -0
- package/corpus/templates/clips/actions/cleanup-transcript.ts +39 -9
- package/corpus/templates/clips/actions/finalize-meeting.ts +81 -12
- package/corpus/templates/clips/actions/lib/agents-md-context.ts +4 -1
- package/corpus/templates/clips/app/components/library/library-layout.tsx +3 -3
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +1 -1
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +2 -2
- package/corpus/templates/clips/changelog/2026-06-28-left-sidebar-footer-controls-use-less-divider-chrome.md +6 -0
- package/corpus/templates/clips/desktop/src/lib/personal-vocabulary.ts +13 -8
- 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/clips/desktop/src/lib/voice-dictation.ts +60 -1
- package/corpus/templates/content/actions/set-image-alt-text.ts +1 -1
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +1 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +1 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +5 -5
- package/corpus/templates/content/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
- 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 +154 -0
- package/corpus/templates/design/AGENTS.md +95 -5
- package/corpus/templates/design/DESIGN.md +82 -0
- package/corpus/templates/design/DEVELOPING.md +43 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +453 -0
- package/corpus/templates/design/actions/apply-shader.ts +277 -0
- package/corpus/templates/design/actions/apply-visual-edit.ts +303 -0
- package/corpus/templates/design/actions/connect-localhost.ts +183 -0
- package/corpus/templates/design/actions/edit-design.ts +195 -42
- package/corpus/templates/design/actions/generate-design.ts +71 -1
- package/corpus/templates/design/actions/generate-screens.ts +193 -0
- package/corpus/templates/design/actions/get-code-layer-projection.ts +191 -0
- package/corpus/templates/design/actions/get-shader.ts +119 -0
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +259 -0
- package/corpus/templates/design/actions/list-localhost-connections.ts +95 -0
- package/corpus/templates/design/actions/navigate.ts +55 -2
- package/corpus/templates/design/actions/update-file.ts +9 -2
- package/corpus/templates/design/actions/view-screen.ts +68 -1
- package/corpus/templates/design/agent-native.app-skill.json +10 -0
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +719 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +2120 -103
- package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +74 -38
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +401 -0
- package/corpus/templates/design/app/components/design/EditPanel.tsx +3966 -440
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +1473 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +3940 -147
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +700 -13
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +105 -63
- package/corpus/templates/design/app/components/design/index.ts +0 -5
- package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +353 -0
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +1589 -0
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +511 -0
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +395 -0
- package/corpus/templates/design/app/components/design/inspector/FigmaColorPicker.tsx +2010 -0
- 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/SHADER_INTEGRATION.md +420 -0
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +238 -0
- package/corpus/templates/design/app/components/design/inspector/ShaderControls.stories.tsx +71 -0
- package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +575 -0
- package/corpus/templates/design/app/components/design/inspector/ShaderEffectRow.tsx +106 -0
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +423 -0
- package/corpus/templates/design/app/components/design/inspector/figma-icons.tsx +741 -0
- package/corpus/templates/design/app/components/design/inspector/index.ts +87 -0
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +201 -0
- package/corpus/templates/design/app/components/design/types.ts +23 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +24 -13
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +4 -4
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +474 -36
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +22 -5
- package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +24 -11
- package/corpus/templates/design/app/global.css +149 -5
- package/corpus/templates/design/app/hooks/use-agent-generating.ts +4 -0
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +130 -8
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +329 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +277 -0
- package/corpus/templates/design/app/i18n-data.ts +3028 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +5831 -904
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +57 -221
- package/corpus/templates/design/changelog/2026-06-28-annotate-mode-now-combines-drawing-and-comment-pins-in-one-c.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-design-editor-panel-borders-are-cleaner-so-the-canvas-edge-s.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-drawing-prompts-now-reliably-open-and-submit-to-the-design-a.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-editor-app-menus-now-use-an-outline-agent-native-mark-with-b.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-figma-style-code-design-mode.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-layer-selection-now-updates-chat-context-without-opening-the.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-layer-selections-now-preserve-hidden-and-locked-canvas-state.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-live-cursors-now-keep-names-beside-a-cleaner-pointer-marker.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-mode-switching-now-lives-in-a-compact-bottom-right-icon-dock.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-screen-overview-now-handles-zooming-drawing-opening-and-dele.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-screen-overview-now-selects-screens-on-click-and-opens-them-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-screen-overview-zooms-into-the-active-screen-with-smoother-e.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-selected-element-chips-in-chat-are-shorter-and-easier-to-sca.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-the-design-editor-toolbar-is-more-compact-and-the-app-menu-m.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-tweaks-now-live-at-the-top-of-the-design-sidebar-instead-of-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-28-tweaks-now-live-in-the-right-inspector-beside-the-design-tab.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-active-sidebar-rows-use-soft-highlights.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-agents-can-now-open-screen-overview-focus-screens-and-add.md +6 -0
- 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-agents-now-edit-broad-copy-changes-more-reliably.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-editor-extensions-now-run-inline-beside-the-artboard.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-sidebars-use-darker-chrome-and-chat-warns-immediately.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/changelog/2026-06-29-visual-edit-can-open-localhost-routes-as-url-backed-screens.md +6 -0
- package/corpus/templates/design/package.json +1 -0
- package/corpus/templates/design/server/db/schema.ts +26 -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/server/plugins/agent-chat.ts +3 -0
- package/corpus/templates/design/shared/api.ts +27 -0
- package/corpus/templates/design/shared/canvas-frames.ts +103 -0
- package/corpus/templates/design/shared/canvas-math.ts +1058 -0
- package/corpus/templates/design/shared/code-layer.ts +2412 -0
- package/corpus/templates/design/shared/color-utils.ts +180 -0
- package/corpus/templates/design/shared/generation-session.ts +61 -0
- package/corpus/templates/design/shared/html-content.ts +21 -0
- package/corpus/templates/design/shared/pen-path.ts +207 -0
- package/corpus/templates/design/shared/shader-presets.ts +782 -0
- package/corpus/templates/design/shared/shader-safety.ts +137 -0
- package/corpus/templates/design/shared/source-mode.ts +237 -0
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +149 -6
- package/corpus/templates/forms/app/i18n/ar-SA.ts +2 -0
- package/corpus/templates/forms/app/i18n/de-DE.ts +2 -0
- package/corpus/templates/forms/app/i18n/en-US.ts +2 -0
- package/corpus/templates/forms/app/i18n/es-ES.ts +2 -0
- package/corpus/templates/forms/app/i18n/fr-FR.ts +2 -0
- package/corpus/templates/forms/app/i18n/hi-IN.ts +2 -0
- package/corpus/templates/forms/app/i18n/ja-JP.ts +2 -0
- package/corpus/templates/forms/app/i18n/ko-KR.ts +2 -0
- package/corpus/templates/forms/app/i18n/pt-BR.ts +2 -0
- package/corpus/templates/forms/app/i18n/zh-CN.ts +2 -0
- package/corpus/templates/forms/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/forms/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +2 -2
- package/corpus/templates/macros/changelog/2026-06-28-left-sidebar-footer-controls-use-less-divider-chrome.md +6 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +408 -247
- package/corpus/templates/mail/app/i18n/ar-SA.ts +4 -0
- package/corpus/templates/mail/app/i18n/de-DE.ts +4 -0
- package/corpus/templates/mail/app/i18n/en-US.ts +4 -0
- package/corpus/templates/mail/app/i18n/es-ES.ts +4 -0
- package/corpus/templates/mail/app/i18n/fr-FR.ts +4 -0
- package/corpus/templates/mail/app/i18n/hi-IN.ts +4 -0
- package/corpus/templates/mail/app/i18n/ja-JP.ts +4 -0
- package/corpus/templates/mail/app/i18n/ko-KR.ts +4 -0
- package/corpus/templates/mail/app/i18n/pt-BR.ts +4 -0
- package/corpus/templates/mail/app/i18n/zh-CN.ts +4 -0
- package/corpus/templates/mail/app/i18n/zh-TW.ts +4 -0
- package/corpus/templates/mail/app/lib/utils.ts +2 -2
- package/corpus/templates/mail/changelog/2026-06-28-pinned-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +6 -8
- package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +1 -1
- package/corpus/templates/plan/app/global.css +61 -78
- package/corpus/templates/plan/app/pages/PlansPage.tsx +8 -0
- package/corpus/templates/plan/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
- package/corpus/templates/plan/changelog/2026-06-28-plans-use-the-shared-neutral-light-and-dark-theme.md +6 -0
- package/corpus/templates/plan/changelog/2026-06-28-sending-open-feedback-from-a-plan-now-opens-the-inline-plan-.md +6 -0
- 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/components/layout/Sidebar.tsx +5 -5
- 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/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
- 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/corpus/templates/videos/app/components/Sidebar.tsx +2 -2
- package/corpus/templates/videos/app/components/layout/Layout.tsx +29 -2
- package/corpus/templates/videos/app/components/layout/NavSidebar.tsx +113 -30
- package/corpus/templates/videos/app/i18n/ar-SA.ts +2 -0
- package/corpus/templates/videos/app/i18n/de-DE.ts +2 -0
- package/corpus/templates/videos/app/i18n/en-US.ts +2 -0
- package/corpus/templates/videos/app/i18n/es-ES.ts +2 -0
- package/corpus/templates/videos/app/i18n/fr-FR.ts +2 -0
- package/corpus/templates/videos/app/i18n/hi-IN.ts +2 -0
- package/corpus/templates/videos/app/i18n/ja-JP.ts +2 -0
- package/corpus/templates/videos/app/i18n/ko-KR.ts +2 -0
- package/corpus/templates/videos/app/i18n/pt-BR.ts +2 -0
- package/corpus/templates/videos/app/i18n/zh-CN.ts +2 -0
- package/corpus/templates/videos/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/videos/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
- package/dist/agent/engine/index.d.ts +1 -1
- package/dist/agent/engine/index.d.ts.map +1 -1
- package/dist/agent/engine/index.js +1 -1
- package/dist/agent/engine/index.js.map +1 -1
- package/dist/agent/engine/registry.d.ts +9 -0
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +37 -0
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +4 -3
- package/dist/agent/production-agent.js.map +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/design-connect.d.ts +52 -0
- package/dist/cli/design-connect.d.ts.map +1 -0
- package/dist/cli/design-connect.js +409 -0
- package/dist/cli/design-connect.js.map +1 -0
- package/dist/cli/index.js +15 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/skills.d.ts +3 -0
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +139 -3
- 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/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +38 -8
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +6 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +17 -3
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/blocks/library/MermaidBlock.js +1 -1
- package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
- package/dist/client/blocks/library/diagram.js +1 -1
- package/dist/client/blocks/library/diagram.js.map +1 -1
- package/dist/client/blocks/library/wireframe.js +1 -1
- package/dist/client/blocks/library/wireframe.js.map +1 -1
- package/dist/client/chat/message-components.js +1 -1
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat-model-groups.d.ts.map +1 -1
- package/dist/client/chat-model-groups.js +1 -2
- package/dist/client/chat-model-groups.js.map +1 -1
- package/dist/client/components/LiveCursorOverlay.d.ts +1 -2
- package/dist/client/components/LiveCursorOverlay.d.ts.map +1 -1
- package/dist/client/components/LiveCursorOverlay.js +19 -40
- package/dist/client/components/LiveCursorOverlay.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +3 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +86 -24
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/useVoiceDictation.d.ts +9 -0
- package/dist/client/composer/useVoiceDictation.d.ts.map +1 -1
- package/dist/client/composer/useVoiceDictation.js +154 -32
- package/dist/client/composer/useVoiceDictation.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/client/frame-protocol.d.ts +17 -2
- package/dist/client/frame-protocol.d.ts.map +1 -1
- package/dist/client/frame-protocol.js.map +1 -1
- package/dist/client/rich-markdown-editor/RegistryBlockNode.js +2 -2
- package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +2 -0
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +7 -6
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/use-agent-engine-configured.d.ts +5 -0
- package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
- package/dist/client/use-agent-engine-configured.js +51 -25
- package/dist/client/use-agent-engine-configured.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/routes.d.ts +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/file-upload/actions/upload-image.d.ts +2 -2
- 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/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- 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/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/agent-engine-api-key-route.d.ts +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/server/transcribe-voice.d.ts.map +1 -1
- package/dist/server/transcribe-voice.js +40 -24
- package/dist/server/transcribe-voice.js.map +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +1 -0
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +20 -1
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/styles/agent-native.css +60 -4
- package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
- package/dist/usage/store.js +2 -2
- package/dist/usage/store.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +2 -0
- package/dist/vite/client.js.map +1 -1
- package/dist/voice/index.d.ts +5 -0
- package/dist/voice/index.d.ts.map +1 -0
- package/dist/voice/index.js +4 -0
- package/dist/voice/index.js.map +1 -0
- package/dist/voice/voice-cleanup-prompt.d.ts +6 -0
- package/dist/voice/voice-cleanup-prompt.d.ts.map +1 -0
- package/dist/voice/voice-cleanup-prompt.js +26 -0
- package/dist/voice/voice-cleanup-prompt.js.map +1 -0
- package/dist/voice/voice-context.d.ts +25 -0
- package/dist/voice/voice-context.d.ts.map +1 -0
- package/dist/voice/voice-context.js +156 -0
- package/dist/voice/voice-context.js.map +1 -0
- package/dist/voice/voice-replacements.d.ts +4 -0
- package/dist/voice/voice-replacements.d.ts.map +1 -0
- package/dist/voice/voice-replacements.js +52 -0
- package/dist/voice/voice-replacements.js.map +1 -0
- package/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
- package/docs/content/locales/ar-SA/template-design.mdx +55 -1
- package/docs/content/locales/de-DE/skills-guide.mdx +5 -0
- package/docs/content/locales/de-DE/template-design.mdx +66 -1
- package/docs/content/locales/es-ES/skills-guide.mdx +5 -0
- package/docs/content/locales/es-ES/template-design.mdx +65 -1
- package/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
- package/docs/content/locales/fr-FR/template-design.mdx +64 -1
- package/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
- package/docs/content/locales/hi-IN/template-design.mdx +59 -1
- package/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
- package/docs/content/locales/ja-JP/template-design.mdx +62 -1
- package/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
- package/docs/content/locales/ko-KR/template-design.mdx +58 -1
- package/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
- package/docs/content/locales/pt-BR/template-design.mdx +62 -1
- package/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
- package/docs/content/locales/zh-CN/template-design.mdx +50 -1
- package/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
- package/docs/content/locales/zh-TW/template-design.mdx +52 -1
- package/docs/content/skills-guide.mdx +5 -0
- package/docs/content/template-design.mdx +59 -1
- package/package.json +2 -1
- package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
- package/corpus/templates/design/actions/import-figma-file.ts +0 -166
- package/corpus/templates/design/app/components/design/DesignToolbar.tsx +0 -339
- package/corpus/templates/design/app/components/design/PresentMode.tsx +0 -68
- package/corpus/templates/design/app/components/design/ViewportTabs.tsx +0 -149
- package/corpus/templates/design/app/components/design/ZoomControls.tsx +0 -157
- package/corpus/templates/slides/server/lib/fig-design-system.ts +0 -110
|
@@ -0,0 +1,1589 @@
|
|
|
1
|
+
import { IconArrowBackUp } from "@tabler/icons-react";
|
|
2
|
+
import { type ReactNode, useState } from "react";
|
|
3
|
+
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
6
|
+
import {
|
|
7
|
+
DropdownMenu,
|
|
8
|
+
DropdownMenuCheckboxItem,
|
|
9
|
+
DropdownMenuContent,
|
|
10
|
+
DropdownMenuItem,
|
|
11
|
+
DropdownMenuSeparator,
|
|
12
|
+
DropdownMenuTrigger,
|
|
13
|
+
} from "@/components/ui/dropdown-menu";
|
|
14
|
+
import {
|
|
15
|
+
Tooltip,
|
|
16
|
+
TooltipContent,
|
|
17
|
+
TooltipProvider,
|
|
18
|
+
TooltipTrigger,
|
|
19
|
+
} from "@/components/ui/tooltip";
|
|
20
|
+
import { cn } from "@/lib/utils";
|
|
21
|
+
|
|
22
|
+
import type {
|
|
23
|
+
AlignmentHorizontal,
|
|
24
|
+
AlignmentMatrixValue,
|
|
25
|
+
AlignmentVertical,
|
|
26
|
+
DistributionAxis,
|
|
27
|
+
} from "./AlignmentMatrix";
|
|
28
|
+
import {
|
|
29
|
+
IconFlowGrid,
|
|
30
|
+
IconFlowHorizontal,
|
|
31
|
+
IconFlowVertical,
|
|
32
|
+
IconGap,
|
|
33
|
+
IconPaddingHorizontal,
|
|
34
|
+
IconPaddingVertical,
|
|
35
|
+
IconSizingFill,
|
|
36
|
+
IconSizingFixed,
|
|
37
|
+
IconSizingHug,
|
|
38
|
+
IconSizingMax,
|
|
39
|
+
IconSizingMin,
|
|
40
|
+
IconSizingRemove,
|
|
41
|
+
IconSizingVariable,
|
|
42
|
+
} from "./figma-icons";
|
|
43
|
+
import { ScrubInput } from "./ScrubInput";
|
|
44
|
+
|
|
45
|
+
export type AutoLayoutDirection = "horizontal" | "vertical";
|
|
46
|
+
export type AutoLayoutWrap = "nowrap" | "wrap";
|
|
47
|
+
export type AutoLayoutSizing = "hug" | "fill" | "fixed";
|
|
48
|
+
export type AutoLayoutSizingAxis = "horizontal" | "vertical";
|
|
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
|
+
|
|
56
|
+
export interface AutoLayoutPadding {
|
|
57
|
+
top: number;
|
|
58
|
+
right: number;
|
|
59
|
+
bottom: number;
|
|
60
|
+
left: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AutoLayoutMatrixValue {
|
|
64
|
+
direction: AutoLayoutDirection;
|
|
65
|
+
wrap: AutoLayoutWrap;
|
|
66
|
+
alignment: AlignmentMatrixValue;
|
|
67
|
+
gap: number;
|
|
68
|
+
padding: AutoLayoutPadding;
|
|
69
|
+
paddingLinked: boolean;
|
|
70
|
+
clipContent?: boolean;
|
|
71
|
+
resolvedSize?: {
|
|
72
|
+
horizontal?: number;
|
|
73
|
+
vertical?: number;
|
|
74
|
+
};
|
|
75
|
+
childSizing: {
|
|
76
|
+
horizontal: AutoLayoutSizing;
|
|
77
|
+
vertical: AutoLayoutSizing;
|
|
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";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface AutoLayoutMatrixLabels {
|
|
98
|
+
title: string;
|
|
99
|
+
alignment: string;
|
|
100
|
+
direction: string;
|
|
101
|
+
horizontal: string;
|
|
102
|
+
vertical: string;
|
|
103
|
+
wrap: string;
|
|
104
|
+
noWrap: string;
|
|
105
|
+
gap: string;
|
|
106
|
+
padding: string;
|
|
107
|
+
linkPadding: string;
|
|
108
|
+
unlinkPadding: string;
|
|
109
|
+
paddingTop: string;
|
|
110
|
+
paddingRight: string;
|
|
111
|
+
paddingBottom: string;
|
|
112
|
+
paddingLeft: string;
|
|
113
|
+
childSizing: string;
|
|
114
|
+
hug: string;
|
|
115
|
+
fill: string;
|
|
116
|
+
fixed: string;
|
|
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;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface AutoLayoutMatrixProps {
|
|
135
|
+
value: AutoLayoutMatrixValue;
|
|
136
|
+
onDirectionChange: (direction: AutoLayoutDirection) => void;
|
|
137
|
+
onWrapChange: (wrap: AutoLayoutWrap) => void;
|
|
138
|
+
onAlignmentChange: (alignment: AlignmentMatrixValue) => void;
|
|
139
|
+
onGapChange: (gap: number) => void;
|
|
140
|
+
onPaddingChange: (padding: AutoLayoutPadding) => void;
|
|
141
|
+
onPaddingLinkedChange: (linked: boolean) => void;
|
|
142
|
+
onClipContentChange?: (clipContent: boolean) => void;
|
|
143
|
+
onDistribute?: (axis: DistributionAxis) => void;
|
|
144
|
+
onChildSizingChange: (
|
|
145
|
+
axis: AutoLayoutSizingAxis,
|
|
146
|
+
sizing: AutoLayoutSizing,
|
|
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;
|
|
171
|
+
availableChildSizing?: Partial<
|
|
172
|
+
Record<AutoLayoutSizingAxis, AutoLayoutSizing[]>
|
|
173
|
+
>;
|
|
174
|
+
labels?: Partial<AutoLayoutMatrixLabels>;
|
|
175
|
+
disabled?: boolean;
|
|
176
|
+
className?: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Default English labels for the auto-layout matrix and SizingField. */
|
|
180
|
+
export const DEFAULT_AUTO_LAYOUT_LABELS: AutoLayoutMatrixLabels = {
|
|
181
|
+
title: "Auto layout", // i18n-ignore fallback component label
|
|
182
|
+
alignment: "Alignment", // i18n-ignore fallback component label
|
|
183
|
+
direction: "Direction", // i18n-ignore fallback component label
|
|
184
|
+
horizontal: "Horizontal", // i18n-ignore fallback component label
|
|
185
|
+
vertical: "Vertical", // i18n-ignore fallback component label
|
|
186
|
+
wrap: "Wrap", // i18n-ignore fallback component label
|
|
187
|
+
noWrap: "No wrap", // i18n-ignore fallback component label
|
|
188
|
+
gap: "Gap", // i18n-ignore fallback component label
|
|
189
|
+
padding: "Padding", // i18n-ignore fallback component label
|
|
190
|
+
linkPadding: "Link padding", // i18n-ignore fallback component label
|
|
191
|
+
unlinkPadding: "Unlink padding", // i18n-ignore fallback component label
|
|
192
|
+
paddingTop: "Top", // i18n-ignore fallback component label
|
|
193
|
+
paddingRight: "Right", // i18n-ignore fallback component label
|
|
194
|
+
paddingBottom: "Bottom", // i18n-ignore fallback component label
|
|
195
|
+
paddingLeft: "Left", // i18n-ignore fallback component label
|
|
196
|
+
childSizing: "Child sizing", // i18n-ignore fallback component label
|
|
197
|
+
hug: "Hug", // i18n-ignore fallback component label
|
|
198
|
+
fill: "Fill", // i18n-ignore fallback component label
|
|
199
|
+
fixed: "Fixed", // i18n-ignore fallback component label
|
|
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
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const SIZING_OPTIONS: AutoLayoutSizing[] = ["hug", "fill", "fixed"];
|
|
218
|
+
|
|
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";
|
|
224
|
+
return "horizontal";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function AutoLayoutMatrix({
|
|
228
|
+
value,
|
|
229
|
+
onDirectionChange,
|
|
230
|
+
onWrapChange,
|
|
231
|
+
onAlignmentChange,
|
|
232
|
+
onGapChange,
|
|
233
|
+
onPaddingChange,
|
|
234
|
+
onPaddingLinkedChange,
|
|
235
|
+
onClipContentChange,
|
|
236
|
+
onDistribute,
|
|
237
|
+
onChildSizingChange,
|
|
238
|
+
onChildMinMaxChange,
|
|
239
|
+
onApplyVariable,
|
|
240
|
+
onDisplayChange,
|
|
241
|
+
availableChildSizing,
|
|
242
|
+
labels,
|
|
243
|
+
disabled = false,
|
|
244
|
+
className,
|
|
245
|
+
}: AutoLayoutMatrixProps) {
|
|
246
|
+
const copy = { ...DEFAULT_AUTO_LAYOUT_LABELS, ...labels };
|
|
247
|
+
|
|
248
|
+
const horizontalPaddingValue = Math.round(
|
|
249
|
+
(value.padding.left + value.padding.right) / 2,
|
|
250
|
+
);
|
|
251
|
+
const verticalPaddingValue = Math.round(
|
|
252
|
+
(value.padding.top + value.padding.bottom) / 2,
|
|
253
|
+
);
|
|
254
|
+
|
|
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
|
+
};
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<TooltipProvider delayDuration={250}>
|
|
281
|
+
<div className={cn("space-y-3", className)}>
|
|
282
|
+
{/* ── Flow ── */}
|
|
283
|
+
<div className="space-y-1.5">
|
|
284
|
+
<ControlLabel>
|
|
285
|
+
{"Flow" /* i18n-ignore Figma inspector label */}
|
|
286
|
+
</ControlLabel>
|
|
287
|
+
<div className="flex items-center gap-1.5">
|
|
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">
|
|
290
|
+
<FlowButton
|
|
291
|
+
label={"Normal flow" /* i18n-ignore Figma inspector label */}
|
|
292
|
+
active={activeFlow === "normal"}
|
|
293
|
+
disabled={disabled}
|
|
294
|
+
onClick={() => selectFlow("normal")}
|
|
295
|
+
>
|
|
296
|
+
<IconFlowNormal />
|
|
297
|
+
</FlowButton>
|
|
298
|
+
<FlowButton
|
|
299
|
+
label={copy.vertical}
|
|
300
|
+
active={activeFlow === "vertical"}
|
|
301
|
+
disabled={disabled}
|
|
302
|
+
onClick={() => selectFlow("vertical")}
|
|
303
|
+
>
|
|
304
|
+
<IconFlowVertical className="size-3.5" />
|
|
305
|
+
</FlowButton>
|
|
306
|
+
<FlowButton
|
|
307
|
+
label={copy.horizontal}
|
|
308
|
+
active={activeFlow === "horizontal"}
|
|
309
|
+
disabled={disabled}
|
|
310
|
+
onClick={() => selectFlow("horizontal")}
|
|
311
|
+
>
|
|
312
|
+
<IconFlowHorizontal className="size-3.5" />
|
|
313
|
+
</FlowButton>
|
|
314
|
+
<FlowButton
|
|
315
|
+
label={"Grid" /* i18n-ignore Figma inspector label */}
|
|
316
|
+
active={activeFlow === "grid"}
|
|
317
|
+
disabled={disabled}
|
|
318
|
+
onClick={() => selectFlow("grid")}
|
|
319
|
+
>
|
|
320
|
+
<IconFlowGrid className="size-3.5" />
|
|
321
|
+
</FlowButton>
|
|
322
|
+
</div>
|
|
323
|
+
{/* Reset / reverse-flow button */}
|
|
324
|
+
<Tooltip>
|
|
325
|
+
<TooltipTrigger asChild>
|
|
326
|
+
<Button
|
|
327
|
+
type="button"
|
|
328
|
+
variant="ghost"
|
|
329
|
+
size="icon"
|
|
330
|
+
disabled={disabled}
|
|
331
|
+
aria-label={
|
|
332
|
+
"Reset auto layout flow" /* i18n-ignore inspector tooltip */
|
|
333
|
+
}
|
|
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"
|
|
336
|
+
>
|
|
337
|
+
<IconArrowBackUp className="size-4" />
|
|
338
|
+
</Button>
|
|
339
|
+
</TooltipTrigger>
|
|
340
|
+
<TooltipContent>
|
|
341
|
+
{"Reset auto layout flow" /* i18n-ignore inspector tooltip */}
|
|
342
|
+
</TooltipContent>
|
|
343
|
+
</Tooltip>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
{/* ── Resizing ── */}
|
|
348
|
+
<div className="space-y-1.5">
|
|
349
|
+
<ControlLabel>
|
|
350
|
+
{"Resizing" /* i18n-ignore Figma inspector label */}
|
|
351
|
+
</ControlLabel>
|
|
352
|
+
<div className="grid grid-cols-[1fr_1fr_auto] items-start gap-1.5">
|
|
353
|
+
<SizingField
|
|
354
|
+
axis="W"
|
|
355
|
+
sizingAxis="horizontal"
|
|
356
|
+
value={value.childSizing.horizontal}
|
|
357
|
+
resolvedSize={value.resolvedSize?.horizontal}
|
|
358
|
+
minMax={value.childMinMax?.horizontal}
|
|
359
|
+
options={resolveSizingOptions(
|
|
360
|
+
availableChildSizing?.horizontal,
|
|
361
|
+
value.childSizing.horizontal,
|
|
362
|
+
)}
|
|
363
|
+
labels={copy}
|
|
364
|
+
disabled={disabled}
|
|
365
|
+
onChange={(next) => onChildSizingChange("horizontal", next)}
|
|
366
|
+
onMinMaxChange={onChildMinMaxChange}
|
|
367
|
+
onApplyVariable={onApplyVariable}
|
|
368
|
+
/>
|
|
369
|
+
<SizingField
|
|
370
|
+
axis="H"
|
|
371
|
+
sizingAxis="vertical"
|
|
372
|
+
value={value.childSizing.vertical}
|
|
373
|
+
resolvedSize={value.resolvedSize?.vertical}
|
|
374
|
+
minMax={value.childMinMax?.vertical}
|
|
375
|
+
options={resolveSizingOptions(
|
|
376
|
+
availableChildSizing?.vertical,
|
|
377
|
+
value.childSizing.vertical,
|
|
378
|
+
)}
|
|
379
|
+
labels={copy}
|
|
380
|
+
disabled={disabled}
|
|
381
|
+
onChange={(next) => onChildSizingChange("vertical", next)}
|
|
382
|
+
onMinMaxChange={onChildMinMaxChange}
|
|
383
|
+
onApplyVariable={onApplyVariable}
|
|
384
|
+
/>
|
|
385
|
+
{/* Resize-to-fit icon button */}
|
|
386
|
+
<Tooltip>
|
|
387
|
+
<TooltipTrigger asChild>
|
|
388
|
+
<Button
|
|
389
|
+
type="button"
|
|
390
|
+
variant="ghost"
|
|
391
|
+
size="icon"
|
|
392
|
+
disabled={disabled}
|
|
393
|
+
aria-label={
|
|
394
|
+
"Resize to fit" /* i18n-ignore inspector tooltip */
|
|
395
|
+
}
|
|
396
|
+
onClick={() => {
|
|
397
|
+
onChildSizingChange("horizontal", "hug");
|
|
398
|
+
onChildSizingChange("vertical", "hug");
|
|
399
|
+
}}
|
|
400
|
+
className="size-7 rounded-md text-muted-foreground hover:bg-[var(--design-editor-control-bg)] hover:text-foreground"
|
|
401
|
+
>
|
|
402
|
+
<IconResizeToFitMini />
|
|
403
|
+
</Button>
|
|
404
|
+
</TooltipTrigger>
|
|
405
|
+
<TooltipContent>
|
|
406
|
+
{"Resize to fit" /* i18n-ignore inspector tooltip */}
|
|
407
|
+
</TooltipContent>
|
|
408
|
+
</Tooltip>
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
|
|
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) */}
|
|
415
|
+
<div className="space-y-1.5">
|
|
416
|
+
<ControlLabel>
|
|
417
|
+
{"Alignment" /* i18n-ignore Figma inspector label */}
|
|
418
|
+
</ControlLabel>
|
|
419
|
+
<CompactAlignmentMatrix
|
|
420
|
+
value={value.alignment}
|
|
421
|
+
onChange={onAlignmentChange}
|
|
422
|
+
direction={value.direction}
|
|
423
|
+
disabled={disabled || isBlock}
|
|
424
|
+
onDistribute={onDistribute}
|
|
425
|
+
/>
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
{/* Right: Gap label + [icon] value [▾] + sliders */}
|
|
429
|
+
<div className="space-y-1.5">
|
|
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
|
+
/>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
|
|
441
|
+
{/* ── Padding ── */}
|
|
442
|
+
<div className="space-y-1.5">
|
|
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) =>
|
|
452
|
+
onPaddingChange({
|
|
453
|
+
top: value.padding.top,
|
|
454
|
+
bottom: value.padding.bottom,
|
|
455
|
+
left: next,
|
|
456
|
+
right: next,
|
|
457
|
+
})
|
|
458
|
+
}
|
|
459
|
+
disabled={disabled}
|
|
460
|
+
/>
|
|
461
|
+
<PaddingField
|
|
462
|
+
icon={IconPaddingVertical}
|
|
463
|
+
ariaLabel={copy.paddingTop + " / " + copy.paddingBottom}
|
|
464
|
+
value={verticalPaddingValue}
|
|
465
|
+
onChange={(next) =>
|
|
466
|
+
onPaddingChange({
|
|
467
|
+
top: next,
|
|
468
|
+
bottom: next,
|
|
469
|
+
left: value.padding.left,
|
|
470
|
+
right: value.padding.right,
|
|
471
|
+
})
|
|
472
|
+
}
|
|
473
|
+
disabled={disabled}
|
|
474
|
+
/>
|
|
475
|
+
<PaddingLinkButton
|
|
476
|
+
linked
|
|
477
|
+
disabled={disabled}
|
|
478
|
+
linkLabel={copy.linkPadding}
|
|
479
|
+
unlinkLabel={copy.unlinkPadding}
|
|
480
|
+
onToggle={() => onPaddingLinkedChange(false)}
|
|
481
|
+
/>
|
|
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}
|
|
526
|
+
disabled={disabled}
|
|
527
|
+
linkLabel={copy.linkPadding}
|
|
528
|
+
unlinkLabel={copy.unlinkPadding}
|
|
529
|
+
onToggle={() => onPaddingLinkedChange(true)}
|
|
530
|
+
/>
|
|
531
|
+
</div>
|
|
532
|
+
)}
|
|
533
|
+
</div>
|
|
534
|
+
|
|
535
|
+
{/* ── Clip content ── */}
|
|
536
|
+
<label className="flex h-6 cursor-pointer items-center gap-2 text-[11px] text-foreground">
|
|
537
|
+
<Checkbox
|
|
538
|
+
checked={Boolean(value.clipContent)}
|
|
539
|
+
disabled={disabled}
|
|
540
|
+
onCheckedChange={(checked) =>
|
|
541
|
+
onClipContentChange?.(checked === true)
|
|
542
|
+
}
|
|
543
|
+
className="size-3.5 rounded-[3px]"
|
|
544
|
+
/>
|
|
545
|
+
<span>{copy.clipContent}</span>
|
|
546
|
+
</label>
|
|
547
|
+
</div>
|
|
548
|
+
</TooltipProvider>
|
|
549
|
+
);
|
|
550
|
+
}
|
|
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
|
+
|
|
580
|
+
// ─────────────────────────────────────────────────
|
|
581
|
+
// Sub-components
|
|
582
|
+
// ─────────────────────────────────────────────────
|
|
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
|
+
|
|
784
|
+
/** A single segment in the flow segmented control. */
|
|
785
|
+
function FlowButton({
|
|
786
|
+
label,
|
|
787
|
+
active,
|
|
788
|
+
disabled,
|
|
789
|
+
onClick,
|
|
790
|
+
children,
|
|
791
|
+
}: {
|
|
792
|
+
label: string;
|
|
793
|
+
active: boolean;
|
|
794
|
+
disabled: boolean;
|
|
795
|
+
onClick: () => void;
|
|
796
|
+
children: ReactNode;
|
|
797
|
+
}) {
|
|
798
|
+
return (
|
|
799
|
+
<Tooltip>
|
|
800
|
+
<TooltipTrigger asChild>
|
|
801
|
+
<button
|
|
802
|
+
type="button"
|
|
803
|
+
aria-label={label}
|
|
804
|
+
aria-pressed={active}
|
|
805
|
+
disabled={disabled}
|
|
806
|
+
onClick={onClick}
|
|
807
|
+
className={cn(
|
|
808
|
+
"flex h-6 flex-1 items-center justify-center rounded-[5px] transition-colors",
|
|
809
|
+
"text-muted-foreground hover:text-foreground",
|
|
810
|
+
"disabled:pointer-events-none disabled:opacity-40",
|
|
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)]",
|
|
817
|
+
)}
|
|
818
|
+
>
|
|
819
|
+
{children}
|
|
820
|
+
</button>
|
|
821
|
+
</TooltipTrigger>
|
|
822
|
+
<TooltipContent>{label}</TooltipContent>
|
|
823
|
+
</Tooltip>
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
|
|
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({
|
|
832
|
+
value,
|
|
833
|
+
onGapChange,
|
|
834
|
+
onDistribute,
|
|
835
|
+
label,
|
|
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,
|
|
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,
|
|
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"). */
|
|
1032
|
+
axis: string;
|
|
1033
|
+
/** Logical axis used by min/max + variable callbacks. */
|
|
1034
|
+
sizingAxis: AutoLayoutSizingAxis;
|
|
1035
|
+
value: AutoLayoutSizing;
|
|
1036
|
+
resolvedSize?: number;
|
|
1037
|
+
/** Currently-set min/max constraints (px). */
|
|
1038
|
+
minMax?: SizingFieldMinMax;
|
|
1039
|
+
options?: AutoLayoutSizing[];
|
|
1040
|
+
/** Optional label overrides; English defaults are used for any omitted key. */
|
|
1041
|
+
labels?: Partial<AutoLayoutMatrixLabels>;
|
|
1042
|
+
disabled: boolean;
|
|
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;
|
|
1250
|
+
}) {
|
|
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
|
+
}
|
|
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
|
+
}) {
|
|
1288
|
+
return (
|
|
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
|
+
/>
|
|
1309
|
+
<Tooltip>
|
|
1310
|
+
<TooltipTrigger asChild>
|
|
1311
|
+
<button
|
|
1312
|
+
type="button"
|
|
1313
|
+
aria-label={removeLabel}
|
|
1314
|
+
disabled={disabled}
|
|
1315
|
+
onClick={onRemove}
|
|
1316
|
+
className={cn(
|
|
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",
|
|
1320
|
+
)}
|
|
1321
|
+
>
|
|
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>
|
|
1588
|
+
);
|
|
1589
|
+
}
|