mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
UI update: new fonts and adjustments
This commit is contained in:
parent
0a4d48f6ef
commit
7414aa0186
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -60,6 +60,7 @@
|
||||||
"filterset",
|
"filterset",
|
||||||
"forceatlas",
|
"forceatlas",
|
||||||
"futr",
|
"futr",
|
||||||
|
"Geologica",
|
||||||
"Grammeme",
|
"Grammeme",
|
||||||
"Grammemes",
|
"Grammemes",
|
||||||
"GRND",
|
"GRND",
|
||||||
|
|
|
@ -1,30 +1,25 @@
|
||||||
{
|
{
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"es2021": true
|
"es2021": true
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:@typescript-eslint/recommended-type-checked",
|
"plugin:@typescript-eslint/recommended-type-checked",
|
||||||
"plugin:react-hooks/recommended"
|
"plugin:react-hooks/recommended"
|
||||||
],
|
],
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": "latest",
|
"ecmaVersion": "latest",
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"project": ["tsconfig.json", "tsconfig.node.json"]
|
"project": ["tsconfig.json", "tsconfig.node.json"]
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": ["react-refresh", "simple-import-sort", "eslint-plugin-tsdoc"],
|
||||||
"react-refresh",
|
"rules": {
|
||||||
"simple-import-sort",
|
"no-console": "off",
|
||||||
"eslint-plugin-tsdoc"
|
"require-jsdoc": "off",
|
||||||
],
|
"react-refresh/only-export-components": ["off", { "allowConstantExport": true }],
|
||||||
"rules": {
|
"simple-import-sort/imports": "warn",
|
||||||
"react-refresh/only-export-components": [
|
"tsdoc/syntax": "warn"
|
||||||
"off",
|
}
|
||||||
{ "allowConstantExport": true }
|
|
||||||
],
|
|
||||||
"simple-import-sort/imports": "warn",
|
|
||||||
"tsdoc/syntax": "warn"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,13 @@
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="description" content="Веб-приложение для работы с концептуальными схемами" />
|
<meta name="description" content="Веб-приложение для работы с концептуальными схемами" />
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Geologica:wght@100;200;300;400;500;600;700;800;900&family=Noto+Sans+Math&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
|
||||||
<title>Концепт Портал</title>
|
<title>Концепт Портал</title>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -35,7 +35,7 @@ function Button({
|
||||||
{
|
{
|
||||||
'border rounded': !noBorder,
|
'border rounded': !noBorder,
|
||||||
'px-1': dense,
|
'px-1': dense,
|
||||||
'px-3 py-2': !dense,
|
'px-3 py-1': !dense,
|
||||||
'cursor-progress': loading,
|
'cursor-progress': loading,
|
||||||
'cursor-pointer': !loading,
|
'cursor-pointer': !loading,
|
||||||
'outline-none': noOutline,
|
'outline-none': noOutline,
|
||||||
|
@ -49,7 +49,7 @@ function Button({
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{icon ? icon : null}
|
{icon ? icon : null}
|
||||||
{text ? <span className='font-semibold'>{text}</span> : null}
|
{text ? <span className='font-medium'>{text}</span> : null}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { globalIDs } from '@/utils/constants';
|
||||||
|
|
||||||
import { CheckboxCheckedIcon } from '../Icons';
|
import { CheckboxCheckedIcon } from '../Icons';
|
||||||
import { CProps } from '../props';
|
import { CProps } from '../props';
|
||||||
import Label from './Label';
|
|
||||||
|
|
||||||
export interface CheckboxProps extends Omit<CProps.Button, 'value' | 'onClick'> {
|
export interface CheckboxProps extends Omit<CProps.Button, 'value' | 'onClick'> {
|
||||||
label?: string;
|
label?: string;
|
||||||
|
@ -57,7 +56,9 @@ function Checkbox({ id, disabled, label, title, className, value, setValue, ...r
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<Label className={cursor} text={label} htmlFor={id} />
|
<label className={clsx('text-sm whitespace-nowrap', cursor)} htmlFor={id}>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ function ConceptSearch({ value, onChange, noBorder, ...restProps }: ConceptSearc
|
||||||
<TextInput
|
<TextInput
|
||||||
noOutline
|
noOutline
|
||||||
placeholder='Поиск'
|
placeholder='Поиск'
|
||||||
className='pl-10'
|
className='w-full pl-10'
|
||||||
noBorder={noBorder}
|
noBorder={noBorder}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={event => (onChange ? onChange(event.target.value) : undefined)}
|
onChange={event => (onChange ? onChange(event.target.value) : undefined)}
|
||||||
|
|
|
@ -15,7 +15,7 @@ function ConceptTab({ label, title, className, ...otherProps }: ConceptTabProps)
|
||||||
'min-w-[6rem]',
|
'min-w-[6rem]',
|
||||||
'px-2 py-1 flex justify-center',
|
'px-2 py-1 flex justify-center',
|
||||||
'clr-tab',
|
'clr-tab',
|
||||||
'text-sm whitespace-nowrap small-caps font-semibold',
|
'text-sm whitespace-nowrap font-controls',
|
||||||
'select-none hover:cursor-pointer',
|
'select-none hover:cursor-pointer',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -44,7 +44,7 @@ function FileInput({ label, acceptType, title, className, style, onChange, ...re
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
<Button text={label} icon={<BiUpload size='1.5rem' />} onClick={handleUploadClick} title={title} />
|
<Button text={label} icon={<BiUpload size='1.25rem' />} onClick={handleUploadClick} title={title} />
|
||||||
<Label text={fileName} />
|
<Label text={fileName} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,7 +11,7 @@ function Label({ text, className, ...restProps }: LabelProps) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<label className={clsx('text-sm font-semibold whitespace-nowrap', className)} {...restProps}>
|
<label className={clsx('text-sm font-medium whitespace-nowrap', className)} {...restProps}>
|
||||||
{text}
|
{text}
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,7 +8,7 @@ interface LabeledValueProps {
|
||||||
function LabeledValue({ id, label, text, title }: LabeledValueProps) {
|
function LabeledValue({ id, label, text, title }: LabeledValueProps) {
|
||||||
return (
|
return (
|
||||||
<div className='flex justify-between gap-3'>
|
<div className='flex justify-between gap-3'>
|
||||||
<label className='font-semibold' title={title} htmlFor={id}>
|
<label title={title} htmlFor={id}>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
<span id={id}>{text}</span>
|
<span id={id}>{text}</span>
|
||||||
|
|
|
@ -28,7 +28,7 @@ function SelectorButton({
|
||||||
data-tooltip-content={title}
|
data-tooltip-content={title}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'px-1 flex flex-start items-center gap-1',
|
'px-1 flex flex-start items-center gap-1',
|
||||||
'text-sm small-caps select-none',
|
'text-sm font-controls select-none',
|
||||||
'text-btn clr-text-controls',
|
'text-btn clr-text-controls',
|
||||||
'disabled:cursor-not-allowed cursor-pointer',
|
'disabled:cursor-not-allowed cursor-pointer',
|
||||||
{
|
{
|
||||||
|
@ -41,7 +41,7 @@ function SelectorButton({
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{icon ? icon : null}
|
{icon ? icon : null}
|
||||||
{text ? <div className={'font-semibold whitespace-nowrap pb-1'}>{text}</div> : null}
|
{text ? <div className={'whitespace-nowrap pb-1'}>{text}</div> : null}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,9 @@ function SubmitButton({ text = 'ОК', icon, disabled, loading, className, ...re
|
||||||
<button
|
<button
|
||||||
type='submit'
|
type='submit'
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'px-3 py-2 flex gap-2 items-center justify-center',
|
'px-3 py-1 flex gap-2 items-center justify-center',
|
||||||
'border',
|
'border',
|
||||||
'font-semibold',
|
'font-medium',
|
||||||
'clr-btn-primary',
|
'clr-btn-primary',
|
||||||
'select-none disabled:cursor-not-allowed',
|
'select-none disabled:cursor-not-allowed',
|
||||||
loading && 'cursor-progress',
|
loading && 'cursor-progress',
|
||||||
|
|
|
@ -30,7 +30,7 @@ function SwitchButton<ValueType>({
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'px-2 py-1',
|
'px-2 py-1',
|
||||||
'border rounded-none',
|
'border rounded-none',
|
||||||
'font-semibold small-caps',
|
'font-controls',
|
||||||
'clr-btn-clear clr-hover',
|
'clr-btn-clear clr-hover',
|
||||||
'cursor-pointer',
|
'cursor-pointer',
|
||||||
isSelected && 'clr-selected',
|
isSelected && 'clr-selected',
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { globalIDs } from '@/utils/constants';
|
||||||
|
|
||||||
import { CheckboxCheckedIcon, CheckboxNullIcon } from '../Icons';
|
import { CheckboxCheckedIcon, CheckboxNullIcon } from '../Icons';
|
||||||
import { CheckboxProps } from './Checkbox';
|
import { CheckboxProps } from './Checkbox';
|
||||||
import Label from './Label';
|
|
||||||
|
|
||||||
export interface TristateProps extends Omit<CheckboxProps, 'value' | 'setValue'> {
|
export interface TristateProps extends Omit<CheckboxProps, 'value' | 'setValue'> {
|
||||||
value: boolean | null;
|
value: boolean | null;
|
||||||
|
@ -65,7 +64,9 @@ function Tristate({ id, disabled, label, title, className, value, setValue, ...r
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<Label className={cursor} text={label} htmlFor={id} />
|
<label className={clsx('text-sm whitespace-nowrap', cursor)} htmlFor={id}>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,19 +30,19 @@ function TableHeader<TData>({ table, headPosition, enableRowSelection, enableSor
|
||||||
<th
|
<th
|
||||||
key={header.id}
|
key={header.id}
|
||||||
colSpan={header.colSpan}
|
colSpan={header.colSpan}
|
||||||
className='px-2 py-2 text-xs font-semibold select-none whitespace-nowrap'
|
className='px-2 py-2 text-xs font-medium select-none whitespace-nowrap'
|
||||||
style={{
|
style={{
|
||||||
textAlign: header.getSize() > 100 ? 'left' : 'center',
|
textAlign: 'center',
|
||||||
width: header.getSize(),
|
width: header.getSize(),
|
||||||
cursor: enableSorting && header.column.getCanSort() ? 'pointer' : 'auto'
|
cursor: enableSorting && header.column.getCanSort() ? 'pointer' : 'auto'
|
||||||
}}
|
}}
|
||||||
onClick={enableSorting ? header.column.getToggleSortingHandler() : undefined}
|
onClick={enableSorting ? header.column.getToggleSortingHandler() : undefined}
|
||||||
>
|
>
|
||||||
{!header.isPlaceholder ? (
|
{!header.isPlaceholder ? (
|
||||||
<div className='flex gap-1'>
|
<span className='inline-flex gap-1'>
|
||||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||||
{enableSorting && header.column.getCanSort() ? <SortingIcon column={header.column} /> : null}
|
{enableSorting && header.column.getCanSort() ? <SortingIcon column={header.column} /> : null}
|
||||||
</div>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -21,7 +21,7 @@ function NavigationButton({ icon, title, onClick, text }: NavigationButtonProps)
|
||||||
'mr-1 h-full', //
|
'mr-1 h-full', //
|
||||||
'flex items-center gap-1',
|
'flex items-center gap-1',
|
||||||
'clr-btn-nav',
|
'clr-btn-nav',
|
||||||
'small-caps whitespace-nowrap',
|
'font-controls whitespace-nowrap',
|
||||||
{
|
{
|
||||||
'px-2': text,
|
'px-2': text,
|
||||||
'px-4': !text
|
'px-4': !text
|
||||||
|
@ -29,7 +29,7 @@ function NavigationButton({ icon, title, onClick, text }: NavigationButtonProps)
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{icon ? <span>{icon}</span> : null}
|
{icon ? <span>{icon}</span> : null}
|
||||||
{text ? <span className='font-semibold'>{text}</span> : null}
|
{text ? <span>{text}</span> : null}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,9 +82,9 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
|
||||||
{ tag: tags.propertyName, color: colors.fgTeal }, // Radical
|
{ tag: tags.propertyName, color: colors.fgTeal }, // Radical
|
||||||
{ tag: tags.keyword, color: colors.fgBlue }, // keywords
|
{ tag: tags.keyword, color: colors.fgBlue }, // keywords
|
||||||
{ tag: tags.literal, color: colors.fgBlue }, // literals
|
{ tag: tags.literal, color: colors.fgBlue }, // literals
|
||||||
{ tag: tags.controlKeyword, fontWeight: '500' }, // R | I | D
|
{ tag: tags.controlKeyword, fontWeight: '400' }, // R | I | D
|
||||||
{ tag: tags.unit, fontSize: '0.75rem' }, // indices
|
{ tag: tags.unit, fontSize: '0.75rem' }, // indices
|
||||||
{ tag: tags.brace, color: colors.fgPurple, fontWeight: '700' } // braces (curly brackets)
|
{ tag: tags.brace, color: colors.fgPurple, fontWeight: '600' } // braces (curly brackets)
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
[disabled, colors, darkMode]
|
[disabled, colors, darkMode]
|
||||||
|
@ -131,6 +131,7 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
|
||||||
<div className={clsx('flex flex-col gap-2', className, cursor)} style={style}>
|
<div className={clsx('flex flex-col gap-2', className, cursor)} style={style}>
|
||||||
<Label text={label} htmlFor={id} />
|
<Label text={label} htmlFor={id} />
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
|
className='font-math'
|
||||||
id={id}
|
id={id}
|
||||||
ref={thisRef}
|
ref={thisRef}
|
||||||
basicSetup={editorSetup}
|
basicSetup={editorSetup}
|
||||||
|
|
|
@ -1,28 +1,25 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
import ConceptTooltip from '@/components/Common/ConceptTooltip';
|
|
||||||
import ConstituentaTooltip from '@/components/Help/ConstituentaTooltip';
|
import ConstituentaTooltip from '@/components/Help/ConstituentaTooltip';
|
||||||
import { IConstituenta } from '@/models/rsform';
|
import { IConstituenta } from '@/models/rsform';
|
||||||
import { isMockCst } from '@/models/rsformAPI';
|
import { isMockCst } from '@/models/rsformAPI';
|
||||||
import { colorFgCstStatus, IColorTheme } from '@/utils/color';
|
import { colorFgCstStatus, IColorTheme } from '@/utils/color';
|
||||||
import { describeExpressionStatus } from '@/utils/labels';
|
|
||||||
|
|
||||||
interface ConstituentaBadgeProps {
|
interface ConstituentaBadgeProps {
|
||||||
prefixID?: string;
|
prefixID?: string;
|
||||||
shortTooltip?: boolean;
|
|
||||||
value: IConstituenta;
|
value: IConstituenta;
|
||||||
theme: IColorTheme;
|
theme: IColorTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ConstituentaBadge({ value, prefixID, shortTooltip, theme }: ConstituentaBadgeProps) {
|
function ConstituentaBadge({ value, prefixID, theme }: ConstituentaBadgeProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id={`${prefixID}${value.alias}`}
|
id={`${prefixID}${value.alias}`}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'min-w-[3.1rem] max-w-[3.1rem]',
|
'min-w-[3.1rem] max-w-[3.1rem]', //
|
||||||
'px-1',
|
'px-1',
|
||||||
'border rounded-md',
|
'border rounded-md',
|
||||||
'text-center font-semibold whitespace-nowrap'
|
'text-center font-medium whitespace-nowrap'
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
borderColor: colorFgCstStatus(value.status, theme),
|
borderColor: colorFgCstStatus(value.status, theme),
|
||||||
|
@ -31,14 +28,7 @@ function ConstituentaBadge({ value, prefixID, shortTooltip, theme }: Constituent
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{value.alias}
|
{value.alias}
|
||||||
{!shortTooltip ? <ConstituentaTooltip anchor={`#${prefixID}${value.alias}`} data={value} /> : null}
|
<ConstituentaTooltip anchor={`#${prefixID}${value.alias}`} data={value} />
|
||||||
{shortTooltip ? (
|
|
||||||
<ConceptTooltip anchorSelect={`#${prefixID}${value.alias}`} place='right'>
|
|
||||||
<p>
|
|
||||||
<b>Статус</b>: {describeExpressionStatus(value.status)}
|
|
||||||
</p>
|
|
||||||
</ConceptTooltip>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,10 @@ function GrammemeBadge({ key, grammeme }: GrammemeBadgeProps) {
|
||||||
<div
|
<div
|
||||||
key={key}
|
key={key}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'min-w-[3rem]',
|
'min-w-[3rem]', //
|
||||||
'px-1',
|
'px-1',
|
||||||
'border rounded-md',
|
'border rounded-md',
|
||||||
'text-sm font-semibold text-center whitespace-nowrap'
|
'text-sm font-medium text-center whitespace-nowrap'
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
borderColor: colorFgGrammeme(grammeme, colors),
|
borderColor: colorFgGrammeme(grammeme, colors),
|
||||||
|
|
|
@ -20,13 +20,7 @@ function InfoCstClass({ header }: InfoCstClassProps) {
|
||||||
return (
|
return (
|
||||||
<p key={`${prefixes.cst_status_list}${index}`}>
|
<p key={`${prefixes.cst_status_list}${index}`}>
|
||||||
<span
|
<span
|
||||||
className={clsx(
|
className={clsx('inline-block', 'min-w-[7rem]', 'px-1', 'border', 'text-center text-sm font-controls')}
|
||||||
'inline-block',
|
|
||||||
'min-w-[7rem]',
|
|
||||||
'px-1',
|
|
||||||
'border',
|
|
||||||
'text-center text-sm small-caps font-semibold'
|
|
||||||
)}
|
|
||||||
style={{ backgroundColor: colorBgCstClass(cstClass, colors) }}
|
style={{ backgroundColor: colorBgCstClass(cstClass, colors) }}
|
||||||
>
|
>
|
||||||
{labelCstClass(cstClass)}
|
{labelCstClass(cstClass)}
|
||||||
|
|
|
@ -22,11 +22,11 @@ function InfoCstStatus({ title }: InfoCstStatusProps) {
|
||||||
<p key={`${prefixes.cst_status_list}${index}`}>
|
<p key={`${prefixes.cst_status_list}${index}`}>
|
||||||
<span
|
<span
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'inline-block',
|
'inline-block', //
|
||||||
'min-w-[7rem]',
|
'min-w-[7rem]',
|
||||||
'px-1',
|
'px-1',
|
||||||
'border',
|
'border',
|
||||||
'text-center text-sm small-caps font-semibold'
|
'text-center text-sm font-controls'
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: colorBgCstStatus(status, colors) }}
|
style={{ backgroundColor: colorBgCstStatus(status, colors) }}
|
||||||
>
|
>
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { useIntl } from 'react-intl';
|
||||||
import { useUsers } from '@/context/UsersContext';
|
import { useUsers } from '@/context/UsersContext';
|
||||||
import { ILibraryItemEx } from '@/models/library';
|
import { ILibraryItemEx } from '@/models/library';
|
||||||
|
|
||||||
|
import LabeledValue from '../Common/LabeledValue';
|
||||||
|
|
||||||
interface InfoLibraryItemProps {
|
interface InfoLibraryItemProps {
|
||||||
item?: ILibraryItemEx;
|
item?: ILibraryItemEx;
|
||||||
}
|
}
|
||||||
|
@ -12,26 +14,13 @@ function InfoLibraryItem({ item }: InfoLibraryItemProps) {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col gap-1'>
|
<div className='flex flex-col gap-1'>
|
||||||
<div className='flex'>
|
<LabeledValue label='Владелец' text={getUserLabel(item?.owner ?? null)} />
|
||||||
<label className='font-semibold'>Владелец:</label>
|
<LabeledValue label='Отслеживают' text={item?.subscribers.length ?? 0} />
|
||||||
<span className='min-w-[200px] ml-2 overflow-ellipsis overflow-hidden whitespace-nowrap'>
|
<LabeledValue
|
||||||
{getUserLabel(item?.owner ?? null)}
|
label='Дата обновления'
|
||||||
</span>
|
text={item ? new Date(item?.time_update).toLocaleString(intl.locale) : ''}
|
||||||
</div>
|
/>
|
||||||
<div className='flex'>
|
<LabeledValue label='Дата создания' text={item ? new Date(item?.time_create).toLocaleString(intl.locale) : ''} />
|
||||||
<label className='font-semibold'>Отслеживают:</label>
|
|
||||||
<span id='subscriber-count' className='ml-2'>
|
|
||||||
{item?.subscribers.length ?? 0}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className='flex'>
|
|
||||||
<label className='font-semibold'>Дата обновления:</label>
|
|
||||||
<span className='ml-2'>{item && new Date(item?.time_update).toLocaleString(intl.locale)}</span>
|
|
||||||
</div>
|
|
||||||
<div className='flex'>
|
|
||||||
<label className='font-semibold'>Дата создания:</label>
|
|
||||||
<span className='ml-8'>{item && new Date(item?.time_create).toLocaleString(intl.locale)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ function SelectedCounter({ total, selected, hideZero, position = 'top-0 left-0'
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Overlay position={`px-2 ${position}`} className='select-none whitespace-nowrap small-caps clr-app'>
|
<Overlay position={`px-2 ${position}`} className='select-none whitespace-nowrap clr-app'>
|
||||||
Выбор {selected} из {total}
|
Выбор {selected} из {total}
|
||||||
</Overlay>
|
</Overlay>
|
||||||
);
|
);
|
||||||
|
|
|
@ -26,7 +26,7 @@ function WordformButton({ text, example, grams, onSelectGrams, isSelected, ...re
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
<p className='font-semibold'>{text}</p>
|
<p className='font-medium'>{text}</p>
|
||||||
<p>{example}</p>
|
<p>{example}</p>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,49 +6,53 @@
|
||||||
|
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
:root {
|
:root {
|
||||||
|
--font-ui: 'Geologica', sans-serif;
|
||||||
|
--font-main: 'Rubik', sans-serif, 'Noto Sans Math', 'Segoe UI Symbol';
|
||||||
|
--font-math: 'Noto Sans Math', sans-serif, 'Segoe UI Symbol';
|
||||||
|
|
||||||
/* Light Theme */
|
/* Light Theme */
|
||||||
--cl-bg-120: hsl(000, 000%, 100%);
|
--cl-bg-120: hsl(000, 000%, 100%);
|
||||||
--cl-bg-100: hsl(000, 000%, 098%);
|
--cl-bg-100: hsl(000, 000%, 098%);
|
||||||
--cl-bg-80: hsl(000, 000%, 094%);
|
--cl-bg-80: hsl(000, 000%, 094%);
|
||||||
--cl-bg-60: hsl(000, 000%, 091%);
|
--cl-bg-60: hsl(000, 000%, 091%);
|
||||||
--cl-bg-40: hsl(000, 000%, 080%);
|
--cl-bg-40: hsl(000, 000%, 080%);
|
||||||
|
|
||||||
--cl-fg-60: hsl(000, 000%, 065%);
|
--cl-fg-60: hsl(000, 000%, 065%);
|
||||||
--cl-fg-80: hsl(000, 000%, 047%);
|
--cl-fg-80: hsl(000, 000%, 047%);
|
||||||
--cl-fg-100: hsl(000, 000%, 000%);
|
--cl-fg-100: hsl(000, 000%, 000%);
|
||||||
|
|
||||||
--cl-prim-bg-100: hsl(220, 100%, 060%);
|
--cl-prim-bg-100: hsl(220, 100%, 060%);
|
||||||
--cl-prim-bg-80: hsl(220, 080%, 092%);
|
--cl-prim-bg-80: hsl(220, 080%, 092%);
|
||||||
--cl-prim-bg-60: hsl(190, 080%, 094%);
|
--cl-prim-bg-60: hsl(190, 080%, 094%);
|
||||||
|
|
||||||
--cl-prim-fg-80: hsl(220, 100%, 050%);
|
|
||||||
--cl-prim-fg-100: hsl(000, 000%, 100%);
|
|
||||||
|
|
||||||
--cl-red-bg-100: hsl(000, 100%, 095%);
|
--cl-prim-fg-80: hsl(220, 100%, 050%);
|
||||||
--cl-red-fg-100: hsl(000, 072%, 051%);
|
--cl-prim-fg-100: hsl(000, 000%, 100%);
|
||||||
--cl-green-fg-100: hsl(120, 080%, 37%);
|
|
||||||
|
--cl-red-bg-100: hsl(000, 100%, 095%);
|
||||||
|
--cl-red-fg-100: hsl(000, 072%, 051%);
|
||||||
|
--cl-green-fg-100: hsl(120, 080%, 37%);
|
||||||
|
|
||||||
/* Dark Theme */
|
/* Dark Theme */
|
||||||
--cd-bg-120: hsl(000, 000%, 005%);
|
--cd-bg-120: hsl(000, 000%, 005%);
|
||||||
--cd-bg-100: hsl(000, 000%, 009%);
|
--cd-bg-100: hsl(000, 000%, 009%);
|
||||||
--cd-bg-80: hsl(000, 000%, 015%);
|
--cd-bg-80: hsl(000, 000%, 015%);
|
||||||
--cd-bg-60: hsl(000, 000%, 022%);
|
--cd-bg-60: hsl(000, 000%, 022%);
|
||||||
--cd-bg-40: hsl(000, 000%, 035%);
|
--cd-bg-40: hsl(000, 000%, 035%);
|
||||||
|
|
||||||
--cd-fg-60: hsl(000, 000%, 055%);
|
|
||||||
--cd-fg-80: hsl(000, 000%, 080%);
|
|
||||||
--cd-fg-100: hsl(000, 000%, 093%);
|
|
||||||
|
|
||||||
--cd-prim-bg-100: hsl(267, 050%, 050%);
|
--cd-fg-60: hsl(000, 000%, 055%);
|
||||||
--cd-prim-bg-80: hsl(267, 050%, 032%);
|
--cd-fg-80: hsl(000, 000%, 080%);
|
||||||
--cd-prim-bg-60: hsl(269, 030%, 028%);
|
--cd-fg-100: hsl(000, 000%, 093%);
|
||||||
|
|
||||||
--cd-prim-fg-80: hsl(267, 070%, 070%);
|
--cd-prim-bg-100: hsl(267, 050%, 050%);
|
||||||
--cd-prim-fg-100: hsl(000, 000%, 100%);
|
--cd-prim-bg-80: hsl(267, 050%, 032%);
|
||||||
|
--cd-prim-bg-60: hsl(269, 030%, 028%);
|
||||||
|
|
||||||
--cd-red-bg-100: hsl(000, 100%, 015%);
|
--cd-prim-fg-80: hsl(267, 070%, 070%);
|
||||||
--cd-red-fg-100: hsl(000, 080%, 055%);
|
--cd-prim-fg-100: hsl(000, 000%, 100%);
|
||||||
--cd-green-fg-100: hsl(120, 080%, 042%);
|
|
||||||
|
--cd-red-bg-100: hsl(000, 100%, 015%);
|
||||||
|
--cd-red-fg-100: hsl(000, 080%, 055%);
|
||||||
|
--cd-green-fg-100: hsl(120, 080%, 042%);
|
||||||
|
|
||||||
/* Import overrides */
|
/* Import overrides */
|
||||||
--toastify-color-dark: var(--cd-bg-60);
|
--toastify-color-dark: var(--cd-bg-60);
|
||||||
|
@ -87,6 +91,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
font-family: var(--font-main);
|
||||||
|
|
||||||
color: var(--cl-fg-100);
|
color: var(--cl-fg-100);
|
||||||
border-color: var(--cl-bg-40);
|
border-color: var(--cl-bg-40);
|
||||||
background-color: var(--cl-bg-100);
|
background-color: var(--cl-bg-100);
|
||||||
|
@ -136,9 +142,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
.small-caps {
|
.font-controls {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-weight: 600;
|
||||||
font-variant: small-caps;
|
font-variant: small-caps;
|
||||||
}
|
}
|
||||||
|
.font-math {
|
||||||
|
font-family: var(--font-math);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
|
|
|
@ -44,8 +44,23 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setFilter }:
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx('sticky top-0', 'w-full max-h-[2.3rem]', 'pr-40 flex', 'border-b', 'clr-input')}>
|
<div
|
||||||
<div className={clsx('min-w-[10rem]', 'px-2 self-center', 'select-none', 'whitespace-nowrap')}>
|
className={clsx(
|
||||||
|
'sticky top-0', //
|
||||||
|
'w-full max-h-[2.3rem]',
|
||||||
|
'pr-40 flex',
|
||||||
|
'border-b',
|
||||||
|
'clr-input'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'min-w-[10rem]', //
|
||||||
|
'px-2 self-center',
|
||||||
|
'select-none',
|
||||||
|
'whitespace-nowrap'
|
||||||
|
)}
|
||||||
|
>
|
||||||
Фильтр
|
Фильтр
|
||||||
<span className='ml-2'>
|
<span className='ml-2'>
|
||||||
{filtered} из {total}
|
{filtered} из {total}
|
||||||
|
|
|
@ -12,14 +12,7 @@ interface TopicsListProps {
|
||||||
function TopicsList({ activeTopic, onChangeTopic }: TopicsListProps) {
|
function TopicsList({ activeTopic, onChangeTopic }: TopicsListProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx('sticky top-0 left-0', 'min-w-[13rem] self-start', 'border-x', 'clr-controls', '', 'select-none')}
|
||||||
'sticky top-0 left-0',
|
|
||||||
'min-w-[13rem] self-start',
|
|
||||||
'border-x',
|
|
||||||
'clr-controls',
|
|
||||||
'small-caps',
|
|
||||||
'select-none'
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<h1 className='my-1'>Справка</h1>
|
<h1 className='my-1'>Справка</h1>
|
||||||
{Object.values(HelpTopic).map((topic, index) => (
|
{Object.values(HelpTopic).map((topic, index) => (
|
||||||
|
|
|
@ -123,7 +123,7 @@ function FormConstituenta({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Overlay position='top-1 left-[4rem]' className='flex select-none'>
|
<Overlay position='top-1 left-[4.1rem]' className='flex select-none'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title={`Редактировать словоформы термина: ${constituenta?.term_forms.length ?? 0}`}
|
title={`Редактировать словоформы термина: ${constituenta?.term_forms.length ?? 0}`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -131,7 +131,7 @@ function FormConstituenta({
|
||||||
onClick={onEditTerm}
|
onClick={onEditTerm}
|
||||||
icon={<LiaEdit size='1rem' className={!disabled ? 'clr-text-primary' : ''} />}
|
icon={<LiaEdit size='1rem' className={!disabled ? 'clr-text-primary' : ''} />}
|
||||||
/>
|
/>
|
||||||
<div className='pt-1 pl-[1.375rem] text-sm font-semibold whitespace-nowrap'>
|
<div className='pt-1 pl-[1.375rem] text-sm font-medium whitespace-nowrap'>
|
||||||
<span>Имя </span>
|
<span>Имя </span>
|
||||||
<span className='ml-1'>{constituenta?.alias ?? ''}</span>
|
<span className='ml-1'>{constituenta?.alias ?? ''}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -205,7 +205,7 @@ function FormConstituenta({
|
||||||
text='Сохранить изменения'
|
text='Сохранить изменения'
|
||||||
className='self-center'
|
className='self-center'
|
||||||
disabled={!isModified || disabled}
|
disabled={!isModified || disabled}
|
||||||
icon={<FiSave size='1.5rem' />}
|
icon={<FiSave size='1.25rem' />}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -22,7 +22,8 @@ function RSLocalButton({ text, title, disabled, onInsert }: RSLocalButtonProps)
|
||||||
'w-[2rem] h-6',
|
'w-[2rem] h-6',
|
||||||
'cursor-pointer disabled:cursor-default',
|
'cursor-pointer disabled:cursor-default',
|
||||||
'rounded-none',
|
'rounded-none',
|
||||||
'clr-hover clr-btn-clear'
|
'clr-hover clr-btn-clear',
|
||||||
|
'font-math'
|
||||||
)}
|
)}
|
||||||
onClick={() => onInsert(TokenID.ID_LOCAL, text)}
|
onClick={() => onInsert(TokenID.ID_LOCAL, text)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -23,6 +23,7 @@ function RSTokenButton({ token, disabled, onInsert }: RSTokenButtonProps) {
|
||||||
'px-1',
|
'px-1',
|
||||||
'outline-none',
|
'outline-none',
|
||||||
'clr-hover clr-btn-clear',
|
'clr-hover clr-btn-clear',
|
||||||
|
'font-math',
|
||||||
'cursor-pointer disabled:cursor-default',
|
'cursor-pointer disabled:cursor-default',
|
||||||
{
|
{
|
||||||
'w-[4.5rem]': label.length > 3,
|
'w-[4.5rem]': label.length > 3,
|
||||||
|
|
|
@ -56,7 +56,7 @@ function StatusBar({ isModified, processing, constituenta, parseData, onAnalyze
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<StatusIcon status={status} />
|
<StatusIcon status={status} />
|
||||||
<span className='pb-[0.125rem] font-semibold small-caps pr-2'>{labelExpressionStatus(status)}</span>
|
<span className='pb-[0.125rem] font-controls pr-2'>{labelExpressionStatus(status)}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,44 +16,51 @@ import { IRSFormCreateData } from '@/models/rsform';
|
||||||
import { classnames, limits, patterns } from '@/utils/constants';
|
import { classnames, limits, patterns } from '@/utils/constants';
|
||||||
|
|
||||||
interface FormRSFormProps {
|
interface FormRSFormProps {
|
||||||
id?: string
|
id?: string;
|
||||||
disabled: boolean
|
disabled: boolean;
|
||||||
isModified: boolean
|
isModified: boolean;
|
||||||
setIsModified: Dispatch<SetStateAction<boolean>>
|
setIsModified: Dispatch<SetStateAction<boolean>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function FormRSForm({
|
function FormRSForm({ id, disabled, isModified, setIsModified }: FormRSFormProps) {
|
||||||
id, disabled, isModified, setIsModified,
|
|
||||||
}: FormRSFormProps) {
|
|
||||||
const { schema, update, processing } = useRSForm();
|
const { schema, update, processing } = useRSForm();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const [alias, setAlias] = useState('');
|
const [alias, setAlias] = useState('');
|
||||||
const [comment, setComment] = useState('');
|
const [comment, setComment] = useState('');
|
||||||
const [common, setCommon] = useState(false);
|
const [common, setCommon] = useState(false);
|
||||||
const [canonical, setCanonical] = useState(false);
|
const [canonical, setCanonical] = useState(false);
|
||||||
|
|
||||||
useEffect(
|
useEffect(() => {
|
||||||
() => {
|
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
setIsModified(false);
|
setIsModified(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsModified(
|
setIsModified(
|
||||||
schema.title !== title ||
|
schema.title !== title ||
|
||||||
schema.alias !== alias ||
|
schema.alias !== alias ||
|
||||||
schema.comment !== comment ||
|
schema.comment !== comment ||
|
||||||
schema.is_common !== common ||
|
schema.is_common !== common ||
|
||||||
schema.is_canonical !== canonical
|
schema.is_canonical !== canonical
|
||||||
);
|
);
|
||||||
return () => setIsModified(false);
|
return () => setIsModified(false);
|
||||||
}, [schema, schema?.title, schema?.alias, schema?.comment,
|
}, [
|
||||||
schema?.is_common, schema?.is_canonical,
|
schema,
|
||||||
title, alias, comment, common, canonical, setIsModified]);
|
schema?.title,
|
||||||
|
schema?.alias,
|
||||||
|
schema?.comment,
|
||||||
|
schema?.is_common,
|
||||||
|
schema?.is_canonical,
|
||||||
|
title,
|
||||||
|
alias,
|
||||||
|
comment,
|
||||||
|
common,
|
||||||
|
canonical,
|
||||||
|
setIsModified
|
||||||
|
]);
|
||||||
|
|
||||||
useLayoutEffect(
|
useLayoutEffect(() => {
|
||||||
() => {
|
|
||||||
if (schema) {
|
if (schema) {
|
||||||
setTitle(schema.title);
|
setTitle(schema.title);
|
||||||
setAlias(schema.alias);
|
setAlias(schema.alias);
|
||||||
|
@ -79,59 +86,55 @@ function FormRSForm({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form id={id}
|
<form id={id} className={clsx('mt-1 min-w-[22rem] w-[30rem]', 'py-1', classnames.flex_col)} onSubmit={handleSubmit}>
|
||||||
className={clsx(
|
<TextInput
|
||||||
'mt-1 min-w-[22rem] w-[30rem]',
|
required
|
||||||
'py-1',
|
label='Полное название'
|
||||||
classnames.flex_col
|
value={title}
|
||||||
)}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
>
|
|
||||||
<TextInput required
|
|
||||||
label='Полное название'
|
|
||||||
value={title}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={event => setTitle(event.target.value)}
|
|
||||||
/>
|
|
||||||
<TextInput required
|
|
||||||
label='Сокращение'
|
|
||||||
className='w-[14rem]'
|
|
||||||
pattern={patterns.alias}
|
|
||||||
title={`не более ${limits.alias_len} символов`}
|
|
||||||
disabled={disabled}
|
|
||||||
value={alias}
|
|
||||||
onChange={event => setAlias(event.target.value)}
|
|
||||||
/>
|
|
||||||
<TextArea
|
|
||||||
label='Комментарий'
|
|
||||||
value={comment}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={event => setComment(event.target.value)}
|
|
||||||
/>
|
|
||||||
<div className='flex justify-between whitespace-nowrap'>
|
|
||||||
<Checkbox
|
|
||||||
label='Общедоступная схема'
|
|
||||||
title='Общедоступные схемы видны всем пользователям и могут быть изменены'
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
value={common}
|
onChange={event => setTitle(event.target.value)}
|
||||||
setValue={value => setCommon(value)}
|
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<TextInput
|
||||||
label='Неизменная схема'
|
required
|
||||||
title='Только администраторы могут присваивать схемам неизменный статус'
|
label='Сокращение'
|
||||||
disabled={disabled || !user?.is_staff}
|
className='w-[14rem]'
|
||||||
value={canonical}
|
pattern={patterns.alias}
|
||||||
setValue={value => setCanonical(value)}
|
title={`не более ${limits.alias_len} символов`}
|
||||||
|
disabled={disabled}
|
||||||
|
value={alias}
|
||||||
|
onChange={event => setAlias(event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
<TextArea
|
||||||
<SubmitButton
|
label='Комментарий'
|
||||||
text='Сохранить изменения'
|
value={comment}
|
||||||
className='self-center'
|
disabled={disabled}
|
||||||
loading={processing}
|
onChange={event => setComment(event.target.value)}
|
||||||
disabled={!isModified || disabled}
|
/>
|
||||||
icon={<FiSave size='1.5rem' />}
|
<div className='flex justify-between whitespace-nowrap'>
|
||||||
/>
|
<Checkbox
|
||||||
</form>);
|
label='Общедоступная схема'
|
||||||
|
title='Общедоступные схемы видны всем пользователям и могут быть изменены'
|
||||||
|
disabled={disabled}
|
||||||
|
value={common}
|
||||||
|
setValue={value => setCommon(value)}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label='Неизменная схема'
|
||||||
|
title='Только администраторы могут присваивать схемам неизменный статус'
|
||||||
|
disabled={disabled || !user?.is_staff}
|
||||||
|
value={canonical}
|
||||||
|
setValue={value => setCanonical(value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<SubmitButton
|
||||||
|
text='Сохранить изменения'
|
||||||
|
className='self-center'
|
||||||
|
loading={processing}
|
||||||
|
disabled={!isModified || disabled}
|
||||||
|
icon={<FiSave size='1.25rem' />}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FormRSForm;
|
export default FormRSForm;
|
||||||
|
|
|
@ -204,7 +204,7 @@ function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Edito
|
||||||
onCreate={handleCreateCst}
|
onCreate={handleCreateCst}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
/>
|
/>
|
||||||
<SelectedCounter total={schema?.stats?.count_all ?? 0} selected={selected.length} position='left-0 top-1' />
|
<SelectedCounter total={schema?.stats?.count_all ?? 0} selected={selected.length} position='left-1 top-2' />
|
||||||
|
|
||||||
<div className='pt-[2.3rem] border-b' />
|
<div className='pt-[2.3rem] border-b' />
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user