openpencil/src/components/LayersPanel.vue
Danila Poyarkov 57eb59769d
feat(components): match Figma asset browsing (#424)
* feat(components): match Figma asset browsing

- Add thumbnail grid and list views grouped by source page
- Support component drag insertion and main-component navigation
- Keep asset previews working across document pages
- Cover views, context actions, and drag-to-canvas behavior

* fix(components): remove redundant Assets heading

* fix(components): render crisp asset thumbnails

- Render preview images at twice their display dimensions
- Preserve the existing grid and list thumbnail sizes

* fix(render): exclude ancestors from selection exports

- Reparent selected export roots directly under the extracted page
- Preserve absolute positions without drawing ancestor backgrounds
- Cover transparent selection export around nested components

* fix(components): address Assets panel review

- Handle thumbnail export failures without stale previews
- Share page lookup and thumbnail sizing across asset views
- Make list actions keyboard accessible and honor requested export pages
2026-07-26 01:18:27 +03:00

86 lines
2.6 KiB
Vue

<script setup lang="ts">
import { computed, ref } from 'vue'
import { SplitterGroup, SplitterPanel, SplitterResizeHandle } from 'reka-ui'
import { useI18n } from '@open-pencil/vue'
import AppMenu from '@/components/Shell/AppMenu.vue'
import SegmentedControl from '@/components/ui/SegmentedControl.vue'
import AssetsPanel from './assets-panel/AssetsPanel.vue'
import LayerTree from './LayerTree/LayerTree.vue'
import PagesPanel from './PagesPanel.vue'
const { menu, panels } = useI18n()
const activePanel = ref<'file' | 'assets'>('file')
const panelModel = computed({
get: () => activePanel.value,
set: (value: string) => {
if (value === 'file' || value === 'assets') activePanel.value = value
}
})
const panelOptions = computed(() => [
{ value: 'file', label: menu.value.file },
{ value: 'assets', label: panels.value.assets }
])
const panelTabsUI = { root: 'w-full' }
</script>
<template>
<aside
data-test-id="layers-panel"
class="flex min-w-0 flex-1 flex-col overflow-hidden border-r border-border bg-panel"
style="contain: paint layout style"
>
<AppMenu />
<div class="shrink-0 border-b border-border px-2 py-1.5">
<SegmentedControl
v-model="panelModel"
:options="panelOptions"
:label="panels.layers"
:ui="panelTabsUI"
>
<template #option="{ option }">
<span
:data-test-id="
option.value === 'file' ? 'left-panel-layers-tab' : 'left-panel-assets-tab'
"
class="truncate"
>
{{ option.label }}
</span>
</template>
</SegmentedControl>
</div>
<AssetsPanel v-if="activePanel === 'assets'" />
<SplitterGroup
v-else
direction="vertical"
auto-save-id="layers-layout"
class="flex-1 overflow-hidden"
>
<SplitterPanel
:default-size="30"
:min-size="10"
:max-size="60"
class="flex flex-col overflow-hidden"
>
<PagesPanel />
</SplitterPanel>
<SplitterResizeHandle class="group relative z-10 -my-1 h-2 cursor-row-resize">
<div
class="pointer-events-none absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-border"
/>
</SplitterResizeHandle>
<SplitterPanel :default-size="70" :min-size="20" class="flex flex-col overflow-hidden">
<header
data-test-id="layers-header"
class="shrink-0 px-3 py-2 text-[11px] font-semibold text-surface"
>
{{ panels.layers }}
</header>
<LayerTree data-test-id="layers-tree" />
</SplitterPanel>
</SplitterGroup>
</aside>
</template>