refactor(ui): organize app component domains

- Move root-level picker, input, and menu components into lowercase domain folders
- Update component namespace linting for kebab-case app domains
- Document FSD-lite component structure in AGENTS.md
This commit is contained in:
Danila Poyarkov 2026-07-01 09:58:10 +03:00
parent d195bb67d2
commit c1207f1be3
36 changed files with 102 additions and 80 deletions

View file

@ -143,14 +143,15 @@ Keep this section light; implementation details move often.
### File and folder naming
OpenPencil follows a Reka UI-inspired component namespace structure:
OpenPencil uses domain namespaces rather than full Feature-Sliced Design ceremony:
- Vue component namespace folders use PascalCase: `ColorPicker/`, `Toolbar/`, `ProviderSettings/`.
- Vue component files use PascalCase: `ColorPickerRoot.vue`, `ToolbarItem.vue`.
- Component-scoped composables use camelCase: `useToolbarState.ts`, `usePageList.ts`.
- Non-component domain folders use lowercase or kebab-case: `scene-graph/`, `figma-api/`, `node-edit/`.
- Non-component TypeScript files use lowercase or kebab-case unless they are conventional entrypoints such as `index.ts`, `types.ts`, `context.ts`, or `use.ts`.
- Multi-file root components live inside their component namespace folder, not beside it.
- App services/state/integration live under `src/app/**`; route/layout views live under `src/views/**`; app UI lives under `src/components/**`.
- `src/components/ui/**` is the shared app design-system layer. `packages/vue/src/primitives/**` is the headless SDK primitive layer. App wrappers around SDK primitives should stay in app component domains and only move to `ui/**` when genuinely generic.
- Root-level `src/components/*.vue` is reserved for broad editor panels/surfaces assembled by views or shell layout. Do not add new root-level base controls; create a domain namespace or use `src/components/ui/**` for reusable primitives.
- App component domain folders should be lowercase or kebab-case (`chat/`, `properties/`, `fill-picker/`, `color-picker-panel/`, `canvas/`, `inputs/`). Avoid adding new `PascalCase/Component.vue` app folders; migrate existing ones gradually when touched.
- Vue component files stay PascalCase: `ColorPickerRoot.vue`, `ToolbarItem.vue`. Component-scoped composables use camelCase: `useToolbarState.ts`, `usePageList.ts`.
- Non-component domain folders use lowercase or kebab-case: `scene-graph/`, `figma-api/`, `node-edit/`. Non-component TypeScript files use lowercase or kebab-case unless they are conventional entrypoints such as `index.ts`, `types.ts`, `context.ts`, or `use.ts`.
- Multi-file root components live inside their component namespace folder, not beside it. When a reusable picker/input/control grows beyond one file, create a namespace instead of leaving related files at `src/components/` root.
- Use subfolders for multi-file domains instead of sibling files with repeated prefixes. Prefer `selection/container.ts`, `selection/hit-test.ts` over `selection-container.ts`, `selection-hit-test.ts`. When adding a second file for a domain (e.g. `eval-wrap.ts` next to `eval.ts`), create the folder immediately (`eval/index.ts` + `eval/wrap.ts`) instead of prefixing. Oxlint catches sibling prefix files when a sibling folder exists; Steiger catches 3+ sibling files with the same prefix. The convention applies even before either rule triggers.
### Repo tools and scripts
@ -258,7 +259,7 @@ Self-review checklist:
- `src/components/ui/**` is the app design-system layer: reusable visual primitives, wrappers around Reka UI primitives, low-level styled controls, and UI class helpers. These files must not import app services/stores or feature panels.
- `src/components/Shell/**` is for app shell chrome and global app services rendered as components (menu bar, toast viewport, update/status chrome). Shell components may use app shell/editor stores.
- `src/components/properties/**`, `src/components/chat/**`, `src/components/LayerTree/**`, `src/components/Toolbar/**`, and similar folders are feature/domain component namespaces. Keep feature-specific controls there unless they are genuinely reusable UI primitives.
- Root-level `src/components/*.vue` is for broad editor panels/surfaces that are assembled by views or shell layout. Do not add new root-level base controls; create a domain folder or move reusable primitives to `src/components/ui/**`.
- Treat existing root-level picker/input/control components as migration candidates when touched; do not expand that pattern.
- Test hooks should be `data-test-id` attributes owned by the rendered markup or generated internally from semantic component state. Do not add `testId`, `visibilityTestId`, `triggerTestId`, or other test-id props to component APIs.
- Use reka-ui for UI components (Splitter, ContextMenu, DropdownMenu, etc.)
@ -267,7 +268,7 @@ Self-review checklist:
- App wrappers around SDK primitives should compose a single `ui` object from shared UI helpers (`useSelectUI`, `usePopoverUI`, etc.) rather than bypassing the design system with raw Tailwind strings spread across multiple props.
- Editor commands share `packages/vue/src/editor/commands/registry.ts` as the canonical source for shortcut display tokens, keyboard bindings, and context-menu test IDs. Store portable shortcuts such as `MOD+D`, `MOD+SHIFT+H`, and `MOD+ALT+K`; format them with `formatShortcut()` at render time so macOS shows `⌘`/`⌥` and Windows/Linux show `Ctrl`/`Alt`.
- Labels and translations must not contain shortcut text. Keep labels semantic (`Add auto layout`, `Show/Hide`) and render shortcuts from command metadata. Steiger enforces this for `packages/vue/src/i18n/messages.ts` and locale JSON files.
- Canvas context-menu structure lives in `packages/vue/src/editor/menu-model/canvas.ts`. Do not hand-build command grouping in `src/components/CanvasMenu.vue`; the component should render menu entries and provide app-specific actions only when unavoidable.
- Canvas context-menu structure lives in `packages/vue/src/editor/menu-model/canvas.ts`. Do not hand-build command grouping in `src/components/canvas/CanvasMenu.vue`; the component should render menu entries and provide app-specific actions only when unavoidable.
- Browser and Tauri menus share `src/app/shell/menu/schema.ts` as the canonical menu model. Do not add menu items directly in `src/components/Shell/AppMenu.vue` or `desktop/src/menu.rs`.
- Regenerate the native menu with `bun run generate:tauri-menu` after editing the shared menu schema; `desktop/generated/menu.json` is consumed by the Tauri menu builder. Tauri also runs this generator from `desktop/tauri.conf.json` via `beforeDevCommand` and `beforeBuildCommand`.
- Every shared menu item with an `id` must be handled by `src/app/shell/menu/use.ts`, an editor command, or explicitly marked browser/native-only in the schema.

View file

@ -1,4 +1,5 @@
import { existsSync } from 'node:fs'
import { parse as parseVueSfc } from 'vue/compiler-sfc'
function normalizedFilename(context) {
@ -311,7 +312,10 @@ const noNativeTitleAttributesInVue = {
let hasTitleAttribute = false
walkVueTemplateAst(template, (templateNode) => {
if (hasTitleAttribute) return
if (isStaticVueAttribute(templateNode, 'title') || isVueBindDirective(templateNode, 'title')) {
if (
isStaticVueAttribute(templateNode, 'title') ||
isVueBindDirective(templateNode, 'title')
) {
hasTitleAttribute = true
}
})
@ -451,7 +455,8 @@ const TEST_ID_FORMAT = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/
const noRawTestIdStringProps = {
meta: {
docs: {
description: 'Disallow test-id component props — use data-test-id attrs or internal semantic ids'
description:
'Disallow test-id component props — use data-test-id attrs or internal semantic ids'
}
},
create(context) {
@ -624,9 +629,7 @@ const noRawTestIdSelectorsInTests = {
function isGeneratedTestIdLiteral(value) {
if (typeof value !== 'string') return false
const toolbarValue = value.startsWith('mobile-toolbar-')
? value.slice('mobile-'.length)
: value
const toolbarValue = value.startsWith('mobile-toolbar-') ? value.slice('mobile-'.length) : value
return (
toolbarValue.startsWith('toolbar-tool-') ||
toolbarValue.startsWith('toolbar-flyout-') ||
@ -714,7 +717,8 @@ const noBrowserSideEffectsInVue = {
) {
context.report({
node,
message: 'Use VueUse useEventListener() instead of direct browser event listeners in Vue components.'
message:
'Use VueUse useEventListener() instead of direct browser event listeners in Vue components.'
})
return
}
@ -743,7 +747,8 @@ const noBrowserSideEffectsInVue = {
const noDocumentQuerySelectorInVue = {
meta: {
docs: {
description: 'Disallow document.querySelector in Vue components — use template refs or composables'
description:
'Disallow document.querySelector in Vue components — use template refs or composables'
}
},
create(context) {
@ -756,7 +761,10 @@ const noDocumentQuerySelectorInVue = {
if (callee?.type !== 'MemberExpression') return
if (callee.object?.type !== 'Identifier' || callee.object.name !== 'document') return
if (callee.property?.type !== 'Identifier') return
if (callee.property.name !== 'querySelector' && callee.property.name !== 'querySelectorAll') {
if (
callee.property.name !== 'querySelector' &&
callee.property.name !== 'querySelectorAll'
) {
return
}
context.report({
@ -772,7 +780,8 @@ const noDocumentQuerySelectorInVue = {
const noDirectSelectionToolStateMutation = {
meta: {
docs: {
description: 'Disallow direct editor selection/tool state assignment outside core editor internals'
description:
'Disallow direct editor selection/tool state assignment outside core editor internals'
}
},
create(context) {
@ -843,7 +852,8 @@ function colorObjectLiteral(node, color) {
const noHardcodedColorConstants = {
meta: {
docs: {
description: 'Use named color constants instead of inline Color object literals for shared colors'
description:
'Use named color constants instead of inline Color object literals for shared colors'
}
},
create(context) {
@ -853,10 +863,17 @@ const noHardcodedColorConstants = {
return {
ObjectExpression(node) {
if (colorObjectLiteral(node, { r: 0, g: 0, b: 0, a: 1 })) {
context.report({ node, message: 'Use BLACK from constants instead of an inline black Color literal.' })
context.report({
node,
message: 'Use BLACK from constants instead of an inline black Color literal.'
})
}
if (colorObjectLiteral(node, { r: 0, g: 0, b: 0, a: 0 })) {
context.report({ node, message: 'Use TRANSPARENT from constants instead of an inline transparent Color literal.' })
context.report({
node,
message:
'Use TRANSPARENT from constants instead of an inline transparent Color literal.'
})
}
}
}
@ -1551,10 +1568,10 @@ const vueComponentFilePascalCase = {
}
}
const componentNamespacePascalCase = {
const componentNamespaceCasing = {
meta: {
docs: {
description: 'Require component namespace folders to use PascalCase names'
description: 'Require component namespace folders to use the project casing convention'
}
},
create(context) {
@ -1577,12 +1594,11 @@ const componentNamespacePascalCase = {
const parts = componentMatch[1].split('/')
const first = parts[0]
const second = parts[1]
const allowedGroups = new Set(['chat', 'properties', 'ui'])
if (parts.length > 1 && !allowedGroups.has(first) && !isPascalCaseName(first)) {
if (parts.length > 1 && !isPascalCaseName(first) && !isKebabOrLowercaseName(first)) {
context.report({
node,
message: `Component namespace folder '${first}' must use PascalCase.`
message: `Component namespace folder '${first}' must use PascalCase or kebab-case.`
})
return
}
@ -1591,11 +1607,12 @@ const componentNamespacePascalCase = {
parts.length > 2 &&
(first === 'chat' || first === 'properties') &&
second !== undefined &&
!isPascalCaseName(second)
!isPascalCaseName(second) &&
!isKebabOrLowercaseName(second)
) {
context.report({
node,
message: `Nested component namespace folder '${first}/${second}' must use PascalCase.`
message: `Nested component namespace folder '${first}/${second}' must use PascalCase or kebab-case.`
})
}
}
@ -1789,7 +1806,8 @@ const noDirectOpenPencilBrowserStore = {
if (!isOpenPencilMember(node.object)) return
context.report({
node,
message: 'Use window.openPencil.getStore() instead of accessing window.openPencil.store directly.'
message:
'Use window.openPencil.getStore() instead of accessing window.openPencil.store directly.'
})
}
}
@ -1887,7 +1905,8 @@ function hasAstChild(node, predicate, seen = new WeakSet()) {
function containsRecordStringUnknownType(node) {
if (isRecordStringUnknownType(node)) return true
if (node?.type === 'TSArrayType') return isRecordStringUnknownType(node.elementType)
if (node?.type === 'TSUnionType') return node.types?.some(containsRecordStringUnknownType) ?? false
if (node?.type === 'TSUnionType')
return node.types?.some(containsRecordStringUnknownType) ?? false
return false
}
@ -1942,7 +1961,8 @@ const noBroadUnknownTypeAssertions = {
function typeNameText(node) {
if (!node) return 'unknown'
if (node.type === 'Identifier') return node.name
if (node.type === 'TSQualifiedName') return `${typeNameText(node.left)}.${typeNameText(node.right)}`
if (node.type === 'TSQualifiedName')
return `${typeNameText(node.left)}.${typeNameText(node.right)}`
return node.type
}
@ -2034,7 +2054,8 @@ const noDuplicateTypeShapes = {
}
context.report({
node,
message: 'Duplicate object type shape. Reuse the existing named type instead of redeclaring the same members.'
message:
'Duplicate object type shape. Reuse the existing named type instead of redeclaring the same members.'
})
}
}
@ -2054,7 +2075,8 @@ const noLocalJsonObjectAliases = {
if (!isRecordStringUnknownType(node.typeAnnotation)) return
context.report({
node,
message: 'Import JsonObject from @open-pencil/scene-graph/primitives instead of declaring a local alias.'
message:
'Import JsonObject from @open-pencil/scene-graph/primitives instead of declaring a local alias.'
})
}
}
@ -2062,7 +2084,6 @@ const noLocalJsonObjectAliases = {
}
const noFlatKiwiModules = createProgramFilenameRule({
description: 'Disallow flat top-level Kiwi modules — group code under Kiwi subdomains',
check(file) {
const marker = '/packages/core/src/kiwi/'
@ -2136,7 +2157,7 @@ const plugin = {
'prefer-vueuse-timeouts': preferVueUseTimeouts,
'max-composition-root-lines': maxCompositionRootLines,
'vue-component-file-pascal-case': vueComponentFilePascalCase,
'component-namespace-pascal-case': componentNamespacePascalCase,
'component-namespace-casing': componentNamespaceCasing,
'non-component-source-directories-kebab-case': nonComponentSourceDirectoriesKebabCase,
'no-component-root-sibling-folder': noComponentRootSiblingFolder,
'no-useless-pass-through-wrappers': noUselessPassThroughWrappers,

View file

@ -208,7 +208,7 @@
"open-pencil/prefer-vueuse-intervals": "error",
"open-pencil/prefer-vueuse-timeouts": "error",
"open-pencil/vue-component-file-pascal-case": "error",
"open-pencil/component-namespace-pascal-case": "error",
"open-pencil/component-namespace-casing": "error",
"open-pencil/non-component-source-directories-kebab-case": "error",
"open-pencil/no-component-root-sibling-folder": "error",
"open-pencil/no-flat-kiwi-modules": "error",

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import { ColorPickerRoot } from '@open-pencil/vue'
import ColorPickerPanel from '@/components/ColorPickerPanel/ColorPickerPanel.vue'
import ColorPickerPanel from '@/components/color-picker-panel/ColorPickerPanel.vue'
import { usePopoverUI } from '@/components/ui/popover'
import type { Color } from '@open-pencil/scene-graph/primitives'

View file

@ -3,7 +3,7 @@ import { computed, ref } from 'vue'
import { useI18n, useSelectionState, useEditorCommands } from '@open-pencil/vue'
import VariablesDialog from './VariablesDialog.vue'
import VariablesDialog from './variables/VariablesDialog.vue'
import BooleanOperationsControl from './properties/BooleanOperationsControl.vue'
import AppearanceSection from './properties/AppearanceSection.vue'
import EffectsSection from './properties/EffectsSection.vue'

View file

@ -31,7 +31,7 @@ import IconLucidePanelLeft from '~icons/lucide/panel-left'
import IconLucidePanelRight from '~icons/lucide/panel-right'
import IconLucidePanelTop from '~icons/lucide/panel-top'
import CanvasMenu from './CanvasMenu.vue'
import ScrubInput from './ScrubInput.vue'
import ScrubInput from './inputs/ScrubInput.vue'
const store = useEditorStore()
const collab = useCollabInjected()

View file

@ -11,7 +11,7 @@ import {
import { LayerTreeRoot, LayerTreeItem, useInlineRename } from '@open-pencil/vue'
import type { LayerDragInstruction, LayerNode } from '@open-pencil/vue'
import { useEditorStore } from '@/app/editor/active-store'
import CanvasMenu from '../CanvasMenu.vue'
import CanvasMenu from '../canvas/CanvasMenu.vue'
import LayerTreeNodeRow from './LayerTreeNodeRow.vue'
import LayerTreeRenameRow from './LayerTreeRenameRow.vue'

View file

@ -7,7 +7,7 @@ import { useAIChat } from '@/app/ai/chat/use'
import ChatPanel from './ChatPanel.vue'
import CodePanel from './CodePanel.vue'
import DesignPanel from './DesignPanel.vue'
import ZoomDropdown from './ZoomDropdown.vue'
import ZoomDropdown from './editor/ZoomDropdown.vue'
const { activeTab } = useAIChat()
const { panels } = useI18n()

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import { ColorAreaArea, ColorAreaRoot, ColorAreaThumb } from 'reka-ui'
import { useColorPickerPanelContext } from '@/components/ColorPickerPanel/context'
import { useColorPickerPanelContext } from '@/components/color-picker-panel/context'
const ctx = useColorPickerPanelContext()
</script>

View file

@ -2,10 +2,10 @@
import type { Color } from '@open-pencil/scene-graph/primitives'
import type { OkHCLControls } from '@open-pencil/vue'
import ColorAreaControl from '@/components/ColorPickerPanel/ColorAreaControl.vue'
import FormatControls from '@/components/ColorPickerPanel/FormatControls.vue'
import HueAlphaSliders from '@/components/ColorPickerPanel/HueAlphaSliders.vue'
import { provideColorPickerPanel } from '@/components/ColorPickerPanel/context'
import ColorAreaControl from '@/components/color-picker-panel/ColorAreaControl.vue'
import FormatControls from '@/components/color-picker-panel/FormatControls.vue'
import HueAlphaSliders from '@/components/color-picker-panel/HueAlphaSliders.vue'
import { provideColorPickerPanel } from '@/components/color-picker-panel/context'
const { color, okhcl = null } = defineProps<{
color: Color

View file

@ -1,10 +1,10 @@
<script setup lang="ts">
import AppSelect from '@/components/ui/AppSelect.vue'
import HsbFields from '@/components/ColorPickerPanel/HsbFields.vue'
import HslFields from '@/components/ColorPickerPanel/HslFields.vue'
import OkhclFields from '@/components/ColorPickerPanel/OkhclFields.vue'
import RgbFields from '@/components/ColorPickerPanel/RgbFields.vue'
import { useColorPickerPanelContext } from '@/components/ColorPickerPanel/context'
import HsbFields from '@/components/color-picker-panel/HsbFields.vue'
import HslFields from '@/components/color-picker-panel/HslFields.vue'
import OkhclFields from '@/components/color-picker-panel/OkhclFields.vue'
import RgbFields from '@/components/color-picker-panel/RgbFields.vue'
import { useColorPickerPanelContext } from '@/components/color-picker-panel/context'
const ctx = useColorPickerPanelContext()
</script>

View file

@ -2,8 +2,8 @@
import { inputNumberValue } from '@open-pencil/vue'
import { colorToCSS } from '@open-pencil/core/color'
import PickerSlider from '@/components/PickerSlider.vue'
import { useColorPickerPanelContext } from '@/components/ColorPickerPanel/context'
import PickerSlider from '@/components/color-picker-panel/PickerSlider.vue'
import { useColorPickerPanelContext } from '@/components/color-picker-panel/context'
const ctx = useColorPickerPanelContext()
</script>

View file

@ -2,8 +2,8 @@
import { inputNumberValue } from '@open-pencil/vue'
import { colorToCSS } from '@open-pencil/core/color'
import PickerSlider from '@/components/PickerSlider.vue'
import { useColorPickerPanelContext } from '@/components/ColorPickerPanel/context'
import PickerSlider from '@/components/color-picker-panel/PickerSlider.vue'
import { useColorPickerPanelContext } from '@/components/color-picker-panel/context'
const ctx = useColorPickerPanelContext()
</script>

View file

@ -1,8 +1,8 @@
<script setup lang="ts">
import { colorToCSS } from '@open-pencil/core/color'
import PickerSlider from '@/components/PickerSlider.vue'
import { useColorPickerPanelContext } from '@/components/ColorPickerPanel/context'
import PickerSlider from '@/components/color-picker-panel/PickerSlider.vue'
import { useColorPickerPanelContext } from '@/components/color-picker-panel/context'
const ctx = useColorPickerPanelContext()
</script>

View file

@ -2,8 +2,8 @@
import { colorToCSS } from '@open-pencil/core/color'
import { fromPercent, toPercent } from '@open-pencil/vue'
import PickerSlider from '@/components/PickerSlider.vue'
import { useColorPickerPanelContext } from '@/components/ColorPickerPanel/context'
import PickerSlider from '@/components/color-picker-panel/PickerSlider.vue'
import { useColorPickerPanelContext } from '@/components/color-picker-panel/context'
const ctx = useColorPickerPanelContext()
</script>

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { inputNumberValue } from '@open-pencil/vue'
import { usePickerSliderUI } from './ui/picker-slider'
import { usePickerSliderUI } from '@/components/ui/picker-slider'
type PickerSliderDisplay = {
value?: number

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import { inputNumberValue } from '@open-pencil/vue'
import { useColorPickerPanelContext } from '@/components/ColorPickerPanel/context'
import { useColorPickerPanelContext } from '@/components/color-picker-panel/context'
const ctx = useColorPickerPanelContext()
</script>

View file

@ -4,10 +4,10 @@ import { twMerge } from 'tailwind-merge'
import { applySolidFillColor, FillPickerRoot, useI18n } from '@open-pencil/vue'
import GradientEditor from './GradientEditor.vue'
import ColorPickerPanel from '@/components/ColorPickerPanel/ColorPickerPanel.vue'
import ColorPickerPanel from '@/components/color-picker-panel/ColorPickerPanel.vue'
import ImageFillPicker from './ImageFillPicker.vue'
import Tip from './ui/Tip.vue'
import { usePopoverUI } from './ui/popover'
import Tip from '@/components/ui/Tip.vue'
import { usePopoverUI } from '@/components/ui/popover'
import type { Fill } from '@open-pencil/scene-graph'
import type { OkHCLControls } from '@open-pencil/vue'

View file

@ -1,8 +1,8 @@
<script setup lang="ts">
import AppSelect from './ui/AppSelect.vue'
import Tip from './ui/Tip.vue'
import ColorPickerPanel from '@/components/ColorPickerPanel/ColorPickerPanel.vue'
import ScrubInput from './ScrubInput.vue'
import AppSelect from '@/components/ui/AppSelect.vue'
import Tip from '@/components/ui/Tip.vue'
import ColorPickerPanel from '@/components/color-picker-panel/ColorPickerPanel.vue'
import ScrubInput from '@/components/inputs/ScrubInput.vue'
import { colorToCSS } from '@open-pencil/core/color'
import {
GradientEditorRoot,

View file

@ -2,7 +2,7 @@
import { computed, shallowRef, watch } from 'vue'
import { useFileDialog, useObjectUrl } from '@vueuse/core'
import AppSelect from './ui/AppSelect.vue'
import AppSelect from '@/components/ui/AppSelect.vue'
import { useEditorStore } from '@/app/editor/active-store'

View file

@ -3,7 +3,7 @@ import { computed, ref } from 'vue'
import { useAppearance, useI18n } from '@open-pencil/vue'
import ScrubInput from '@/components/ScrubInput.vue'
import ScrubInput from '@/components/inputs/ScrubInput.vue'
import VariableScrubInput from '@/components/properties/VariableScrubInput.vue'
import IconButton from '@/components/ui/IconButton.vue'
import PanelSection from '@/components/ui/PanelSection.vue'

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, useAttrs } from 'vue'
import ScrubInput from '@/components/ScrubInput.vue'
import ScrubInput from '@/components/inputs/ScrubInput.vue'
import BoundVariableButton from '@/components/properties/BoundVariableButton.vue'
import VariablePickerPopover from '@/components/properties/VariablePickerPopover.vue'
import IconButton from '@/components/ui/IconButton.vue'

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import AppSelect from '@/components/ui/AppSelect.vue'
import ColorInput from '@/components/ColorPicker/ColorInput.vue'
import ScrubInput from '@/components/ScrubInput.vue'
import ScrubInput from '@/components/inputs/ScrubInput.vue'
import IconButton from '@/components/ui/IconButton.vue'
import PanelSection from '@/components/ui/PanelSection.vue'
import Tip from '@/components/ui/Tip.vue'

View file

@ -2,7 +2,7 @@
import { PropertyListRoot, useFillControls, useOkHCL, useI18n, inputValue } from '@open-pencil/vue'
import { colorToHexRaw, parseColor } from '@open-pencil/core/color'
import FillPicker from '@/components/FillPicker.vue'
import FillPicker from '@/components/fill-picker/FillPicker.vue'
import IconButton from '@/components/ui/IconButton.vue'
import PanelSection from '@/components/ui/PanelSection.vue'
import ColorStyleRow from '@/components/properties/ColorStyleRow.vue'

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import AppSelect from '@/components/ui/AppSelect.vue'
import ScrubInput from '@/components/ScrubInput.vue'
import ScrubInput from '@/components/inputs/ScrubInput.vue'
import IconButton from '@/components/ui/IconButton.vue'
import { useI18n, useLayoutControlsContext } from '@open-pencil/vue'

View file

@ -12,7 +12,7 @@ import {
SelectViewport
} from 'reka-ui'
import ScrubInput from '@/components/ScrubInput.vue'
import ScrubInput from '@/components/inputs/ScrubInput.vue'
import VariableScrubInput from '@/components/properties/VariableScrubInput.vue'
import BoundVariableButton from '@/components/properties/BoundVariableButton.vue'
import VariablePickerPopover from '@/components/properties/VariablePickerPopover.vue'

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import ScrubInput from '@/components/ScrubInput.vue'
import ScrubInput from '@/components/inputs/ScrubInput.vue'
import IconButton from '@/components/ui/IconButton.vue'
import PanelRow from '@/components/ui/PanelRow.vue'
import PanelSection from '@/components/ui/PanelSection.vue'

View file

@ -14,7 +14,7 @@ import ColorStyleRow from '@/components/properties/ColorStyleRow.vue'
import { boundVariableColor } from '@/components/properties/color-style-row'
import AppSelect from '@/components/ui/AppSelect.vue'
import ColorInput from '@/components/ColorPicker/ColorInput.vue'
import ScrubInput from '@/components/ScrubInput.vue'
import ScrubInput from '@/components/inputs/ScrubInput.vue'
import IconButton from '@/components/ui/IconButton.vue'
import PanelSection from '@/components/ui/PanelSection.vue'
import Tip from '@/components/ui/Tip.vue'

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import { TypographyControlsRoot, useI18n } from '@open-pencil/vue'
import FontPicker from '@/components/FontPicker.vue'
import FontPicker from '@/components/font-picker/FontPicker.vue'
import FontSettingsPopover from '@/components/FontSettings/FontSettingsPopover.vue'
import VariableScrubInput from '@/components/properties/VariableScrubInput.vue'
import AppSelect from '@/components/ui/AppSelect.vue'

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import ScrubInput from '@/components/ScrubInput.vue'
import ScrubInput from '@/components/inputs/ScrubInput.vue'
import BoundVariableButton from '@/components/properties/BoundVariableButton.vue'
import VariablePickerPopover from '@/components/properties/VariablePickerPopover.vue'
import { useI18n, useNumberVariableBinding } from '@open-pencil/vue'

View file

@ -35,7 +35,7 @@ import IconToggleLeft from '~icons/lucide/toggle-left'
import IconType from '~icons/lucide/type'
import IconX from '~icons/lucide/x'
import ColorInput from '@/components/ColorPicker/ColorInput.vue'
import Tip from './ui/Tip.vue'
import Tip from '@/components/ui/Tip.vue'
import { useDialogUI } from '@/components/ui/dialog'
import { useMenuUI } from '@/components/ui/menu'