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
209 lines
7.0 KiB
TypeScript
209 lines
7.0 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { useRouter } from "next/navigation"
|
|
import Script from "next/script"
|
|
import { DiceFaces05Icon, Undo02Icon } from "@hugeicons/core-free-icons"
|
|
import { HugeiconsIcon } from "@hugeicons/react"
|
|
import { useQueryStates } from "nuqs"
|
|
|
|
import { cn } from "@/lib/utils"
|
|
import {
|
|
BASE_COLORS,
|
|
DEFAULT_CONFIG,
|
|
getThemesForBaseColor,
|
|
iconLibraries,
|
|
MENU_ACCENTS,
|
|
MENU_COLORS,
|
|
RADII,
|
|
STYLES,
|
|
} from "@/registry/config"
|
|
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
import { Kbd } from "@/registry/new-york-v4/ui/kbd"
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from "@/registry/new-york-v4/ui/tooltip"
|
|
import { useLocks } from "@/app/(create)/hooks/use-locks"
|
|
import { FONTS } from "@/app/(create)/lib/fonts"
|
|
import {
|
|
applyBias,
|
|
RANDOMIZE_BIASES,
|
|
type RandomizeContext,
|
|
} from "@/app/(create)/lib/randomize-biases"
|
|
import { designSystemSearchParams } from "@/app/(create)/lib/search-params"
|
|
|
|
export const RANDOMIZE_FORWARD_TYPE = "randomize-forward"
|
|
|
|
function randomItem<T>(array: readonly T[]): T {
|
|
return array[Math.floor(Math.random() * array.length)]
|
|
}
|
|
|
|
export function CustomizerControls({ className }: { className?: string }) {
|
|
const router = useRouter()
|
|
const { locks } = useLocks()
|
|
const [params, setParams] = useQueryStates(designSystemSearchParams, {
|
|
shallow: false,
|
|
history: "push",
|
|
})
|
|
|
|
const handleReset = React.useCallback(() => {
|
|
setParams({
|
|
base: params.base, // Keep the current base value
|
|
style: DEFAULT_CONFIG.style,
|
|
baseColor: DEFAULT_CONFIG.baseColor,
|
|
theme: DEFAULT_CONFIG.theme,
|
|
iconLibrary: DEFAULT_CONFIG.iconLibrary,
|
|
font: DEFAULT_CONFIG.font,
|
|
menuAccent: DEFAULT_CONFIG.menuAccent,
|
|
menuColor: DEFAULT_CONFIG.menuColor,
|
|
radius: DEFAULT_CONFIG.radius,
|
|
template: DEFAULT_CONFIG.template,
|
|
item: "preview",
|
|
})
|
|
}, [setParams, params.base])
|
|
|
|
const handleRandomize = React.useCallback(() => {
|
|
// Use current value if locked, otherwise randomize.
|
|
const baseColor = locks.has("baseColor")
|
|
? params.baseColor
|
|
: randomItem(BASE_COLORS).name
|
|
const selectedStyle = locks.has("style")
|
|
? params.style
|
|
: randomItem(STYLES).name
|
|
|
|
// Build context for bias application.
|
|
const context: RandomizeContext = {
|
|
style: selectedStyle,
|
|
baseColor,
|
|
}
|
|
|
|
const availableThemes = getThemesForBaseColor(baseColor)
|
|
const availableFonts = applyBias(FONTS, context, RANDOMIZE_BIASES.fonts)
|
|
const availableRadii = applyBias(RADII, context, RANDOMIZE_BIASES.radius)
|
|
|
|
const selectedTheme = locks.has("theme")
|
|
? params.theme
|
|
: randomItem(availableThemes).name
|
|
const selectedFont = locks.has("font")
|
|
? params.font
|
|
: randomItem(availableFonts).value
|
|
const selectedRadius = locks.has("radius")
|
|
? params.radius
|
|
: randomItem(availableRadii).name
|
|
const selectedIconLibrary = locks.has("iconLibrary")
|
|
? params.iconLibrary
|
|
: randomItem(Object.values(iconLibraries)).name
|
|
const selectedMenuAccent = locks.has("menuAccent")
|
|
? params.menuAccent
|
|
: randomItem(MENU_ACCENTS).value
|
|
const selectedMenuColor = locks.has("menuColor")
|
|
? params.menuColor
|
|
: randomItem(MENU_COLORS).value
|
|
|
|
// Update context with selected values for potential future biases.
|
|
context.theme = selectedTheme
|
|
context.font = selectedFont
|
|
context.radius = selectedRadius
|
|
|
|
setParams({
|
|
style: selectedStyle,
|
|
baseColor,
|
|
theme: selectedTheme,
|
|
iconLibrary: selectedIconLibrary,
|
|
font: selectedFont,
|
|
menuAccent: selectedMenuAccent,
|
|
menuColor: selectedMenuColor,
|
|
radius: selectedRadius,
|
|
})
|
|
}, [setParams, locks, params])
|
|
|
|
React.useEffect(() => {
|
|
const down = (e: KeyboardEvent) => {
|
|
if ((e.key === "r" || e.key === "R") && !e.metaKey && !e.ctrlKey) {
|
|
if (
|
|
(e.target instanceof HTMLElement && e.target.isContentEditable) ||
|
|
e.target instanceof HTMLInputElement ||
|
|
e.target instanceof HTMLTextAreaElement ||
|
|
e.target instanceof HTMLSelectElement
|
|
) {
|
|
return
|
|
}
|
|
|
|
e.preventDefault()
|
|
handleRandomize()
|
|
}
|
|
}
|
|
|
|
document.addEventListener("keydown", down)
|
|
return () => document.removeEventListener("keydown", down)
|
|
}, [handleRandomize])
|
|
|
|
return (
|
|
<div className={cn("items-center gap-0", className)}>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={handleRandomize}
|
|
className="border-foreground/10 bg-muted/50 h-[calc(--spacing(13.5))] w-[140px] touch-manipulation justify-between rounded-xl border select-none focus-visible:border-transparent focus-visible:ring-1 sm:rounded-lg md:w-full md:rounded-lg md:border-transparent md:bg-transparent md:pr-3.5! md:pl-2!"
|
|
>
|
|
<div className="flex flex-col justify-start text-left">
|
|
<div className="text-muted-foreground text-xs">Shuffle</div>
|
|
<div className="text-foreground text-sm font-medium">Try Random</div>
|
|
</div>
|
|
<HugeiconsIcon icon={DiceFaces05Icon} className="size-5 md:hidden" />
|
|
<Kbd className="bg-foreground/10 text-foreground hidden md:flex">R</Kbd>
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={handleReset}
|
|
className="border-foreground/10 bg-muted/50 hidden h-[calc(--spacing(13.5))] w-[140px] touch-manipulation justify-between rounded-xl border select-none focus-visible:border-transparent focus-visible:ring-1 sm:rounded-lg md:flex md:w-full md:rounded-lg md:border-transparent md:bg-transparent md:pr-3.5! md:pl-2!"
|
|
>
|
|
<div className="flex flex-col justify-start text-left">
|
|
<div className="text-muted-foreground text-xs">Reset</div>
|
|
<div className="text-foreground text-sm font-medium">Start Over</div>
|
|
</div>
|
|
<HugeiconsIcon icon={Undo02Icon} className="-translate-x-0.5" />
|
|
</Button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function RandomizeScript() {
|
|
return (
|
|
<Script
|
|
id="randomize-listener"
|
|
strategy="beforeInteractive"
|
|
dangerouslySetInnerHTML={{
|
|
__html: `
|
|
(function() {
|
|
// Forward R key
|
|
document.addEventListener('keydown', function(e) {
|
|
if ((e.key === 'r' || e.key === 'R') && !e.metaKey && !e.ctrlKey) {
|
|
if (
|
|
(e.target instanceof HTMLElement && e.target.isContentEditable) ||
|
|
e.target instanceof HTMLInputElement ||
|
|
e.target instanceof HTMLTextAreaElement ||
|
|
e.target instanceof HTMLSelectElement
|
|
) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
if (window.parent && window.parent !== window) {
|
|
window.parent.postMessage({
|
|
type: '${RANDOMIZE_FORWARD_TYPE}',
|
|
key: e.key
|
|
}, '*');
|
|
}
|
|
}
|
|
});
|
|
|
|
})();
|
|
`,
|
|
}}
|
|
/>
|
|
)
|
|
}
|