Files
shadcn-ui/apps/v4/app/(create)/components/design-system-provider.tsx
shadcn 86d9b00084 chore: update deps (#9022)
* feat: init

* fix

* fix

* fix

* feat

* feat

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* feat: implement icons

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* feat: update init command

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* feat: dialog

* feat

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* feat: add registry:base item type

* feat: rename frame to canva

* fix

* feat

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fi

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* feat: add all colors

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* feat: add outfit font

* fix

* fix

* fix

* fix

* fix

* chore: changeset

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix
2025-12-12 21:01:44 +04:00

173 lines
4.3 KiB
TypeScript

"use client"
import * as React from "react"
import {
buildRegistryTheme,
DEFAULT_CONFIG,
type DesignSystemConfig,
} from "@/registry/config"
import { useDesignSystemParam } from "@/app/(create)/hooks/use-design-system"
import { FONTS } from "@/app/(create)/lib/fonts"
export function DesignSystemProvider({
children,
}: {
children: React.ReactNode
}) {
const style = useDesignSystemParam("style")
const theme = useDesignSystemParam("theme")
const font = useDesignSystemParam("font")
const baseColor = useDesignSystemParam("baseColor")
const menuAccent = useDesignSystemParam("menuAccent")
const menuColor = useDesignSystemParam("menuColor")
const radius = useDesignSystemParam("radius")
const [isReady, setIsReady] = React.useState(false)
// Use useLayoutEffect for synchronous style updates to prevent flash.
React.useLayoutEffect(() => {
if (!style || !theme || !font || !baseColor) {
return
}
const body = document.body
// Update style class in place (remove old, add new).
body.classList.forEach((className) => {
if (className.startsWith("style-")) {
body.classList.remove(className)
}
})
body.classList.add(`style-${style}`)
// Update base color class in place.
body.classList.forEach((className) => {
if (className.startsWith("base-color-")) {
body.classList.remove(className)
}
})
body.classList.add(`base-color-${baseColor}`)
// Update font.
const selectedFont = FONTS.find((f) => f.value === font)
if (selectedFont) {
const fontFamily = selectedFont.font.style.fontFamily
document.documentElement.style.setProperty("--font-sans", fontFamily)
}
setIsReady(true)
}, [style, theme, font, baseColor])
const registryTheme = React.useMemo(() => {
if (!baseColor || !theme || !menuAccent || !radius) {
return null
}
const config: DesignSystemConfig = {
...DEFAULT_CONFIG,
baseColor,
theme,
menuAccent,
radius,
}
return buildRegistryTheme(config)
}, [baseColor, theme, menuAccent, radius])
// Use useLayoutEffect for synchronous CSS var updates.
React.useLayoutEffect(() => {
if (!registryTheme || !registryTheme.cssVars) {
return
}
const styleId = "design-system-theme-vars"
let styleElement = document.getElementById(
styleId
) as HTMLStyleElement | null
if (!styleElement) {
styleElement = document.createElement("style")
styleElement.id = styleId
document.head.appendChild(styleElement)
}
const {
light: lightVars,
dark: darkVars,
theme: themeVars,
} = registryTheme.cssVars
let cssText = ":root {\n"
// Add theme vars (shared across light/dark).
if (themeVars) {
Object.entries(themeVars).forEach(([key, value]) => {
if (value) {
cssText += ` --${key}: ${value};\n`
}
})
}
// Add light mode vars.
if (lightVars) {
Object.entries(lightVars).forEach(([key, value]) => {
if (value) {
cssText += ` --${key}: ${value};\n`
}
})
}
cssText += "}\n\n"
cssText += ".dark {\n"
if (darkVars) {
Object.entries(darkVars).forEach(([key, value]) => {
if (value) {
cssText += ` --${key}: ${value};\n`
}
})
}
cssText += "}\n"
styleElement.textContent = cssText
}, [registryTheme])
// Handle menu color inversion by adding/removing dark class to elements with cn-menu-target.
React.useEffect(() => {
if (!menuColor) {
return
}
const updateMenuElements = () => {
const menuElements = document.querySelectorAll(".cn-menu-target")
menuElements.forEach((element) => {
if (menuColor === "inverted") {
element.classList.add("dark")
} else {
element.classList.remove("dark")
}
})
}
// Update existing menu elements.
updateMenuElements()
// Watch for new menu elements being added to the DOM.
const observer = new MutationObserver(() => {
updateMenuElements()
})
observer.observe(document.body, {
childList: true,
subtree: true,
})
return () => {
observer.disconnect()
}
}, [menuColor])
if (!isReady) {
return null
}
return <>{children}</>
}