feat(components): surface library metadata in assets

- Show imported library badges in the local Assets panel
- Display component descriptions and documentation links from Figma metadata
- Cover library metadata rendering in the Assets panel test
This commit is contained in:
Danila Poyarkov 2026-05-12 21:56:07 +03:00
parent 66f91de825
commit 880fcf3d5b
2 changed files with 49 additions and 8 deletions

View file

@ -6,6 +6,7 @@ import { useI18n } from '@open-pencil/vue'
import { nodeIcon } from '@/app/editor/icons'
import { useEditorStore } from '@/app/editor/active-store'
import { openExternalLink } from '@/app/shell/ui'
import { useButtonUI } from '@/components/ui/button'
import { useInputUI } from '@/components/ui/input'
import Tip from '@/components/ui/Tip.vue'
@ -18,6 +19,9 @@ type LocalAsset = {
variants: Array<{ name: string; values: string[] }>
variantCount: number
hasConflicts: boolean
sourceLibraryKey: string | null
description: string
docsUrl: string | null
}
const editor = useEditorStore()
@ -59,7 +63,10 @@ const assets = computed<LocalAsset[]>(() => {
componentId: defaultVariant?.id ?? null,
variants,
variantCount: node.type === 'COMPONENT_SET' ? node.childIds.length : 0,
hasConflicts: conflicts.length > 0
hasConflicts: conflicts.length > 0,
sourceLibraryKey: node.sourceLibraryKey,
description: node.symbolDescription,
docsUrl: node.symbolLinks[0]?.uri ?? null
}
})
.sort((a, b) => a.name.localeCompare(b.name))
@ -129,7 +136,16 @@ function insertAsset(asset: LocalAsset) {
aria-hidden="true"
/>
<span class="min-w-0 flex-1">
<span data-test-id="asset-name" class="block truncate">{{ asset.name }}</span>
<span class="flex min-w-0 items-center gap-1.5">
<span data-test-id="asset-name" class="truncate">{{ asset.name }}</span>
<span
v-if="asset.sourceLibraryKey"
data-test-id="asset-library-badge"
class="shrink-0 rounded bg-component/15 px-1 py-px text-[9px] font-medium text-component uppercase"
>
Library
</span>
</span>
<span
v-if="asset.variants.length > 0"
data-test-id="asset-variant-summary"
@ -138,6 +154,13 @@ function insertAsset(asset: LocalAsset) {
{{ asset.variantCount }} variants ·
{{ asset.variants.map((variant) => variant.name).join(', ') }}
</span>
<span
v-if="asset.description"
data-test-id="asset-description"
class="mt-0.5 block truncate text-[10px] text-muted"
>
{{ asset.description }}
</span>
<span
v-if="asset.hasConflicts"
data-test-id="asset-variant-conflict"
@ -146,6 +169,17 @@ function insertAsset(asset: LocalAsset) {
Duplicate variant values
</span>
</span>
<Tip v-if="asset.docsUrl" label="Open documentation">
<span
:class="insertButton.base"
data-test-id="asset-docs"
@pointerdown.stop
@dblclick.stop
@click.stop="asset.docsUrl ? openExternalLink(asset.docsUrl) : undefined"
>
<icon-lucide-book-open class="size-3" />
</span>
</Tip>
<Tip :label="commands.createInstance">
<span
:class="insertButton.base"

View file

@ -44,6 +44,9 @@ test('assets panel groups component sets and inserts the default variant', async
y: 80,
width: 260,
height: 100,
sourceLibraryKey: 'lk-test-library',
symbolDescription: 'Reusable button component',
symbolLinks: [{ uri: 'https://example.com/button', displayName: 'Button docs' }],
componentPropertyDefinitions: [
{
id: 'prop:type',
@ -117,12 +120,16 @@ test('assets panel groups component sets and inserts the default variant', async
await expect(assetsPanel).toContainText('Card')
await expect(assetsPanel).not.toContainText('Type=Primary')
await expect(assetsPanel).not.toContainText('Type=Secondary')
await expect(
page.locator(`[data-asset-id="${ids.setId}"] [data-test-id="asset-variant-summary"]`)
).toContainText('Type')
await expect(
page.locator(`[data-asset-id="${ids.setId}"] [data-test-id="asset-variant-conflict"]`)
).toContainText('Duplicate variant values')
const buttonAsset = page.locator(`[data-asset-id="${ids.setId}"]`)
await expect(buttonAsset.locator('[data-test-id="asset-variant-summary"]')).toContainText('Type')
await expect(buttonAsset.locator('[data-test-id="asset-library-badge"]')).toContainText('Library')
await expect(buttonAsset.locator('[data-test-id="asset-description"]')).toContainText(
'Reusable button component'
)
await expect(buttonAsset.locator('[data-test-id="asset-docs"]')).toBeVisible()
await expect(buttonAsset.locator('[data-test-id="asset-variant-conflict"]')).toContainText(
'Duplicate variant values'
)
await page.locator('[data-test-id="assets-search"]').fill('card')
await expect(assetItems).toHaveCount(1)