mirror of
https://github.com/nuxt/ui.git
synced 2026-09-14 19:51:10 +08:00
docs(theme): computed view icons, no remount on a pack swap
This commit is contained in:
@@ -11,7 +11,7 @@ import shiki from '@comark/vue/plugins/shiki'
|
||||
const appConfig = useAppConfig()
|
||||
const studioIcons = useStudioIcons()
|
||||
|
||||
const introLinks = [{
|
||||
const introLinks = computed(() => [{
|
||||
label: 'Documentation',
|
||||
icon: studioIcons.bookOpen,
|
||||
color: 'neutral' as const,
|
||||
@@ -23,7 +23,7 @@ const introLinks = [{
|
||||
color: 'neutral' as const,
|
||||
variant: 'ghost' as const,
|
||||
size: 'md' as const
|
||||
}]
|
||||
}])
|
||||
|
||||
/**
|
||||
* Invented releases in the shape the template fetches from GitHub: a tag, a
|
||||
|
||||
@@ -51,21 +51,21 @@ function newChat() {
|
||||
input.value = ''
|
||||
}
|
||||
|
||||
const menuItems: NavigationMenuItem[] = [
|
||||
const menuItems = computed<NavigationMenuItem[]>(() => [
|
||||
{ label: 'New chat', icon: appConfig.ui.icons.plus, kbds: ['meta', 'o'], onSelect: () => newChat() },
|
||||
{ label: 'Search', icon: appConfig.ui.icons.search, kbds: ['meta', 'k'] }
|
||||
]
|
||||
])
|
||||
|
||||
// The per-chat menu, on the sidebar rows and behind the navbar title.
|
||||
const chatActions: DropdownMenuItem[] = [
|
||||
const chatActions = computed<DropdownMenuItem[]>(() => [
|
||||
{ label: 'Rename', icon: studioIcons.pencil },
|
||||
{ label: 'Delete', icon: studioIcons.trash, color: 'error' }
|
||||
]
|
||||
])
|
||||
|
||||
const userItems: DropdownMenuItem[][] = [
|
||||
const userItems = computed<DropdownMenuItem[][]>(() => [
|
||||
[{ label: 'Benjamin Canac', avatar: { src: 'https://github.com/benjamincanac.png', alt: 'Benjamin Canac' }, type: 'label' }],
|
||||
[{ label: 'Settings', icon: studioIcons.settings }, { label: 'Log out', icon: studioIcons.logout }]
|
||||
]
|
||||
])
|
||||
|
||||
const historyItems: NavigationMenuItem[] = [
|
||||
{ label: 'Today', type: 'label' },
|
||||
|
||||
@@ -88,7 +88,7 @@ const user = {
|
||||
avatar: { src: 'https://github.com/benjamincanac.png', alt: 'Benjamin Canac' }
|
||||
}
|
||||
|
||||
const userItems: DropdownMenuItem[][] = [[{
|
||||
const userItems = computed<DropdownMenuItem[][]>(() => [[{
|
||||
type: 'label',
|
||||
label: user.name,
|
||||
avatar: user.avatar
|
||||
@@ -98,12 +98,12 @@ const userItems: DropdownMenuItem[][] = [[{
|
||||
{ label: 'Settings', icon: studioIcons.settings }
|
||||
], [
|
||||
{ label: 'Log out', icon: studioIcons.logout }
|
||||
]]
|
||||
]])
|
||||
|
||||
const newItems: DropdownMenuItem[][] = [[
|
||||
const newItems = computed<DropdownMenuItem[][]>(() => [[
|
||||
{ label: 'New mail', icon: studioIcons.send },
|
||||
{ label: 'New customer', icon: studioIcons.userPlus }
|
||||
]]
|
||||
]])
|
||||
|
||||
// lowercase like the template, capitalized back through the select's `ui`
|
||||
const periodItems = ['daily', 'weekly', 'monthly']
|
||||
@@ -153,12 +153,12 @@ function selectDateRange(range: DateRangePreset) {
|
||||
}
|
||||
const period = ref('daily')
|
||||
|
||||
const stats = [
|
||||
const stats = computed(() => [
|
||||
{ title: 'Customers', icon: studioIcons.users, value: '892', variation: 14 },
|
||||
{ title: 'Conversions', icon: studioIcons.chart, value: '1,436', variation: 8 },
|
||||
{ title: 'Revenue', icon: studioIcons.dollar, value: '$312,540', variation: 23 },
|
||||
{ title: 'Orders', icon: studioIcons.cart, value: '254', variation: -5 }
|
||||
]
|
||||
])
|
||||
|
||||
const revenue = [6200, 7400, 6800, 9100, 8600, 10400, 9800, 11900, 11200, 13000, 12400, 14100, 13600, 15200]
|
||||
const revenueMin = Math.min(...revenue)
|
||||
@@ -299,12 +299,12 @@ watch(filteredMails, () => {
|
||||
}
|
||||
})
|
||||
|
||||
const mailDropdownItems: DropdownMenuItem[][] = [[
|
||||
const mailDropdownItems = computed<DropdownMenuItem[][]>(() => [[
|
||||
{ label: 'Mark as unread', icon: appConfig.ui.icons.success },
|
||||
{ label: 'Mark as important', icon: appConfig.ui.icons.warning }
|
||||
], [
|
||||
{ label: 'Star thread', icon: appConfig.ui.icons.star }
|
||||
]]
|
||||
]])
|
||||
|
||||
const reply = ref('')
|
||||
|
||||
@@ -350,14 +350,14 @@ const customerStatusItems = [
|
||||
{ label: 'Bounced', value: 'bounced' }
|
||||
]
|
||||
|
||||
const customerRowItems: DropdownMenuItem[] = [
|
||||
const customerRowItems = computed<DropdownMenuItem[]>(() => [
|
||||
{ type: 'label', label: 'Actions' },
|
||||
{ label: 'Copy customer ID', icon: appConfig.ui.icons.copy },
|
||||
{ type: 'separator' },
|
||||
{ label: 'View customer details', icon: studioIcons.list },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Delete customer', icon: studioIcons.trash, color: 'error' }
|
||||
]
|
||||
])
|
||||
|
||||
const customerColumns: TableColumn<Customer>[] = [{
|
||||
id: 'select',
|
||||
@@ -417,7 +417,7 @@ const customerColumns: TableColumn<Customer>[] = [{
|
||||
cell: () => {
|
||||
return h('div', { class: 'text-right' }, h(
|
||||
UDropdownMenu,
|
||||
{ content: { align: 'end' }, items: customerRowItems },
|
||||
{ content: { align: 'end' }, items: customerRowItems.value },
|
||||
() => h(UButton, { icon: appConfig.ui.icons.ellipsis, color: 'neutral', variant: 'ghost', class: 'ml-auto' })
|
||||
))
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ const studioIcons = useStudioIcons()
|
||||
// `title`, and `active` forces the state without a route, so the tree is a
|
||||
// static array. Paths are empty so nothing navigates out of the studio: the
|
||||
// type wants the key, and both the mapper and ULink drop a falsy one.
|
||||
const navLinks: ContentNavigationLink[] = [{
|
||||
const navLinks = computed<ContentNavigationLink[]>(() => [{
|
||||
path: '',
|
||||
title: 'Getting Started',
|
||||
children: [
|
||||
@@ -34,7 +34,7 @@ const navLinks: ContentNavigationLink[] = [{
|
||||
{ path: '', title: 'MCP Server', icon: studioIcons.cpu },
|
||||
{ path: '', title: 'LLMs Integration', icon: appConfig.ui.icons.file }
|
||||
]
|
||||
}]
|
||||
}])
|
||||
|
||||
// The template's prev/next pair, inert for the same reason.
|
||||
const surround: ContentSurroundLink[] = [{
|
||||
@@ -49,12 +49,12 @@ const surround: ContentSurroundLink[] = [{
|
||||
}]
|
||||
|
||||
// PageHeaderLinks dropdown, minus the real clipboard / external navigations.
|
||||
const copyItems: DropdownMenuItem[] = [
|
||||
const copyItems = computed<DropdownMenuItem[]>(() => [
|
||||
{ label: 'Copy Markdown link', icon: studioIcons.link },
|
||||
{ label: 'View as Markdown', icon: 'i-simple-icons:markdown' },
|
||||
{ label: 'Open in ChatGPT', icon: 'i-simple-icons:openai' },
|
||||
{ label: 'Open in Claude', icon: 'i-simple-icons:anthropic' }
|
||||
]
|
||||
])
|
||||
|
||||
// Right column: UContentToc is content-coupled and router-driven, so the
|
||||
// same sticky layout is hand-rolled with faked active states.
|
||||
@@ -70,11 +70,11 @@ const tocLinks = [
|
||||
}
|
||||
]
|
||||
|
||||
const communityLinks: PageLink[] = [
|
||||
const communityLinks = computed<PageLink[]>(() => [
|
||||
{ label: 'Edit this page', icon: appConfig.ui.icons.external },
|
||||
{ label: 'Star on GitHub', icon: appConfig.ui.icons.star },
|
||||
{ label: 'Nuxt UI docs', icon: studioIcons.bookOpen }
|
||||
]
|
||||
])
|
||||
|
||||
/**
|
||||
* The template's `content/1.getting-started/3.usage.md`, verbatim. It renders
|
||||
|
||||
@@ -80,7 +80,7 @@ const headingItems = [1, 2, 3, 4].map(level => ({
|
||||
icon: `i-lucide-heading-${level}`
|
||||
}))
|
||||
|
||||
const blockItems = [{
|
||||
const blockItems = computed(() => [{
|
||||
kind: 'bulletList' as const,
|
||||
label: 'Bullet List',
|
||||
icon: studioIcons.list
|
||||
@@ -96,7 +96,7 @@ const blockItems = [{
|
||||
kind: 'codeBlock' as const,
|
||||
label: 'Code Block',
|
||||
icon: 'i-lucide-square-code'
|
||||
}]
|
||||
}])
|
||||
|
||||
const markItems = [{
|
||||
kind: 'mark' as const,
|
||||
@@ -125,7 +125,7 @@ const markItems = [{
|
||||
tooltip: { text: 'Code' }
|
||||
}]
|
||||
|
||||
const toolbarItems: EditorToolbarItem[][] = [[{
|
||||
const toolbarItems = computed<EditorToolbarItem[][]>(() => [[{
|
||||
kind: 'undo',
|
||||
icon: studioIcons.undo,
|
||||
tooltip: { text: 'Undo' }
|
||||
@@ -138,9 +138,9 @@ const toolbarItems: EditorToolbarItem[][] = [[{
|
||||
tooltip: { text: 'Headings' },
|
||||
content: { align: 'start' },
|
||||
items: headingItems
|
||||
}, ...blockItems.map(({ label, ...item }) => ({ ...item, tooltip: { text: label } }))], markItems]
|
||||
}, ...blockItems.value.map(({ label, ...item }) => ({ ...item, tooltip: { text: label } }))], markItems])
|
||||
|
||||
const bubbleToolbarItems: EditorToolbarItem[][] = [[{
|
||||
const bubbleToolbarItems = computed<EditorToolbarItem[][]>(() => [[{
|
||||
label: 'Turn into',
|
||||
trailingIcon: appConfig.ui.icons.chevronDown,
|
||||
activeColor: 'neutral',
|
||||
@@ -155,28 +155,28 @@ const bubbleToolbarItems: EditorToolbarItem[][] = [[{
|
||||
kind: 'paragraph',
|
||||
label: 'Paragraph',
|
||||
icon: studioIcons.text
|
||||
}, ...headingItems, ...blockItems]
|
||||
}, ...headingItems, ...blockItems.value]
|
||||
}], markItems, [{
|
||||
slot: 'link' as const,
|
||||
icon: studioIcons.link,
|
||||
tooltip: { text: 'Link' }
|
||||
}]]
|
||||
}]])
|
||||
|
||||
const suggestionItems: EditorSuggestionMenuItem[][] = [[{
|
||||
const suggestionItems = computed<EditorSuggestionMenuItem[][]>(() => [[{
|
||||
type: 'label',
|
||||
label: 'Style'
|
||||
}, {
|
||||
kind: 'paragraph',
|
||||
label: 'Paragraph',
|
||||
icon: studioIcons.text
|
||||
}, ...headingItems, ...blockItems], [{
|
||||
}, ...headingItems, ...blockItems.value], [{
|
||||
type: 'label',
|
||||
label: 'Insert'
|
||||
}, {
|
||||
kind: 'horizontalRule',
|
||||
label: 'Horizontal Rule',
|
||||
icon: 'i-lucide-separator-horizontal'
|
||||
}]]
|
||||
}]])
|
||||
|
||||
const mentionItems: EditorMentionMenuItem[] = [
|
||||
{ label: 'benjamincanac', avatar: { src: 'https://github.com/benjamincanac.png', loading: 'lazy' as const } },
|
||||
@@ -215,7 +215,7 @@ function dragHandleItems(editor: Editor): DropdownMenuItem[][] {
|
||||
}, {
|
||||
label: 'Turn into',
|
||||
icon: 'i-lucide-repeat-2',
|
||||
children: [{ kind: 'paragraph', label: 'Paragraph', icon: studioIcons.text }, ...headingItems, ...blockItems]
|
||||
children: [{ kind: 'paragraph', label: 'Paragraph', icon: studioIcons.text }, ...headingItems, ...blockItems.value]
|
||||
}], [{
|
||||
kind: 'duplicate',
|
||||
pos,
|
||||
|
||||
@@ -61,7 +61,7 @@ const logos = [
|
||||
'i-simple-icons-stripe'
|
||||
]
|
||||
|
||||
const features = [{
|
||||
const features = computed(() => [{
|
||||
icon: studioIcons.zap,
|
||||
title: 'Predictive Alerts',
|
||||
description: 'ML models trained on your baselines detect anomalies 4 minutes before they hit your SLOs.'
|
||||
@@ -85,7 +85,7 @@ const features = [{
|
||||
icon: studioIcons.notebook,
|
||||
title: 'Team Notebooks',
|
||||
description: 'Collaborative investigation notebooks that turn incident debugging into reusable runbooks.'
|
||||
}]
|
||||
}])
|
||||
|
||||
const metrics = [
|
||||
{ value: '99.99%', label: 'Uptime SLA', class: 'text-success' },
|
||||
|
||||
@@ -7,13 +7,13 @@ const studioIcons = useStudioIcons()
|
||||
// Replicates the Nuxt UI Portfolio template home page: floating pill nav,
|
||||
// avatar hero with polaroid marquee, about + work experience columns,
|
||||
// blog list, testimonial carousel, FAQ tabs and footer.
|
||||
const navItems: NavigationMenuItem[] = [
|
||||
const navItems = computed<NavigationMenuItem[]>(() => [
|
||||
{ label: 'Home', icon: studioIcons.home, active: true },
|
||||
{ label: 'Projects', icon: appConfig.ui.icons.folder },
|
||||
{ label: 'Blog', icon: appConfig.ui.icons.file },
|
||||
{ label: 'Speaking', icon: studioIcons.mic },
|
||||
{ label: 'About', icon: studioIcons.user }
|
||||
]
|
||||
])
|
||||
|
||||
const socialLinks = [
|
||||
{ 'icon': 'i-simple-icons-discord', 'aria-label': 'Discord' },
|
||||
@@ -23,7 +23,7 @@ const socialLinks = [
|
||||
|
||||
// The template scrolls travel photos in a marquee; gradient polaroids stand in
|
||||
// because a tinted surface tracks the theme and a photo does not.
|
||||
const heroImages = [
|
||||
const heroImages = computed(() => [
|
||||
{ icon: studioIcons.mountain, label: 'Alps, 2024' },
|
||||
{ icon: studioIcons.coffee, label: 'Café sketching' },
|
||||
{ icon: studioIcons.bike, label: 'Canal ride' },
|
||||
@@ -33,7 +33,7 @@ const heroImages = [
|
||||
{ icon: studioIcons.palette, label: 'Color studies' },
|
||||
{ icon: studioIcons.ferrisWheel, label: 'Rotterdam fair' },
|
||||
{ icon: studioIcons.waves, label: 'North Sea' }
|
||||
]
|
||||
])
|
||||
|
||||
const about = {
|
||||
title: 'About Me',
|
||||
@@ -41,7 +41,7 @@ const about = {
|
||||
description: 'I\'m an English designer and developer based in Amsterdam. I sit right between design and engineering, which means both teams assume I\'m the other team\'s problem.'
|
||||
}
|
||||
|
||||
const experience = [{
|
||||
const experience = computed(() => [{
|
||||
date: '2025 - Present',
|
||||
position: 'Public Contributor for',
|
||||
company: { name: 'Nuxt UI', logo: 'i-simple-icons-nuxtdotjs' }
|
||||
@@ -57,7 +57,7 @@ const experience = [{
|
||||
date: '1995 - Present',
|
||||
position: 'Human person on',
|
||||
company: { name: 'Earth', logo: studioIcons.globe }
|
||||
}]
|
||||
}])
|
||||
|
||||
const posts = [{
|
||||
title: 'Twelve Identical Greys: A Field Guide to Choosing Your Neutral',
|
||||
|
||||
@@ -11,7 +11,7 @@ const navItems: NavigationMenuItem[] = [
|
||||
{ label: 'Changelog' }
|
||||
]
|
||||
|
||||
const heroLinks = [{
|
||||
const heroLinks = computed(() => [{
|
||||
label: 'Get started',
|
||||
trailingIcon: appConfig.ui.icons.arrowRight,
|
||||
size: 'xl' as const
|
||||
@@ -21,9 +21,9 @@ const heroLinks = [{
|
||||
size: 'xl' as const,
|
||||
color: 'neutral' as const,
|
||||
variant: 'subtle' as const
|
||||
}]
|
||||
}])
|
||||
|
||||
const sections = [{
|
||||
const sections = computed(() => [{
|
||||
title: 'Powered by Nuxt UI Components',
|
||||
description: 'Access a complete component library with beautifully styled, accessible and customizable Vue components. Everything you need to build professional SaaS applications.',
|
||||
reverse: false,
|
||||
@@ -57,9 +57,9 @@ const sections = [{
|
||||
description: 'Built-in i18n support for 50+ languages with RTL/LTR layouts and font optimization.',
|
||||
icon: studioIcons.globe
|
||||
}]
|
||||
}]
|
||||
}])
|
||||
|
||||
const features = [{
|
||||
const features = computed(() => [{
|
||||
title: 'Beautiful design system',
|
||||
description: 'Semantic color aliases, comprehensive design tokens and Tailwind Variants for consistent styling.',
|
||||
icon: studioIcons.palette
|
||||
@@ -83,7 +83,7 @@ const features = [{
|
||||
title: 'Infinitely customizable',
|
||||
description: 'Override any style with the ui prop, customize globally with AppConfig, or use Tailwind classes directly.',
|
||||
icon: studioIcons.settings
|
||||
}]
|
||||
}])
|
||||
|
||||
// The template's pricing page: three plans over a billing cycle toggle, no
|
||||
// badge, and Standard scaled up. Prices and feature lists are content/2.pricing.yml.
|
||||
@@ -159,14 +159,14 @@ const testimonials = [{
|
||||
user: { name: 'Lisa Patel', description: 'CEO at AutoScale' }
|
||||
}]
|
||||
|
||||
const ctaLinks = [{
|
||||
const ctaLinks = computed(() => [{
|
||||
label: 'Start building',
|
||||
trailingIcon: appConfig.ui.icons.arrowRight
|
||||
}, {
|
||||
label: 'View on GitHub',
|
||||
icon: 'i-simple-icons-github',
|
||||
variant: 'subtle' as const
|
||||
}]
|
||||
}])
|
||||
|
||||
const footerColumns = [{
|
||||
label: 'Resources',
|
||||
|
||||
@@ -4,7 +4,6 @@ import { decodeThemeDoc } from '../utils/theme/link'
|
||||
import { snapshotStoredTheme, writeStoredTheme } from '../utils/theme/storage'
|
||||
|
||||
const { track } = useAnalytics()
|
||||
const { icon: iconSet } = useTheme()
|
||||
|
||||
const { open: chatOpen } = useChat()
|
||||
|
||||
@@ -15,7 +14,6 @@ function toggleChat() {
|
||||
chatOpen.value = !chatOpen.value
|
||||
}
|
||||
|
||||
// The chrome skins to the applied icon pack.
|
||||
const studioIcons = useStudioIcons()
|
||||
|
||||
const { view, views, applyDoc, presets, activePreset } = useThemeStudio()
|
||||
@@ -100,14 +98,12 @@ onMounted(() => {
|
||||
if (linkApplied) track('Theme Link Applied', { preset: linkedPreset?.id })
|
||||
})
|
||||
|
||||
// Color mode rides the app-wide `d` binding in app.vue, no page copy needed.
|
||||
defineShortcuts({
|
||||
meta_z: undo,
|
||||
meta_shift_z: redo,
|
||||
ctrl_y: redo
|
||||
})
|
||||
|
||||
/** The export modal, opened from the header. */
|
||||
const shareOpen = ref(false)
|
||||
</script>
|
||||
|
||||
@@ -186,9 +182,8 @@ const shareOpen = ref(false)
|
||||
|
||||
<div class="flex flex-col bg-default rounded-xl overflow-hidden shadow ring ring-default h-[calc(100dvh-var(--ui-header-height)-0.5rem)] lg:h-[calc(100dvh-var(--ui-header-height)-var(--ui-header-height)-0.5rem)] mx-2">
|
||||
<!-- [contain:paint]: Chromium won't clip nested composited layers by
|
||||
an ancestor's overflow alone. Keyed on the icon pack: demo views
|
||||
resolve icons at setup, so a pack swap remounts to re-resolve. -->
|
||||
<div :key="iconSet" class="flex-1 min-h-0 overflow-hidden *:contain-[paint]">
|
||||
an ancestor's overflow alone -->
|
||||
<div class="flex-1 min-h-0 overflow-hidden *:contain-[paint]">
|
||||
<Playground v-if="view === 'grid'" />
|
||||
<LazyThemeStudioViewDashboard v-else-if="view === 'dashboard'" />
|
||||
<LazyThemeStudioViewChat v-else-if="view === 'chat'" />
|
||||
@@ -205,10 +200,6 @@ const shareOpen = ref(false)
|
||||
(min-w-0), so the toolbar inside it scrolls instead of widening the bar -->
|
||||
<UFooter class="hidden lg:block ring ring-default rounded-xl bg-default mx-2 mt-2" :ui="{ container: 'py-3! px-6!', left: 'mt-0 gap-0 lg:flex-none', center: 'flex-1 min-w-0 justify-start', right: 'mt-0 lg:flex-none' }">
|
||||
<template #left>
|
||||
<!-- one cluster: these four move the whole theme, the controls beside
|
||||
them each change one setting. Framed like the mode tabs' own track
|
||||
at the other end, a size down on the buttons so both land at the
|
||||
height of the plain controls between them. -->
|
||||
<div class="flex items-center gap-0.5 p-0.5 rounded-lg ring ring-default bg-elevated/50">
|
||||
<UTooltip text="Undo" :kbds="['meta', 'Z']">
|
||||
<UButton
|
||||
@@ -234,7 +225,6 @@ const shareOpen = ref(false)
|
||||
/>
|
||||
</UTooltip>
|
||||
|
||||
<!-- undo/redo step through history, the two beside them rewrite it -->
|
||||
<USeparator orientation="vertical" class="h-4 mx-0.5" />
|
||||
|
||||
<ThemeStudioResetButton size="sm" />
|
||||
@@ -248,7 +238,6 @@ const shareOpen = ref(false)
|
||||
|
||||
<template #right>
|
||||
<UTooltip text="Switch color mode" :kbds="['d']">
|
||||
<!-- framed like the history cluster, the bar's two ends match -->
|
||||
<ThemeStudioColorModeTabs
|
||||
data-keep-panels
|
||||
class="shrink-0"
|
||||
|
||||
Reference in New Issue
Block a user