@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.
Files changed (497) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +34 -0
  3. package/corpus/core/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
  4. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +55 -1
  5. package/corpus/core/docs/content/locales/de-DE/skills-guide.mdx +5 -0
  6. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +66 -1
  7. package/corpus/core/docs/content/locales/es-ES/skills-guide.mdx +5 -0
  8. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +65 -1
  9. package/corpus/core/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
  10. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +64 -1
  11. package/corpus/core/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
  12. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +59 -1
  13. package/corpus/core/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
  14. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +62 -1
  15. package/corpus/core/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
  16. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +58 -1
  17. package/corpus/core/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
  18. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +62 -1
  19. package/corpus/core/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
  20. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +50 -1
  21. package/corpus/core/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
  22. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +52 -1
  23. package/corpus/core/docs/content/skills-guide.mdx +5 -0
  24. package/corpus/core/docs/content/template-design.mdx +59 -1
  25. package/corpus/core/package.json +2 -1
  26. package/corpus/core/src/agent/engine/index.ts +1 -0
  27. package/corpus/core/src/agent/engine/registry.ts +40 -0
  28. package/corpus/core/src/agent/production-agent.ts +6 -2
  29. package/corpus/core/src/application-state/handlers.ts +1 -1
  30. package/corpus/core/src/brand-kit/index.ts +1 -1
  31. package/corpus/core/src/cli/design-connect.ts +490 -0
  32. package/corpus/core/src/cli/index.ts +16 -0
  33. package/corpus/core/src/cli/skills.ts +144 -3
  34. package/corpus/core/src/client/AgentPanel.tsx +1 -1
  35. package/corpus/core/src/client/AssistantChat.tsx +43 -7
  36. package/corpus/core/src/client/MultiTabAssistantChat.tsx +1 -1
  37. package/corpus/core/src/client/agent-chat.ts +27 -3
  38. package/corpus/core/src/client/blocks/library/MermaidBlock.tsx +1 -1
  39. package/corpus/core/src/client/blocks/library/diagram.tsx +2 -2
  40. package/corpus/core/src/client/blocks/library/wireframe.tsx +1 -1
  41. package/corpus/core/src/client/chat/message-components.tsx +1 -1
  42. package/corpus/core/src/client/chat-model-groups.ts +1 -2
  43. package/corpus/core/src/client/components/LiveCursorOverlay.tsx +33 -66
  44. package/corpus/core/src/client/composer/TiptapComposer.tsx +99 -24
  45. package/corpus/core/src/client/composer/useVoiceDictation.ts +429 -260
  46. package/corpus/core/src/client/extensions/portable-extension.ts +1 -1
  47. package/corpus/core/src/client/frame-protocol.ts +17 -2
  48. package/corpus/core/src/client/rich-markdown-editor/RegistryBlockNode.tsx +2 -2
  49. package/corpus/core/src/client/sharing/ShareButton.tsx +20 -15
  50. package/corpus/core/src/client/use-agent-engine-configured.ts +80 -28
  51. package/corpus/core/src/code-agents/transcript-normalizer.ts +1 -1
  52. package/corpus/core/src/extensions/path.ts +1 -4
  53. package/corpus/core/src/integrations/a2a-continuation-processor.ts +1 -1
  54. package/corpus/core/src/mcp/build-server.ts +1 -1
  55. package/corpus/core/src/org/handlers.ts +5 -5
  56. package/corpus/core/src/org/plugin.ts +2 -2
  57. package/corpus/core/src/scripts/call-agent.ts +1 -1
  58. package/corpus/core/src/scripts/db/safety.ts +1 -1
  59. package/corpus/core/src/server/auth.ts +3 -0
  60. package/corpus/core/src/server/builder-design-systems.ts +308 -0
  61. package/corpus/core/src/server/core-routes-plugin.ts +29 -3
  62. package/corpus/core/src/server/index.ts +8 -0
  63. package/corpus/core/src/server/transcribe-voice.ts +57 -22
  64. package/corpus/core/src/shared/mcp-embed-headers.ts +23 -1
  65. package/corpus/core/src/styles/agent-native.css +60 -4
  66. package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
  67. package/corpus/core/src/usage/store.ts +2 -2
  68. package/corpus/core/src/vite/client.ts +2 -0
  69. package/corpus/core/src/voice/index.ts +18 -0
  70. package/corpus/core/src/voice/voice-cleanup-prompt.ts +38 -0
  71. package/corpus/core/src/voice/voice-context.ts +217 -0
  72. package/corpus/core/src/voice/voice-replacements.ts +75 -0
  73. package/corpus/templates/analytics/AGENTS.md +4 -3
  74. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +298 -15
  75. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +9 -4
  76. package/corpus/templates/analytics/actions/list-session-recordings.ts +6 -1
  77. package/corpus/templates/analytics/actions/mutate-dashboard.ts +18 -2
  78. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +12 -2
  79. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +557 -400
  80. package/corpus/templates/analytics/app/global.css +0 -4
  81. package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +66 -0
  82. package/corpus/templates/analytics/app/i18n/zh-TW.ts +39 -0
  83. package/corpus/templates/analytics/app/i18n-data.ts +424 -0
  84. package/corpus/templates/analytics/app/pages/Settings.tsx +333 -0
  85. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +2 -1
  86. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +121 -3
  87. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +82 -34
  88. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +7 -0
  89. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +144 -36
  90. package/corpus/templates/analytics/changelog/2026-06-28-ask-tab-now-matches-the-darker-app-background.md +6 -0
  91. package/corpus/templates/analytics/changelog/2026-06-28-dashboard-email-reports-now-use-cleaner-light-mode-snapshots.md +6 -0
  92. package/corpus/templates/analytics/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  93. package/corpus/templates/analytics/changelog/2026-06-28-sessions-now-show-only-signed-in-recordings-with-playable-re.md +6 -0
  94. package/corpus/templates/analytics/changelog/2026-06-29-analytics-agents-can-now-place-new-charts-into-requested-das.md +6 -0
  95. package/corpus/templates/analytics/changelog/2026-06-29-analytics-queries-now-report-failed-source-query-errors-inst.md +6 -0
  96. package/corpus/templates/analytics/changelog/2026-06-29-charts-can-now-be-dropped-between-full-dashboard-rows-to-cre.md +6 -0
  97. package/corpus/templates/analytics/changelog/2026-06-29-dashboard-email-reports-include-the-full-dashboard-image-wit.md +6 -0
  98. package/corpus/templates/analytics/changelog/2026-06-29-main-surface-border-reaches-the-top.md +6 -0
  99. package/corpus/templates/analytics/changelog/2026-06-29-session-replay-now-records-playable-sessions-in-production-a.md +6 -0
  100. package/corpus/templates/analytics/changelog/2026-06-29-set-up-session-replay-storage-from-settings-connect-builder-.md +6 -0
  101. package/corpus/templates/analytics/scripts/cleanup-empty-session-recordings.ts +102 -0
  102. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +3 -3
  103. package/corpus/templates/analytics/server/lib/dashboard-report.ts +75 -30
  104. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +5 -2
  105. package/corpus/templates/analytics/server/lib/real-data-actions.ts +81 -20
  106. package/corpus/templates/analytics/server/lib/replay-storage.ts +79 -0
  107. package/corpus/templates/analytics/server/lib/s3-upload-provider.ts +255 -0
  108. package/corpus/templates/analytics/server/lib/session-replay.ts +53 -12
  109. package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -2
  110. package/corpus/templates/analytics/server/plugins/onboarding.ts +97 -0
  111. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -4
  112. package/corpus/templates/assets/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  113. package/corpus/templates/brain/app/components/layout/Layout.tsx +29 -2
  114. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +131 -42
  115. package/corpus/templates/brain/app/i18n/zh-TW.ts +2 -0
  116. package/corpus/templates/brain/app/i18n-data.ts +20 -0
  117. package/corpus/templates/brain/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  118. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +29 -1
  119. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +417 -299
  120. package/corpus/templates/calendar/app/i18n/zh-TW.ts +2 -0
  121. package/corpus/templates/calendar/app/i18n-data.ts +20 -0
  122. package/corpus/templates/calendar/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  123. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +6 -19
  124. package/corpus/templates/chat/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  125. package/corpus/templates/clips/AGENTS.md +4 -0
  126. package/corpus/templates/clips/actions/cleanup-transcript.ts +39 -9
  127. package/corpus/templates/clips/actions/finalize-meeting.ts +81 -12
  128. package/corpus/templates/clips/actions/lib/agents-md-context.ts +4 -1
  129. package/corpus/templates/clips/app/components/library/library-layout.tsx +3 -3
  130. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +1 -1
  131. package/corpus/templates/clips/app/components/player/share-dialog.tsx +2 -2
  132. package/corpus/templates/clips/changelog/2026-06-28-left-sidebar-footer-controls-use-less-divider-chrome.md +6 -0
  133. package/corpus/templates/clips/desktop/src/lib/personal-vocabulary.ts +13 -8
  134. package/corpus/templates/clips/desktop/src/lib/recorder.ts +24 -0
  135. package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +105 -5
  136. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +60 -1
  137. package/corpus/templates/content/actions/set-image-alt-text.ts +1 -1
  138. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +1 -1
  139. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +1 -1
  140. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +5 -5
  141. package/corpus/templates/content/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  142. package/corpus/templates/content/shared/document-export.ts +1 -1
  143. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -18
  144. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +154 -0
  145. package/corpus/templates/design/AGENTS.md +95 -5
  146. package/corpus/templates/design/DESIGN.md +82 -0
  147. package/corpus/templates/design/DEVELOPING.md +43 -0
  148. package/corpus/templates/design/actions/add-localhost-screens.ts +453 -0
  149. package/corpus/templates/design/actions/apply-shader.ts +277 -0
  150. package/corpus/templates/design/actions/apply-visual-edit.ts +303 -0
  151. package/corpus/templates/design/actions/connect-localhost.ts +183 -0
  152. package/corpus/templates/design/actions/edit-design.ts +195 -42
  153. package/corpus/templates/design/actions/generate-design.ts +71 -1
  154. package/corpus/templates/design/actions/generate-screens.ts +193 -0
  155. package/corpus/templates/design/actions/get-code-layer-projection.ts +191 -0
  156. package/corpus/templates/design/actions/get-shader.ts +119 -0
  157. package/corpus/templates/design/actions/index-design-system-with-builder.ts +259 -0
  158. package/corpus/templates/design/actions/list-localhost-connections.ts +95 -0
  159. package/corpus/templates/design/actions/navigate.ts +55 -2
  160. package/corpus/templates/design/actions/update-file.ts +9 -2
  161. package/corpus/templates/design/actions/view-screen.ts +68 -1
  162. package/corpus/templates/design/agent-native.app-skill.json +10 -0
  163. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +719 -0
  164. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +2120 -103
  165. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +74 -38
  166. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +401 -0
  167. package/corpus/templates/design/app/components/design/EditPanel.tsx +3966 -440
  168. package/corpus/templates/design/app/components/design/LayersPanel.tsx +1473 -0
  169. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +3940 -147
  170. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +700 -13
  171. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +105 -63
  172. package/corpus/templates/design/app/components/design/index.ts +0 -5
  173. package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +353 -0
  174. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +1589 -0
  175. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +511 -0
  176. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +395 -0
  177. package/corpus/templates/design/app/components/design/inspector/FigmaColorPicker.tsx +2010 -0
  178. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +601 -0
  179. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +242 -0
  180. package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +420 -0
  181. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +238 -0
  182. package/corpus/templates/design/app/components/design/inspector/ShaderControls.stories.tsx +71 -0
  183. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +575 -0
  184. package/corpus/templates/design/app/components/design/inspector/ShaderEffectRow.tsx +106 -0
  185. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +423 -0
  186. package/corpus/templates/design/app/components/design/inspector/figma-icons.tsx +741 -0
  187. package/corpus/templates/design/app/components/design/inspector/index.ts +87 -0
  188. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +201 -0
  189. package/corpus/templates/design/app/components/design/types.ts +23 -0
  190. package/corpus/templates/design/app/components/layout/Layout.tsx +24 -13
  191. package/corpus/templates/design/app/components/layout/Sidebar.tsx +4 -4
  192. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +474 -36
  193. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +22 -5
  194. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +24 -11
  195. package/corpus/templates/design/app/global.css +149 -5
  196. package/corpus/templates/design/app/hooks/use-agent-generating.ts +4 -0
  197. package/corpus/templates/design/app/hooks/use-navigation-state.ts +130 -8
  198. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +329 -0
  199. package/corpus/templates/design/app/i18n/zh-TW.ts +277 -0
  200. package/corpus/templates/design/app/i18n-data.ts +3028 -1
  201. package/corpus/templates/design/app/pages/DesignEditor.tsx +5831 -904
  202. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +57 -221
  203. package/corpus/templates/design/changelog/2026-06-28-annotate-mode-now-combines-drawing-and-comment-pins-in-one-c.md +6 -0
  204. package/corpus/templates/design/changelog/2026-06-28-design-editor-panel-borders-are-cleaner-so-the-canvas-edge-s.md +6 -0
  205. package/corpus/templates/design/changelog/2026-06-28-drawing-prompts-now-reliably-open-and-submit-to-the-design-a.md +6 -0
  206. package/corpus/templates/design/changelog/2026-06-28-editor-app-menus-now-use-an-outline-agent-native-mark-with-b.md +6 -0
  207. package/corpus/templates/design/changelog/2026-06-28-figma-style-code-design-mode.md +6 -0
  208. package/corpus/templates/design/changelog/2026-06-28-layer-selection-now-updates-chat-context-without-opening-the.md +6 -0
  209. package/corpus/templates/design/changelog/2026-06-28-layer-selections-now-preserve-hidden-and-locked-canvas-state.md +6 -0
  210. package/corpus/templates/design/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  211. package/corpus/templates/design/changelog/2026-06-28-live-cursors-now-keep-names-beside-a-cleaner-pointer-marker.md +6 -0
  212. package/corpus/templates/design/changelog/2026-06-28-mode-switching-now-lives-in-a-compact-bottom-right-icon-dock.md +6 -0
  213. package/corpus/templates/design/changelog/2026-06-28-screen-overview-now-handles-zooming-drawing-opening-and-dele.md +6 -0
  214. package/corpus/templates/design/changelog/2026-06-28-screen-overview-now-selects-screens-on-click-and-opens-them-.md +6 -0
  215. package/corpus/templates/design/changelog/2026-06-28-screen-overview-zooms-into-the-active-screen-with-smoother-e.md +6 -0
  216. package/corpus/templates/design/changelog/2026-06-28-selected-element-chips-in-chat-are-shorter-and-easier-to-sca.md +6 -0
  217. package/corpus/templates/design/changelog/2026-06-28-the-design-editor-toolbar-is-more-compact-and-the-app-menu-m.md +6 -0
  218. package/corpus/templates/design/changelog/2026-06-28-tweaks-now-live-at-the-top-of-the-design-sidebar-instead-of-.md +6 -0
  219. package/corpus/templates/design/changelog/2026-06-28-tweaks-now-live-in-the-right-inspector-beside-the-design-tab.md +6 -0
  220. package/corpus/templates/design/changelog/2026-06-29-active-sidebar-rows-use-soft-highlights.md +6 -0
  221. package/corpus/templates/design/changelog/2026-06-29-agents-can-now-open-screen-overview-focus-screens-and-add.md +6 -0
  222. package/corpus/templates/design/changelog/2026-06-29-canvas-selection-no-longer-flashes-broken-prototype-html-whe.md +6 -0
  223. package/corpus/templates/design/changelog/2026-06-29-command-menu-selections-are-clearer-in-dark-mode.md +6 -0
  224. package/corpus/templates/design/changelog/2026-06-29-design-agents-now-edit-broad-copy-changes-more-reliably.md +6 -0
  225. package/corpus/templates/design/changelog/2026-06-29-design-editor-extensions-now-run-inline-beside-the-artboard.md +6 -0
  226. package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +6 -0
  227. package/corpus/templates/design/changelog/2026-06-29-element-selection-now-stays-on-the-intended-layer-after-sele.md +6 -0
  228. package/corpus/templates/design/changelog/2026-06-29-screen-overview-full-view-controls-now-sit-above-frames-inst.md +6 -0
  229. package/corpus/templates/design/changelog/2026-06-29-screen-overview-tools-can-draw-shapes-create-screens-and-kee.md +6 -0
  230. package/corpus/templates/design/changelog/2026-06-29-sidebars-use-darker-chrome-and-chat-warns-immediately.md +6 -0
  231. package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-now-creates-anchor-based-bezier-vectors-with-ha.md +6 -0
  232. package/corpus/templates/design/changelog/2026-06-29-visual-edit-can-open-localhost-routes-as-url-backed-screens.md +6 -0
  233. package/corpus/templates/design/package.json +1 -0
  234. package/corpus/templates/design/server/db/schema.ts +26 -0
  235. package/corpus/templates/design/server/handlers/import-figma-system.ts +38 -85
  236. package/corpus/templates/design/server/lib/design-snapshot.ts +9 -1
  237. package/corpus/templates/design/server/plugins/agent-chat.ts +3 -0
  238. package/corpus/templates/design/shared/api.ts +27 -0
  239. package/corpus/templates/design/shared/canvas-frames.ts +103 -0
  240. package/corpus/templates/design/shared/canvas-math.ts +1058 -0
  241. package/corpus/templates/design/shared/code-layer.ts +2412 -0
  242. package/corpus/templates/design/shared/color-utils.ts +180 -0
  243. package/corpus/templates/design/shared/generation-session.ts +61 -0
  244. package/corpus/templates/design/shared/html-content.ts +21 -0
  245. package/corpus/templates/design/shared/pen-path.ts +207 -0
  246. package/corpus/templates/design/shared/shader-presets.ts +782 -0
  247. package/corpus/templates/design/shared/shader-safety.ts +137 -0
  248. package/corpus/templates/design/shared/source-mode.ts +237 -0
  249. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +149 -6
  250. package/corpus/templates/forms/app/i18n/ar-SA.ts +2 -0
  251. package/corpus/templates/forms/app/i18n/de-DE.ts +2 -0
  252. package/corpus/templates/forms/app/i18n/en-US.ts +2 -0
  253. package/corpus/templates/forms/app/i18n/es-ES.ts +2 -0
  254. package/corpus/templates/forms/app/i18n/fr-FR.ts +2 -0
  255. package/corpus/templates/forms/app/i18n/hi-IN.ts +2 -0
  256. package/corpus/templates/forms/app/i18n/ja-JP.ts +2 -0
  257. package/corpus/templates/forms/app/i18n/ko-KR.ts +2 -0
  258. package/corpus/templates/forms/app/i18n/pt-BR.ts +2 -0
  259. package/corpus/templates/forms/app/i18n/zh-CN.ts +2 -0
  260. package/corpus/templates/forms/app/i18n/zh-TW.ts +2 -0
  261. package/corpus/templates/forms/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  262. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +2 -2
  263. package/corpus/templates/macros/changelog/2026-06-28-left-sidebar-footer-controls-use-less-divider-chrome.md +6 -0
  264. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +408 -247
  265. package/corpus/templates/mail/app/i18n/ar-SA.ts +4 -0
  266. package/corpus/templates/mail/app/i18n/de-DE.ts +4 -0
  267. package/corpus/templates/mail/app/i18n/en-US.ts +4 -0
  268. package/corpus/templates/mail/app/i18n/es-ES.ts +4 -0
  269. package/corpus/templates/mail/app/i18n/fr-FR.ts +4 -0
  270. package/corpus/templates/mail/app/i18n/hi-IN.ts +4 -0
  271. package/corpus/templates/mail/app/i18n/ja-JP.ts +4 -0
  272. package/corpus/templates/mail/app/i18n/ko-KR.ts +4 -0
  273. package/corpus/templates/mail/app/i18n/pt-BR.ts +4 -0
  274. package/corpus/templates/mail/app/i18n/zh-CN.ts +4 -0
  275. package/corpus/templates/mail/app/i18n/zh-TW.ts +4 -0
  276. package/corpus/templates/mail/app/lib/utils.ts +2 -2
  277. package/corpus/templates/mail/changelog/2026-06-28-pinned-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  278. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +6 -8
  279. package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +1 -1
  280. package/corpus/templates/plan/app/global.css +61 -78
  281. package/corpus/templates/plan/app/pages/PlansPage.tsx +8 -0
  282. package/corpus/templates/plan/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  283. package/corpus/templates/plan/changelog/2026-06-28-plans-use-the-shared-neutral-light-and-dark-theme.md +6 -0
  284. package/corpus/templates/plan/changelog/2026-06-28-sending-open-feedback-from-a-plan-now-opens-the-inline-plan-.md +6 -0
  285. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +9 -10
  286. package/corpus/templates/slides/AGENTS.md +3 -2
  287. package/corpus/templates/slides/actions/import-file.ts +26 -17
  288. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +259 -0
  289. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +35 -167
  290. package/corpus/templates/slides/app/components/design-system/fig-import-response.ts +9 -13
  291. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +5 -5
  292. package/corpus/templates/slides/app/context/DeckContext.tsx +1 -1
  293. package/corpus/templates/slides/app/i18n/ar-SA.ts +9 -37
  294. package/corpus/templates/slides/app/i18n/de-DE.ts +10 -25
  295. package/corpus/templates/slides/app/i18n/en-US.ts +9 -26
  296. package/corpus/templates/slides/app/i18n/es-ES.ts +10 -28
  297. package/corpus/templates/slides/app/i18n/fr-FR.ts +10 -28
  298. package/corpus/templates/slides/app/i18n/hi-IN.ts +9 -26
  299. package/corpus/templates/slides/app/i18n/ja-JP.ts +10 -22
  300. package/corpus/templates/slides/app/i18n/ko-KR.ts +9 -23
  301. package/corpus/templates/slides/app/i18n/pt-BR.ts +10 -28
  302. package/corpus/templates/slides/app/i18n/zh-CN.ts +9 -22
  303. package/corpus/templates/slides/app/i18n/zh-TW.ts +9 -22
  304. package/corpus/templates/slides/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  305. package/corpus/templates/slides/server/handlers/import-figma-system.ts +44 -12
  306. package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
  307. package/corpus/templates/videos/app/components/NewCompositionPopover.tsx +1 -1
  308. package/corpus/templates/videos/app/components/Sidebar.tsx +2 -2
  309. package/corpus/templates/videos/app/components/layout/Layout.tsx +29 -2
  310. package/corpus/templates/videos/app/components/layout/NavSidebar.tsx +113 -30
  311. package/corpus/templates/videos/app/i18n/ar-SA.ts +2 -0
  312. package/corpus/templates/videos/app/i18n/de-DE.ts +2 -0
  313. package/corpus/templates/videos/app/i18n/en-US.ts +2 -0
  314. package/corpus/templates/videos/app/i18n/es-ES.ts +2 -0
  315. package/corpus/templates/videos/app/i18n/fr-FR.ts +2 -0
  316. package/corpus/templates/videos/app/i18n/hi-IN.ts +2 -0
  317. package/corpus/templates/videos/app/i18n/ja-JP.ts +2 -0
  318. package/corpus/templates/videos/app/i18n/ko-KR.ts +2 -0
  319. package/corpus/templates/videos/app/i18n/pt-BR.ts +2 -0
  320. package/corpus/templates/videos/app/i18n/zh-CN.ts +2 -0
  321. package/corpus/templates/videos/app/i18n/zh-TW.ts +2 -0
  322. package/corpus/templates/videos/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  323. package/dist/agent/engine/index.d.ts +1 -1
  324. package/dist/agent/engine/index.d.ts.map +1 -1
  325. package/dist/agent/engine/index.js +1 -1
  326. package/dist/agent/engine/index.js.map +1 -1
  327. package/dist/agent/engine/registry.d.ts +9 -0
  328. package/dist/agent/engine/registry.d.ts.map +1 -1
  329. package/dist/agent/engine/registry.js +37 -0
  330. package/dist/agent/engine/registry.js.map +1 -1
  331. package/dist/agent/production-agent.d.ts.map +1 -1
  332. package/dist/agent/production-agent.js +4 -3
  333. package/dist/agent/production-agent.js.map +1 -1
  334. package/dist/application-state/handlers.js +1 -1
  335. package/dist/application-state/handlers.js.map +1 -1
  336. package/dist/brand-kit/index.d.ts +1 -1
  337. package/dist/brand-kit/index.js +1 -1
  338. package/dist/brand-kit/index.js.map +1 -1
  339. package/dist/cli/design-connect.d.ts +52 -0
  340. package/dist/cli/design-connect.d.ts.map +1 -0
  341. package/dist/cli/design-connect.js +409 -0
  342. package/dist/cli/design-connect.js.map +1 -0
  343. package/dist/cli/index.js +15 -0
  344. package/dist/cli/index.js.map +1 -1
  345. package/dist/cli/skills.d.ts +3 -0
  346. package/dist/cli/skills.d.ts.map +1 -1
  347. package/dist/cli/skills.js +139 -3
  348. package/dist/cli/skills.js.map +1 -1
  349. package/dist/client/AgentPanel.js +1 -1
  350. package/dist/client/AgentPanel.js.map +1 -1
  351. package/dist/client/AssistantChat.d.ts.map +1 -1
  352. package/dist/client/AssistantChat.js +38 -8
  353. package/dist/client/AssistantChat.js.map +1 -1
  354. package/dist/client/MultiTabAssistantChat.js +1 -1
  355. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  356. package/dist/client/agent-chat.d.ts +6 -0
  357. package/dist/client/agent-chat.d.ts.map +1 -1
  358. package/dist/client/agent-chat.js +17 -3
  359. package/dist/client/agent-chat.js.map +1 -1
  360. package/dist/client/blocks/library/MermaidBlock.js +1 -1
  361. package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
  362. package/dist/client/blocks/library/diagram.js +1 -1
  363. package/dist/client/blocks/library/diagram.js.map +1 -1
  364. package/dist/client/blocks/library/wireframe.js +1 -1
  365. package/dist/client/blocks/library/wireframe.js.map +1 -1
  366. package/dist/client/chat/message-components.js +1 -1
  367. package/dist/client/chat/message-components.js.map +1 -1
  368. package/dist/client/chat-model-groups.d.ts.map +1 -1
  369. package/dist/client/chat-model-groups.js +1 -2
  370. package/dist/client/chat-model-groups.js.map +1 -1
  371. package/dist/client/components/LiveCursorOverlay.d.ts +1 -2
  372. package/dist/client/components/LiveCursorOverlay.d.ts.map +1 -1
  373. package/dist/client/components/LiveCursorOverlay.js +19 -40
  374. package/dist/client/components/LiveCursorOverlay.js.map +1 -1
  375. package/dist/client/composer/TiptapComposer.d.ts +3 -1
  376. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  377. package/dist/client/composer/TiptapComposer.js +86 -24
  378. package/dist/client/composer/TiptapComposer.js.map +1 -1
  379. package/dist/client/composer/useVoiceDictation.d.ts +9 -0
  380. package/dist/client/composer/useVoiceDictation.d.ts.map +1 -1
  381. package/dist/client/composer/useVoiceDictation.js +154 -32
  382. package/dist/client/composer/useVoiceDictation.js.map +1 -1
  383. package/dist/client/extensions/portable-extension.d.ts +1 -1
  384. package/dist/client/extensions/portable-extension.d.ts.map +1 -1
  385. package/dist/client/extensions/portable-extension.js.map +1 -1
  386. package/dist/client/frame-protocol.d.ts +17 -2
  387. package/dist/client/frame-protocol.d.ts.map +1 -1
  388. package/dist/client/frame-protocol.js.map +1 -1
  389. package/dist/client/rich-markdown-editor/RegistryBlockNode.js +2 -2
  390. package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
  391. package/dist/client/sharing/ShareButton.d.ts +2 -0
  392. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  393. package/dist/client/sharing/ShareButton.js +7 -6
  394. package/dist/client/sharing/ShareButton.js.map +1 -1
  395. package/dist/client/use-agent-engine-configured.d.ts +5 -0
  396. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  397. package/dist/client/use-agent-engine-configured.js +51 -25
  398. package/dist/client/use-agent-engine-configured.js.map +1 -1
  399. package/dist/code-agents/transcript-normalizer.js +1 -1
  400. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  401. package/dist/collab/routes.d.ts +1 -1
  402. package/dist/extensions/path.d.ts +1 -1
  403. package/dist/extensions/path.d.ts.map +1 -1
  404. package/dist/extensions/path.js.map +1 -1
  405. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  406. package/dist/integrations/a2a-continuation-processor.js +1 -1
  407. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  408. package/dist/mcp/build-server.d.ts +1 -1
  409. package/dist/mcp/build-server.d.ts.map +1 -1
  410. package/dist/mcp/build-server.js.map +1 -1
  411. package/dist/notifications/routes.d.ts +3 -3
  412. package/dist/observability/routes.d.ts +5 -5
  413. package/dist/org/handlers.js +5 -5
  414. package/dist/org/handlers.js.map +1 -1
  415. package/dist/org/plugin.js +2 -2
  416. package/dist/org/plugin.js.map +1 -1
  417. package/dist/scripts/call-agent.js +1 -1
  418. package/dist/scripts/call-agent.js.map +1 -1
  419. package/dist/scripts/db/safety.js +1 -1
  420. package/dist/scripts/db/safety.js.map +1 -1
  421. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  422. package/dist/server/auth.d.ts.map +1 -1
  423. package/dist/server/auth.js +3 -0
  424. package/dist/server/auth.js.map +1 -1
  425. package/dist/server/builder-design-systems.d.ts +28 -0
  426. package/dist/server/builder-design-systems.d.ts.map +1 -0
  427. package/dist/server/builder-design-systems.js +217 -0
  428. package/dist/server/builder-design-systems.js.map +1 -0
  429. package/dist/server/core-routes-plugin.d.ts +1 -0
  430. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  431. package/dist/server/core-routes-plugin.js +20 -3
  432. package/dist/server/core-routes-plugin.js.map +1 -1
  433. package/dist/server/index.d.ts +1 -0
  434. package/dist/server/index.d.ts.map +1 -1
  435. package/dist/server/index.js +1 -0
  436. package/dist/server/index.js.map +1 -1
  437. package/dist/server/transcribe-voice.d.ts +1 -1
  438. package/dist/server/transcribe-voice.d.ts.map +1 -1
  439. package/dist/server/transcribe-voice.js +40 -24
  440. package/dist/server/transcribe-voice.js.map +1 -1
  441. package/dist/shared/mcp-embed-headers.d.ts +1 -0
  442. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  443. package/dist/shared/mcp-embed-headers.js +20 -1
  444. package/dist/shared/mcp-embed-headers.js.map +1 -1
  445. package/dist/styles/agent-native.css +60 -4
  446. package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
  447. package/dist/usage/store.js +2 -2
  448. package/dist/usage/store.js.map +1 -1
  449. package/dist/vite/client.d.ts.map +1 -1
  450. package/dist/vite/client.js +2 -0
  451. package/dist/vite/client.js.map +1 -1
  452. package/dist/voice/index.d.ts +5 -0
  453. package/dist/voice/index.d.ts.map +1 -0
  454. package/dist/voice/index.js +4 -0
  455. package/dist/voice/index.js.map +1 -0
  456. package/dist/voice/voice-cleanup-prompt.d.ts +6 -0
  457. package/dist/voice/voice-cleanup-prompt.d.ts.map +1 -0
  458. package/dist/voice/voice-cleanup-prompt.js +26 -0
  459. package/dist/voice/voice-cleanup-prompt.js.map +1 -0
  460. package/dist/voice/voice-context.d.ts +25 -0
  461. package/dist/voice/voice-context.d.ts.map +1 -0
  462. package/dist/voice/voice-context.js +156 -0
  463. package/dist/voice/voice-context.js.map +1 -0
  464. package/dist/voice/voice-replacements.d.ts +4 -0
  465. package/dist/voice/voice-replacements.d.ts.map +1 -0
  466. package/dist/voice/voice-replacements.js +52 -0
  467. package/dist/voice/voice-replacements.js.map +1 -0
  468. package/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
  469. package/docs/content/locales/ar-SA/template-design.mdx +55 -1
  470. package/docs/content/locales/de-DE/skills-guide.mdx +5 -0
  471. package/docs/content/locales/de-DE/template-design.mdx +66 -1
  472. package/docs/content/locales/es-ES/skills-guide.mdx +5 -0
  473. package/docs/content/locales/es-ES/template-design.mdx +65 -1
  474. package/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
  475. package/docs/content/locales/fr-FR/template-design.mdx +64 -1
  476. package/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
  477. package/docs/content/locales/hi-IN/template-design.mdx +59 -1
  478. package/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
  479. package/docs/content/locales/ja-JP/template-design.mdx +62 -1
  480. package/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
  481. package/docs/content/locales/ko-KR/template-design.mdx +58 -1
  482. package/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
  483. package/docs/content/locales/pt-BR/template-design.mdx +62 -1
  484. package/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
  485. package/docs/content/locales/zh-CN/template-design.mdx +50 -1
  486. package/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
  487. package/docs/content/locales/zh-TW/template-design.mdx +52 -1
  488. package/docs/content/skills-guide.mdx +5 -0
  489. package/docs/content/template-design.mdx +59 -1
  490. package/package.json +2 -1
  491. package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
  492. package/corpus/templates/design/actions/import-figma-file.ts +0 -166
  493. package/corpus/templates/design/app/components/design/DesignToolbar.tsx +0 -339
  494. package/corpus/templates/design/app/components/design/PresentMode.tsx +0 -68
  495. package/corpus/templates/design/app/components/design/ViewportTabs.tsx +0 -149
  496. package/corpus/templates/design/app/components/design/ZoomControls.tsx +0 -157
  497. 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
+ }