mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00:00
* 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
173 lines
4.3 KiB
TypeScript
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}</>
|
|
}
|