Skip to content

Commit affd084

Browse files
committed
fix(storybook): render inline dock and launcher icons
1 parent 5706837 commit affd084

2 files changed

Lines changed: 37 additions & 6 deletions

File tree

storybook/src/client/icons.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
// @unocss-include
2+
import type { DevframeIcon } from 'devframe/types'
3+
import { getIconSource, getIconSvg, isIconImage } from '../../../packages/hub-ui/src/client/utils/icons'
4+
25
// Map a devframe dock `icon` (e.g. `ph:git-branch-duotone`) to a UnoCSS
36
// `preset-icons` class. Keeping the class strings literal lets UnoCSS
47
// statically extract them and inline only these glyphs from `@iconify-json/ph`
@@ -20,9 +23,32 @@ const ICON_CLASS: Record<string, string> = {
2023
* Resolve a dock icon to its UnoCSS class, or an empty string when the icon
2124
* isn't mapped (the caller falls back to a text initial).
2225
*/
23-
export function iconClass(name: string | { light: string, dark: string } | undefined): string {
26+
export function iconClass(name: DevframeIcon | undefined): string {
2427
if (!name)
2528
return ''
26-
const id = typeof name === 'string' ? name : name.light
27-
return ICON_CLASS[id] ?? ''
29+
const source = getIconSource(name, matchMedia('(prefers-color-scheme: dark)').matches)
30+
return typeof source === 'string' ? ICON_CLASS[source] ?? '' : ''
31+
}
32+
33+
/** Fill a rendered placeholder without applying late results to a replacement view. */
34+
export async function fillIcon(element: HTMLElement | null, icon: DevframeIcon | undefined): Promise<void> {
35+
if (!element || !icon || iconClass(icon))
36+
return
37+
const source = getIconSource(icon, matchMedia('(prefers-color-scheme: dark)').matches)
38+
if (isIconImage(source)) {
39+
const image = document.createElement('img')
40+
image.src = source
41+
image.className = 'w-full h-full'
42+
image.alt = ''
43+
element.replaceChildren(image)
44+
return
45+
}
46+
try {
47+
const svg = await getIconSvg(source)
48+
if (svg && element.isConnected)
49+
element.innerHTML = svg
50+
}
51+
catch {
52+
/** Preserve the placeholder when a remote icon is unavailable. */
53+
}
2854
}

storybook/src/client/main.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { DevframeDockEntry, DevframeViewLauncher } from '@devframes/hub/types'
22
import { connectDevframe } from '@devframes/hub/client'
33
import { createIframePanes } from 'iframe-pane'
4-
import { iconClass } from './icons'
4+
import { fillIcon, iconClass } from './icons'
55
import 'virtual:uno.css'
66
import '@antfu/design/styles.css'
77

@@ -67,7 +67,7 @@ function dockIcon(entry: DevframeDockEntry): string {
6767
if (cls)
6868
return `<span class="${cls} shrink-0 text-lg"></span>`
6969
const initial = (entry.title?.[0] ?? '?').toUpperCase()
70-
return `<span class="grid h-5 w-5 shrink-0 place-items-center rounded bg-active text-[0.7rem] font-bold">${initial}</span>`
70+
return `<span data-dock-icon="${entry.id}" class="grid h-5 w-5 shrink-0 place-items-center rounded bg-active text-[0.7rem] font-bold">${initial}</span>`
7171
}
7272

7373
function overlay(html: string) {
@@ -79,7 +79,7 @@ function overlay(html: string) {
7979
function launcherTile(entry: LauncherDock): string {
8080
const l = entry.launcher
8181
const cls = iconClass(l.icon ?? entry.icon)
82-
const glyph = cls ? `<span class="${cls} text-4xl color-active"></span>` : '<span class="i-ph-books-duotone text-4xl op-fade"></span>'
82+
const glyph = cls ? `<span class="${cls} text-4xl color-active"></span>` : '<span data-launcher-icon class="w-10 h-10"><span class="i-ph-books-duotone text-4xl op-fade"></span></span>'
8383
return `
8484
${glyph}
8585
<div class="text-base font-medium">${l.title}</div>
@@ -148,6 +148,7 @@ function updateStage() {
148148

149149
if (entry && isLauncherDock(entry)) {
150150
overlay(launcherStageTile(entry, rt, title))
151+
void fillIcon(overlayEl.querySelector('[data-launcher-icon]'), entry.launcher.icon ?? entry.icon)
151152
return
152153
}
153154

@@ -248,6 +249,10 @@ async function main() {
248249
`<li><button type="button" data-dock-id="${d.id}" class="relative inline-flex items-center gap-2.5 w-full px-2 py-1 rounded-md border border-transparent text-sm op-fade select-none cursor-pointer transition hover:op100 hover:bg-active${d.id === selectedId ? ' op100! bg-active border-base! color-base' : ''}" title="${d.title}">${dockIcon(d)}<span class="truncate">${d.title}</span></button></li>`).join('')
249250
return `<li class="px2 pt2 pb1 text-[0.68rem] uppercase tracking-wider color-muted">${category}</li>${buttons}`
250251
}).join('')
252+
for (const element of docksEl.querySelectorAll<HTMLElement>('[data-dock-icon]')) {
253+
const entry = docks.find(dock => dock.id === element.dataset.dockIcon)
254+
void fillIcon(element, entry?.icon)
255+
}
251256
}
252257

253258
// Docks, read from `devframe:docks` shared state, keeping launcher (Storybook)

0 commit comments

Comments
 (0)