F: Replace SelectSingle with combobox

This commit is contained in:
Ivan 2025-04-11 19:59:08 +03:00
parent bcd54d22b6
commit b91ec793e9
21 changed files with 220 additions and 422 deletions

View File

@ -1,93 +0,0 @@
'use client';
import { useState } from 'react';
import { Check, ChevronDownIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '../ui/button';
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '../ui/command';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
interface ComboBoxProps<Option> {
items?: Option[];
value: Option | null;
onChange: (newValue: Option | null) => void;
filterFunc: (item: Option, query: string) => boolean;
idFunc: (item: Option) => string;
labelValueFunc: (item: Option) => string;
labelOptionFunc: (item: Option) => string;
placeholder?: string;
className?: string;
noBorder?: boolean;
}
/**
* Displays a combo-select component.
*/
export function ComboBox<Option>({
items,
filterFunc,
value,
onChange,
labelValueFunc,
labelOptionFunc,
idFunc,
noBorder,
placeholder,
className
}: ComboBoxProps<Option>) {
const [open, setOpen] = useState(false);
const [query, setQuery] = useState('');
const filtered = items?.filter(item => filterFunc(item, query)) ?? [];
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant='ghost'
role='combobox'
aria-expanded={open}
className={cn(
'justify-between font-normal hover:bg-input',
"[&_svg:not([class*='text-'])]:text-muted-foreground",
open && 'cursor-auto',
!noBorder && 'border',
!value && 'text-muted-foreground hover:text-muted-foreground',
className
)}
>
<span className='truncate'>{value ? labelValueFunc(value) : placeholder}</span>
<ChevronDownIcon />
</Button>
</PopoverTrigger>
<PopoverContent className='p-0'>
<Command>
<CommandInput value={query} onValueChange={setQuery} placeholder='Поиск...' className='h-9' />
<CommandList>
<CommandEmpty>Список пуст</CommandEmpty>
<CommandGroup>
{filtered.map(item => (
<CommandItem
key={idFunc(item)}
value={idFunc(item)}
onSelect={() => {
onChange(item);
setOpen(false);
}}
>
{labelOptionFunc(item)}
<Check className={cn('ml-auto', value === item ? 'opacity-100' : 'opacity-0')} />
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@ -5,7 +5,6 @@ export { FileInput } from './file-input';
export { Label } from './label';
export { SearchBar } from './search-bar';
export { SelectMulti, type SelectMultiProps } from './select-multi';
export { SelectSingle } from './select-single';
export { SelectTree } from './select-tree';
export { TextArea } from './text-area';
export { TextInput } from './text-input';

View File

@ -1,126 +0,0 @@
'use client';
import Select, {
type ClearIndicatorProps,
components,
type DropdownIndicatorProps,
type GroupBase,
type Props,
type StylesConfig
} from 'react-select';
import { useWindowSize } from '@/hooks/use-window-size';
import { APP_COLORS, SELECT_THEME } from '@/styling/colors';
import { IconClose, IconDropArrow, IconDropArrowUp } from '../icons';
function DropdownIndicator<Option, Group extends GroupBase<Option> = GroupBase<Option>>(
props: DropdownIndicatorProps<Option, false, Group>
) {
return (
components.DropdownIndicator && (
<components.DropdownIndicator {...props}>
{props.selectProps.menuIsOpen ? <IconDropArrowUp size='1.25rem' /> : <IconDropArrow size='1.25rem' />}
</components.DropdownIndicator>
)
);
}
function ClearIndicator<Option, Group extends GroupBase<Option> = GroupBase<Option>>(
props: ClearIndicatorProps<Option, false, Group>
) {
return (
components.ClearIndicator && (
<components.ClearIndicator {...props}>
<IconClose size='1.25rem' />
</components.ClearIndicator>
)
);
}
interface SelectSingleProps<Option, Group extends GroupBase<Option> = GroupBase<Option>>
extends Omit<Props<Option, false, Group>, 'theme' | 'menuPortalTarget'> {
noPortal?: boolean;
noBorder?: boolean;
}
/**
* Displays a single-select component.
*/
export function SelectSingle<Option, Group extends GroupBase<Option> = GroupBase<Option>>({
noPortal,
noBorder,
...restProps
}: SelectSingleProps<Option, Group>) {
const size = useWindowSize();
const adjustedStyles: StylesConfig<Option, false, Group> = {
container: defaultStyles => ({
...defaultStyles,
borderRadius: '0.25rem'
}),
control: (defaultStyles, { isDisabled }) => ({
...defaultStyles,
borderRadius: '0.25rem',
...(noBorder ? { borderWidth: 0 } : {}),
cursor: isDisabled ? 'not-allowed' : 'pointer',
boxShadow: 'none'
}),
menuPortal: defaultStyles => ({
...defaultStyles,
zIndex: 9999
}),
menuList: defaultStyles => ({
...defaultStyles,
padding: 0
}),
option: (defaultStyles, { isSelected }) => ({
...defaultStyles,
padding: '0.25rem 0.75rem',
fontSize: '0.875rem',
lineHeight: '1.25rem',
backgroundColor: isSelected ? APP_COLORS.bgSelected : defaultStyles.backgroundColor,
color: isSelected ? APP_COLORS.fgSelected : defaultStyles.color,
borderWidth: '1px',
borderColor: APP_COLORS.border
}),
input: defaultStyles => ({ ...defaultStyles }),
placeholder: defaultStyles => ({ ...defaultStyles }),
singleValue: defaultStyles => ({ ...defaultStyles }),
dropdownIndicator: base => ({
...base,
paddingTop: 0,
paddingBottom: 0
}),
clearIndicator: base => ({
...base,
paddingTop: 0,
paddingBottom: 0
})
};
return (
<Select
noOptionsMessage={() => 'Список пуст'}
components={{ DropdownIndicator, ClearIndicator }}
theme={theme => ({
...theme,
borderRadius: 0,
spacing: {
...theme.spacing,
baseUnit: size.isSmall ? 2 : 4,
menuGutter: 2,
controlHeight: size.isSmall ? 28 : 38
},
colors: {
...theme.colors,
...SELECT_THEME
}
})}
menuPortalTarget={!noPortal ? document.body : null}
styles={adjustedStyles}
classNames={{ container: () => 'focus-frame' }}
{...restProps}
/>
);
}

View File

@ -1,4 +1,3 @@
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';

View File

@ -0,0 +1,128 @@
'use client';
import { useEffect, useRef, useState } from 'react';
import { ChevronDownIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
import { MiniButton } from '../control';
import { IconRemove } from '../icons';
import { type Styling } from '../props';
import { Button } from './button';
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from './command';
import { Popover, PopoverContent, PopoverTrigger } from './popover';
interface ComboBoxProps<Option> extends Styling {
id?: string;
items?: Option[];
value: Option | null;
onChange: (newValue: Option | null) => void;
idFunc: (item: Option) => string;
labelValueFunc: (item: Option) => string;
labelOptionFunc: (item: Option) => string;
placeholder?: string;
hidden?: boolean;
noBorder?: boolean;
clearable?: boolean;
noSearch?: boolean;
}
/**
* Displays a combo-select component.
*/
export function ComboBox<Option>({
id,
items,
value,
onChange,
labelValueFunc,
labelOptionFunc,
idFunc,
noBorder,
placeholder,
className,
style,
hidden,
clearable,
noSearch
}: ComboBoxProps<Option>) {
const [open, setOpen] = useState(false);
const [popoverWidth, setPopoverWidth] = useState<number | undefined>(undefined);
const triggerRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
if (triggerRef.current) {
setPopoverWidth(triggerRef.current.offsetWidth);
}
}, [open]);
function handleChangeValue(newValue: Option | null) {
onChange(newValue);
setOpen(false);
}
function handleClear(event: React.MouseEvent<HTMLButtonElement>) {
event.stopPropagation();
handleChangeValue(null);
}
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
id={id}
ref={triggerRef}
variant='ghost'
role='combobox'
aria-expanded={open}
className={cn(
'relative justify-between font-normal bg-input hover:bg-input',
open && 'cursor-auto',
!noBorder && 'border',
noBorder && 'rounded-md',
!value && 'text-muted-foreground hover:text-muted-foreground',
className
)}
style={style}
hidden={hidden && !open}
>
<span className='truncate'>{value ? labelValueFunc(value) : placeholder}</span>
<ChevronDownIcon className={cn('text-muted-foreground', clearable && !!value && 'opacity-0')} />
{clearable && !!value ? (
<MiniButton
noHover
title='Очистить'
aria-label='Очистить'
className='absolute right-2 text-muted-foreground hover:text-warn-600'
icon={<IconRemove size='1rem' />}
onClick={handleClear}
/>
) : null}
</Button>
</PopoverTrigger>
<PopoverContent sideOffset={-1} className='p-0' style={{ width: popoverWidth }}>
<Command>
{!noSearch ? <CommandInput placeholder='Поиск...' className='h-9' /> : null}
<CommandList>
<CommandEmpty>Список пуст</CommandEmpty>
<CommandGroup>
{items?.map(item => (
<CommandItem
key={idFunc(item)}
value={labelOptionFunc(item)}
onSelect={() => handleChangeValue(item)}
className={cn(value === item && 'bg-selected text-selected-foreground')}
>
{labelOptionFunc(item)}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@ -1,4 +1,3 @@
import * as React from 'react';
import { Command as CommandPrimitive } from 'cmdk';
import { SearchIcon } from 'lucide-react';

View File

@ -1,6 +1,5 @@
'use client';
import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { XIcon } from 'lucide-react';

View File

@ -1,4 +1,3 @@
import * as React from 'react';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { cn } from '@/lib/utils';

View File

@ -1,6 +1,7 @@
import * as React from 'react';
'use client';
import * as SelectPrimitive from '@radix-ui/react-select';
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
import { ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
@ -114,15 +115,11 @@ function SelectItem({ className, children, ...props }: React.ComponentProps<type
'outline-none focus:bg-accent focus:text-accent-foreground',
'*:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2',
"[&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"data-[state='checked']:not-[:hover]:bg-selected data-[state='checked']:not-[:hover]:text-selected-foreground",
className
)}
{...props}
>
<span className='absolute right-2 flex size-3.5 items-center justify-center'>
<SelectPrimitive.ItemIndicator>
<CheckIcon className='size-4' />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
);

View File

@ -102,11 +102,13 @@ export function EditorLibraryItem({ schema, isAttachedToOSS }: EditorLibraryItem
</div>
<div className='relative' ref={ownerSelector.ref} onBlur={ownerSelector.handleBlur}>
{ownerSelector.isOpen ? (
<div className='absolute -top-2 right-0'>
<SelectUser className='w-100 text-sm' value={schema.owner} onChange={onSelectUser} />
</div>
) : null}
<SelectUser
className='absolute -top-2 right-0 w-100 text-sm'
value={schema.owner}
onChange={user => user && onSelectUser(user)}
hidden={!ownerSelector.isOpen}
/>
<ValueIcon
className='sm:mb-1'
icon={<IconOwner size='1.25rem' className='icon-primary' />}

View File

@ -1,7 +1,5 @@
'use client';
import { type Styling } from '@/components/props';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { ComboBox } from '@/components/ui/combo-box';
import { type ILibraryItem } from '../backend/types';
@ -15,31 +13,15 @@ interface SelectLibraryItemProps extends Styling {
noBorder?: boolean;
}
export function SelectLibraryItem({
id,
items,
value,
onChange,
placeholder = 'Выберите схему',
...restProps
}: SelectLibraryItemProps) {
function handleSelect(newValue: string) {
const newItem = items?.find(item => item.id === Number(newValue)) ?? null;
onChange(newItem);
}
export function SelectLibraryItem({ items, placeholder = 'Выберите схему', ...restProps }: SelectLibraryItemProps) {
return (
<Select onValueChange={handleSelect} defaultValue={value ? String(value.id) : undefined}>
<SelectTrigger id={id} {...restProps}>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent className='max-w-80'>
{items?.map(item => (
<SelectItem key={`${id ?? 'default'}-item-select-${item.id}`} value={String(item.id)}>
{`${item.alias}: ${item.title}`}
</SelectItem>
))}
</SelectContent>
</Select>
<ComboBox
items={items}
placeholder={placeholder}
idFunc={item => String(item.id)}
labelValueFunc={item => `${item.alias}: ${item.title}`}
labelOptionFunc={item => `${item.alias}: ${item.title}`}
{...restProps}
/>
);
}

View File

@ -63,7 +63,8 @@ export function DlgEditEditors() {
<SelectUser
filter={id => !selected.includes(id)} //
value={null}
onChange={onAddEditor}
noAnonymous
onChange={user => user && onAddEditor(user)}
className='w-100'
/>
</div>

View File

@ -112,7 +112,7 @@ export function ToolbarSearch({ className, total, filtered }: ToolbarSearchProps
aria-label='Выбор пользователя для фильтра по владельцу'
placeholder='Выберите владельца'
noBorder
className='min-w-60 text-sm mx-1 mb-1'
className='min-w-60 mx-1 mb-1'
value={filterUser}
onChange={setFilterUser}
/>

View File

@ -1,49 +1,27 @@
'use client';
import clsx from 'clsx';
import { SelectSingle } from '@/components/input';
import { type Styling } from '@/components/props';
import { ComboBox } from '@/components/ui/combo-box';
import { type IOperation } from '../models/oss';
import { matchOperation } from '../models/oss-api';
interface SelectOperationProps extends Styling {
id?: string;
value: IOperation | null;
onChange: (newValue: IOperation | null) => void;
items?: IOperation[];
placeholder?: string;
noBorder?: boolean;
popoverClassname?: string;
}
export function SelectOperation({
className,
items,
value,
onChange,
placeholder = 'Выберите операцию',
...restProps
}: SelectOperationProps) {
const options =
items?.map(cst => ({
value: cst.id,
label: `${cst.alias}: ${cst.title}`
})) ?? [];
function filter(option: { value: string | undefined; label: string }, query: string) {
const operation = items?.find(item => item.id === Number(option.value));
return !operation ? false : matchOperation(operation, query);
}
export function SelectOperation({ items, placeholder = 'Выберите операцию', ...restProps }: SelectOperationProps) {
return (
<SelectSingle
className={clsx('text-ellipsis', className)}
options={options}
value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : null}
onChange={data => onChange(items?.find(cst => cst.id === data?.value) ?? null)}
filterOption={filter}
<ComboBox
items={items}
placeholder={placeholder}
idFunc={operation => String(operation.id)}
labelValueFunc={operation => `${operation.alias}: ${operation.title}`}
labelOptionFunc={operation => `${operation.alias}: ${operation.title}`}
{...restProps}
/>
);

View File

@ -20,13 +20,12 @@ import {
} from '@/features/rsform/models/rslang-api';
import { infoMsg } from '@/utils/labels';
import { TextMatcher } from '@/utils/utils';
import { Graph } from '../../../models/graph';
import { type IOssLayout } from '../backend/types';
import { describeSubstitutionError } from '../labels';
import { type IOperation, type IOperationSchema, SubstitutionErrorType } from './oss';
import { type IOperationSchema, SubstitutionErrorType } from './oss';
import { type Position2D } from './oss-layout';
export const GRID_SIZE = 10; // pixels - size of OSS grid
@ -36,17 +35,6 @@ const DISTANCE_Y = 100; // pixels - insert y-distance between node centers
const STARTING_SUB_INDEX = 900; // max semantic index for starting substitution
/**
* Checks if a given target {@link IOperation} matches the specified query using.
*
* @param target - The target object to be matched.
* @param query - The query string used for matching.
*/
export function matchOperation(target: IOperation, query: string): boolean {
const matcher = new TextMatcher(query);
return matcher.test(target.alias) || matcher.test(target.title);
}
/**
* Sorts library items relevant for the specified {@link IOperationSchema}.
*/

View File

@ -1,11 +1,10 @@
import { ComboBox } from '@/components/input/combo-box';
import { ComboBox } from '@/components/ui/combo-box';
import { describeConstituenta, describeConstituentaTerm } from '../labels';
import { type IConstituenta } from '../models/rsform';
import { matchConstituenta } from '../models/rsform-api';
import { CstMatchMode } from '../stores/cst-search';
interface SelectConstituentaProps {
id?: string;
value: IConstituenta | null;
onChange: (newValue: IConstituenta | null) => void;
@ -24,7 +23,6 @@ export function SelectConstituenta({
<ComboBox
items={items}
placeholder={placeholder}
filterFunc={(item, query) => matchConstituenta(item, query, CstMatchMode.ALL)}
idFunc={cst => String(cst.id)}
labelValueFunc={cst => `${cst.alias}: ${describeConstituentaTerm(cst)}`}
labelOptionFunc={cst => `${cst.alias}${cst.is_inherited ? '*' : ''}: ${describeConstituenta(cst)}`}

View File

@ -20,6 +20,7 @@ export function SelectMultiGrammeme({ value, onChange, ...restProps }: SelectMul
<SelectMulti
options={options}
value={value}
isSearchable={false}
onChange={newValue => onChange([...newValue].sort((left, right) => grammemeCompare(left.value, right.value)))}
{...restProps}
/>

View File

@ -2,7 +2,8 @@
import { useTemplatesSuspense } from '@/features/library/backend/use-templates';
import { SelectSingle, TextArea } from '@/components/input';
import { TextArea } from '@/components/input';
import { ComboBox } from '@/components/ui/combo-box';
import { useRSForm } from '../../backend/use-rsform';
import { PickConstituenta } from '../../components/pick-constituenta';
@ -24,6 +25,7 @@ export function TabTemplate() {
const { templates } = useTemplatesSuspense();
const { schema: templateSchema } = useRSForm({ itemID: templateID ?? undefined });
const selectedTemplate = templates.find(item => item.id === templateID);
if (!templateID) {
onChangeTemplateID(templates[0].id);
@ -40,47 +42,37 @@ export function TabTemplate() {
? ''
: `${prototype?.term_raw}${prototype?.definition_raw ? `${prototype?.definition_raw}` : ''}`;
const templateSelector = templates.map(template => ({
value: template.id,
label: template.title
}));
const categorySelector: { value: number; label: string }[] = !templateSchema
const categorySelector = !templateSchema
? []
: templateSchema.items
.filter(cst => cst.cst_type === CATEGORY_CST_TYPE)
.map(cst => ({
value: cst.id,
label: cst.term_raw
}));
: templateSchema.items.filter(cst => cst.cst_type === CATEGORY_CST_TYPE);
return (
<div className='cc-fade-in'>
<div className='flex border-t border-x rounded-t-md clr-input'>
<SelectSingle
<div className='flex gap-1 border-t border-x rounded-t-md clr-input'>
<ComboBox
value={selectedTemplate ?? null}
items={templates}
noBorder
noSearch
placeholder='Источник'
className='w-48'
options={templateSelector}
value={templateID ? { value: templateID, label: templates.find(item => item.id == templateID)!.title } : null}
onChange={data => onChangeTemplateID(data ? data.value : null)}
idFunc={item => String(item.id)}
labelValueFunc={item => item.title}
labelOptionFunc={item => item.title}
onChange={item => onChangeTemplateID(item?.id ?? null)}
/>
<SelectSingle
<ComboBox
value={filterCategory}
items={categorySelector}
noBorder
isSearchable={false}
noSearch
clearable
placeholder='Выберите категорию'
className='grow ml-1 border-none'
options={categorySelector}
value={
filterCategory && templateSchema
? {
value: filterCategory.id,
label: filterCategory.term_raw
}
: null
}
onChange={data => onChangeFilterCategory(data ? templateSchema?.cstByID.get(data?.value) ?? null : null)}
isClearable
className='grow'
idFunc={cst => String(cst.id)}
labelValueFunc={cst => cst.term_raw}
labelOptionFunc={cst => cst.term_raw}
onChange={cst => onChangeFilterCategory(cst)}
/>
</div>
<PickConstituenta

View File

@ -1,22 +1,21 @@
'use client';
import clsx from 'clsx';
import { SelectSingle } from '@/components/input';
import { type Styling } from '@/components/props';
import { ComboBox } from '@/components/ui/combo-box';
import { type IUserInfo } from '../backend/types';
import { useLabelUser } from '../backend/use-label-user';
import { useUsers } from '../backend/use-users';
import { matchUser } from '../models/user-api';
interface SelectUserProps extends Styling {
value: number | null;
onChange: (newValue: number) => void;
onChange: (newValue: number | null) => void;
filter?: (userID: number) => boolean;
placeholder?: string;
noBorder?: boolean;
noAnonymous?: boolean;
hidden?: boolean;
}
function compareUsers(a: IUserInfo, b: IUserInfo) {
@ -33,10 +32,8 @@ function compareUsers(a: IUserInfo, b: IUserInfo) {
}
export function SelectUser({
className,
filter,
value,
onChange,
noAnonymous,
placeholder = 'Выберите пользователя',
...restProps
}: SelectUserProps) {
@ -46,28 +43,16 @@ export function SelectUser({
const items = filter ? users.filter(user => filter(user.id)) : users;
const sorted = [
...items.filter(user => !!user.first_name || !!user.last_name).sort(compareUsers),
...items.filter(user => !user.first_name && !user.last_name)
];
const options = sorted.map(user => ({
value: user.id,
label: getUserLabel(user.id)
}));
function filterLabel(option: { value: string | undefined; label: string }, query: string) {
const user = items.find(item => item.id === Number(option.value));
return !user ? false : matchUser(user, query);
}
...(!noAnonymous ? items.filter(user => !user.first_name && !user.last_name) : [])
].map(user => user.id);
return (
<SelectSingle
className={clsx('text-ellipsis', className)}
options={options}
value={value ? { value: value, label: getUserLabel(value) } : null}
onChange={data => {
if (data?.value !== undefined) onChange(data.value);
}}
filterOption={filterLabel}
<ComboBox
items={sorted}
placeholder={placeholder}
idFunc={user => String(user)}
labelValueFunc={user => getUserLabel(user)}
labelOptionFunc={user => getUserLabel(user)}
{...restProps}
/>
);

View File

@ -1,19 +0,0 @@
/**
* Module: API for formal representation for Users.
*/
import { TextMatcher } from '@/utils/utils';
import { type IUserInfo } from '../backend/types';
/**
* Checks if a given target {@link IConstituenta} matches the specified query using the provided matching mode.
*
* @param target - The target object to be matched.
* @param query - The query string used for matching.
* @param mode - The matching mode to determine which properties to include in the matching process.
*/
export function matchUser(target: IUserInfo, query: string): boolean {
const matcher = new TextMatcher(query);
return matcher.test(target.last_name) || matcher.test(target.first_name);
}

View File

@ -26,6 +26,21 @@
outline: 2px solid hotpink !important;
} */
:root {
/* --radius: 0.625rem; */
--primary: oklch(20.5% 0 0deg);
--primary-foreground: oklch(98.5% 0 0deg);
--secondary: oklch(97% 0 0deg);
--secondary-foreground: oklch(20.5% 0 0deg);
}
.dark {
--primary: oklch(92.2% 0 0deg);
--primary-foreground: oklch(20.5% 0 0deg);
--secondary: oklch(26.9% 0 0deg);
--secondary-foreground: oklch(98.5% 0 0deg);
}
@theme inline {
/* stylelint-disable-next-line custom-property-pattern */
--color-*: initial;
@ -54,6 +69,9 @@
--color-ok-600: var(--clr-ok-600);
--color-selected: var(--clr-sec-200);
--color-selected-foreground: var(--clr-prim-999);
/* stylelint-disable-next-line custom-property-pattern */
--z-index-*: initial;
--z-index-bottom: 0;
@ -80,38 +98,9 @@
--duration-fade: 300ms;
--duration-dropdown: 200ms;
--duration-select: 100ms;
}
/* ========= shadcn theme ============ */
:root {
/* --radius: 0.625rem; */
--primary: oklch(20.5% 0 0deg);
--primary-foreground: oklch(98.5% 0 0deg);
--secondary: oklch(97% 0 0deg);
--secondary-foreground: oklch(20.5% 0 0deg);
--chart-1: oklch(64.6% 0.222 41.116deg);
--chart-2: oklch(60% 0.118 184.704deg);
--chart-3: oklch(39.8% 0.07 227.392deg);
--chart-4: oklch(82.8% 0.189 84.429deg);
--chart-5: oklch(76.9% 0.188 70.08deg);
}
.dark {
--primary: oklch(92.2% 0 0deg);
--primary-foreground: oklch(20.5% 0 0deg);
--secondary: oklch(26.9% 0 0deg);
--secondary-foreground: oklch(98.5% 0 0deg);
--chart-1: oklch(48.8% 0.243 264.376deg);
--chart-2: oklch(69.6% 0.17 162.48deg);
--chart-3: oklch(76.9% 0.188 70.08deg);
--chart-4: oklch(62.7% 0.265 303.9deg);
--chart-5: oklch(64.5% 0.246 16.439deg);
}
@theme inline {
/* --radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);