import { type SVGProps } from 'react'
import { Radio as RadioPrimitive } from '@base-ui/react/radio'
import { RadioGroup as Radio } from '@base-ui/react/radio-group'
import { CircleCheck, Palette, RotateCcw } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { IconDir } from '@/assets/custom/icon-dir'
import { IconLayoutCompact } from '@/assets/custom/icon-layout-compact'
import { IconLayoutDefault } from '@/assets/custom/icon-layout-default'
import { IconLayoutFull } from '@/assets/custom/icon-layout-full'
import { IconSidebarFloating } from '@/assets/custom/icon-sidebar-floating'
import { IconSidebarInset } from '@/assets/custom/icon-sidebar-inset'
import { IconSidebarSidebar } from '@/assets/custom/icon-sidebar-sidebar'
import { IconThemeDark } from '@/assets/custom/icon-theme-dark'
import { IconThemeLight } from '@/assets/custom/icon-theme-light'
import { IconThemeSystem } from '@/assets/custom/icon-theme-system'
import {
type ContentLayout,
THEME_PRESETS,
type ThemePreset,
type ThemeRadius,
type ThemeScale,
} from '@/lib/theme-customization'
import { cn } from '@/lib/utils'
import { useDirection } from '@/context/direction-provider'
import { type Collapsible, useLayout } from '@/context/layout-provider'
import { useThemeCustomization } from '@/context/theme-customization-provider'
import { useTheme } from '@/context/theme-provider'
import { Button } from '@/components/ui/button'
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@/components/ui/sheet'
import { useSidebar } from './ui/sidebar'
const Item = RadioPrimitive.Root
export function ConfigDrawer() {
const { t } = useTranslation()
const { setOpen } = useSidebar()
const { resetDir } = useDirection()
const { resetTheme } = useTheme()
const { resetLayout } = useLayout()
const { resetCustomization } = useThemeCustomization()
const handleReset = () => {
setOpen(true)
resetDir()
resetTheme()
resetLayout()
resetCustomization()
}
return (
}
>
{t('Theme Settings')}
{t('Adjust the appearance and layout to suit your preferences.')}
{t('Reset')}
)
}
function SectionTitle(props: {
title: string
showReset?: boolean
onReset?: () => void
className?: string
}) {
return (
{props.title}
{props.showReset && props.onReset && (
)}
)
}
function RadioGroupItem(props: {
item: {
value: string
label: string
icon: (props: SVGProps) => React.ReactElement
}
isTheme?: boolean
}) {
const isTheme = props.isTheme ?? false
return (
-
{props.item.label}
)
}
function ThemeConfig() {
const { t } = useTranslation()
const { defaultTheme, theme, setTheme } = useTheme()
return (
setTheme(defaultTheme)}
/>
{[
{ value: 'system', label: t('System'), icon: IconThemeSystem },
{ value: 'light', label: t('Light'), icon: IconThemeLight },
{ value: 'dark', label: t('Dark'), icon: IconThemeDark },
].map((item) => (
))}
{t('Choose between system preference, light mode, or dark mode')}
)
}
function PresetConfig() {
const { t } = useTranslation()
const { defaults, customization, setPreset } = useThemeCustomization()
return (
setPreset(defaults.preset)}
/>
setPreset(v as ThemePreset)}
className='grid w-full grid-cols-4 gap-3'
aria-label={t('Select color preset')}
>
{THEME_PRESETS.map((preset) => (
-
{t(`preset.${preset.value}`)}
))}
)
}
const RADIUS_OPTIONS: {
value: ThemeRadius
label: string
// CSS border-radius value used to render the visual preview corner.
preview: string
}[] = [
{ value: 'default', label: 'Auto', preview: '999px' },
{ value: 'none', label: '0', preview: '0' },
{ value: 'sm', label: '0.3', preview: '0.3rem' },
{ value: 'md', label: '0.5', preview: '0.5rem' },
{ value: 'lg', label: '0.75', preview: '0.75rem' },
{ value: 'xl', label: '1.0', preview: '1rem' },
]
function RadiusConfig() {
const { t } = useTranslation()
const { defaults, customization, setRadius } = useThemeCustomization()
return (
setRadius(defaults.radius)}
/>
setRadius(v as ThemeRadius)}
className='grid w-full grid-cols-6 gap-2'
aria-label={t('Select border radius')}
>
{RADIUS_OPTIONS.map((option) => (
-
{option.label}
))}
)
}
/**
* Visual preview rows for the density preset. Each row's height represents
* the relative line-height density (compact = tight rows, comfortable = wide).
*/
function ScalePreview(props: { rows: number; rowGap: string }) {
return (
{Array.from({ length: props.rows }).map((_, i) => (
))}
)
}
function ScaleConfig() {
const { t } = useTranslation()
const { defaults, customization, setScale } = useThemeCustomization()
const scaleOptions: {
value: ThemeScale
label: string
rows: number
rowGap: string
}[] = [
{ value: 'sm', label: t('Compact'), rows: 4, rowGap: '3px' },
{ value: 'default', label: t('Default'), rows: 3, rowGap: '6px' },
{ value: 'lg', label: t('Comfortable'), rows: 2, rowGap: '10px' },
]
return (
setScale(defaults.scale)}
/>
setScale(v as ThemeScale)}
className='grid w-full grid-cols-3 gap-4'
aria-label={t('Select interface density')}
>
{scaleOptions.map((option) => (
-
{option.label}
))}
)
}
function SidebarConfig() {
const { t } = useTranslation()
const { defaultVariant, variant, setVariant } = useLayout()
return (
setVariant(defaultVariant)}
/>
{[
{ value: 'inset', label: t('Inset'), icon: IconSidebarInset },
{
value: 'floating',
label: t('Floating'),
icon: IconSidebarFloating,
},
{ value: 'sidebar', label: t('Sidebar'), icon: IconSidebarSidebar },
].map((item) => (
))}
)
}
function LayoutConfig() {
const { t } = useTranslation()
const { open, setOpen } = useSidebar()
const { defaultCollapsible, collapsible, setCollapsible } = useLayout()
const radioState = open ? 'default' : collapsible
return (
{
setOpen(true)
setCollapsible(defaultCollapsible)
}}
/>
{
if (v === 'default') {
setOpen(true)
return
}
setOpen(false)
setCollapsible(v as Collapsible)
}}
className='grid w-full max-w-md grid-cols-3 gap-4'
aria-label={t('Select layout style')}
aria-describedby='layout-description'
>
{[
{ value: 'default', label: t('Default'), icon: IconLayoutDefault },
{ value: 'icon', label: t('Compact'), icon: IconLayoutCompact },
{
value: 'offcanvas',
label: t('Full layout'),
icon: IconLayoutFull,
},
].map((item) => (
))}
{t(
'Choose between default expanded, compact icon-only, or full layout mode'
)}
)
}
function ContentLayoutConfig() {
const { t } = useTranslation()
const { defaults, customization, setContentLayout } = useThemeCustomization()
return (
setContentLayout(defaults.contentLayout)}
/>
setContentLayout(v as ContentLayout)}
className='grid w-full grid-cols-2 gap-4'
aria-label={t('Select content width')}
>
{[
{ value: 'full', label: t('Full width') },
{ value: 'centered', label: t('Centered') },
].map((option) => (
-
{option.label}
))}
)
}
/**
* Mini "page" mock used as the visual preview for content-width options.
* `full` fills horizontally, `centered` clamps the body to a narrow column.
*/
function ContentLayoutPreview(props: { centered: boolean }) {
return (
)
}
function DirConfig() {
const { t } = useTranslation()
const { defaultDir, dir, setDir } = useDirection()
return (
setDir(defaultDir)}
/>
{[
{
value: 'ltr',
label: t('Left to Right'),
icon: (props: SVGProps) => (
),
},
{
value: 'rtl',
label: t('Right to Left'),
icon: (props: SVGProps) => (
),
},
].map((item) => (
))}
{t('Choose between left-to-right or right-to-left site direction')}
)
}