mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-30 16:14:13 +00:00
This reverts commit e327cef2c1.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { MENU_ACCENTS, type MenuAccentValue } from "@/registry/config"
|
||||
import { useCustomizerLayout } from "@/app/(create)/components/customizer-layout"
|
||||
import { LockButton } from "@/app/(create)/components/lock-button"
|
||||
import {
|
||||
Picker,
|
||||
@@ -9,21 +8,18 @@ import {
|
||||
PickerGroup,
|
||||
PickerRadioGroup,
|
||||
PickerRadioItem,
|
||||
PickerValueTrigger,
|
||||
PickerTrigger,
|
||||
} from "@/app/(create)/components/picker"
|
||||
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
|
||||
|
||||
export function MenuAccentPicker({
|
||||
isMobile,
|
||||
anchorRef,
|
||||
collapsed = false,
|
||||
}: {
|
||||
isMobile: boolean
|
||||
anchorRef: React.RefObject<HTMLDivElement | null>
|
||||
collapsed?: boolean
|
||||
}) {
|
||||
const [params, setParams] = useDesignSystemSearchParams()
|
||||
const { desktopPickerSide } = useCustomizerLayout()
|
||||
|
||||
const currentAccent = MENU_ACCENTS.find(
|
||||
(accent) => accent.value === params.menuAccent
|
||||
@@ -32,11 +28,14 @@ export function MenuAccentPicker({
|
||||
return (
|
||||
<div className="group/picker relative pr-3 md:pr-0">
|
||||
<Picker>
|
||||
<PickerValueTrigger
|
||||
label="Menu Accent"
|
||||
value={currentAccent?.label}
|
||||
valueText={currentAccent?.label}
|
||||
indicator={
|
||||
<PickerTrigger>
|
||||
<div className="flex flex-col justify-start text-left">
|
||||
<div className="text-xs text-muted-foreground">Menu Accent</div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{currentAccent?.label}
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute top-1/2 right-4 flex size-4 -translate-y-1/2 items-center justify-center text-base text-foreground select-none md:right-2.5">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="128"
|
||||
@@ -64,12 +63,11 @@ export function MenuAccentPicker({
|
||||
className="fill-muted-foreground/30 data-[accent=bold]:fill-foreground"
|
||||
></path>
|
||||
</svg>
|
||||
}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
</div>
|
||||
</PickerTrigger>
|
||||
<PickerContent
|
||||
anchor={isMobile ? anchorRef : undefined}
|
||||
side={isMobile ? "top" : desktopPickerSide}
|
||||
side={isMobile ? "top" : "right"}
|
||||
align={isMobile ? "center" : "start"}
|
||||
>
|
||||
<PickerRadioGroup
|
||||
@@ -97,12 +95,10 @@ export function MenuAccentPicker({
|
||||
</PickerRadioGroup>
|
||||
</PickerContent>
|
||||
</Picker>
|
||||
{!collapsed ? (
|
||||
<LockButton
|
||||
param="menuAccent"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
) : null}
|
||||
<LockButton
|
||||
param="menuAccent"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as React from "react"
|
||||
|
||||
import { useMounted } from "@/hooks/use-mounted"
|
||||
import { BASE_COLORS, type BaseColorName } from "@/registry/config"
|
||||
import { useCustomizerLayout } from "@/app/(create)/components/customizer-layout"
|
||||
import { LockButton } from "@/app/(create)/components/lock-button"
|
||||
import {
|
||||
Picker,
|
||||
@@ -12,51 +11,50 @@ import {
|
||||
PickerGroup,
|
||||
PickerRadioGroup,
|
||||
PickerRadioItem,
|
||||
PickerValueTrigger,
|
||||
PickerTrigger,
|
||||
} from "@/app/(create)/components/picker"
|
||||
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
|
||||
|
||||
export function BaseColorPicker({
|
||||
isMobile,
|
||||
anchorRef,
|
||||
collapsed = false,
|
||||
}: {
|
||||
isMobile: boolean
|
||||
anchorRef: React.RefObject<HTMLDivElement | null>
|
||||
collapsed?: boolean
|
||||
}) {
|
||||
const mounted = useMounted()
|
||||
const [params, setParams] = useDesignSystemSearchParams()
|
||||
const { desktopPickerSide } = useCustomizerLayout()
|
||||
|
||||
const currentBaseColor = React.useMemo(
|
||||
() => BASE_COLORS.find((baseColor) => baseColor.name === params.baseColor),
|
||||
[params.baseColor]
|
||||
)
|
||||
const baseColorIndicator = mounted ? (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"--color": currentBaseColor?.cssVars?.dark?.["muted-foreground"],
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className="size-4 rounded-full bg-(--color)"
|
||||
/>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<div className="group/picker relative">
|
||||
<Picker>
|
||||
<PickerValueTrigger
|
||||
label="Base Color"
|
||||
value={currentBaseColor?.title}
|
||||
valueText={currentBaseColor?.title}
|
||||
indicator={baseColorIndicator}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
<PickerTrigger>
|
||||
<div className="flex flex-col justify-start text-left">
|
||||
<div className="text-xs text-muted-foreground">Base Color</div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{currentBaseColor?.title}
|
||||
</div>
|
||||
</div>
|
||||
{mounted && (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"--color":
|
||||
currentBaseColor?.cssVars?.dark?.["muted-foreground"],
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className="pointer-events-none absolute top-1/2 right-4 size-4 -translate-y-1/2 rounded-full bg-(--color) select-none md:right-2.5"
|
||||
/>
|
||||
)}
|
||||
</PickerTrigger>
|
||||
<PickerContent
|
||||
anchor={isMobile ? anchorRef : undefined}
|
||||
side={isMobile ? "top" : desktopPickerSide}
|
||||
side={isMobile ? "top" : "right"}
|
||||
align={isMobile ? "center" : "start"}
|
||||
>
|
||||
<PickerRadioGroup
|
||||
@@ -79,12 +77,10 @@ export function BaseColorPicker({
|
||||
</PickerRadioGroup>
|
||||
</PickerContent>
|
||||
</Picker>
|
||||
{!collapsed ? (
|
||||
<LockButton
|
||||
param="baseColor"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
) : null}
|
||||
<LockButton
|
||||
param="baseColor"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { BASES } from "@/registry/config"
|
||||
import { useCustomizerLayout } from "@/app/(create)/components/customizer-layout"
|
||||
import {
|
||||
Picker,
|
||||
PickerContent,
|
||||
@@ -22,7 +21,6 @@ export function BasePicker({
|
||||
anchorRef: React.RefObject<HTMLDivElement | null>
|
||||
}) {
|
||||
const [params, setParams] = useDesignSystemSearchParams()
|
||||
const { desktopPickerSide } = useCustomizerLayout()
|
||||
|
||||
const currentBase = React.useMemo(
|
||||
() => BASES.find((base) => base.name === params.base),
|
||||
@@ -62,7 +60,7 @@ export function BasePicker({
|
||||
</PickerTrigger>
|
||||
<PickerContent
|
||||
anchor={isMobile ? anchorRef : undefined}
|
||||
side={isMobile ? "top" : desktopPickerSide}
|
||||
side={isMobile ? "top" : "right"}
|
||||
align={isMobile ? "center" : "start"}
|
||||
>
|
||||
<PickerRadioGroup
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
getThemesForBaseColor,
|
||||
type ChartColorName,
|
||||
} from "@/registry/config"
|
||||
import { useCustomizerLayout } from "@/app/(create)/components/customizer-layout"
|
||||
import { LockButton } from "@/app/(create)/components/lock-button"
|
||||
import {
|
||||
Picker,
|
||||
@@ -17,22 +16,19 @@ import {
|
||||
PickerRadioGroup,
|
||||
PickerRadioItem,
|
||||
PickerSeparator,
|
||||
PickerValueTrigger,
|
||||
PickerTrigger,
|
||||
} from "@/app/(create)/components/picker"
|
||||
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
|
||||
|
||||
export function ChartColorPicker({
|
||||
isMobile,
|
||||
anchorRef,
|
||||
collapsed = false,
|
||||
}: {
|
||||
isMobile: boolean
|
||||
anchorRef: React.RefObject<HTMLDivElement | null>
|
||||
collapsed?: boolean
|
||||
}) {
|
||||
const mounted = useMounted()
|
||||
const [params, setParams] = useDesignSystemSearchParams()
|
||||
const { desktopPickerSide } = useCustomizerLayout()
|
||||
|
||||
const availableChartColors = React.useMemo(
|
||||
() => getThemesForBaseColor(params.baseColor),
|
||||
@@ -56,33 +52,35 @@ export function ChartColorPicker({
|
||||
}
|
||||
}, [currentChartColor, availableChartColors, setParams])
|
||||
|
||||
const chartColorIndicator = mounted ? (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"--color":
|
||||
currentChartColor?.cssVars?.dark?.[
|
||||
currentChartColorIsBaseColor ? "muted-foreground" : "primary"
|
||||
],
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className="size-4 rounded-full bg-(--color)"
|
||||
/>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<div className="group/picker relative">
|
||||
<Picker>
|
||||
<PickerValueTrigger
|
||||
label="Chart Color"
|
||||
value={currentChartColor?.title}
|
||||
valueText={currentChartColor?.title}
|
||||
indicator={chartColorIndicator}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
<PickerTrigger>
|
||||
<div className="flex flex-col justify-start text-left">
|
||||
<div className="text-xs text-muted-foreground">Chart Color</div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{currentChartColor?.title}
|
||||
</div>
|
||||
</div>
|
||||
{mounted && (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"--color":
|
||||
currentChartColor?.cssVars?.dark?.[
|
||||
currentChartColorIsBaseColor
|
||||
? "muted-foreground"
|
||||
: "primary"
|
||||
],
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className="pointer-events-none absolute top-1/2 right-4 size-4 -translate-y-1/2 rounded-full bg-(--color) select-none md:right-2.5"
|
||||
/>
|
||||
)}
|
||||
</PickerTrigger>
|
||||
<PickerContent
|
||||
anchor={isMobile ? anchorRef : undefined}
|
||||
side={isMobile ? "top" : desktopPickerSide}
|
||||
side={isMobile ? "top" : "right"}
|
||||
align={isMobile ? "center" : "start"}
|
||||
className="max-h-92"
|
||||
>
|
||||
@@ -129,12 +127,10 @@ export function ChartColorPicker({
|
||||
</PickerRadioGroup>
|
||||
</PickerContent>
|
||||
</Picker>
|
||||
{!collapsed ? (
|
||||
<LockButton
|
||||
param="chartColor"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
) : null}
|
||||
<LockButton
|
||||
param="chartColor"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,24 +2,12 @@
|
||||
|
||||
import * as React from "react"
|
||||
import { Button } from "@/examples/base/ui/button"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/examples/base/ui/tooltip"
|
||||
import { Copy01Icon, Tick02Icon } from "@hugeicons/core-free-icons"
|
||||
import { HugeiconsIcon } from "@hugeicons/react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { copyToClipboardWithMeta } from "@/components/copy-button"
|
||||
import { usePresetCode } from "@/app/(create)/hooks/use-design-system"
|
||||
|
||||
export function CopyPreset({
|
||||
className,
|
||||
collapsed = false,
|
||||
}: React.ComponentProps<typeof Button> & {
|
||||
collapsed?: boolean
|
||||
}) {
|
||||
export function CopyPreset({ className }: React.ComponentProps<typeof Button>) {
|
||||
const presetCode = usePresetCode()
|
||||
const [hasCopied, setHasCopied] = React.useState(false)
|
||||
|
||||
@@ -40,40 +28,6 @@ export function CopyPreset({
|
||||
setHasCopied(true)
|
||||
}, [presetCode])
|
||||
|
||||
const tooltipLabel = hasCopied
|
||||
? "Copied preset"
|
||||
: `Copy --preset ${presetCode}`
|
||||
|
||||
if (collapsed) {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label={tooltipLabel}
|
||||
onClick={handleCopy}
|
||||
className={cn(
|
||||
"size-10 touch-manipulation rounded-xl bg-transparent! transition-none select-none hover:bg-muted!",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={hasCopied ? Tick02Icon : Copy01Icon}
|
||||
strokeWidth={2}
|
||||
/>
|
||||
<span className="sr-only">{tooltipLabel}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={10}>
|
||||
{tooltipLabel}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import {
|
||||
getCustomizerDesktopPickerSide,
|
||||
type CustomizerDesktopPickerSide,
|
||||
type CustomizerPosition,
|
||||
} from "@/app/(create)/lib/customizer"
|
||||
|
||||
type CustomizerLayoutContextValue = {
|
||||
position: CustomizerPosition
|
||||
desktopPickerSide: CustomizerDesktopPickerSide
|
||||
}
|
||||
|
||||
const CustomizerLayoutContext =
|
||||
React.createContext<CustomizerLayoutContextValue | null>(null)
|
||||
|
||||
export function CustomizerLayoutProvider({
|
||||
position,
|
||||
children,
|
||||
}: React.PropsWithChildren<{
|
||||
position: CustomizerPosition
|
||||
}>) {
|
||||
const value = React.useMemo(
|
||||
() => ({
|
||||
position,
|
||||
desktopPickerSide: getCustomizerDesktopPickerSide(position),
|
||||
}),
|
||||
[position]
|
||||
)
|
||||
|
||||
return (
|
||||
<CustomizerLayoutContext.Provider value={value}>
|
||||
{children}
|
||||
</CustomizerLayoutContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useCustomizerLayout() {
|
||||
const context = React.useContext(CustomizerLayoutContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
"useCustomizerLayout must be used within a CustomizerLayoutProvider"
|
||||
)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import Script from "next/script"
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -11,7 +10,7 @@ import {
|
||||
import { FieldGroup, FieldSeparator } from "@/examples/base/ui/field"
|
||||
import { type RegistryItem } from "shadcn/schema"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useIsMobile } from "@/hooks/use-mobile"
|
||||
import { getThemesForBaseColor, STYLES } from "@/registry/config"
|
||||
import { MenuAccentPicker } from "@/app/(create)/components/accent-picker"
|
||||
import { ActionMenu } from "@/app/(create)/components/action-menu"
|
||||
@@ -19,7 +18,6 @@ import { BaseColorPicker } from "@/app/(create)/components/base-color-picker"
|
||||
import { BasePicker } from "@/app/(create)/components/base-picker"
|
||||
import { ChartColorPicker } from "@/app/(create)/components/chart-color-picker"
|
||||
import { CopyPreset } from "@/app/(create)/components/copy-preset"
|
||||
import { CustomizerLayoutProvider } from "@/app/(create)/components/customizer-layout"
|
||||
import { FontPicker } from "@/app/(create)/components/font-picker"
|
||||
import { IconLibraryPicker } from "@/app/(create)/components/icon-library-picker"
|
||||
import { MainMenu } from "@/app/(create)/components/main-menu"
|
||||
@@ -29,293 +27,78 @@ import { RandomButton } from "@/app/(create)/components/random-button"
|
||||
import { ResetDialog } from "@/app/(create)/components/reset-button"
|
||||
import { StylePicker } from "@/app/(create)/components/style-picker"
|
||||
import { ThemePicker } from "@/app/(create)/components/theme-picker"
|
||||
import {
|
||||
CUSTOMIZER_POSITION_COOKIE_NAME,
|
||||
CUSTOMIZER_STATE_COOKIE_MAX_AGE,
|
||||
CUSTOMIZER_STATE_COOKIE_NAME,
|
||||
type CustomizerPosition,
|
||||
} from "@/app/(create)/lib/customizer"
|
||||
import { V0Button } from "@/app/(create)/components/v0-button"
|
||||
import { FONT_HEADING_OPTIONS, FONTS } from "@/app/(create)/lib/fonts"
|
||||
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
|
||||
|
||||
export const CUSTOMIZER_TOGGLE_FORWARD_TYPE = "customizer-toggle-forward"
|
||||
|
||||
export function Customizer({
|
||||
itemsByBase,
|
||||
defaultCollapsed = false,
|
||||
defaultIsMobile = false,
|
||||
defaultPosition = "left",
|
||||
}: {
|
||||
itemsByBase: Record<string, Pick<RegistryItem, "name" | "title" | "type">[]>
|
||||
defaultCollapsed?: boolean
|
||||
defaultIsMobile?: boolean
|
||||
defaultPosition?: CustomizerPosition
|
||||
}) {
|
||||
const [params] = useDesignSystemSearchParams()
|
||||
const [isMobile, setIsMobile] = React.useState(defaultIsMobile)
|
||||
const isMobile = useIsMobile()
|
||||
const anchorRef = React.useRef<HTMLDivElement | null>(null)
|
||||
const [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed)
|
||||
const [position, setPosition] =
|
||||
React.useState<CustomizerPosition>(defaultPosition)
|
||||
const isDesktopCollapsed = !isMobile && isCollapsed
|
||||
const toggleCollapse = React.useCallback(() => {
|
||||
setIsCollapsed((collapsed) => {
|
||||
const nextCollapsed = !collapsed
|
||||
document.cookie = `${CUSTOMIZER_STATE_COOKIE_NAME}=${nextCollapsed}; path=/; max-age=${CUSTOMIZER_STATE_COOKIE_MAX_AGE}`
|
||||
return nextCollapsed
|
||||
})
|
||||
}, [])
|
||||
const handlePositionChange = React.useCallback(
|
||||
(nextPosition: CustomizerPosition) => {
|
||||
setPosition(nextPosition)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const availableThemes = React.useMemo(
|
||||
() => getThemesForBaseColor(params.baseColor),
|
||||
[params.baseColor]
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia("(max-width: 767px)")
|
||||
const onChange = () => {
|
||||
setIsMobile(window.innerWidth < 768)
|
||||
}
|
||||
|
||||
mql.addEventListener("change", onChange)
|
||||
onChange()
|
||||
|
||||
return () => {
|
||||
mql.removeEventListener("change", onChange)
|
||||
}
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
document.cookie = `${CUSTOMIZER_POSITION_COOKIE_NAME}=${position}; path=/; max-age=${CUSTOMIZER_STATE_COOKIE_MAX_AGE}`
|
||||
|
||||
const layout = document.querySelector<HTMLElement>('[data-slot="layout"]')
|
||||
const designer = document.querySelector<HTMLElement>(
|
||||
'[data-slot="designer"]'
|
||||
)
|
||||
|
||||
if (layout) {
|
||||
layout.dataset.customizerPosition = position
|
||||
}
|
||||
|
||||
if (designer) {
|
||||
designer.dataset.customizerPosition = position
|
||||
}
|
||||
}, [position])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isMobile) {
|
||||
return
|
||||
}
|
||||
|
||||
const down = (e: KeyboardEvent) => {
|
||||
if ((!e.metaKey && !e.ctrlKey) || e.shiftKey || e.altKey) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
(e.target instanceof HTMLElement && e.target.isContentEditable) ||
|
||||
e.target instanceof HTMLInputElement ||
|
||||
e.target instanceof HTMLTextAreaElement ||
|
||||
e.target instanceof HTMLSelectElement
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key.toLowerCase() !== "b") {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
toggleCollapse()
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", down)
|
||||
return () => {
|
||||
document.removeEventListener("keydown", down)
|
||||
}
|
||||
}, [isMobile, toggleCollapse])
|
||||
|
||||
return (
|
||||
<CustomizerLayoutProvider position={position}>
|
||||
<Card
|
||||
ref={anchorRef}
|
||||
className={cn(
|
||||
"dark isolate z-10 max-h-full min-h-0 w-full self-start rounded-2xl bg-card/90 shadow-xl backdrop-blur-xl transition-[width] duration-300 ease-out md:w-(--customizer-width)",
|
||||
isDesktopCollapsed && "md:w-16"
|
||||
)}
|
||||
size="sm"
|
||||
>
|
||||
<CardHeader
|
||||
className={cn(
|
||||
"hidden items-center gap-2 border-b transition-[padding,border-color] duration-200 ease-out group-data-[customizer-position=right]/layout:flex-row-reverse md:flex",
|
||||
isDesktopCollapsed ? "justify-center" : "justify-between"
|
||||
)}
|
||||
>
|
||||
<MainMenu
|
||||
onToggleCollapse={toggleCollapse}
|
||||
onPositionChange={handlePositionChange}
|
||||
position={position}
|
||||
collapsed={isDesktopCollapsed}
|
||||
<Card
|
||||
className="dark top-24 right-12 isolate z-10 max-h-full min-h-0 w-full self-start rounded-2xl bg-card/90 shadow-xl backdrop-blur-xl md:w-(--customizer-width)"
|
||||
ref={anchorRef}
|
||||
size="sm"
|
||||
>
|
||||
<CardHeader className="hidden items-center justify-between gap-2 border-b group-data-reversed/layout:flex-row-reverse md:flex">
|
||||
<MainMenu />
|
||||
</CardHeader>
|
||||
<CardContent className="no-scrollbar min-h-0 flex-1 overflow-x-auto overflow-y-hidden md:overflow-y-auto">
|
||||
<FieldGroup className="flex-row gap-2.5 py-px **:data-[slot=field-separator]:-mx-4 **:data-[slot=field-separator]:w-auto md:flex-col md:gap-3.25">
|
||||
{isMobile && <BasePicker isMobile={isMobile} anchorRef={anchorRef} />}
|
||||
<StylePicker
|
||||
styles={STYLES}
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
/>
|
||||
</CardHeader>
|
||||
<CardContent className="no-scrollbar min-h-0 flex-1 overflow-x-auto overflow-y-hidden transition-[padding] duration-200 ease-out md:overflow-x-hidden md:overflow-y-auto">
|
||||
<FieldGroup
|
||||
className={cn(
|
||||
"flex-row gap-2.5 py-px transition-[gap] duration-200 ease-out **:data-[slot=field-separator]:-mx-4 **:data-[slot=field-separator]:w-auto md:flex-col md:gap-3.25",
|
||||
isDesktopCollapsed && "md:gap-2"
|
||||
)}
|
||||
>
|
||||
{isMobile && (
|
||||
<BasePicker isMobile={isMobile} anchorRef={anchorRef} />
|
||||
)}
|
||||
<StylePicker
|
||||
styles={STYLES}
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
collapsed={isDesktopCollapsed}
|
||||
/>
|
||||
<FieldSeparator
|
||||
className={cn(
|
||||
"hidden transition-opacity duration-150 ease-out md:block",
|
||||
isDesktopCollapsed && "pointer-events-none opacity-0"
|
||||
)}
|
||||
/>
|
||||
<BaseColorPicker
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
collapsed={isDesktopCollapsed}
|
||||
/>
|
||||
<ThemePicker
|
||||
themes={availableThemes}
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
collapsed={isDesktopCollapsed}
|
||||
/>
|
||||
<ChartColorPicker
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
collapsed={isDesktopCollapsed}
|
||||
/>
|
||||
<FieldSeparator
|
||||
className={cn(
|
||||
"hidden transition-opacity duration-150 ease-out md:block",
|
||||
isDesktopCollapsed && "pointer-events-none opacity-0"
|
||||
)}
|
||||
/>
|
||||
<FontPicker
|
||||
label="Heading"
|
||||
param="fontHeading"
|
||||
fonts={FONT_HEADING_OPTIONS}
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
collapsed={isDesktopCollapsed}
|
||||
/>
|
||||
<FontPicker
|
||||
label="Font"
|
||||
param="font"
|
||||
fonts={FONTS}
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
collapsed={isDesktopCollapsed}
|
||||
/>
|
||||
<FieldSeparator
|
||||
className={cn(
|
||||
"hidden transition-opacity duration-150 ease-out md:block",
|
||||
isDesktopCollapsed && "pointer-events-none opacity-0"
|
||||
)}
|
||||
/>
|
||||
<IconLibraryPicker
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
collapsed={isDesktopCollapsed}
|
||||
/>
|
||||
<RadiusPicker
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
collapsed={isDesktopCollapsed}
|
||||
/>
|
||||
<FieldSeparator
|
||||
className={cn(
|
||||
"hidden transition-opacity duration-150 ease-out md:block",
|
||||
isDesktopCollapsed && "pointer-events-none opacity-0"
|
||||
)}
|
||||
/>
|
||||
<MenuColorPicker
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
collapsed={isDesktopCollapsed}
|
||||
/>
|
||||
<MenuAccentPicker
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
collapsed={isDesktopCollapsed}
|
||||
/>
|
||||
</FieldGroup>
|
||||
</CardContent>
|
||||
<CardFooter
|
||||
className={cn(
|
||||
"flex min-w-0 gap-2 transition-[max-height,opacity,padding,border-color] duration-200 ease-out md:max-h-72 md:flex-col md:**:[button,a]:w-full",
|
||||
isDesktopCollapsed && "justify-center md:max-h-28 md:px-2 md:py-2"
|
||||
)}
|
||||
>
|
||||
<CopyPreset
|
||||
className="flex-1 md:flex-none"
|
||||
collapsed={isDesktopCollapsed}
|
||||
<FieldSeparator className="hidden md:block" />
|
||||
<BaseColorPicker isMobile={isMobile} anchorRef={anchorRef} />
|
||||
<ThemePicker
|
||||
themes={availableThemes}
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
/>
|
||||
<RandomButton
|
||||
className="flex-1 md:flex-none"
|
||||
collapsed={isDesktopCollapsed}
|
||||
<ChartColorPicker isMobile={isMobile} anchorRef={anchorRef} />
|
||||
<FieldSeparator className="hidden md:block" />
|
||||
<FontPicker
|
||||
label="Heading"
|
||||
param="fontHeading"
|
||||
fonts={FONT_HEADING_OPTIONS}
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
/>
|
||||
{!isDesktopCollapsed ? (
|
||||
<ActionMenu itemsByBase={itemsByBase} />
|
||||
) : null}
|
||||
<ResetDialog />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</CustomizerLayoutProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export function CustomizerToggleScript() {
|
||||
return (
|
||||
<Script
|
||||
id="customizer-toggle-listener"
|
||||
strategy="beforeInteractive"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
(function() {
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if ((!e.metaKey && !e.ctrlKey) || e.shiftKey || e.altKey) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
(e.target instanceof HTMLElement && e.target.isContentEditable) ||
|
||||
e.target instanceof HTMLInputElement ||
|
||||
e.target instanceof HTMLTextAreaElement ||
|
||||
e.target instanceof HTMLSelectElement
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (e.key.toLowerCase() !== 'b') {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage({
|
||||
type: '${CUSTOMIZER_TOGGLE_FORWARD_TYPE}',
|
||||
key: e.key
|
||||
}, '*');
|
||||
}
|
||||
});
|
||||
})();
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<FontPicker
|
||||
label="Font"
|
||||
param="font"
|
||||
fonts={FONTS}
|
||||
isMobile={isMobile}
|
||||
anchorRef={anchorRef}
|
||||
/>
|
||||
<FieldSeparator className="hidden md:block" />
|
||||
<IconLibraryPicker isMobile={isMobile} anchorRef={anchorRef} />
|
||||
<RadiusPicker isMobile={isMobile} anchorRef={anchorRef} />
|
||||
<FieldSeparator className="hidden md:block" />
|
||||
<MenuColorPicker isMobile={isMobile} anchorRef={anchorRef} />
|
||||
<MenuAccentPicker isMobile={isMobile} anchorRef={anchorRef} />
|
||||
</FieldGroup>
|
||||
</CardContent>
|
||||
<CardFooter className="flex min-w-0 gap-2 md:flex-col md:**:[button,a]:w-full">
|
||||
<CopyPreset className="flex-1 md:flex-none" />
|
||||
<RandomButton className="flex-1 md:flex-none" />
|
||||
<ActionMenu itemsByBase={itemsByBase} />
|
||||
<ResetDialog />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { useCustomizerLayout } from "@/app/(create)/components/customizer-layout"
|
||||
import { LockButton } from "@/app/(create)/components/lock-button"
|
||||
import {
|
||||
Picker,
|
||||
@@ -12,7 +11,7 @@ import {
|
||||
PickerRadioGroup,
|
||||
PickerRadioItem,
|
||||
PickerSeparator,
|
||||
PickerValueTrigger,
|
||||
PickerTrigger,
|
||||
} from "@/app/(create)/components/picker"
|
||||
import { FONTS } from "@/app/(create)/lib/fonts"
|
||||
import {
|
||||
@@ -37,17 +36,14 @@ export function FontPicker({
|
||||
fonts,
|
||||
isMobile,
|
||||
anchorRef,
|
||||
collapsed = false,
|
||||
}: {
|
||||
label: string
|
||||
param: "font" | "fontHeading"
|
||||
fonts: readonly FontPickerOption[]
|
||||
isMobile: boolean
|
||||
anchorRef: React.RefObject<HTMLDivElement | null>
|
||||
collapsed?: boolean
|
||||
}) {
|
||||
const [params, setParams] = useDesignSystemSearchParams()
|
||||
const { desktopPickerSide } = useCustomizerLayout()
|
||||
const currentValue = param === "font" ? params.font : params.fontHeading
|
||||
const handleFontChange = React.useCallback(
|
||||
(value: string) => {
|
||||
@@ -98,26 +94,27 @@ export function FontPicker({
|
||||
return (
|
||||
<div className="group/picker relative">
|
||||
<Picker>
|
||||
<PickerValueTrigger
|
||||
label={label}
|
||||
value={displayFontName}
|
||||
valueText={displayFontName}
|
||||
indicator={
|
||||
<span
|
||||
style={{
|
||||
fontFamily:
|
||||
currentFont?.font?.style.fontFamily ??
|
||||
currentBodyFont?.font.style.fontFamily,
|
||||
}}
|
||||
>
|
||||
Aa
|
||||
</span>
|
||||
}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
<PickerTrigger>
|
||||
<div className="flex flex-col justify-start text-left">
|
||||
<div className="text-xs text-muted-foreground">{label}</div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{displayFontName}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pointer-events-none absolute top-1/2 right-4 flex size-4 -translate-y-1/2 items-center justify-center text-base text-foreground select-none md:right-2.5"
|
||||
style={{
|
||||
fontFamily:
|
||||
currentFont?.font?.style.fontFamily ??
|
||||
currentBodyFont?.font.style.fontFamily,
|
||||
}}
|
||||
>
|
||||
Aa
|
||||
</div>
|
||||
</PickerTrigger>
|
||||
<PickerContent
|
||||
anchor={isMobile ? anchorRef : undefined}
|
||||
side={isMobile ? "top" : desktopPickerSide}
|
||||
side={isMobile ? "top" : "right"}
|
||||
align={isMobile ? "center" : "start"}
|
||||
className="max-h-96"
|
||||
>
|
||||
@@ -152,12 +149,10 @@ export function FontPicker({
|
||||
</PickerRadioGroup>
|
||||
</PickerContent>
|
||||
</Picker>
|
||||
{!collapsed ? (
|
||||
<LockButton
|
||||
param={param}
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
) : null}
|
||||
<LockButton
|
||||
param={param}
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { iconLibraries, type IconLibraryName } from "@/registry/config"
|
||||
import { useCustomizerLayout } from "@/app/(create)/components/customizer-layout"
|
||||
import { LockButton } from "@/app/(create)/components/lock-button"
|
||||
import {
|
||||
Picker,
|
||||
@@ -11,7 +10,7 @@ import {
|
||||
PickerGroup,
|
||||
PickerRadioGroup,
|
||||
PickerRadioItem,
|
||||
PickerValueTrigger,
|
||||
PickerTrigger,
|
||||
} from "@/app/(create)/components/picker"
|
||||
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
|
||||
|
||||
@@ -113,14 +112,11 @@ const logos = {
|
||||
export function IconLibraryPicker({
|
||||
isMobile,
|
||||
anchorRef,
|
||||
collapsed = false,
|
||||
}: {
|
||||
isMobile: boolean
|
||||
anchorRef: React.RefObject<HTMLDivElement | null>
|
||||
collapsed?: boolean
|
||||
}) {
|
||||
const [params, setParams] = useDesignSystemSearchParams()
|
||||
const { desktopPickerSide } = useCustomizerLayout()
|
||||
|
||||
const currentIconLibrary = React.useMemo(
|
||||
() => iconLibraries[params.iconLibrary as keyof typeof iconLibraries],
|
||||
@@ -130,16 +126,20 @@ export function IconLibraryPicker({
|
||||
return (
|
||||
<div className="group/picker relative">
|
||||
<Picker>
|
||||
<PickerValueTrigger
|
||||
label="Icon Library"
|
||||
value={currentIconLibrary?.title}
|
||||
valueText={currentIconLibrary?.title}
|
||||
indicator={logos[currentIconLibrary?.name as keyof typeof logos]}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
<PickerTrigger>
|
||||
<div className="flex flex-col justify-start text-left">
|
||||
<div className="text-xs text-muted-foreground">Icon Library</div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{currentIconLibrary?.title}
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute top-1/2 right-4 flex size-4 -translate-y-1/2 items-center justify-center text-base text-foreground select-none md:right-2.5 *:[svg]:text-foreground!">
|
||||
{logos[currentIconLibrary?.name as keyof typeof logos]}
|
||||
</div>
|
||||
</PickerTrigger>
|
||||
<PickerContent
|
||||
anchor={isMobile ? anchorRef : undefined}
|
||||
side={isMobile ? "top" : desktopPickerSide}
|
||||
side={isMobile ? "top" : "right"}
|
||||
align={isMobile ? "center" : "start"}
|
||||
>
|
||||
<PickerRadioGroup
|
||||
@@ -162,12 +162,10 @@ export function IconLibraryPicker({
|
||||
</PickerRadioGroup>
|
||||
</PickerContent>
|
||||
</Picker>
|
||||
{!collapsed ? (
|
||||
<LockButton
|
||||
param="iconLibrary"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
) : null}
|
||||
<LockButton
|
||||
param="iconLibrary"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { type Button } from "@/examples/base/ui/button"
|
||||
import { Menu09Icon } from "@hugeicons/core-free-icons"
|
||||
import { HugeiconsIcon } from "@hugeicons/react"
|
||||
|
||||
@@ -10,9 +11,6 @@ import {
|
||||
PickerContent,
|
||||
PickerGroup,
|
||||
PickerItem,
|
||||
PickerLabel,
|
||||
PickerRadioGroup,
|
||||
PickerRadioItem,
|
||||
PickerSeparator,
|
||||
PickerShortcut,
|
||||
PickerTrigger,
|
||||
@@ -22,23 +20,10 @@ import { useHistory } from "@/app/(create)/hooks/use-history"
|
||||
import { useRandom } from "@/app/(create)/hooks/use-random"
|
||||
import { useReset } from "@/app/(create)/hooks/use-reset"
|
||||
import { useThemeToggle } from "@/app/(create)/hooks/use-theme-toggle"
|
||||
import { type CustomizerPosition } from "@/app/(create)/lib/customizer"
|
||||
|
||||
const APPLE_PLATFORM_REGEX = /Mac|iPhone|iPad|iPod/
|
||||
|
||||
export function MainMenu({
|
||||
className,
|
||||
onToggleCollapse,
|
||||
onPositionChange,
|
||||
position = "left",
|
||||
collapsed = false,
|
||||
}: {
|
||||
className?: string
|
||||
onToggleCollapse?: () => void
|
||||
onPositionChange?: (position: CustomizerPosition) => void
|
||||
position?: CustomizerPosition
|
||||
collapsed?: boolean
|
||||
}) {
|
||||
export function MainMenu({ className }: React.ComponentProps<typeof Button>) {
|
||||
const [isMac, setIsMac] = React.useState(false)
|
||||
const { canGoBack, canGoForward, goBack, goForward } = useHistory()
|
||||
const { openActionMenu } = useActionMenuTrigger()
|
||||
@@ -56,42 +41,15 @@ export function MainMenu({
|
||||
<React.Fragment>
|
||||
<Picker>
|
||||
<PickerTrigger
|
||||
aria-label="Menu"
|
||||
collapsed={collapsed}
|
||||
tooltip="Menu"
|
||||
className={cn("relative block w-full text-left", className)}
|
||||
className={cn(
|
||||
"flex items-center justify-between gap-2 rounded-lg px-1.75 ring-1 ring-foreground/10 focus-visible:ring-1",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
aria-hidden={collapsed}
|
||||
className={cn(
|
||||
"w-full pr-8 transition-[opacity,transform] duration-150 ease-out",
|
||||
collapsed
|
||||
? "translate-x-1 opacity-0"
|
||||
: "translate-x-0 opacity-100"
|
||||
)}
|
||||
>
|
||||
<div className="truncate font-medium text-foreground">Menu</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"pointer-events-none absolute top-1/2 flex size-4 -translate-y-1/2 items-center justify-center text-foreground transition-[left,transform] duration-200 ease-out",
|
||||
collapsed
|
||||
? "left-1/2 -translate-x-1/2"
|
||||
: "left-[calc(100%-1.625rem)] translate-x-0"
|
||||
)}
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={Menu09Icon}
|
||||
strokeWidth={2}
|
||||
className="size-5"
|
||||
/>
|
||||
</div>
|
||||
<span className="font-medium">Menu</span>
|
||||
<HugeiconsIcon icon={Menu09Icon} strokeWidth={2} className="size-5" />
|
||||
</PickerTrigger>
|
||||
<PickerContent
|
||||
side={position === "right" ? "left" : "right"}
|
||||
align="start"
|
||||
alignOffset={-8}
|
||||
>
|
||||
<PickerContent side="right" align="start" alignOffset={-8}>
|
||||
<PickerGroup>
|
||||
<PickerItem onClick={openActionMenu}>
|
||||
Navigate...
|
||||
@@ -103,35 +61,8 @@ export function MainMenu({
|
||||
<PickerItem onClick={toggleTheme}>
|
||||
Light/Dark <PickerShortcut>D</PickerShortcut>
|
||||
</PickerItem>
|
||||
{onToggleCollapse ? (
|
||||
<PickerItem onClick={onToggleCollapse}>
|
||||
{collapsed ? "Expand" : "Collapse"}{" "}
|
||||
<PickerShortcut>{isMac ? "⌘B" : "Ctrl+B"}</PickerShortcut>
|
||||
</PickerItem>
|
||||
) : null}
|
||||
</PickerGroup>
|
||||
<PickerSeparator />
|
||||
{onPositionChange ? (
|
||||
<>
|
||||
<PickerGroup>
|
||||
<PickerLabel>Position</PickerLabel>
|
||||
<PickerRadioGroup
|
||||
value={position}
|
||||
onValueChange={(value) => {
|
||||
onPositionChange(value as CustomizerPosition)
|
||||
}}
|
||||
>
|
||||
<PickerRadioItem value="left" closeOnClick>
|
||||
Left
|
||||
</PickerRadioItem>
|
||||
<PickerRadioItem value="right" closeOnClick>
|
||||
Right
|
||||
</PickerRadioItem>
|
||||
</PickerRadioGroup>
|
||||
</PickerGroup>
|
||||
<PickerSeparator />
|
||||
</>
|
||||
) : null}
|
||||
<PickerGroup>
|
||||
<PickerItem onClick={goBack} disabled={!canGoBack}>
|
||||
Undo <PickerShortcut>{isMac ? "⌘Z" : "Ctrl+Z"}</PickerShortcut>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useTheme } from "next-themes"
|
||||
|
||||
import { useMounted } from "@/hooks/use-mounted"
|
||||
import { type MenuColorValue } from "@/registry/config"
|
||||
import { useCustomizerLayout } from "@/app/(create)/components/customizer-layout"
|
||||
import { LockButton } from "@/app/(create)/components/lock-button"
|
||||
import {
|
||||
Picker,
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
PickerRadioGroup,
|
||||
PickerRadioItem,
|
||||
PickerSeparator,
|
||||
PickerValueTrigger,
|
||||
PickerTrigger,
|
||||
} from "@/app/(create)/components/picker"
|
||||
import {
|
||||
isTranslucentMenuColor,
|
||||
@@ -48,14 +47,11 @@ const MENU_OPTIONS: { value: MenuColorValue; label: string }[] = [
|
||||
export function MenuColorPicker({
|
||||
isMobile,
|
||||
anchorRef,
|
||||
collapsed = false,
|
||||
}: {
|
||||
isMobile: boolean
|
||||
anchorRef: React.RefObject<HTMLDivElement | null>
|
||||
collapsed?: boolean
|
||||
}) {
|
||||
const [params, setParams] = useDesignSystemSearchParams()
|
||||
const { desktopPickerSide } = useCustomizerLayout()
|
||||
const { resolvedTheme } = useTheme()
|
||||
const mounted = useMounted()
|
||||
const lastSolidMenuAccentRef = React.useRef(params.menuAccent)
|
||||
@@ -105,23 +101,24 @@ export function MenuColorPicker({
|
||||
return (
|
||||
<div className="group/picker relative">
|
||||
<Picker>
|
||||
<PickerValueTrigger
|
||||
label="Menu"
|
||||
value={currentMenu?.label}
|
||||
valueText={currentMenu?.label}
|
||||
valueClassName="line-clamp-1"
|
||||
indicator={
|
||||
<PickerTrigger>
|
||||
<div className="flex flex-col justify-start text-left">
|
||||
<div className="text-xs text-muted-foreground">Menu</div>
|
||||
<div className="line-clamp-1 text-sm font-medium text-foreground">
|
||||
{currentMenu?.label}
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute top-1/2 right-4 flex size-4 -translate-y-1/2 items-center justify-center text-base text-foreground select-none md:right-2.5">
|
||||
<HugeiconsIcon
|
||||
icon={Menu02Icon}
|
||||
strokeWidth={2}
|
||||
className="size-4"
|
||||
/>
|
||||
}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
</div>
|
||||
</PickerTrigger>
|
||||
<PickerContent
|
||||
anchor={isMobile ? anchorRef : undefined}
|
||||
side={isMobile ? "top" : desktopPickerSide}
|
||||
side={isMobile ? "top" : "right"}
|
||||
align={isMobile ? "center" : "start"}
|
||||
>
|
||||
<PickerGroup>
|
||||
@@ -163,12 +160,10 @@ export function MenuColorPicker({
|
||||
</PickerGroup>
|
||||
</PickerContent>
|
||||
</Picker>
|
||||
{!collapsed ? (
|
||||
<LockButton
|
||||
param="menuColor"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
) : null}
|
||||
<LockButton
|
||||
param="menuColor"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,12 +5,6 @@ import { Menu as MenuPrimitive } from "@base-ui/react/menu"
|
||||
|
||||
import { cn } from "@/registry/bases/base/lib/utils"
|
||||
import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
|
||||
import {
|
||||
createTooltipHandle,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/app/(create)/components/tooltip"
|
||||
|
||||
function Picker({ ...props }: MenuPrimitive.Root.Props) {
|
||||
return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
||||
@@ -20,129 +14,16 @@ function PickerPortal({ ...props }: MenuPrimitive.Portal.Props) {
|
||||
return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||
}
|
||||
|
||||
function PickerTrigger({
|
||||
className,
|
||||
collapsed = false,
|
||||
tooltip,
|
||||
...triggerProps
|
||||
}: MenuPrimitive.Trigger.Props & {
|
||||
collapsed?: boolean
|
||||
tooltip?: string
|
||||
}) {
|
||||
const [tooltipHandle] = React.useState(() =>
|
||||
createTooltipHandle<{ text: string }>()
|
||||
)
|
||||
|
||||
const trigger = (
|
||||
function PickerTrigger({ className, ...props }: MenuPrimitive.Trigger.Props) {
|
||||
return (
|
||||
<MenuPrimitive.Trigger
|
||||
data-slot="dropdown-menu-trigger"
|
||||
data-collapsed={collapsed ? "true" : undefined}
|
||||
className={cn(
|
||||
"relative w-40 shrink-0 touch-manipulation overflow-hidden rounded-xl p-3 ring-1 ring-foreground/10 transition-[width,padding,border-radius] duration-200 ease-out select-none hover:bg-muted focus-visible:ring-foreground/50 focus-visible:outline-none disabled:opacity-50 data-popup-open:bg-muted md:w-full md:rounded-lg md:px-2.5 md:py-2",
|
||||
collapsed &&
|
||||
"flex size-10 w-10 items-center justify-center rounded-xl p-0 md:size-10 md:w-10 md:rounded-xl md:p-0",
|
||||
className
|
||||
)}
|
||||
{...triggerProps}
|
||||
/>
|
||||
)
|
||||
|
||||
if (!collapsed || !tooltip) {
|
||||
return trigger
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TooltipTrigger
|
||||
handle={tooltipHandle}
|
||||
payload={{ text: tooltip }}
|
||||
render={trigger}
|
||||
/>
|
||||
<Tooltip handle={tooltipHandle}>
|
||||
{(state) => {
|
||||
const payload = state.payload as { text: string } | null | undefined
|
||||
|
||||
return payload ? (
|
||||
<TooltipContent side="right" sideOffset={10}>
|
||||
{payload.text}
|
||||
</TooltipContent>
|
||||
) : null
|
||||
}}
|
||||
</Tooltip>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
function PickerValueTrigger({
|
||||
label,
|
||||
value,
|
||||
valueText,
|
||||
indicator,
|
||||
valueClassName,
|
||||
indicatorClassName,
|
||||
collapsed = false,
|
||||
className,
|
||||
...props
|
||||
}: MenuPrimitive.Trigger.Props & {
|
||||
label: string
|
||||
value?: React.ReactNode
|
||||
valueText?: string
|
||||
indicator?: React.ReactNode
|
||||
valueClassName?: string
|
||||
indicatorClassName?: string
|
||||
collapsed?: boolean
|
||||
}) {
|
||||
const accessibleValue =
|
||||
valueText ?? (typeof value === "string" ? value : undefined)
|
||||
const accessibleLabel = accessibleValue
|
||||
? `${label}: ${accessibleValue}`
|
||||
: label
|
||||
const tooltip = accessibleValue ? `${label}: ${accessibleValue}` : label
|
||||
|
||||
return (
|
||||
<PickerTrigger
|
||||
aria-label={accessibleLabel}
|
||||
collapsed={collapsed}
|
||||
tooltip={tooltip}
|
||||
className={cn(
|
||||
"flex items-center",
|
||||
collapsed ? "justify-center gap-0" : "justify-start gap-2.5",
|
||||
"relative w-40 shrink-0 touch-manipulation rounded-xl p-3 ring-1 ring-foreground/10 select-none hover:bg-muted focus-visible:ring-foreground/50 focus-visible:outline-none disabled:opacity-50 data-popup-open:bg-muted md:w-full md:rounded-lg md:px-2.5 md:py-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
aria-hidden={collapsed}
|
||||
className={cn(
|
||||
"min-w-0 flex-1 overflow-hidden text-left transition-[flex-basis,max-width,opacity,transform] duration-200 ease-out",
|
||||
collapsed
|
||||
? "max-w-0 basis-0 -translate-x-1 opacity-0"
|
||||
: "max-w-full basis-auto opacity-100"
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col justify-start text-left">
|
||||
<div className="truncate text-xs text-muted-foreground">{label}</div>
|
||||
<div
|
||||
className={cn(
|
||||
"truncate text-sm font-medium text-foreground",
|
||||
valueClassName
|
||||
)}
|
||||
>
|
||||
{value}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{indicator ? (
|
||||
<div
|
||||
className={cn(
|
||||
"pointer-events-none flex size-4 shrink-0 items-center justify-center text-base text-foreground transition-transform duration-200 ease-out select-none *:[svg]:size-4 *:[svg]:text-foreground!",
|
||||
indicatorClassName
|
||||
)}
|
||||
>
|
||||
{indicator}
|
||||
</div>
|
||||
) : null}
|
||||
</PickerTrigger>
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -159,13 +40,6 @@ function PickerContent({
|
||||
MenuPrimitive.Positioner.Props,
|
||||
"align" | "alignOffset" | "side" | "sideOffset" | "anchor"
|
||||
>) {
|
||||
const bridgeClassName =
|
||||
side === "left"
|
||||
? "absolute inset-y-0 left-0 right-62 z-40 bg-transparent"
|
||||
: side === "right"
|
||||
? "absolute inset-y-0 right-0 left-62 z-40 bg-transparent"
|
||||
: null
|
||||
|
||||
return (
|
||||
<MenuPrimitive.Portal>
|
||||
<MenuPrimitive.Positioner
|
||||
@@ -185,7 +59,7 @@ function PickerContent({
|
||||
{...props}
|
||||
/>
|
||||
</MenuPrimitive.Positioner>
|
||||
{bridgeClassName ? <div className={bridgeClassName} /> : null}
|
||||
<div className="absolute inset-y-0 right-0 left-62 z-40 bg-transparent" />
|
||||
</MenuPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
@@ -404,7 +278,6 @@ export {
|
||||
Picker,
|
||||
PickerPortal,
|
||||
PickerTrigger,
|
||||
PickerValueTrigger,
|
||||
PickerContent,
|
||||
PickerGroup,
|
||||
PickerLabel,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { CMD_K_FORWARD_TYPE } from "@/app/(create)/components/action-menu"
|
||||
import { CUSTOMIZER_TOGGLE_FORWARD_TYPE } from "@/app/(create)/components/customizer"
|
||||
import {
|
||||
REDO_FORWARD_TYPE,
|
||||
UNDO_FORWARD_TYPE,
|
||||
@@ -79,17 +78,6 @@ function handleMessage(event: MessageEvent) {
|
||||
cancelable: true,
|
||||
})
|
||||
)
|
||||
} else if (type === CUSTOMIZER_TOGGLE_FORWARD_TYPE) {
|
||||
const isMac = MAC_REGEX.test(navigator.userAgent)
|
||||
document.dispatchEvent(
|
||||
new KeyboardEvent("keydown", {
|
||||
key: event.data.key || "b",
|
||||
metaKey: isMac,
|
||||
ctrlKey: !isMac,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { RADII, type RadiusValue } from "@/registry/config"
|
||||
import { useCustomizerLayout } from "@/app/(create)/components/customizer-layout"
|
||||
import { LockButton } from "@/app/(create)/components/lock-button"
|
||||
import {
|
||||
Picker,
|
||||
@@ -12,21 +11,18 @@ import {
|
||||
PickerRadioGroup,
|
||||
PickerRadioItem,
|
||||
PickerSeparator,
|
||||
PickerValueTrigger,
|
||||
PickerTrigger,
|
||||
} from "@/app/(create)/components/picker"
|
||||
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
|
||||
|
||||
export function RadiusPicker({
|
||||
isMobile,
|
||||
anchorRef,
|
||||
collapsed = false,
|
||||
}: {
|
||||
isMobile: boolean
|
||||
anchorRef: React.RefObject<HTMLDivElement | null>
|
||||
collapsed?: boolean
|
||||
}) {
|
||||
const [params, setParams] = useDesignSystemSearchParams()
|
||||
const { desktopPickerSide } = useCustomizerLayout()
|
||||
const isRadiusLocked = params.style === "lyra"
|
||||
const selectedRadiusName = isRadiusLocked ? "none" : params.radius
|
||||
|
||||
@@ -39,17 +35,20 @@ export function RadiusPicker({
|
||||
return (
|
||||
<div className="group/picker relative">
|
||||
<Picker>
|
||||
<PickerValueTrigger
|
||||
label="Radius"
|
||||
value={currentRadius?.label}
|
||||
valueText={currentRadius?.label}
|
||||
indicator={
|
||||
<PickerTrigger disabled={isRadiusLocked}>
|
||||
<div className="flex flex-col justify-start text-left">
|
||||
<div className="text-xs text-muted-foreground">Radius</div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{currentRadius?.label}
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute top-1/2 right-4 flex size-4 -translate-y-1/2 rotate-90 items-center justify-center text-base text-foreground select-none md:right-2.5">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
className="rotate-90 text-foreground"
|
||||
className="text-foreground"
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
@@ -60,13 +59,11 @@ export function RadiusPicker({
|
||||
d="M4 20v-5C4 8.925 8.925 4 15 4h5"
|
||||
/>
|
||||
</svg>
|
||||
}
|
||||
collapsed={collapsed}
|
||||
disabled={isRadiusLocked}
|
||||
/>
|
||||
</div>
|
||||
</PickerTrigger>
|
||||
<PickerContent
|
||||
anchor={isMobile ? anchorRef : undefined}
|
||||
side={isMobile ? "top" : desktopPickerSide}
|
||||
side={isMobile ? "top" : "right"}
|
||||
align={isMobile ? "center" : "start"}
|
||||
>
|
||||
<PickerRadioGroup
|
||||
@@ -104,12 +101,10 @@ export function RadiusPicker({
|
||||
</PickerRadioGroup>
|
||||
</PickerContent>
|
||||
</Picker>
|
||||
{!collapsed ? (
|
||||
<LockButton
|
||||
param="radius"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
) : null}
|
||||
<LockButton
|
||||
param="radius"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
|
||||
import Script from "next/script"
|
||||
import { Button } from "@/examples/base/ui/button"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/examples/base/ui/tooltip"
|
||||
import { DiceFaces05Icon } from "@hugeicons/core-free-icons"
|
||||
import { HugeiconsIcon } from "@hugeicons/react"
|
||||
|
||||
@@ -18,41 +13,10 @@ export const RANDOMIZE_FORWARD_TYPE = "randomize-forward"
|
||||
export function RandomButton({
|
||||
variant = "outline",
|
||||
className,
|
||||
collapsed = false,
|
||||
...props
|
||||
}: React.ComponentProps<typeof Button> & {
|
||||
collapsed?: boolean
|
||||
}) {
|
||||
}: React.ComponentProps<typeof Button>) {
|
||||
const { randomize } = useRandom()
|
||||
|
||||
if (collapsed) {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<Button
|
||||
variant={variant}
|
||||
size="icon"
|
||||
aria-label="Shuffle"
|
||||
onClick={randomize}
|
||||
className={cn(
|
||||
"size-10 touch-manipulation rounded-xl bg-transparent! transition-none select-none hover:bg-muted!",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<HugeiconsIcon icon={DiceFaces05Icon} strokeWidth={2} />
|
||||
<span className="sr-only">Shuffle</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={10}>
|
||||
Shuffle
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant={variant}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { type Style, type StyleName } from "@/registry/config"
|
||||
import { useCustomizerLayout } from "@/app/(create)/components/customizer-layout"
|
||||
import { LockButton } from "@/app/(create)/components/lock-button"
|
||||
import {
|
||||
Picker,
|
||||
@@ -11,7 +10,7 @@ import {
|
||||
PickerGroup,
|
||||
PickerRadioGroup,
|
||||
PickerRadioItem,
|
||||
PickerValueTrigger,
|
||||
PickerTrigger,
|
||||
} from "@/app/(create)/components/picker"
|
||||
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
|
||||
|
||||
@@ -19,36 +18,36 @@ export function StylePicker({
|
||||
styles,
|
||||
isMobile,
|
||||
anchorRef,
|
||||
collapsed = false,
|
||||
}: {
|
||||
styles: readonly Style[]
|
||||
isMobile: boolean
|
||||
anchorRef: React.RefObject<HTMLDivElement | null>
|
||||
collapsed?: boolean
|
||||
}) {
|
||||
const [params, setParams] = useDesignSystemSearchParams()
|
||||
const { desktopPickerSide } = useCustomizerLayout()
|
||||
|
||||
const currentStyle = styles.find((style) => style.name === params.style)
|
||||
const currentStyleIcon = currentStyle?.icon
|
||||
? React.cloneElement(currentStyle.icon, {
|
||||
className: "size-4",
|
||||
})
|
||||
: null
|
||||
|
||||
return (
|
||||
<div className="group/picker relative">
|
||||
<Picker>
|
||||
<PickerValueTrigger
|
||||
label="Style"
|
||||
value={currentStyle?.title}
|
||||
valueText={currentStyle?.title}
|
||||
indicator={currentStyleIcon}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
<PickerTrigger>
|
||||
<div className="flex flex-col justify-start text-left">
|
||||
<div className="text-xs text-muted-foreground">Style</div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{currentStyle?.title}
|
||||
</div>
|
||||
</div>
|
||||
{currentStyle?.icon && (
|
||||
<div className="pointer-events-none absolute top-1/2 right-4 flex size-4 -translate-y-1/2 items-center justify-center select-none md:right-2.5">
|
||||
{React.cloneElement(currentStyle.icon, {
|
||||
className: "size-4",
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</PickerTrigger>
|
||||
<PickerContent
|
||||
anchor={isMobile ? anchorRef : undefined}
|
||||
side={isMobile ? "top" : desktopPickerSide}
|
||||
side={isMobile ? "top" : "right"}
|
||||
align={isMobile ? "center" : "start"}
|
||||
>
|
||||
<PickerRadioGroup
|
||||
@@ -71,12 +70,10 @@ export function StylePicker({
|
||||
</PickerRadioGroup>
|
||||
</PickerContent>
|
||||
</Picker>
|
||||
{!collapsed ? (
|
||||
<LockButton
|
||||
param="style"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
) : null}
|
||||
<LockButton
|
||||
param="style"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as React from "react"
|
||||
|
||||
import { useMounted } from "@/hooks/use-mounted"
|
||||
import { BASE_COLORS, type Theme, type ThemeName } from "@/registry/config"
|
||||
import { useCustomizerLayout } from "@/app/(create)/components/customizer-layout"
|
||||
import { LockButton } from "@/app/(create)/components/lock-button"
|
||||
import {
|
||||
Picker,
|
||||
@@ -13,7 +12,7 @@ import {
|
||||
PickerRadioGroup,
|
||||
PickerRadioItem,
|
||||
PickerSeparator,
|
||||
PickerValueTrigger,
|
||||
PickerTrigger,
|
||||
} from "@/app/(create)/components/picker"
|
||||
import { useDesignSystemSearchParams } from "@/app/(create)/lib/search-params"
|
||||
|
||||
@@ -21,16 +20,13 @@ export function ThemePicker({
|
||||
themes,
|
||||
isMobile,
|
||||
anchorRef,
|
||||
collapsed = false,
|
||||
}: {
|
||||
themes: readonly Theme[]
|
||||
isMobile: boolean
|
||||
anchorRef: React.RefObject<HTMLDivElement | null>
|
||||
collapsed?: boolean
|
||||
}) {
|
||||
const mounted = useMounted()
|
||||
const [params, setParams] = useDesignSystemSearchParams()
|
||||
const { desktopPickerSide } = useCustomizerLayout()
|
||||
|
||||
const currentTheme = React.useMemo(
|
||||
() => themes.find((theme) => theme.name === params.theme),
|
||||
@@ -48,33 +44,33 @@ export function ThemePicker({
|
||||
}
|
||||
}, [currentTheme, themes, setParams])
|
||||
|
||||
const themeIndicator = mounted ? (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"--color":
|
||||
currentTheme?.cssVars?.dark?.[
|
||||
currentThemeIsBaseColor ? "muted-foreground" : "primary"
|
||||
],
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className="size-4 rounded-full bg-(--color)"
|
||||
/>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<div className="group/picker relative">
|
||||
<Picker>
|
||||
<PickerValueTrigger
|
||||
label="Theme"
|
||||
value={currentTheme?.title}
|
||||
valueText={currentTheme?.title}
|
||||
indicator={themeIndicator}
|
||||
collapsed={collapsed}
|
||||
/>
|
||||
<PickerTrigger>
|
||||
<div className="flex flex-col justify-start text-left">
|
||||
<div className="text-xs text-muted-foreground">Theme</div>
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{currentTheme?.title}
|
||||
</div>
|
||||
</div>
|
||||
{mounted && (
|
||||
<div
|
||||
style={
|
||||
{
|
||||
"--color":
|
||||
currentTheme?.cssVars?.dark?.[
|
||||
currentThemeIsBaseColor ? "muted-foreground" : "primary"
|
||||
],
|
||||
} as React.CSSProperties
|
||||
}
|
||||
className="pointer-events-none absolute top-1/2 right-4 size-4 -translate-y-1/2 rounded-full bg-(--color) select-none md:right-2.5"
|
||||
/>
|
||||
)}
|
||||
</PickerTrigger>
|
||||
<PickerContent
|
||||
anchor={isMobile ? anchorRef : undefined}
|
||||
side={isMobile ? "top" : desktopPickerSide}
|
||||
side={isMobile ? "top" : "right"}
|
||||
align={isMobile ? "center" : "start"}
|
||||
className="max-h-92"
|
||||
>
|
||||
@@ -125,12 +121,10 @@ export function ThemePicker({
|
||||
</PickerRadioGroup>
|
||||
</PickerContent>
|
||||
</Picker>
|
||||
{!collapsed ? (
|
||||
<LockButton
|
||||
param="theme"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
) : null}
|
||||
<LockButton
|
||||
param="theme"
|
||||
className="absolute top-1/2 right-8 -translate-y-1/2"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
|
||||
|
||||
export {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/examples/base/ui/tooltip"
|
||||
|
||||
export const createTooltipHandle = TooltipPrimitive.createHandle
|
||||
@@ -1,5 +1,4 @@
|
||||
import { type Metadata } from "next"
|
||||
import { cookies, headers } from "next/headers"
|
||||
|
||||
import { siteConfig } from "@/lib/config"
|
||||
import { absoluteUrl } from "@/lib/utils"
|
||||
@@ -9,11 +8,6 @@ import { PresetHandler } from "@/app/(create)/components/preset-handler"
|
||||
import { Preview } from "@/app/(create)/components/preview"
|
||||
import { WelcomeDialog } from "@/app/(create)/components/welcome-dialog"
|
||||
import { getAllItems } from "@/app/(create)/lib/api"
|
||||
import {
|
||||
CUSTOMIZER_POSITION_COOKIE_NAME,
|
||||
CUSTOMIZER_STATE_COOKIE_NAME,
|
||||
parseCustomizerPosition,
|
||||
} from "@/app/(create)/lib/customizer"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "New Project",
|
||||
@@ -45,39 +39,20 @@ export const metadata: Metadata = {
|
||||
}
|
||||
|
||||
export default async function CreatePage() {
|
||||
const cookieStore = await cookies()
|
||||
const headersList = await headers()
|
||||
const itemsByBase = await getAllItems()
|
||||
const defaultCollapsed =
|
||||
cookieStore.get(CUSTOMIZER_STATE_COOKIE_NAME)?.value === "true"
|
||||
const defaultPosition = parseCustomizerPosition(
|
||||
cookieStore.get(CUSTOMIZER_POSITION_COOKIE_NAME)?.value
|
||||
)
|
||||
const defaultIsMobile =
|
||||
headersList.get("sec-ch-ua-mobile") === "?1" ||
|
||||
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||
headersList.get("user-agent") ?? ""
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
data-slot="layout"
|
||||
data-customizer-position={defaultPosition}
|
||||
className="group/layout relative z-10 flex h-svh flex-col overflow-hidden section-soft [--customizer-width:--spacing(56)] [--gap:--spacing(4)] md:[--gap:--spacing(6)]"
|
||||
>
|
||||
<SiteHeader />
|
||||
<main
|
||||
data-slot="designer"
|
||||
data-customizer-position={defaultPosition}
|
||||
className="flex min-h-0 flex-1 flex-col gap-(--gap) p-(--gap) pt-[calc(var(--gap)*0.25)] md:flex-row data-[customizer-position=left]:md:flex-row-reverse"
|
||||
className="flex min-h-0 flex-1 flex-col gap-(--gap) p-(--gap) pt-[calc(var(--gap)*0.25)] md:flex-row-reverse"
|
||||
>
|
||||
<Preview />
|
||||
<Customizer
|
||||
itemsByBase={itemsByBase}
|
||||
defaultCollapsed={defaultCollapsed}
|
||||
defaultIsMobile={defaultIsMobile}
|
||||
defaultPosition={defaultPosition}
|
||||
/>
|
||||
<Customizer itemsByBase={itemsByBase} />
|
||||
<PresetHandler />
|
||||
<WelcomeDialog />
|
||||
</main>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
export const CUSTOMIZER_STATE_COOKIE_NAME = "customizer_state"
|
||||
export const CUSTOMIZER_STATE_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
||||
|
||||
export const CUSTOMIZER_POSITION_COOKIE_NAME = "customizer_position"
|
||||
|
||||
export const CUSTOMIZER_POSITIONS = ["left", "right"] as const
|
||||
|
||||
export type CustomizerPosition = (typeof CUSTOMIZER_POSITIONS)[number]
|
||||
export type CustomizerDesktopPickerSide = "left" | "right"
|
||||
|
||||
export function parseCustomizerPosition(
|
||||
value: string | undefined
|
||||
): CustomizerPosition {
|
||||
return value === "right" ? "right" : "left"
|
||||
}
|
||||
|
||||
export function getCustomizerDesktopPickerSide(
|
||||
position: CustomizerPosition
|
||||
): CustomizerDesktopPickerSide {
|
||||
return position === "right" ? "left" : "right"
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import { absoluteUrl } from "@/lib/utils"
|
||||
import { TailwindIndicator } from "@/components/tailwind-indicator"
|
||||
import { BASES, type Base, type BaseName } from "@/registry/config"
|
||||
import { ActionMenuScript } from "@/app/(create)/components/action-menu"
|
||||
import { CustomizerToggleScript } from "@/app/(create)/components/customizer"
|
||||
import { DesignSystemProvider } from "@/app/(create)/components/design-system-provider"
|
||||
import { HistoryScript } from "@/app/(create)/components/history-buttons"
|
||||
import { DarkModeScript } from "@/app/(create)/components/mode-switcher"
|
||||
@@ -143,7 +142,6 @@ export default async function BlockPage({
|
||||
<RandomizeScript />
|
||||
<HistoryScript />
|
||||
<DarkModeScript />
|
||||
<CustomizerToggleScript />
|
||||
<DesignSystemProvider>
|
||||
<Component />
|
||||
</DesignSystemProvider>
|
||||
|
||||
Reference in New Issue
Block a user