diff --git a/docs/app/app.config.ts b/docs/app/app.config.ts
index 92bc09ebe6..5e90f55fd7 100644
--- a/docs/app/app.config.ts
+++ b/docs/app/app.config.ts
@@ -5,12 +5,6 @@ export default defineAppConfig({
max: 5,
expand: true
},
- theme: {
- radius: 0.25,
- blackAsPrimary: false,
- icons: 'lucide',
- font: 'Public Sans'
- },
ui: {
colors: {
primary: 'green',
diff --git a/docs/app/app.vue b/docs/app/app.vue
index 9880bed14c..0694de03c7 100644
--- a/docs/app/app.vue
+++ b/docs/app/app.vue
@@ -7,6 +7,7 @@ import { SpeedInsights } from '@vercel/speed-insights/nuxt'
const route = useRoute()
const appConfig = useAppConfig()
const colorMode = useColorMode()
+const { style, link } = useTheme()
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('docs', ['framework', 'category', 'description']))
const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('docs', {
@@ -16,24 +17,18 @@ const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSe
})
const color = computed(() => colorMode.value === 'dark' ? (colors as any)[appConfig.ui.colors.neutral][900] : 'white')
-const radius = computed(() => `:root { --ui-radius: ${appConfig.theme.radius}rem; }`)
-const blackAsPrimary = computed(() => appConfig.theme.blackAsPrimary ? `:root { --ui-primary: black; } .dark { --ui-primary: white; }` : ':root {}')
-const font = computed(() => `:root { --font-sans: '${appConfig.theme.font}', sans-serif; }`)
useHead({
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ key: 'theme-color', name: 'theme-color', content: color }
],
- link: [
+ link: computed(() => [
// { rel: 'icon', type: 'image/svg+xml', href: '/icon.svg' },
- { rel: 'canonical', href: `https://ui.nuxt.com${withoutTrailingSlash(route.path)}` }
- ],
- style: [
- { innerHTML: radius, id: 'nuxt-ui-radius', tagPriority: -2 },
- { innerHTML: blackAsPrimary, id: 'nuxt-ui-black-as-primary', tagPriority: -2 },
- { innerHTML: font, id: 'nuxt-ui-font', tagPriority: -2 }
- ],
+ { rel: 'canonical', href: `https://ui.nuxt.com${withoutTrailingSlash(route.path)}` },
+ ...link.value
+ ]),
+ style,
htmlAttrs: {
lang: 'en'
}
@@ -58,22 +53,30 @@ provide('navigation', rootNavigation)
-
-
-
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
-
+
diff --git a/docs/app/components/chat/Chat.vue b/docs/app/components/chat/Chat.vue
new file mode 100644
index 0000000000..d63db9dc92
--- /dev/null
+++ b/docs/app/components/chat/Chat.vue
@@ -0,0 +1,361 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Powered by AI Gateway
+
+
+
+
+
+
+
+
+
diff --git a/docs/app/components/chat/ChatReasoning.vue b/docs/app/components/chat/ChatReasoning.vue
new file mode 100644
index 0000000000..e8a294968d
--- /dev/null
+++ b/docs/app/components/chat/ChatReasoning.vue
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ text }}
+
+
+
+
+
+
+
diff --git a/docs/app/components/chat/ChatShimmer.vue b/docs/app/components/chat/ChatShimmer.vue
new file mode 100644
index 0000000000..55bb1380e4
--- /dev/null
+++ b/docs/app/components/chat/ChatShimmer.vue
@@ -0,0 +1,39 @@
+
+
+
+
+ {{ text }}
+
+
diff --git a/docs/app/components/chat/ChatTool.vue b/docs/app/components/chat/ChatTool.vue
new file mode 100644
index 0000000000..660354adb0
--- /dev/null
+++ b/docs/app/components/chat/ChatTool.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/app/components/content/IconsTheme.vue b/docs/app/components/content/IconsTheme.vue
index d19d13681d..66d45e4895 100644
--- a/docs/app/components/content/IconsTheme.vue
+++ b/docs/app/components/content/IconsTheme.vue
@@ -2,9 +2,9 @@
import json5 from 'json5'
import { themeIcons } from '../../utils/theme'
-const appConfig = useAppConfig()
+const { icon: iconSet } = useTheme()
-const icons = computed(() => themeIcons[appConfig.theme.icons as keyof typeof themeIcons || 'lucide'])
+const icons = computed(() => themeIcons[iconSet.value as keyof typeof themeIcons || 'lucide'])
const { data: ast } = useAsyncData(`icons-theme`, async () => {
const md = `
diff --git a/docs/app/components/content/examples/chat/ChatPaletteContentSearchExample.vue b/docs/app/components/content/examples/chat/ChatPaletteContentSearchExample.vue
index 434ee78ddd..7e4490824d 100644
--- a/docs/app/components/content/examples/chat/ChatPaletteContentSearchExample.vue
+++ b/docs/app/components/content/examples/chat/ChatPaletteContentSearchExample.vue
@@ -1,6 +1,7 @@
-
-
-
-
-
-
- {{ part.text }}
-
+
+
+
+
+
+
+
-
-
+
-
-
-
-
+
+
+
+
+
diff --git a/docs/app/components/content/examples/chat/ChatPaletteModalExample.vue b/docs/app/components/content/examples/chat/ChatPaletteModalExample.vue
index 27461db297..0cd8e1cac4 100644
--- a/docs/app/components/content/examples/chat/ChatPaletteModalExample.vue
+++ b/docs/app/components/content/examples/chat/ChatPaletteModalExample.vue
@@ -1,8 +1,17 @@
-
-
-
-
-
-
- {{ part.text }}
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
diff --git a/docs/app/components/content/examples/sidebar/SidebarChatExample.vue b/docs/app/components/content/examples/sidebar/SidebarChatExample.vue
new file mode 100644
index 0000000000..c4cc6f40f1
--- /dev/null
+++ b/docs/app/components/content/examples/sidebar/SidebarChatExample.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/app/components/content/examples/sidebar/SidebarExample.vue b/docs/app/components/content/examples/sidebar/SidebarExample.vue
new file mode 100644
index 0000000000..22cde75c6a
--- /dev/null
+++ b/docs/app/components/content/examples/sidebar/SidebarExample.vue
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/app/components/content/examples/sidebar/SidebarHeaderExample.vue b/docs/app/components/content/examples/sidebar/SidebarHeaderExample.vue
new file mode 100644
index 0000000000..1b6f267401
--- /dev/null
+++ b/docs/app/components/content/examples/sidebar/SidebarHeaderExample.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
diff --git a/docs/app/components/content/examples/sidebar/SidebarModeExample.vue b/docs/app/components/content/examples/sidebar/SidebarModeExample.vue
new file mode 100644
index 0000000000..0704d16f95
--- /dev/null
+++ b/docs/app/components/content/examples/sidebar/SidebarModeExample.vue
@@ -0,0 +1,49 @@
+
+
+
+
+
diff --git a/docs/app/components/content/examples/sidebar/SidebarOpenExample.vue b/docs/app/components/content/examples/sidebar/SidebarOpenExample.vue
new file mode 100644
index 0000000000..a10bcbf45a
--- /dev/null
+++ b/docs/app/components/content/examples/sidebar/SidebarOpenExample.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
diff --git a/docs/app/components/content/examples/sidebar/SidebarPersistExample.vue b/docs/app/components/content/examples/sidebar/SidebarPersistExample.vue
new file mode 100644
index 0000000000..cdd1463904
--- /dev/null
+++ b/docs/app/components/content/examples/sidebar/SidebarPersistExample.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
diff --git a/docs/app/components/content/examples/sidebar/SidebarPropsExample.vue b/docs/app/components/content/examples/sidebar/SidebarPropsExample.vue
new file mode 100644
index 0000000000..52779c609a
--- /dev/null
+++ b/docs/app/components/content/examples/sidebar/SidebarPropsExample.vue
@@ -0,0 +1,73 @@
+
+
+
+
+
diff --git a/docs/app/components/content/examples/sidebar/SidebarWidthExample.vue b/docs/app/components/content/examples/sidebar/SidebarWidthExample.vue
new file mode 100644
index 0000000000..8b638d5173
--- /dev/null
+++ b/docs/app/components/content/examples/sidebar/SidebarWidthExample.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
diff --git a/docs/app/components/search/Search.vue b/docs/app/components/search/Search.vue
index d7375ead63..ca6d8fdd9b 100644
--- a/docs/app/components/search/Search.vue
+++ b/docs/app/components/search/Search.vue
@@ -14,7 +14,7 @@ defineProps<{
navigation?: ContentNavigationItem[]
}>()
-const { links, groups, fullscreen, chat, searchTerm, messages } = useSearch()
+const { links, groups, searchTerm } = useSearch()
const { track } = useAnalytics()
watchDebounced(searchTerm, (term) => {
@@ -22,12 +22,6 @@ watchDebounced(searchTerm, (term) => {
track('Search Performed', { term })
}
}, { debounce: 500 })
-
-function onClose() {
- chat.value = false
-
- fullscreen.value = false
-}
@@ -37,11 +31,6 @@ function onClose() {
:files="files"
:groups="groups"
:navigation="navigation"
- :fullscreen="fullscreen"
:fuse="{ resultLimit: 115 }"
- >
-
-
-
-
+ />
diff --git a/docs/app/components/search/SearchChat.vue b/docs/app/components/search/SearchChat.vue
deleted file mode 100644
index 7ab98f359e..0000000000
--- a/docs/app/components/search/SearchChat.vue
+++ /dev/null
@@ -1,154 +0,0 @@
-
-
-
-
-
-
-
-
-
- {{ part.text }}
-
-
-
- {{ getCachedToolMessage(part.state, part.toolName, JSON.stringify(part.input || {})) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/app/components/theme-picker/ThemePicker.vue b/docs/app/components/theme-picker/ThemePicker.vue
index 13d09bbea2..dd8bbd6ccf 100644
--- a/docs/app/components/theme-picker/ThemePicker.vue
+++ b/docs/app/components/theme-picker/ThemePicker.vue
@@ -1,5 +1,4 @@
@@ -149,12 +157,12 @@ const communityLinks = computed(() => [{
-
-
+
+
-
+
diff --git a/docs/app/pages/index.vue b/docs/app/pages/index.vue
index c81c3903aa..132601c494 100644
--- a/docs/app/pages/index.vue
+++ b/docs/app/pages/index.vue
@@ -88,8 +88,8 @@ useIntersectionObserver(contributorsRef, ([entry]) => {
pause-on-hover
:overlay="false"
:ui="{
- root: '[--gap:--spacing(4)] [--duration:40s] border-default absolute w-full left-0 border-y lg:border-x lg:border-y-0 lg:w-[calc(50%-6px)] 2xl:w-[320px] lg:flex-col',
- content: 'lg:w-auto lg:flex-col lg:animate-[marquee-vertical_var(--duration)_linear_infinite] lg:rtl:animate-[marquee-vertical-rtl_var(--duration)_linear_infinite] lg:h-[fit-content]'
+ root: '[--gap:--spacing(4)] [--duration:40s] border-default absolute w-full left-0 border-y lg:border-x lg:border-y-0 lg:w-[calc(50%-6px)] 2xl:max-w-[320px] lg:flex-col',
+ content: 'lg:w-auto lg:flex-col lg:animate-[marquee-vertical_var(--duration)_linear_infinite] lg:rtl:animate-[marquee-vertical-rtl_var(--duration)_linear_infinite] lg:h-fit'
}"
>
{
reverse
:overlay="false"
:ui="{
- root: '[--gap:--spacing(4)] [--duration:40s] border-default absolute w-full mt-[180px] left-0 border-y lg:mt-auto lg:left-auto lg:border-y-0 lg:border-x lg:w-[calc(50%-6px)] 2xl:w-[320px] lg:right-0 lg:flex-col',
- content: 'lg:w-auto lg:flex-col lg:animate-[marquee-vertical_var(--duration)_linear_infinite] lg:rtl:animate-[marquee-vertical-rtl_var(--duration)_linear_infinite] lg:h-[fit-content] lg:[animation-direction:reverse]'
+ root: '[--gap:--spacing(4)] [--duration:40s] border-default absolute w-full mt-[180px] left-0 border-y lg:mt-auto lg:left-auto lg:border-y-0 lg:border-x lg:w-[calc(50%-6px)] 2xl:max-w-[320px] lg:right-0 lg:flex-col',
+ content: 'lg:w-auto lg:flex-col lg:animate-[marquee-vertical_var(--duration)_linear_infinite] lg:rtl:animate-[marquee-vertical-rtl_var(--duration)_linear_infinite] lg:h-fit lg:[animation-direction:reverse]'
}"
>
(key: string) {
+ try {
+ const raw = localStorage.getItem(key)
+ if (raw) {
+ const state = useState(key)
+ state.value = JSON.parse(raw)
+ }
+ } catch {
+ // ignore malformed localStorage
}
}
- updateColor('primary')
- updateColor('neutral')
- updateRadius()
- updateBlackAsPrimary()
- updateFont()
- }
+ restoreState('nuxt-ui-ai-theme')
+ restoreState('nuxt-ui-custom-colors')
+ restoreState('nuxt-ui-css-variables')
- onNuxtReady(() => {
- function updateIcons() {
- const icons = localStorage.getItem('nuxt-ui-icons')
- if (icons) {
- appConfig.theme.icons = icons
- appConfig.ui.icons = themeIcons[icons as keyof typeof themeIcons] as any
+ try {
+ const extras = JSON.parse(localStorage.getItem('nuxt-ui-ai-theme') || '{}')
+ if (extras.colors) {
+ for (const [key, value] of Object.entries(extras.colors)) {
+ (appConfig.ui.colors as any)[key] = value
+ }
}
+ if (extras.ui) {
+ onNuxtReady(() => {
+ for (const [key, value] of Object.entries(extras.ui)) {
+ if (key === 'colors' || key === 'icons') continue
+ (appConfig.ui as any)[key] = defu(value as Record, (appConfig.ui as any)[key] || {})
+ }
+ })
+ }
+ } catch {
+ // ignore malformed localStorage
}
-
- updateIcons()
- })
+ }
if (import.meta.server) {
+ // Inline scripts below intentionally duplicate logic from the client-side composable
+ // (useTheme / injectCustomColors / injectCSSVariables) to restore persisted theme
+ // settings on first paint and prevent a flash of unstyled content (FOUC).
+ // The script IDs (chat-custom-colors, chat-css-variables, nuxt-ui-radius, etc.)
+ // correspond to the