F: Improve color and animation styling

This commit is contained in:
Ivan 2025-04-16 15:22:31 +03:00
parent 3b26457b83
commit f0715df343
36 changed files with 333 additions and 194 deletions

View File

@ -6,11 +6,11 @@
"fractalbrew.backticks",
"streetsidesoftware.code-spell-checker",
"streetsidesoftware.code-spell-checker-russian",
"kamikillerto.vscode-colorize",
"batisteo.vscode-django",
"ms-azuretools.vscode-docker",
"dbaeumer.vscode-eslint",
"seyyedkhandon.firacode",
"nize.oklch-preview",
"ms-python.isort",
"ms-vscode.powershell",
"esbenp.prettier-vscode",

View File

@ -85,7 +85,7 @@ This readme file is used mostly to document project dependencies and conventions
<summary>VS Code plugins</summary>
<pre>
- ESLint
- Colorize
- Oklch Color Preview
- Tailwind CSS IntelliSense
- Code Spell Checker (eng + rus)
- Backticks

View File

@ -34,7 +34,7 @@ export function Navigation() {
<div
className={clsx(
'pl-2 sm:pr-4 h-12 flex cc-shadow-border',
'transition-[max-height,translate] ease-bezier duration-(--duration-move)',
'transition-[max-height,translate] ease-bezier duration-move',
noNavigationAnimation ? '-translate-y-6 max-h-0' : 'max-h-12'
)}
>

View File

@ -19,19 +19,10 @@ import { PaginationTools } from './pagination-tools';
import { TableBody } from './table-body';
import { TableFooter } from './table-footer';
import { TableHeader } from './table-header';
import { useDataTable } from './use-data-table';
import { type IConditionalStyle, useDataTable } from './use-data-table';
export { createColumnHelper, type RowSelectionState, type VisibilityState };
/** Style to conditionally apply to rows. */
export interface IConditionalStyle<TData> {
/** Callback to determine if the style should be applied. */
when: (rowData: TData) => boolean;
/** Style to apply. */
style: React.CSSProperties;
}
export interface DataTableProps<TData extends RowData>
extends Styling,
Pick<TableOptions<TData>, 'data' | 'columns' | 'onRowSelectionChange' | 'onColumnVisibilityChange'> {

View File

@ -1,7 +1,2 @@
export {
createColumnHelper,
DataTable,
type IConditionalStyle,
type RowSelectionState,
type VisibilityState
} from './data-table';
export { createColumnHelper, DataTable, type RowSelectionState, type VisibilityState } from './data-table';
export { type IConditionalStyle } from './use-data-table';

View File

@ -1,13 +1,12 @@
'use no memo';
'use client';
import { useCallback } from 'react';
import { type Table } from '@tanstack/react-table';
import { prefixes } from '@/utils/constants';
import { IconPageFirst, IconPageLast, IconPageLeft, IconPageRight } from '../icons';
import { SelectPagination } from './select-pagination';
interface PaginationToolsProps<TData> {
id?: string;
table: Table<TData>;
@ -21,15 +20,6 @@ export function PaginationTools<TData>({
onChangePaginationOption,
paginationOptions
}: PaginationToolsProps<TData>) {
const handlePaginationOptionsChange = useCallback(
(event: React.ChangeEvent<HTMLSelectElement>) => {
const perPage = Number(event.target.value);
table.setPageSize(perPage);
onChangePaginationOption?.(perPage);
},
[table, onChangePaginationOption]
);
return (
<div className='flex justify-end items-center my-2 text-sm cc-controls select-none'>
<span className='mr-3'>
@ -93,19 +83,12 @@ export function PaginationTools<TData>({
<IconPageLast size='1.5rem' />
</button>
</div>
<select
<SelectPagination
id={id ? `${id}__per_page` : undefined}
aria-label='Выбор количества строчек на странице'
value={table.getState().pagination.pageSize}
onChange={handlePaginationOptionsChange}
className='mx-2 cursor-pointer bg-transparent focus-outline'
>
{paginationOptions.map(pageSize => (
<option key={`${prefixes.page_size}${pageSize}`} value={pageSize} aria-label={`${pageSize} на страницу`}>
{pageSize} на стр
</option>
))}
</select>
table={table}
paginationOptions={paginationOptions}
onChange={onChangePaginationOption}
/>
</div>
);
}

View File

@ -0,0 +1,46 @@
'use no memo';
'use client';
import { useCallback } from 'react';
import { type Table } from '@tanstack/react-table';
import { prefixes } from '@/utils/constants';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../input/select';
interface SelectPaginationProps<TData> {
id?: string;
table: Table<TData>;
paginationOptions: number[];
onChange?: (newValue: number) => void;
}
export function SelectPagination<TData>({ id, table, paginationOptions, onChange }: SelectPaginationProps<TData>) {
const handlePaginationOptionsChange = useCallback(
(newValue: string) => {
const perPage = Number(newValue);
table.setPageSize(perPage);
onChange?.(perPage);
},
[table, onChange]
);
return (
<Select onValueChange={handlePaginationOptionsChange} defaultValue={String(table.getState().pagination.pageSize)}>
<SelectTrigger
id={id}
aria-label='Выбор количества строчек на странице'
className='mx-2 cursor-pointer bg-transparent focus-outline border-0 w-28 max-h-6 px-2 justify-end'
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{paginationOptions?.map(option => (
<SelectItem key={`${prefixes.page_size}${option}`} value={String(option)}>
{option} на стр
</SelectItem>
))}
</SelectContent>
</Select>
);
}

View File

@ -1,11 +1,11 @@
'use no memo';
'use client';
import { useCallback } from 'react';
import { type Cell, flexRender, type Row, type Table } from '@tanstack/react-table';
import clsx from 'clsx';
import { type Row, type Table } from '@tanstack/react-table';
import { SelectRow } from './select-row';
import { type IConditionalStyle } from '.';
import { TableRow } from './table-row';
import { type IConditionalStyle } from './use-data-table';
interface TableBodyProps<TData> {
table: Table<TData>;
@ -30,82 +30,43 @@ export function TableBody<TData>({
onRowClicked,
onRowDoubleClicked
}: TableBodyProps<TData>) {
const handleRowClicked = useCallback(
(target: Row<TData>, event: React.MouseEvent<Element>) => {
onRowClicked?.(target.original, event);
if (table.options.enableRowSelection && target.getCanSelect()) {
if (event.shiftKey && !!lastSelected && lastSelected !== target.id) {
const { rows, rowsById } = table.getRowModel();
const lastIndex = rowsById[lastSelected].index;
const currentIndex = target.index;
const toggleRows = rows.slice(
lastIndex > currentIndex ? currentIndex : lastIndex + 1,
lastIndex > currentIndex ? lastIndex : currentIndex + 1
);
const newSelection: Record<string, boolean> = {};
toggleRows.forEach(row => {
newSelection[row.id] = !target.getIsSelected();
});
table.setRowSelection(prev => ({ ...prev, ...newSelection }));
onChangeLastSelected(null);
} else {
onChangeLastSelected(target.id);
target.toggleSelected(!target.getIsSelected());
}
}
},
[table, lastSelected, onChangeLastSelected, onRowClicked]
const getRowStyles = useCallback(
(row: Row<TData>) =>
conditionalRowStyles
?.filter(item => !!item.style && item.when(row.original))
?.reduce((prev, item) => ({ ...prev, ...item.style }), {}),
[conditionalRowStyles]
);
const getRowStyles = useCallback(
const getRowClasses = useCallback(
(row: Row<TData>) => {
return {
...conditionalRowStyles!
.filter(item => item.when(row.original))
.reduce((prev, item) => ({ ...prev, ...item.style }), {})
};
return conditionalRowStyles
?.filter(item => !!item.className && item.when(row.original))
?.reduce((prev, item) => {
prev.push(item.className!);
return prev;
}, [] as string[]);
},
[conditionalRowStyles]
);
return (
<tbody>
{table.getRowModel().rows.map((row: Row<TData>, index) => (
<tr
{table.getRowModel().rows.map((row: Row<TData>) => (
<TableRow
key={row.id}
className={clsx(
'cc-scroll-row',
'cc-hover cc-animate-background duration-(--duration-fade)',
!noHeader && 'scroll-mt-[calc(2px+2rem)]',
table.options.enableRowSelection && row.getIsSelected()
? 'cc-selected'
: 'odd:bg-secondary even:bg-background'
)}
style={{ ...(conditionalRowStyles ? getRowStyles(row) : []) }}
onClick={event => handleRowClicked(row, event)}
onDoubleClick={event => onRowDoubleClicked?.(row.original, event)}
>
{table.options.enableRowSelection ? (
<td key={`select-${row.id}`} className='pl-2 border-y'>
<SelectRow row={row} onChangeLastSelected={onChangeLastSelected} />
</td>
) : null}
{row.getVisibleCells().map((cell: Cell<TData, unknown>) => (
<td
key={cell.id}
className={clsx(
'px-2 align-middle border-y',
dense ? 'py-1' : 'py-2',
onRowClicked || onRowDoubleClicked ? 'cursor-pointer' : 'cursor-auto'
)}
style={{
width: noHeader && index === 0 ? `calc(var(--col-${cell.column.id}-size) * 1px)` : undefined
}}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
table={table}
row={row}
className={getRowClasses(row)?.join(' ')}
style={conditionalRowStyles ? { ...getRowStyles(row) } : undefined}
noHeader={noHeader}
dense={dense}
lastSelected={lastSelected}
onChangeLastSelected={onChangeLastSelected}
onRowClicked={onRowClicked}
onRowDoubleClicked={onRowDoubleClicked}
/>
))}
</tbody>
);

View File

@ -1,4 +1,5 @@
'use no memo';
'use client';
import { flexRender, type Header, type HeaderGroup, type Table } from '@tanstack/react-table';
import clsx from 'clsx';

View File

@ -0,0 +1,108 @@
'use no memo';
import { useCallback } from 'react';
import { type Cell, flexRender, type Row, type Table } from '@tanstack/react-table';
import clsx from 'clsx';
import { cn } from '../utils';
import { SelectRow } from './select-row';
interface TableRowProps<TData> {
table: Table<TData>;
row: Row<TData>;
className?: string;
style?: React.CSSProperties;
noHeader?: boolean;
dense?: boolean;
lastSelected: string | null;
onChangeLastSelected: (newValue: string | null) => void;
onRowClicked?: (rowData: TData, event: React.MouseEvent<Element>) => void;
onRowDoubleClicked?: (rowData: TData, event: React.MouseEvent<Element>) => void;
}
export function TableRow<TData>({
table,
row,
className,
style,
noHeader,
dense,
lastSelected,
onChangeLastSelected,
onRowClicked,
onRowDoubleClicked
}: TableRowProps<TData>) {
const hasBG = className?.includes('bg-') ?? false;
const handleRowClicked = useCallback(
(target: Row<TData>, event: React.MouseEvent<Element>) => {
onRowClicked?.(target.original, event);
if (table.options.enableRowSelection && target.getCanSelect()) {
if (event.shiftKey && !!lastSelected && lastSelected !== target.id) {
const { rows, rowsById } = table.getRowModel();
const lastIndex = rowsById[lastSelected].index;
const currentIndex = target.index;
const toggleRows = rows.slice(
lastIndex > currentIndex ? currentIndex : lastIndex + 1,
lastIndex > currentIndex ? lastIndex : currentIndex + 1
);
const newSelection: Record<string, boolean> = {};
toggleRows.forEach(row => {
newSelection[row.id] = !target.getIsSelected();
});
table.setRowSelection(prev => ({ ...prev, ...newSelection }));
onChangeLastSelected(null);
} else {
onChangeLastSelected(target.id);
target.toggleSelected(!target.getIsSelected());
}
}
},
[table, lastSelected, onChangeLastSelected, onRowClicked]
);
return (
<tr
className={cn(
'cc-scroll-row',
'cc-hover cc-animate-background duration-fade',
!noHeader && 'scroll-mt-[calc(2px+2rem)]',
table.options.enableRowSelection && row.getIsSelected()
? 'cc-selected'
: !hasBG
? 'odd:bg-secondary even:bg-background'
: '',
className
)}
style={style}
onClick={event => handleRowClicked(row, event)}
onDoubleClick={event => onRowDoubleClicked?.(row.original, event)}
>
{table.options.enableRowSelection ? (
<td key={`select-${row.id}`} className='pl-2 border-y'>
<SelectRow row={row} onChangeLastSelected={onChangeLastSelected} />
</td>
) : null}
{row.getVisibleCells().map((cell: Cell<TData, unknown>) => (
<td
key={cell.id}
className={clsx(
'px-2 align-middle border-y',
dense ? 'py-1' : 'py-2',
onRowClicked || onRowDoubleClicked ? 'cursor-pointer' : 'cursor-auto'
)}
style={{
width: noHeader && row.index === 0 ? `calc(var(--col-${cell.column.id}-size) * 1px)` : undefined
}}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
);
}

View File

@ -21,7 +21,10 @@ export interface IConditionalStyle<TData> {
when: (rowData: TData) => boolean;
/** Style to apply. */
style: React.CSSProperties;
style?: React.CSSProperties;
/** Classname to apply. */
className?: string;
}
interface UseDataTableProps<TData extends RowData>

View File

@ -200,7 +200,7 @@ export function IconLogin(props: IconProps) {
export function CheckboxChecked() {
return (
<svg className='w-4 h-4 p-0.75 -ml-0.25' viewBox='0 0 512 512' fill='#ffffff'>
<svg className='w-4 h-4 p-0.75 -ml-0.25 -mt-0.25' viewBox='0 0 512 512' fill='#ffffff'>
<path d='M470.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 338.7l233.4-233.3c12.5-12.5 32.8-12.5 45.3 0z' />
</svg>
);
@ -208,7 +208,7 @@ export function CheckboxChecked() {
export function CheckboxNull() {
return (
<svg className='w-4 h-4 px-0.25' viewBox='0 0 16 16' fill='#ffffff'>
<svg className='w-4 h-4 px-0.25 -ml-0.25 -mt-0.25' viewBox='0 0 16 16' fill='#ffffff'>
<path d='M2 7.75A.75.75 0 012.75 7h10a.75.75 0 010 1.5h-10A.75.75 0 012 7.75z' />
</svg>
);

View File

@ -1,7 +1,5 @@
'use client';
import { APP_COLORS } from '@/styling/colors';
interface LoaderProps {
/** Scale of the loader from 1 to 10. */
scale?: number;
@ -57,8 +55,8 @@ const animatePulse = (startBig: boolean, duration: string) => {
export function Loader({ scale = 5, circular }: LoaderProps) {
if (circular) {
return (
<div className='flex justify-center' aria-label='three-circles-loading' aria-busy='true' role='progressbar'>
<svg height={`${scale * 20}`} width={`${scale * 20}`} viewBox='0 0 100 100' fill={APP_COLORS.bgPrimary}>
<div className='flex justify-center text-primary' aria-busy='true' role='progressbar'>
<svg height={`${scale * 20}`} width={`${scale * 20}`} viewBox='0 0 100 100' fill='currentColor'>
<path d='M31.6,3.5C5.9,13.6-6.6,42.7,3.5,68.4c10.1,25.7,39.2,38.3,64.9,28.1l-3.1-7.9c-21.3,8.4-45.4-2-53.8-23.3 c-8.4-21.3,2-45.4,23.3-53.8L31.6,3.5z'>
{animateRotation('2.25s')}
</path>
@ -73,8 +71,8 @@ export function Loader({ scale = 5, circular }: LoaderProps) {
);
} else {
return (
<div className='flex justify-center' aria-busy='true' role='progressbar'>
<svg height={`${scale * 20}`} width={`${scale * 20}`} viewBox='0 0 120 30' fill={APP_COLORS.bgPrimary}>
<div className='flex justify-center text-primary' aria-busy='true' role='progressbar'>
<svg height={`${scale * 20}`} width={`${scale * 20}`} viewBox='0 0 120 30' fill='currentColor'>
<circle cx='15' cy='15' r='16'>
{animatePulse(true, '0.8s')}
</circle>

View File

@ -28,7 +28,7 @@ export function TabLabel({
className={clsx(
'min-w-20 h-full',
'px-2 py-1 flex justify-center',
'cc-hover cc-animate-color duration-150',
'cc-hover cc-animate-color duration-select',
'text-sm whitespace-nowrap font-controls',
'select-none hover:cursor-pointer',
'outline-hidden',

View File

@ -51,7 +51,7 @@ export function BadgeHelp({ topic, padding = 'p-1', className, contentClass, sty
{...restProps}
>
<Suspense fallback={<Loader />}>
<div className='absolute right-1 text-sm top-2 bg-input' onClick={event => event.stopPropagation()}>
<div className='absolute right-1 text-sm top-2 bg-' onClick={event => event.stopPropagation()}>
<TextURL text='Справка...' href={`/manuals?topic=${topic}`} />
</div>
<TopicPage topic={topic} />

View File

@ -71,12 +71,12 @@ export function HelpRSEditor() {
<span className='bg-selected'>текущая конституента</span>
</li>
<li>
<span className='bg-(--acc-bg-green50)'>
<span className='bg-accent-green50'>
<LinkTopic text='основа' topic={HelpTopic.CC_RELATIONS} /> текущей
</span>
</li>
<li>
<span className='bg-(--acc-bg-orange50)'>
<span className='bg-accent-orange50'>
<LinkTopic text='порожденные' topic={HelpTopic.CC_RELATIONS} /> текущей
</span>
</li>

View File

@ -8,7 +8,6 @@ import { IconClose, IconFolderTree } from '@/components/icons';
import { SearchBar } from '@/components/input';
import { type Styling } from '@/components/props';
import { cn } from '@/components/utils';
import { APP_COLORS } from '@/styling/colors';
import { prefixes } from '@/utils/constants';
import { type ILibraryItem, type LibraryItemType } from '../backend/types';
@ -91,7 +90,7 @@ export function PickSchema({
const conditionalRowStyles: IConditionalStyle<ILibraryItem>[] = [
{
when: (item: ILibraryItem) => item.id === value,
style: { backgroundColor: APP_COLORS.bgSelected }
className: 'bg-selected'
}
];

View File

@ -62,7 +62,7 @@ export function SelectLocation({ value, dense, prefix, onClick, className, style
!dense && 'h-7 sm:h-8',
'pr-3 py-1 flex items-center gap-2',
'cc-scroll-row',
'cc-hover cc-animate-color',
'cc-hover cc-animate-color duration-fade',
'cursor-pointer',
'leading-3 sm:leading-4',
activeNode === item && 'cc-selected'

View File

@ -5,7 +5,6 @@ import { useIntl } from 'react-intl';
import { MiniButton } from '@/components/control';
import { createColumnHelper, DataTable, type IConditionalStyle } from '@/components/data-table';
import { IconRemove } from '@/components/icons';
import { APP_COLORS } from '@/styling/colors';
import { type IVersionInfo } from '../../backend/types';
@ -77,9 +76,7 @@ export function TableVersions({ processing, items, onDelete, selected, onSelect
const conditionalRowStyles: IConditionalStyle<IVersionInfo>[] = [
{
when: (version: IVersionInfo) => version.id === selected,
style: {
backgroundColor: APP_COLORS.bgSelected
}
className: 'bg-selected'
}
];

View File

@ -9,7 +9,6 @@ import { DataTable, type IConditionalStyle, type VisibilityState } from '@/compo
import { useWindowSize } from '@/hooks/use-window-size';
import { useFitHeight } from '@/stores/app-layout';
import { usePreferencesStore } from '@/stores/preferences';
import { APP_COLORS } from '@/styling/colors';
import { type ILibraryItem, LibraryItemType } from '../../backend/types';
import { useLibrarySearchStore } from '../../stores/library-search';
@ -35,9 +34,7 @@ export function TableLibraryItems({ items }: TableLibraryItemsProps) {
const conditionalRowStyles: IConditionalStyle<ILibraryItem>[] = [
{
when: (item: ILibraryItem) => item.item_type === LibraryItemType.OSS,
style: {
color: APP_COLORS.fgGreen
}
className: 'text-accent-green-foreground'
}
];
const tableHeight = useFitHeight('2.25rem');

View File

@ -1,6 +1,5 @@
import clsx from 'clsx';
import { APP_COLORS } from '@/styling/colors';
import { globalIDs } from '@/utils/constants';
import { colorFgCstStatus } from '../colors';
@ -24,11 +23,14 @@ export function BadgeConstituenta({ value, prefixID }: BadgeConstituentaProps) {
return (
<div
id={prefixID ? `${prefixID}${value.id}` : undefined}
className={clsx('cc-badge-constituenta', value.is_inherited && 'border-dashed')}
className={clsx(
'cc-badge-constituenta',
value.is_inherited && 'border-dashed',
value.cst_class === CstClass.BASIC ? 'bg-accent-green25' : 'bg-input'
)}
style={{
borderColor: colorFgCstStatus(value.status),
color: colorFgCstStatus(value.status),
backgroundColor: value.cst_class === CstClass.BASIC ? APP_COLORS.bgGreen25 : APP_COLORS.bgInput
color: colorFgCstStatus(value.status)
}}
data-tooltip-id={globalIDs.constituenta_tooltip}
onMouseEnter={() => setActiveCst(value)}

View File

@ -7,7 +7,6 @@ import { SearchBar } from '@/components/input';
import { type Styling } from '@/components/props';
import { cn } from '@/components/utils';
import { NoData } from '@/components/view';
import { APP_COLORS } from '@/styling/colors';
import { describeConstituenta } from '../labels';
import { type IConstituenta } from '../models/rsform';
@ -68,7 +67,7 @@ export function PickConstituenta({
const conditionalRowStyles: IConditionalStyle<IConstituenta>[] = [
{
when: (cst: IConstituenta) => cst.id === value?.id,
style: { backgroundColor: APP_COLORS.bgSelected }
className: 'bg-selected'
}
];

View File

@ -12,7 +12,6 @@ import { IconAccept, IconPageLeft, IconPageRight, IconRemove, IconReplace } from
import { type Styling } from '@/components/props';
import { cn } from '@/components/utils';
import { NoData } from '@/components/view';
import { APP_COLORS } from '@/styling/colors';
import { errorMsg } from '@/utils/labels';
import { type ICstSubstitute } from '../backend/types';
@ -224,7 +223,7 @@ export function PickSubstitutions({
const conditionalRowStyles: IConditionalStyle<IMultiSubstitution>[] = [
{
when: (item: IMultiSubstitution) => item.is_suggestion,
style: { backgroundColor: APP_COLORS.bgOrange50 }
className: 'bg-accent-orange50'
}
];

View File

@ -3,7 +3,6 @@ import { type Extension } from '@codemirror/state';
import { hoverTooltip, type TooltipView } from '@codemirror/view';
import clsx from 'clsx';
import { APP_COLORS } from '@/styling/colors';
import { findContainedNodes } from '@/utils/codemirror';
import { describeConstituentaTerm, labelGrammeme } from '../../labels';
@ -67,9 +66,9 @@ function domTooltipEntityReference(ref: IEntityReference, cst: IConstituenta | n
'max-h-100 max-w-100 min-w-40',
'dense',
'p-2 flex flex-col',
'border shadow-md',
'rounded-md shadow-md',
'cc-scroll-y',
'text-sm',
'text-sm bg-card',
'select-none cursor-auto'
);
@ -86,9 +85,8 @@ function domTooltipEntityReference(ref: IEntityReference, cst: IConstituenta | n
parseGrammemes(ref.form).forEach(gramStr => {
const gram = document.createElement('div');
gram.id = `tooltip-${gramStr}`;
gram.className = 'min-w-12 px-1 border rounded-lg text-sm text-center whitespace-nowrap';
gram.className = 'min-w-12 px-1 border rounded-lg text-sm text-center whitespace-nowrap bg-accent';
gram.style.borderWidth = '1px';
gram.style.backgroundColor = APP_COLORS.bgInput;
gram.innerText = labelGrammeme(gramStr);
grams.appendChild(gram);
});
@ -117,9 +115,9 @@ function domTooltipSyntacticReference(
'max-h-100 max-w-100 min-w-40',
'dense',
'p-2 flex flex-col',
'border shadow-md',
'rounded-md shadow-md',
'cc-scroll-y',
'text-sm',
'text-sm bg-card',
'select-none cursor-auto'
);

View File

@ -1,5 +1,6 @@
import { type Extension } from '@codemirror/state';
import { hoverTooltip, type TooltipView } from '@codemirror/view';
import clsx from 'clsx';
import { labelCstTypification } from '../../labels';
import { type IConstituenta, type IRSForm } from '../../models/rsform';
@ -32,7 +33,15 @@ export function rsHoverTooltip(schema: IRSForm, canClick?: boolean): Extension {
*/
function domTooltipConstituenta(cst?: IConstituenta, canClick?: boolean): TooltipView {
const dom = document.createElement('div');
dom.className = 'max-h-100 max-w-100 min-w-40 dense p-2 border shadow-md cc-scroll-y text-sm font-main';
dom.className = clsx(
'max-h-100 max-w-100 min-w-40',
'dense',
'p-2',
'rounded-md shadow-md',
'cc-scroll-y',
'text-sm font-main bg-card',
'select-none cursor-auto'
);
if (!cst) {
const text = document.createElement('p');

View File

@ -21,7 +21,7 @@ export function WordformButton({ text, example, grams, onSelectGrams, isSelected
'p-1',
'border rounded-none',
'cursor-pointer',
'cc-controls cc-hover cc-animate-color',
'cc-controls hover:bg-accent hover:text-foreground cc-animate-color',
isSelected && 'cc-selected'
)}
{...restProps}

View File

@ -9,7 +9,6 @@ import { DataTable, type IConditionalStyle } from '@/components/data-table';
import { IconAccept, IconRemove, IconReset } from '@/components/icons';
import { NoData } from '@/components/view';
import { useDialogsStore } from '@/stores/dialogs';
import { APP_COLORS } from '@/styling/colors';
import { type ICstCreateDTO } from '../../backend/types';
import { PickConstituenta } from '../../components/pick-constituenta';
@ -100,7 +99,7 @@ export function TabArguments() {
const conditionalRowStyles: IConditionalStyle<IArgumentValue>[] = [
{
when: (arg: IArgumentValue) => arg.alias === selectedArgument?.alias,
style: { backgroundColor: APP_COLORS.bgSelected }
className: 'bg-selected'
}
];

View File

@ -47,7 +47,7 @@ export function DlgShowAST() {
{!isDragging && hoverNodeDebounced ? (
<div key={hoverNodeDebounced.uid}>
<span>{expression.slice(0, hoverNodeDebounced.start)}</span>
<span className='bg-selected cc-animate-background starting:bg-background duration-500'>
<span className='bg-selected cc-animate-background starting:bg-background duration-move'>
{expression.slice(hoverNodeDebounced.start, hoverNodeDebounced.finish)}
</span>
<span>{expression.slice(hoverNodeDebounced.finish)}</span>

View File

@ -49,7 +49,7 @@ export function StatusBar({ className, isModified, processing, activeCst, parseD
'select-none',
'cursor-pointer',
'focus-frame outline-none',
'transition-colors duration-500'
'transition-colors duration-fade'
)}
style={{ backgroundColor: processing ? APP_COLORS.bgDefault : colorStatusBar(status) }}
data-tooltip-id={globalIDs.tooltip}

View File

@ -4,7 +4,6 @@ import { useEffect } from 'react';
import { createColumnHelper, DataTable, type IConditionalStyle } from '@/components/data-table';
import { NoData, TextContent } from '@/components/view';
import { APP_COLORS } from '@/styling/colors';
import { PARAMETER, prefixes } from '@/utils/constants';
import { BadgeConstituenta } from '../../../components/badge-constituenta';
@ -66,21 +65,15 @@ export function TableSideConstituents({ autoScroll = true, maxHeight }: TableSid
const conditionalRowStyles: IConditionalStyle<IConstituenta>[] = [
{
when: (cst: IConstituenta) => !!activeCst && cst.id === activeCst.id,
style: {
backgroundColor: APP_COLORS.bgSelected
}
className: 'bg-selected'
},
{
when: (cst: IConstituenta) => !!activeCst && cst.spawner === activeCst.id && cst.id !== activeCst.id,
style: {
backgroundColor: APP_COLORS.bgOrange50
}
className: 'bg-accent-orange50'
},
{
when: (cst: IConstituenta) => !!activeCst && cst.spawn.includes(activeCst.id),
style: {
backgroundColor: APP_COLORS.bgGreen50
}
className: 'bg-accent-green50'
}
];

View File

@ -45,6 +45,23 @@
--color-sec-400: var(--clr-sec-400);
--color-sec-600: var(--clr-sec-600);
--color-accent-red: var(--acc-bg-red);
--color-accent-green: var(--acc-bg-green);
--color-accent-green25: var(--acc-bg-green25);
--color-accent-green50: var(--acc-bg-green50);
--color-accent-blue: var(--acc-bg-blue);
--color-accent-purple: var(--acc-bg-purple);
--color-accent-teal: var(--acc-bg-teal);
--color-accent-orange: var(--acc-bg-orange);
--color-accent-orange50: var(--acc-bg-orange50);
--color-accent-red-foreground: var(--acc-fg-red);
--color-accent-green-foreground: var(--acc-fg-green);
--color-accent-blue-foreground: var(--acc-fg-blue);
--color-accent-purple-foreground: var(--acc-fg-purple);
--color-accent-teal-foreground: var(--acc-fg-teal);
--color-accent-orange-foreground: var(--acc-fg-orange);
/* stylelint-disable-next-line custom-property-pattern */
--z-index-*: initial;
--z-index-bottom: 0;
@ -66,11 +83,12 @@
--ease-bezier: cubic-bezier(0.4, 0, 0.2, 1);
--duration-move: 500ms;
/* Animation durations */
--duration-select: 100ms;
--duration-dropdown: 200ms;
--duration-modal: 300ms;
--duration-fade: 300ms;
--duration-dropdown: 200ms;
--duration-select: 100ms;
--duration-move: 500ms;
/* ========= shadcn theme ============ */

View File

@ -29,7 +29,6 @@ export const APP_COLORS = {
bgGreen25: 'var(--acc-bg-green25)',
bgGreen50: 'var(--acc-bg-green50)',
bgOrange50: 'var(--acc-bg-orange50)',
fgRed: 'var(--acc-fg-red)',
fgGreen: 'var(--acc-fg-green)',

View File

@ -9,13 +9,21 @@
cursor: pointer;
white-space: nowrap;
transition-property: background-color;
transition-property: background-color, color;
transition-timing-function: var(--ease-bezier);
transition-duration: 500ms;
&:hover {
background-color: var(--color-accent);
}
.dark & {
color: color-mix(in oklab, var(--color-foreground) 70%, transparent);
&:hover {
color: var(--color-foreground);
}
}
}
@utility cc-btn-primary {

View File

@ -28,10 +28,10 @@
--clr-prim-999: oklch(000% 0 0deg);
--clr-sec-0: oklch(100% 0 0deg);
--clr-sec-100: oklch(095% 0.050 262deg);
--clr-sec-200: oklch(088% 0.050 262deg);
--clr-sec-400: oklch(075% 0.150 262deg);
--clr-sec-600: oklch(060% 0.250 262deg);
--clr-sec-100: oklch(095% 0.025 262deg);
--clr-sec-200: oklch(090% 0.050 262deg);
--clr-sec-400: oklch(073% 0.140 262deg);
--clr-sec-600: oklch(060% 0.230 262deg);
--clr-warn-600: oklch(060% 0.250 27deg);
--clr-ok-600: oklch(060% 0.250 138deg);
@ -39,23 +39,27 @@
--clr-select-node: oklch(080% 0.250 180deg);
/* Highlight accents */
--acc-bg-red: oklch(085% 0.150 27deg);
--acc-bg-red: oklch(083% 0.120 27deg);
--acc-bg-green: oklch(085% 0.150 138deg);
--acc-bg-blue: oklch(085% 0.150 262deg);
--acc-bg-purple: oklch(085% 0.200 295deg);
--acc-bg-teal: oklch(085% 0.200 210deg);
--acc-bg-orange: oklch(085% 0.150 62deg);
--acc-bg-teal: oklch(082% 0.180 210deg);
--acc-bg-orange: oklch(085% 0.130 62deg);
--acc-bg-green25: oklch(097% 0.150 138deg);
--acc-bg-green50: oklch(090% 0.150 138deg);
--acc-bg-green25: oklch(097% 0.080 138deg);
--acc-bg-green50: oklch(092% 0.150 138deg);
--acc-bg-orange50: oklch(090% 0.044 62deg);
--acc-fg-red: oklch(060% 0.220 27deg);
--acc-fg-green: oklch(060% 0.250 138deg);
--acc-fg-blue: oklch(060% 0.300 262deg);
--acc-fg-green: oklch(060% 0.220 138deg);
--acc-fg-blue: oklch(060% 0.230 262deg);
--acc-fg-purple: oklch(060% 0.250 295deg);
--acc-fg-teal: oklch(060% 0.150 210deg);
--acc-fg-teal: oklch(060% 0.140 210deg);
--acc-fg-orange: oklch(060% 0.150 62deg);
/* React Tooltip */
--rt-color-white: var(--clr-prim-0);
--rt-color-dark: var(--clr-prim-999);
}
/* Dark Theme */
@ -69,15 +73,15 @@
--clr-prim-999: oklch(095% 0 0deg);
--clr-sec-0: oklch(100% 0 0deg);
--clr-sec-100: oklch(025% 0.200 295deg);
--clr-sec-200: oklch(035% 0.150 295deg);
--clr-sec-100: oklch(030% 0.075 70deg);
--clr-sec-200: oklch(040% 0.150 295deg);
--clr-sec-400: oklch(055% 0.200 295deg);
--clr-sec-600: oklch(070% 0.250 295deg);
--clr-sec-600: oklch(070% 0.170 295deg);
--clr-warn-600: oklch(065% 0.200 27deg);
--clr-ok-600: oklch(065% 0.200 138deg);
--clr-select-node: oklch(070% 0.250 180deg);
--clr-select-node: oklch(070% 0.160 180deg);
/* Highlight accents */
--acc-bg-red: oklch(050% 0.150 27deg);
@ -91,10 +95,14 @@
--acc-bg-green50: oklch(038% 0.200 138deg);
--acc-bg-orange50: oklch(038% 0.075 62deg);
--acc-fg-red: oklch(075% 0.200 27deg);
--acc-fg-red: oklch(073% 0.200 27deg);
--acc-fg-green: oklch(075% 0.150 138deg);
--acc-fg-blue: oklch(075% 0.150 262deg);
--acc-fg-purple: oklch(075% 0.250 295deg);
--acc-fg-blue: oklch(075% 0.135 262deg);
--acc-fg-purple: oklch(075% 0.150 295deg);
--acc-fg-teal: oklch(075% 0.150 210deg);
--acc-fg-orange: oklch(075% 0.150 62deg);
/* React Tooltip */
--rt-color-white: var(--clr-prim-999);
--rt-color-dark: var(--clr-prim-0);
}

View File

@ -19,6 +19,10 @@
.cm-tooltip {
z-index: 100;
border-color: var(--clr-prim-400);
border-width: 1px;
border-radius: 0.25rem;
}
.cm-selectionBackground {

View File

@ -22,7 +22,6 @@
@utility cc-hover {
&:hover:not(:disabled) {
color: var(--color-accent-foreground);
background-color: var(--color-accent);
}
}
@ -181,3 +180,28 @@
mask-composite: intersect;
}
@utility duration-select {
--tw-duration: var(--duration-select);
transition-duration: var(--duration-select);
}
@utility duration-dropdown {
--tw-duration: var(--duration-dropdown);
transition-duration: var(--duration-dropdown);
}
@utility duration-modal {
--tw-duration: var(--duration-modal);
transition-duration: var(--duration-modal);
}
@utility duration-fade {
--tw-duration: var(--duration-fade);
transition-duration: var(--duration-fade);
}
@utility duration-move {
--tw-duration: var(--duration-move);
transition-duration: var(--duration-move);
}