Refactoring: use clsx to clarify classNames

This commit is contained in:
IRBorisov 2023-12-15 17:34:50 +03:00
parent a9586a8e8e
commit 44b30a9bd8
110 changed files with 899 additions and 460 deletions

View File

@ -12,17 +12,18 @@ This readme file is used mostly to document project dependencies
<summary>npm install</summary>
<pre>
- axios
- clsx
- react-router-dom
- react-toastify
- react-loader-spinner
- js-file-download
- react-tabs
- react-intl
- react-select
- react-error-boundary
- react-pdf
- reagraph
- react-tooltip
- js-file-download
- reagraph
- @tanstack/react-table
- @uiw/react-codemirror
- @uiw/codemirror-themes

View File

@ -0,0 +1,21 @@
# Frontend Developer guidelines
Styling conventions
- static > conditional static > props. All dynamic styling should go in styles props
- dimensions = rectangle + outer layout
<details>
<summary>clsx className groupind and order</summary>
<pre>
- layer: z-position
- outer layout: fixed bottom-1/2 left-0 -translate-x-1/2
- rectangle: mt-3 w-full min-w-10 h-fit
- inner layout: px-3 py-2 flex flex-col gap-3 justify-start items-center
- overflow behavior: overflow-auto
- border: borer-2 outline-none shadow-md
- colors: clr-controls
- text: text-start text-sm font-semibold whitespace-nowrap
- behavior modifiers: select-none disabled:cursor-not-allowed
- transitions:
</pre>
</details>

View File

@ -13,6 +13,7 @@
"@uiw/codemirror-themes": "^4.21.21",
"@uiw/react-codemirror": "^4.21.21",
"axios": "^1.6.2",
"clsx": "^2.0.0",
"js-file-download": "^0.4.12",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@ -17,6 +17,7 @@
"@uiw/codemirror-themes": "^4.21.21",
"@uiw/react-codemirror": "^4.21.21",
"axios": "^1.6.2",
"clsx": "^2.0.0",
"js-file-download": "^0.4.12",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@ -1,3 +1,4 @@
import clsx from 'clsx';
import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
import ConceptToaster from './components/ConceptToaster';
@ -21,7 +22,11 @@ function Root() {
const { noNavigation, noFooter, viewportHeight, mainHeight, showScroll } = useConceptTheme();
return (
<NavigationState>
<div className='w-screen antialiased clr-app min-w-[30rem] overflow-hidden'>
<div className={clsx(
'w-screen min-w-[30rem]',
'clr-app',
'antialiased'
)}>
<ConceptToaster
className='mt-[4rem] text-sm'
@ -39,7 +44,13 @@ function Root() {
overflowY: showScroll ? 'scroll': 'auto'
}}
>
<main className='flex flex-col items-center w-full h-full min-w-fit' style={{minHeight: mainHeight}}>
<main
className={clsx(
'w-full h-full min-w-fit',
'flex flex-col items-center'
)}
style={{minHeight: mainHeight}}
>
<Outlet />
</main>
@ -101,4 +112,4 @@ function App () {
);
}
export default App;
export default App;

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
import { IColorsProps, IControlProps } from './commonInterfaces';
interface ButtonProps
@ -17,21 +19,30 @@ function Button({
loading,
...restProps
}: ButtonProps) {
const borderClass = noBorder ? '' : 'border rounded';
const padding = dense ? 'px-1' : 'px-3 py-2';
const outlineClass = noOutline ? 'outline-none': 'clr-outline';
const cursor = 'disabled:cursor-not-allowed ' + (loading ? 'cursor-progress ' : 'cursor-pointer ');
return (
<button type='button'
disabled={disabled ?? loading}
title={tooltip}
className={`inline-flex items-center gap-2 align-middle justify-center select-none ${padding} ${colors} ${outlineClass} ${borderClass} ${dimensions} ${cursor}`}
{...restProps}
>
{icon ? icon : null}
{text ? <span className='font-semibold'>{text}</span> : null}
</button>
);
<button type='button'
disabled={disabled ?? loading}
title={tooltip}
className={clsx(
'inline-flex gap-2 items-center justify-center',
'select-none disabled:cursor-not-allowed',
{
'border rounded': !noBorder,
'px-1': dense,
'px-3 py-2': !dense,
'cursor-progress': loading,
'cursor-pointer': !loading,
'outline-none': noOutline,
'clr-outline': !noOutline,
},
colors,
dimensions
)}
{...restProps}
>
{icon ? icon : null}
{text ? <span className='font-semibold'>{text}</span> : null}
</button>);
}
export default Button;
export default Button;

View File

@ -1,3 +1,4 @@
import clsx from 'clsx';
import { useMemo } from 'react';
import { CheckboxCheckedIcon } from '../Icons';
@ -25,14 +26,10 @@ function Checkbox({
} else if (setValue) {
return 'cursor-pointer';
} else {
return ''
return '';
}
}, [disabled, setValue]);
const bgColor = useMemo(
() => {
return value !== false ? 'clr-primary' : 'clr-app'
}, [value]);
function handleClick(event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void {
event.preventDefault();
if (disabled || !setValue) {
@ -42,27 +39,31 @@ function Checkbox({
}
return (
<button type='button' id={id}
className={`flex items-center outline-none ${dimensions}`}
title={tooltip}
disabled={disabled}
onClick={handleClick}
{...restProps}
>
<div className={`max-w-[1rem] min-w-[1rem] h-4 mt-0.5 border rounded-sm ${bgColor} ${cursor}`} >
{value ?
<div className='mt-[1px] ml-[1px]'>
<CheckboxCheckedIcon />
</div> : null}
</div>
{label ?
<Label
className={`${cursor} px-2 text-start`}
text={label}
htmlFor={id}
/> : null}
</button>
);
<button type='button' id={id}
className={clsx(
'flex items-center gap-2',
'outline-none',
'text-start',
dimensions,
cursor
)}
title={tooltip}
disabled={disabled}
onClick={handleClick}
{...restProps}
>
<div className={clsx(
'max-w-[1rem] min-w-[1rem] h-4',
'border rounded-sm',
{
'clr-primary': value !== false,
'clr-app': value === false
}
)}>
{value ? <div className='mt-[1px] ml-[1px]'><CheckboxCheckedIcon /></div> : null}
</div>
<Label className={cursor} text={label} htmlFor={id} />
</button>);
}
export default Checkbox;
export default Checkbox;

View File

@ -10,15 +10,13 @@ interface ConceptLoaderProps {
export function ConceptLoader({size=10}: ConceptLoaderProps) {
const {colors} = useConceptTheme();
return (
<div className='flex justify-center w-full h-full'>
<ThreeDots
color={colors.bgSelected}
height={size*10}
width={size*10}
radius={size}
/>
</div>
);
}
<div className='flex justify-center w-full h-full'>
<ThreeDots
color={colors.bgSelected}
height={size*10}
width={size*10}
radius={size}
/>
</div>);
}

View File

@ -5,31 +5,27 @@ import TextInput from './TextInput';
interface ConceptSearchProps {
value: string
onChange?: (newValue: string) => void
dense?: boolean
noBorder?: boolean
dimensions?: string
}
function ConceptSearch({ value, onChange, noBorder, dimensions, dense }: ConceptSearchProps) {
const borderClass = dense && !noBorder ? 'border-t border-x': '';
function ConceptSearch({ value, onChange, noBorder, dimensions }: ConceptSearchProps) {
return (
<div className={dimensions}>
<Overlay
position='top-0 left-3 translate-y-1/2'
className='flex items-center pointer-events-none text-controls'
position='top-[-0.125rem] left-3 translate-y-1/2'
className='pointer-events-none clr-text-controls'
>
<MagnifyingGlassIcon size={5} />
</Overlay>
<TextInput noOutline
placeholder='Поиск'
dimensions={`w-full pl-10 hover:text-clip outline-none ${borderClass}`}
noBorder={dense || noBorder}
dimensions='w-full pl-10'
noBorder={noBorder}
value={value}
onChange={event => (onChange ? onChange(event.target.value) : undefined)}
/>
</div>);
}
export default ConceptSearch;
export default ConceptSearch;

View File

@ -1,3 +1,4 @@
import clsx from 'clsx';
import type { TabProps } from 'react-tabs';
import { Tab } from 'react-tabs';
@ -11,7 +12,14 @@ extends Omit<TabProps, 'title' | 'children'> {
function ConceptTab({ label, tooltip, className, ...otherProps }: ConceptTabProps) {
return (
<Tab
className={`px-2 py-1 h-full min-w-[6rem] flex justify-center text-sm hover:cursor-pointer clr-tab whitespace-nowrap small-caps select-none font-semibold ${className}`}
className={clsx(
'h-full min-w-[6rem]',
'px-2 py-1 flex justify-center',
'clr-tab',
'text-sm whitespace-nowrap small-caps font-semibold',
'select-none hover:cursor-pointer',
className
)}
title={tooltip}
{...otherProps}
>
@ -21,4 +29,4 @@ function ConceptTab({ label, tooltip, className, ...otherProps }: ConceptTabProp
ConceptTab.tabsRole = 'Tab';
export default ConceptTab;
export default ConceptTab;

View File

@ -1,5 +1,6 @@
'use client';
import clsx from 'clsx';
import { createPortal } from 'react-dom';
import { ITooltip, Tooltip } from 'react-tooltip';
@ -26,7 +27,12 @@ function ConceptTooltip({
<Tooltip
opacity={0.97}
style={{...{ paddingTop: '2px', paddingBottom: '2px'}, ...style}}
className={`overflow-auto border shadow-md ${layer} ${className}`}
className={clsx(
'overflow-auto',
'border shadow-md',
layer,
className
)}
variant={(darkMode ? 'dark' : 'light')}
place={place}
{...restProps}

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
interface DividerProps {
vertical?: boolean
margins?: string
@ -5,8 +7,13 @@ interface DividerProps {
function Divider({ vertical, margins = 'mx-2' }: DividerProps) {
return (
<div className={`${margins} ${vertical ? 'border-x h-full': 'border-y w-full'}`} />
);
<div className={clsx(
margins,
{
'border-x h-full': vertical,
'border-y w-full': !vertical
}
)}/>);
}
export default Divider;
export default Divider;

View File

@ -1,16 +1,36 @@
import clsx from 'clsx';
import Overlay from './Overlay';
interface DropdownProps {
children: React.ReactNode
stretchLeft?: boolean
dimensions?: string
children: React.ReactNode
}
function Dropdown({ children, dimensions = 'w-fit', stretchLeft }: DropdownProps) {
function Dropdown({
dimensions = 'w-fit',
stretchLeft,
children
}: DropdownProps) {
return (
<div className='relative text-sm'>
<div className={`absolute ${stretchLeft ? 'right-0' : 'left-0'} mt-3 z-modal-tooltip flex flex-col items-stretch justify-start origin-top-right border rounded-md shadow-lg clr-input ${dimensions}`}>
<Overlay
layer='z-modal-tooltip'
position='mt-3'
className={clsx(
'flex flex-col items-stretch justify-start',
'border rounded-md shadow-lg',
'text-sm',
'clr-input',
{
'right-0': stretchLeft,
'left-0': !stretchLeft
},
dimensions
)}
>
{children}
</div>
</div>);
</Overlay>);
}
export default Dropdown;
export default Dropdown;

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
interface DropdownButtonProps {
tooltip?: string | undefined
onClick?: () => void
@ -6,17 +8,24 @@ interface DropdownButtonProps {
}
function DropdownButton({ tooltip, onClick, disabled, children }: DropdownButtonProps) {
const behavior = onClick ? 'cursor-pointer disabled:cursor-not-allowed clr-hover' : 'cursor-default';
const text = disabled ? 'text-controls' : '';
return (
<button type='button'
disabled={disabled}
title={tooltip}
onClick={onClick}
className={`px-3 py-1 text-left overflow-ellipsis whitespace-nowrap ${behavior} ${text}`}
className={clsx(
'px-3 py-1',
'text-left overflow-ellipsis whitespace-nowrap',
'disabled:clr-text-controls',
{
'clr-hover': onClick,
'cursor-pointer disabled:cursor-not-allowed': onClick,
'cursor-default': !onClick
}
)}
>
{children}
</button>);
}
export default DropdownButton;
export default DropdownButton;

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
import Checkbox from './Checkbox';
interface DropdownCheckboxProps {
@ -9,11 +11,15 @@ interface DropdownCheckboxProps {
}
function DropdownCheckbox({ tooltip, setValue, disabled, ...restProps }: DropdownCheckboxProps) {
const behavior = (setValue && !disabled) ? 'clr-hover' : '';
return (
<div
title={tooltip}
className={`px-4 py-1 text-left overflow-ellipsis ${behavior} w-full whitespace-nowrap`}
className={clsx(
'px-3 py-1',
'text-left overflow-ellipsis whitespace-nowrap',
'disabled:clr-text-controls',
!!setValue && !disabled && 'clr-hover'
)}
>
<Checkbox
dimensions='w-full'
@ -24,4 +30,4 @@ function DropdownCheckbox({ tooltip, setValue, disabled, ...restProps }: Dropdow
</div>);
}
export default DropdownCheckbox;
export default DropdownCheckbox;

View File

@ -11,13 +11,20 @@ function EmbedYoutube({ videoID, pxHeight, pxWidth }: EmbedYoutubeProps) {
return (
<div
className='relative'
style={{height: 0, paddingBottom: `${pxHeight}px`, paddingLeft: `${pxWidth}px`}}
style={{
height: 0,
paddingBottom: `${pxHeight}px`,
paddingLeft: `${pxWidth}px`
}}
>
<iframe allowFullScreen
title='Встроенное видео Youtube'
allow='accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture'
className='absolute top-0 left-0 border'
style={{minHeight: `${pxHeight}px`, minWidth: `${pxWidth}px`}}
style={{
minHeight: `${pxHeight}px`,
minWidth: `${pxWidth}px`
}}
width={`${pxWidth}px`}
height={`${pxHeight}px`}
src={`https://www.youtube.com/embed/${videoID}`}
@ -25,4 +32,4 @@ function EmbedYoutube({ videoID, pxHeight, pxWidth }: EmbedYoutubeProps) {
</div>);
}
export default EmbedYoutube;
export default EmbedYoutube;

View File

@ -1,5 +1,6 @@
'use client';
import clsx from 'clsx';
import { useRef, useState } from 'react';
import { UploadIcon } from '../Icons';
@ -40,7 +41,11 @@ function FileInput({
};
return (
<div className={`flex flex-col gap-2 py-2 items-start ${dimensions}`}>
<div className={clsx(
'py-2',
'flex flex-col gap-2 items-start',
dimensions
)}>
<input type='file'
ref={inputRef}
style={{ display: 'none' }}
@ -54,10 +59,8 @@ function FileInput({
onClick={handleUploadClick}
tooltip={tooltip}
/>
<Label
text={fileName}
/>
<Label text={fileName} />
</div>);
}
export default FileInput;
export default FileInput;

View File

@ -1,3 +1,4 @@
// Reexporting reagraph types to wrap in 'use client'.
'use client';
import { GraphCanvas as GraphUI } from 'reagraph';

View File

@ -1,15 +1,22 @@
import clsx from 'clsx';
import { LabelHTMLAttributes } from 'react';
interface LabelProps
extends Omit<React.DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>, 'children' | 'title'> {
text: string
text?: string
tooltip?: string
}
function Label({ text, tooltip, className, ...restProps }: LabelProps) {
if (!text) {
return null;
}
return (
<label
className={`text-sm font-semibold whitespace-nowrap ${className}`}
className={clsx(
'text-sm font-semibold whitespace-nowrap',
className
)}
title={tooltip}
{...restProps}
>
@ -17,4 +24,4 @@ function Label({ text, tooltip, className, ...restProps }: LabelProps) {
</label>);
}
export default Label;
export default Label;

View File

@ -1,13 +1,13 @@
interface LabeledTextProps {
interface LabeledValueProps {
id?: string
label: string
text: string | number
tooltip?: string
}
function LabeledText({ id, label, text, tooltip }: LabeledTextProps) {
function LabeledValue({ id, label, text, tooltip }: LabeledValueProps) {
return (
<div className='flex justify-between gap-4'>
<div className='flex justify-between gap-3'>
<label
className='font-semibold'
title={tooltip}
@ -21,4 +21,4 @@ function LabeledText({ id, label, text, tooltip }: LabeledTextProps) {
</div>);
}
export default LabeledText;
export default LabeledValue;

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
interface MiniButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'title' | 'children' > {
icon: React.ReactNode
@ -15,11 +17,21 @@ function MiniButton({
<button type='button'
title={tooltip}
tabIndex={tabIndex ?? -1}
className={`px-1 py-1 rounded-full cursor-pointer disabled:cursor-not-allowed clr-btn-clear ${noHover ? 'outline-none' : 'clr-hover'} ${dimensions}`}
className={clsx(
'px-1 py-1',
'rounded-full',
'clr-btn-clear',
'cursor-pointer disabled:cursor-not-allowed',
{
'outline-none': noHover,
'clr-hover': !noHover
},
dimensions
)}
{...restProps}
>
{icon}
</button>);
}
export default MiniButton;
export default MiniButton;

View File

@ -1,5 +1,6 @@
'use client';
import clsx from 'clsx';
import { useRef } from 'react';
import useEscapeKey from '@/hooks/useEscapeKey';
@ -44,9 +45,19 @@ function Modal({
return (
<>
<div className='fixed top-0 left-0 w-full h-full z-navigation clr-modal-backdrop' />
<div className={clsx(
'z-navigation',
'fixed top-0 left-0',
'w-full h-full',
'clr-modal-backdrop'
)}/>
<div ref={ref}
className='fixed -translate-x-1/2 translate-y-1/2 border shadow-md bottom-1/2 left-1/2 z-modal clr-app'
className={clsx(
'z-modal',
'fixed bottom-1/2 left-1/2 -translate-x-1/2 translate-y-1/2',
'border shadow-md',
'clr-app'
)}
>
<Overlay position='right-[0.3rem] top-2' className='text-disabled'>
<MiniButton
@ -56,20 +67,27 @@ function Modal({
/>
</Overlay>
{title ? <h1 className='px-12 py-2 text-lg select-none'>{title}</h1> : null}
{title ? <h1 className='px-12 py-2 select-none'>{title}</h1> : null}
<div
className={`w-full h-fit ${className}`}
className={clsx(
'w-full h-fit',
'overflow-auto',
className
)}
style={{
maxHeight: 'calc(100vh - 8rem)',
maxWidth: 'calc(100vw - 2rem)',
overflow: 'auto'
}}
>
{children}
</div>
<div className='flex justify-center w-full gap-12 px-6 py-3 z-modal-controls min-w-fit'>
<div className={clsx(
'w-full min-w-fit',
'z-modal-controls',
'px-6 py-3 flex gap-12 justify-center'
)}>
{!readonly ?
<Button autoFocus
text={submitText}
@ -89,4 +107,4 @@ function Modal({
</>);
}
export default Modal;
export default Modal;

View File

@ -19,4 +19,4 @@ function Overlay({
</div>);
}
export default Overlay;
export default Overlay;

View File

@ -40,7 +40,10 @@ function PDFViewer({ file }: PDFViewerProps) {
loading='Загрузка PDF файла...'
error='Не удалось загрузить файл.'
>
<Overlay position='top-6 left-1/2 -translate-x-1/2' className='flex select-none'>
<Overlay
position='top-6 left-1/2 -translate-x-1/2'
className='flex select-none'
>
<PageControls
pageCount={pageCount}
pageNumber={pageNumber}

View File

@ -12,14 +12,14 @@ function PageControls({
return (
<>
<button type='button'
className='clr-hover text-controls'
className='clr-hover clr-text-controls'
onClick={() => setPageNumber(1)}
disabled={pageNumber < 2}
>
<GotoFirstIcon />
</button>
<button type='button'
className='clr-hover text-controls'
className='clr-hover clr-text-controls'
onClick={() => setPageNumber(prev => prev - 1)}
disabled={pageNumber < 2}
>
@ -27,14 +27,14 @@ function PageControls({
</button>
<p className='px-3 text-black'>Страница {pageNumber} из {pageCount}</p>
<button type='button'
className='clr-hover text-controls'
className='clr-hover clr-text-controls'
onClick={() => setPageNumber(prev => prev + 1)}
disabled={pageNumber >= pageCount}
>
<GotoNextIcon />
</button>
<button type='button'
className='clr-hover text-controls'
className='clr-hover clr-text-controls'
onClick={() => setPageNumber(pageCount)}
disabled={pageNumber >= pageCount}
>

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
interface SelectorButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children' | 'title' | 'type'> {
text?: string
@ -9,7 +11,6 @@ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'child
transparent?: boolean
}
function SelectorButton({
text, icon, tooltip,
colors = 'clr-btn-default',
@ -17,12 +18,20 @@ function SelectorButton({
transparent,
...restProps
}: SelectorButtonProps) {
const cursor = 'disabled:cursor-not-allowed cursor-pointer';
const position = `px-1 flex flex-start items-center gap-1 ${dimensions}`;
const design = (transparent ? 'clr-hover' : `border ${colors}`) + ' text-btn text-controls';
return (
<button type='button'
className={`text-sm small-caps select-none ${cursor} ${position} ${design}`}
className={clsx(
'px-1 flex flex-start items-center gap-1',
'text-sm small-caps select-none',
'text-btn clr-text-controls',
'disabled:cursor-not-allowed cursor-pointer',
{
'clr-hover': transparent,
'border': !transparent,
},
!transparent && colors,
dimensions
)}
title={tooltip}
{...restProps}
>

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
interface SubmitButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'children' | 'title'> {
text?: string
@ -14,7 +16,16 @@ function SubmitButton({
return (
<button type='submit'
title={tooltip}
className={`px-3 py-2 inline-flex items-center gap-2 align-middle justify-center font-semibold select-none disabled:cursor-not-allowed border clr-btn-primary ${dimensions} ${loading ? ' cursor-progress' : ''}`}
className={clsx(
'px-3 py-2',
'inline-flex items-center gap-2 align-middle justify-center',
'border',
'font-semibold',
'clr-btn-primary',
'select-none disabled:cursor-not-allowed',
loading && 'cursor-progress',
dimensions
)}
disabled={disabled ?? loading}
{...restProps}
>

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
interface SwitchButtonProps<ValueType> {
id?: string
value: ValueType
@ -19,7 +21,15 @@ function SwitchButton<ValueType>({
<button type='button' tabIndex={-1}
title={tooltip}
onClick={() => onSelect(value)}
className={`px-2 py-1 border font-semibold small-caps rounded-none cursor-pointer clr-btn-clear clr-hover ${dimensions} ${isSelected ? 'clr-selected': ''}`}
className={clsx(
'px-2 py-1',
'border rounded-none',
'font-semibold small-caps',
'clr-btn-clear clr-hover',
'cursor-pointer',
isSelected && 'clr-selected',
dimensions
)}
{...restProps}
>
{icon ? icon : null}

View File

@ -1,3 +1,4 @@
import clsx from 'clsx';
import { TextareaHTMLAttributes } from 'react';
import { IColorsProps, IEditorProps } from './commonInterfaces';
@ -15,18 +16,28 @@ function TextArea({
colors = 'clr-input',
...restProps
}: TextAreaProps) {
const borderClass = noBorder ? '': 'border';
const outlineClass = noOutline ? '': 'clr-outline';
return (
<div className={`flex ${dense ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}>
{label &&
<Label
text={label}
htmlFor={id}
/>}
<div className={clsx(
{
'flex items-center gap-3': dense,
'flex flex-col items-start gap-2': !dense
},
dense && dimensions,
)}>
<Label text={label} htmlFor={id} />
<textarea id={id}
title={tooltip}
className={`px-3 py-2 leading-tight ${outlineClass} ${borderClass} ${colors} ${dense ? 'w-full' : dimensions}`}
className={clsx(
'px-3 py-2',
'leading-tight',
{
'w-full': dense,
'border': !noBorder,
'clr-outline': !noOutline
},
colors,
!dense && dimensions
)}
rows={rows}
required={required}
{...restProps}

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
import { IColorsProps, IEditorProps } from './commonInterfaces';
import Label from './Label';
@ -14,24 +16,37 @@ function preventEnterCapture(event: React.KeyboardEvent<HTMLInputElement>) {
}
function TextInput({
id, label, dense, tooltip, noBorder, noOutline, allowEnter, onKeyDown,
id, label, dense, tooltip, noBorder, noOutline, allowEnter, disabled,
dimensions = 'w-full',
colors = 'clr-input',
onKeyDown,
...restProps
}: TextInputProps) {
const borderClass = noBorder ? '' : 'border px-3';
const outlineClass = noOutline ? '' : 'clr-outline';
return (
<div className={`flex ${dense ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}>
{label ?
<Label
text={label}
htmlFor={id}
/> : null}
<div className={clsx(
{
'flex flex-col items-start gap-2': !dense,
'flex items-center gap-3': dense,
},
dense && dimensions
)}>
<Label text={label} htmlFor={id} />
<input id={id}
title={tooltip}
className={clsx(
'py-2',
'leading-tight truncate hover:text-clip',
{
'px-3': !noBorder || !disabled,
'w-full': dense,
'border': !noBorder,
'clr-outline': !noOutline
},
colors,
!dense && dimensions
)}
onKeyDown={!allowEnter && !onKeyDown ? preventEnterCapture : onKeyDown}
className={`py-2 leading-tight truncate hover:text-clip ${colors} ${outlineClass} ${borderClass} ${dense ? 'w-full' : dimensions}`}
disabled={disabled}
{...restProps}
/>
</div>);

View File

@ -9,7 +9,7 @@ interface TextURLProps {
onClick?: () => void
}
function TextURL({ text, href, tooltip, color='text-url', onClick }: TextURLProps) {
function TextURL({ text, href, tooltip, color='clr-text-url', onClick }: TextURLProps) {
const design = `cursor-pointer hover:underline ${color}`;
if (href) {
return (

View File

@ -1,4 +1,5 @@
import clsx from 'clsx';
import { useMemo } from 'react';
import { CheckboxCheckedIcon, CheckboxNullIcon } from '../Icons';
@ -27,11 +28,7 @@ function Tristate({
return ''
}
}, [disabled, setValue]);
const bgColor = useMemo(
() => {
return value !== false ? 'clr-primary' : 'clr-app'
}, [value]);
function handleClick(event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void {
event.preventDefault();
if (disabled || !setValue) {
@ -48,22 +45,29 @@ function Tristate({
return (
<button type='button' id={id}
className={`flex items-center clr-outline focus:outline-dotted focus:outline-1 ${dimensions}`}
className={clsx(
'flex items-center gap-2 text-start',
'outline-none',
dimensions,
cursor
)}
title={tooltip}
disabled={disabled}
onClick={handleClick}
{...restProps}
>
<div className={`w-4 h-4 shrink-0 mt-0.5 border rounded-sm ${bgColor} ${cursor}`} >
<div className={clsx(
'w-4 h-4',
'border rounded-sm',
{
'clr-primary': value !== false,
'clr-app': value === false
}
)}>
{value ? <div className='mt-[1px] ml-[1px]'><CheckboxCheckedIcon /></div> : null}
{value == null ? <div className='mt-[1px] ml-[1px]'><CheckboxNullIcon /></div> : null}
</div>
{label ?
<Label
className={`${cursor} px-2 text-start`}
text={label}
htmlFor={id}
/> : null}
<Label className={cursor} text={label} htmlFor={id} />
</button>);
}

View File

@ -14,4 +14,4 @@ export interface IEditorProps extends IControlProps {
export interface IColorsProps {
colors?: string
}
}

View File

@ -14,4 +14,4 @@ function ToasterThemed(props: ToasterThemedProps) {
/>);
}
export default ToasterThemed;
export default ToasterThemed;

View File

@ -1,6 +1,7 @@
'use client';
import { Table } from '@tanstack/react-table';
import clsx from 'clsx';
import { useCallback } from 'react';
import { prefixes } from '@/utils/constants';
@ -24,7 +25,13 @@ function PaginationTools<TData>({ table, paginationOptions, onChangePaginationOp
}, [onChangePaginationOption, table]);
return (
<div className='flex items-center justify-end w-full my-2 text-sm select-none text-controls'>
<div className={clsx(
'flex justify-end items-center',
'w-full my-2',
'text-sm',
'clr-text-controls',
'select-none'
)}>
<div className='flex items-center gap-1 mr-3'>
<div className=''>
{table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1}
@ -36,14 +43,14 @@ function PaginationTools<TData>({ table, paginationOptions, onChangePaginationOp
</div>
<div className='flex'>
<button type='button'
className='clr-hover text-controls'
className='clr-hover clr-text-controls'
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<GotoFirstIcon />
</button>
<button type='button'
className='clr-hover text-controls'
className='clr-hover clr-text-controls'
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
@ -61,14 +68,14 @@ function PaginationTools<TData>({ table, paginationOptions, onChangePaginationOp
}}
/>
<button type='button'
className='clr-hover text-controls'
className='clr-hover clr-text-controls'
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<GotoNextIcon />
</button>
<button type='button'
className='clr-hover text-controls'
className='clr-hover clr-text-controls'
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>

View File

@ -17,4 +17,4 @@ function SortingIcon<TData>({ column }: SortingIconProps<TData>) {
</>);
}
export default SortingIcon;
export default SortingIcon;

View File

@ -6,7 +6,7 @@ import InfoError from './InfoError';
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div className='flex flex-col items-center antialiased clr-app' role='alert'>
<h1 className='text-lg font-semibold'>Что-то пошло не так!</h1>
<h1>Что-то пошло не так!</h1>
<Button
onClick={resetErrorBoundary}
text='Попробовать еще раз'
@ -15,4 +15,4 @@ function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
</div>);
}
export default ErrorFallback;
export default ErrorFallback;

View File

@ -22,7 +22,7 @@ function ExpectedAnonymous() {
<TextURL text='Справка' href='/manuals'/>
<span> | </span>
<span
className='cursor-pointer hover:underline text-url'
className='cursor-pointer hover:underline clr-text-url'
onClick={logoutAndRedirect}
>
Выйти
@ -31,4 +31,4 @@ function ExpectedAnonymous() {
</div>);
}
export default ExpectedAnonymous;
export default ExpectedAnonymous;

View File

@ -1,17 +1,26 @@
import clsx from 'clsx';
import { urls } from '@/utils/constants';
import TextURL from './Common/TextURL';
function Footer() {
return (
<footer tabIndex={-1} className='flex flex-col items-center w-full gap-1 px-4 py-2 text-sm select-none z-navigation whitespace-nowrap'>
<div className='flex gap-3 text-center'>
<footer tabIndex={-1}
className={clsx(
'w-full z-navigation',
'px-4 py-2 flex flex-col items-center gap-1',
'text-sm select-none whitespace-nowrap'
)}
>
<div className='flex gap-3'>
<TextURL text='Библиотека' href='/library' color='clr-footer'/>
<TextURL text='Справка' href='/manuals' color='clr-footer'/>
<TextURL text='Центр Концепт' href={urls.concept} color='clr-footer'/>
<TextURL text='Экстеор' href='/manuals?topic=exteor' color='clr-footer'/>
</div>
<div>
<p className='mt-0.5 text-center clr-footer'>© 2023 ЦИВТ КОНЦЕПТ</p>
<p className='clr-footer'>© 2023 ЦИВТ КОНЦЕПТ</p>
</div>
</footer>);
}

View File

@ -16,9 +16,9 @@ function HelpButton({ topic, offset, dimensions }: HelpButtonProps) {
<>
<div
id={`help-${topic}`}
className='px-1 py-1'
className='p-1'
>
<HelpIcon color='text-primary' size={5} />
<HelpIcon color='clr-text-primary' size={5} />
</div>
<ConceptTooltip clickable
anchorSelect={`#help-${topic}`}

View File

@ -529,4 +529,4 @@ export function CrossIcon(props: IconProps) {
</g>
</IconSVG>
);
}
}

View File

@ -46,9 +46,9 @@ function DescribeError({error} : {error: ErrorData}) {
function InfoError({ error }: InfoErrorProps) {
return (
<div className='px-3 py-2 min-w-[15rem] text-sm font-semibold select-text text-warning'>
<div className='px-3 py-2 min-w-[15rem] text-sm font-semibold select-text clr-text-warning'>
<DescribeError error={error} />
</div>);
}
export default InfoError;
export default InfoError;

View File

@ -4,7 +4,7 @@ function Logo() {
const { darkMode } = useConceptTheme();
return (
<img alt='Логотип КонцептПортал'
className='max-h-[1.6rem]'
className='max-h-[1.6rem] min-w-[11rem]'
src={!darkMode ? '/logo_full.svg' : '/logo_full_dark.svg'}
/>);
}

View File

@ -1,7 +1,9 @@
import clsx from 'clsx';
import { EducationIcon, LibraryIcon, PlusIcon } from '@/components/Icons';
import { useConceptNavigation } from '@/context/NagivationContext';
import { useConceptTheme } from '@/context/ThemeContext';
import { EducationIcon, LibraryIcon, PlusIcon } from '@/components/Icons';
import Logo from './Logo';
import NavigationButton from './NavigationButton';
import ToggleNavigationButton from './ToggleNavigationButton';
@ -17,14 +19,25 @@ function Navigation () {
const navigateCreateNew = () => router.push('/library/create');
return (
<nav className='sticky top-0 left-0 right-0 select-none clr-app z-navigation'>
<nav className={clsx(
'z-navigation',
'sticky top-0 left-0 right-0',
'clr-app',
'select-none'
)}>
<ToggleNavigationButton />
{!noNavigation ?
<div className='flex items-stretch justify-between pl-2 pr-[0.8rem] border-b-2 rounded-none h-[3rem]'>
<div
className={clsx(
'pl-2 pr-[0.8rem] h-[3rem]',
'flex justify-between',
'border-b-2 rounded-none'
)}
>
<div className='flex items-center mr-2 cursor-pointer' onClick={navigateHome} tabIndex={-1}>
<Logo />
</div>
<div className='flex items-center h-full'>
<div className='flex'>
<NavigationButton
text='Новая схема'
description='Создать новую схему'
@ -49,4 +62,4 @@ function Navigation () {
</nav>);
}
export default Navigation;
export default Navigation;

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
interface NavigationButtonProps {
id?: string
text?: string
@ -11,11 +13,20 @@ function NavigationButton({ id, icon, description, onClick, text }: NavigationBu
<button id={id} type='button' tabIndex={-1}
title={description}
onClick={onClick}
className={`flex items-center h-full gap-1 ${text ? 'px-2' : 'px-4'} mr-1 small-caps whitespace-nowrap clr-btn-nav`}
className={clsx(
'mr-1 h-full',
'flex items-center gap-1',
'clr-btn-nav',
'small-caps whitespace-nowrap',
{
'px-2': text,
'px-4': !text
}
)}
>
{icon ? <span>{icon}</span> : null}
{text ? <span className='font-semibold'>{text}</span> : null}
</button>);
}
export default NavigationButton;
export default NavigationButton;

View File

@ -22,4 +22,4 @@ function ThemeSwitcher() {
}
}
export default ThemeSwitcher;
export default ThemeSwitcher;

View File

@ -1,19 +1,32 @@
import clsx from 'clsx';
import { useMemo } from 'react';
import { useConceptTheme } from '@/context/ThemeContext';
function ToggleNavigationButton() {
const { noNavigation, toggleNoNavigation } = useConceptTheme();
const dimensions = useMemo(() => (noNavigation ? 'px-1 h-[1.6rem]' : 'w-[1.2rem] h-[3rem]'), [noNavigation]);
const text = useMemo(() => (
noNavigation ? '' : <><p>{'>'}</p><p>{'>'}</p></>), [noNavigation]
noNavigation ?
''
:
<>
<p>{'>'}</p>
<p>{'>'}</p>
</>
), [noNavigation]
);
const tooltip = useMemo(() => (noNavigation ? 'Показать навигацию' : 'Скрыть навигацию'), [noNavigation]);
return (
<button type='button' tabIndex={-1}
title={tooltip}
className={`absolute top-0 right-0 border-b-2 border-l-2 rounded-none z-navigation clr-btn-nav ${dimensions}`}
title={noNavigation ? 'Показать навигацию' : 'Скрыть навигацию'}
className={clsx(
'absolute top-0 right-0 z-navigation',
'border-b-2 border-l-2 rounded-none',
'clr-btn-nav',
{
'px-1 h-[1.6rem]': noNavigation,
'w-[1.2rem] h-[3rem]': !noNavigation
}
)}
onClick={toggleNoNavigation}
>
{text}

View File

@ -44,4 +44,4 @@ function UserDropdown({ hideDropdown }: UserDropdownProps) {
</Dropdown>);
}
export default UserDropdown;
export default UserDropdown;

View File

@ -35,4 +35,4 @@ function UserMenu() {
</div>);
}
export default UserMenu;
export default UserMenu;

View File

@ -1,8 +1,10 @@
'use client';
import { Extension } from '@codemirror/state';
import { tags } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
import clsx from 'clsx';
import { EditorView } from 'codemirror';
import { RefObject, useCallback, useMemo, useRef } from 'react';
@ -122,12 +124,12 @@ function RSInput({
}, [thisRef]);
return (
<div className={`flex flex-col gap-2 ${dimensions} ${cursor}`}>
{label ?
<Label
text={label}
htmlFor={id}
/> : null}
<div className={clsx(
'flex flex-col gap-2',
dimensions,
cursor
)}>
<Label text={label} htmlFor={id}/>
<CodeMirror id={id}
ref={thisRef}
basicSetup={editorSetup}
@ -142,4 +144,4 @@ function RSInput({
</div>);
}
export default RSInput;
export default RSInput;

View File

@ -228,4 +228,4 @@ export class RSTextWrapper extends CodeMirrorWrapper {
}
return false;
}
}
}

View File

@ -6,6 +6,7 @@ import { EditorState } from '@uiw/react-codemirror';
import { IConstituenta } from '@/models/rsform';
import { findEnvelopingNodes } from '@/utils/codemirror';
import { domTooltipConstituenta } from '@/utils/codemirror';
import { GlobalTokens } from './rslang';
function findAliasAt(pos: number, state: EditorState) {
@ -43,4 +44,4 @@ const globalsHoverTooltip = (items: IConstituenta[]) => {
export function rsHoverTooltip(items: IConstituenta[]): Extension {
return [globalsHoverTooltip(items)];
}
}

View File

@ -1,18 +1,17 @@
'use client';
import { Extension } from '@codemirror/state';
import { tags } from '@lezer/highlight';
import { createTheme } from '@uiw/codemirror-themes';
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
import clsx from 'clsx';
import { EditorView } from 'codemirror';
import { RefObject, useCallback, useMemo, useRef, useState } from 'react';
import Label from '@/components/Common/Label';
import Modal from '@/components/Common/Modal';
import PrettyJson from '@/components/Common/PrettyJSON';
import { useRSForm } from '@/context/RSFormContext';
import { useConceptTheme } from '@/context/ThemeContext';
import DlgEditReference from '@/dialogs/DlgEditReference';
import useResolveText from '@/hooks/useResolveText';
import { ReferenceType } from '@/models/language';
import { IConstituenta } from '@/models/rsform';
import { CodeMirrorWrapper } from '@/utils/codemirror';
@ -73,9 +72,6 @@ function RefsInput({
const { darkMode, colors } = useConceptTheme();
const { schema } = useRSForm();
const { resolveText, refsData } = useResolveText({schema: schema});
const [showResolve, setShowResolve] = useState(false);
const [isFocused, setIsFocused] = useState(false);
const [showEditor, setShowEditor] = useState(false);
@ -135,15 +131,6 @@ function RefsInput({
event.preventDefault();
return;
}
if (event.altKey) {
if (event.key === 'r' && value) {
event.preventDefault();
resolveText(value, () => {
setShowResolve(true);
});
return;
}
}
if (event.ctrlKey && event.code === 'Space') {
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
wrap.fixSelection(ReferenceTokens);
@ -164,7 +151,7 @@ function RefsInput({
setShowEditor(true);
}
}, [thisRef, resolveText, value]);
}, [thisRef]);
const handleInputReference = useCallback(
(referenceText: string) => {
@ -176,37 +163,26 @@ function RefsInput({
wrap.replaceWith(referenceText);
}, [thisRef]);
return (
<>
{showEditor ?
<DlgEditReference
hideWindow={() => setShowEditor(false)}
items={items ?? []}
initial={{
type: currentType,
refRaw: refText,
text: hintText,
basePosition: basePosition,
mainRefs: mainRefs
}}
onSave={handleInputReference}
/> : null}
{showResolve ?
<Modal
readonly
hideWindow={() => setShowResolve(false)}
>
<div className='max-h-[60vh] max-w-[80vw] overflow-auto'>
<PrettyJson data={refsData} />
</div>
</Modal> : null}
<div className={`flex flex-col w-full ${cursor}`}>
{label ?
<Label
text={label}
htmlFor={id}
className='mb-2'
/> : null}
return (<>
{showEditor ?
<DlgEditReference
hideWindow={() => setShowEditor(false)}
items={items ?? []}
initial={{
type: currentType,
refRaw: refText,
text: hintText,
basePosition: basePosition,
mainRefs: mainRefs
}}
onSave={handleInputReference}
/> : null}
<div className={clsx(
'w-full',
'flex flex-col gap-2',
cursor
)}>
<Label text={label} htmlFor={id} />
<CodeMirror id={id} ref={thisRef}
basicSetup={editorSetup}
theme={customTheme}
@ -223,8 +199,8 @@ function RefsInput({
// spellCheck= // TODO: figure out while automatic spellcheck doesnt work or implement with extension
{...restProps}
/>
</div>
</div>
</>);
}
export default RefsInput;
export default RefsInput;

View File

@ -6,6 +6,7 @@ import { parseEntityReference, parseSyntacticReference } from '@/models/language
import { IConstituenta } from '@/models/rsform';
import { domTooltipEntityReference, domTooltipSyntacticReference, findContainedNodes, findEnvelopingNodes } from '@/utils/codemirror';
import { IColorTheme } from '@/utils/color';
import { ReferenceTokens } from './parse';
import { RefEntity, RefSyntactic } from './parse/parser.terms';
@ -58,4 +59,4 @@ export const globalsHoverTooltip = (items: IConstituenta[], colors: IColorTheme)
export function refsHoverTooltip(items: IConstituenta[], colors: IColorTheme): Extension {
return [globalsHoverTooltip(items, colors)];
}
}

View File

@ -24,4 +24,4 @@ function RequireAuth({ children }: RequireAuthProps) {
}
}
export default RequireAuth;
export default RequireAuth;

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
import ConceptTooltip from '@/components/Common/ConceptTooltip';
import ConstituentaTooltip from '@/components/Help/ConstituentaTooltip';
import { IConstituenta } from '@/models/rsform';
@ -17,13 +19,16 @@ function ConstituentaBadge({ value, prefixID, shortTooltip, theme }: Constituent
<div className='w-fit'>
<div
id={`${prefixID}${value.alias}`}
className='min-w-[3.1rem] max-w-[3.1rem] px-1 text-center rounded-md whitespace-nowrap'
className={clsx(
'min-w-[3.1rem] max-w-[3.1rem]',
'px-1',
'border rounded-md',
'text-center font-semibold whitespace-nowrap'
)}
style={{
borderWidth: '1px',
borderColor: colorfgCstStatus(value.status, theme),
color: colorfgCstStatus(value.status, theme),
backgroundColor: isMockCst(value) ? theme.bgWarning : theme.bgInput,
fontWeight: 600
backgroundColor: isMockCst(value) ? theme.bgWarning : theme.bgInput
}}
>
{value.alias}
@ -38,9 +43,9 @@ function ConstituentaBadge({ value, prefixID, shortTooltip, theme }: Constituent
anchorSelect={`#${prefixID}${value.alias}`}
place='right'
>
<p><span className='font-semibold'>Статус</span>: {describeExpressionStatus(value.status)}</p>
<p><b>Статус</b>: {describeExpressionStatus(value.status)}</p>
</ConceptTooltip> : null}
</div>);
}
export default ConstituentaBadge;
export default ConstituentaBadge;

View File

@ -82,8 +82,8 @@ function ConstituentaPicker({
}], [value, colors]);
return (
<>
<ConceptSearch dense
<div>
<ConceptSearch
value={filterText}
onChange={newValue => setFilterText(newValue)}
/>
@ -96,7 +96,7 @@ function ConstituentaPicker({
columns={columns}
conditionalRowStyles={conditionalRowStyles}
noDataComponent={
<span className='flex flex-col justify-center p-2 text-center min-h-[5rem]'>
<span className='p-2 min-h-[5rem] flex flex-col justify-center text-center'>
<p>Список конституент пуст</p>
<p>Измените параметры фильтра</p>
</span>
@ -104,7 +104,7 @@ function ConstituentaPicker({
onRowClicked={onSelectValue}
/>
</div>
</>);
</div>);
}
export default ConstituentaPicker;
export default ConstituentaPicker;

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
import { useConceptTheme } from '@/context/ThemeContext';
import { GramData } from '@/models/language';
import { colorfgGrammeme } from '@/utils/color';
@ -13,12 +15,15 @@ function GrammemeBadge({ key, grammeme }: GrammemeBadgeProps) {
return (
<div
key={key}
className='min-w-[3rem] px-1 text-sm text-center rounded-md whitespace-nowrap'
className={clsx(
'min-w-[3rem]',
'px-1',
'border rounded-md',
'text-sm font-semibold text-center whitespace-nowrap'
)}
style={{
borderWidth: '1px',
borderColor: colorfgGrammeme(grammeme, colors),
color: colorfgGrammeme(grammeme, colors),
fontWeight: 600,
backgroundColor: colors.bgInput
}}
>

View File

@ -36,4 +36,4 @@ function InfoConstituenta({ data, ...restProps }: InfoConstituentaProps) {
</div>);
}
export default InfoConstituenta;
export default InfoConstituenta;

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
import { useConceptTheme } from '@/context/ThemeContext';
import { CstClass } from '@/models/rsform';
import { colorbgCstClass } from '@/utils/color';
@ -19,7 +21,13 @@ function InfoCstClass({ title }: InfoCstClassProps) {
return (
<p key={`${prefixes.cst_status_list}${index}`}>
<span
className='px-1 inline-block font-semibold min-w-[7rem] text-center border text-sm small-caps'
className={clsx(
'inline-block',
'min-w-[7rem]',
'px-1',
'border',
'text-center text-sm small-caps font-semibold'
)}
style={{backgroundColor: colorbgCstClass(cclass, colors)}}
>
{labelCstClass(cclass)}
@ -33,4 +41,4 @@ function InfoCstClass({ title }: InfoCstClassProps) {
</div>);
}
export default InfoCstClass;
export default InfoCstClass;

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
import { useConceptTheme } from '@/context/ThemeContext';
import { ExpressionStatus } from '@/models/rsform';
import { colorbgCstStatus } from '@/utils/color';
@ -17,11 +19,16 @@ function InfoCstStatus({ title }: InfoCstStatusProps) {
{Object.values(ExpressionStatus)
.filter(status => status !== ExpressionStatus.UNDEFINED)
.map(
(status, index) => {
return (
(status, index) =>
<p key={`${prefixes.cst_status_list}${index}`}>
<span
className='px-1 inline-block font-semibold min-w-[7rem] text-center border text-sm small-caps'
className={clsx(
'inline-block',
'min-w-[7rem]',
'px-1',
'border',
'text-center text-sm small-caps font-semibold'
)}
style={{backgroundColor: colorbgCstStatus(status, colors)}}
>
{labelExpressionStatus(status)}
@ -30,10 +37,9 @@ function InfoCstStatus({ title }: InfoCstStatusProps) {
<span>
{describeExpressionStatus(status)}
</span>
</p>);
}
</p>
)}
</div>);
}
export default InfoCstStatus;
export default InfoCstStatus;

View File

@ -122,4 +122,4 @@ export const AuthState = ({ children }: AuthStateProps) => {
>
{children}
</AuthContext.Provider>);
};
};

View File

@ -1,6 +1,7 @@
'use client';
import { createColumnHelper } from '@tanstack/react-table';
import clsx from 'clsx';
import { Dispatch, useCallback, useEffect, useMemo, useState } from 'react';
import MiniButton from '@/components/Common/MiniButton';
@ -95,7 +96,10 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
size: 40,
minSize: 40,
maxSize: 40,
cell: props => <div className='w-full text-center'>{props.getValue()}</div>
cell: props =>
<div className='w-full text-center'>
{props.getValue()}
</div>
}),
argumentsHelper.accessor(arg => arg.value || 'свободный аргумент', {
id: 'value',
@ -111,7 +115,13 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
minSize: 150,
maxSize: 150,
enableHiding: true,
cell: props => <div className='text-sm min-w-[9.3rem] max-w-[9.3rem] break-words'>{props.getValue()}</div>
cell: props =>
<div className={clsx(
'min-w-[9.3rem] max-w-[9.3rem]',
'text-sm break-words'
)}>
{props.getValue()}
</div>
}),
argumentsHelper.display({
id: 'actions',
@ -120,13 +130,13 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
maxSize: 50,
cell: props =>
<div className='max-h-[1.2rem]'>
{props.row.original.value &&
{props.row.original.value ?
<MiniButton
tooltip='Очистить значение'
icon={<CrossIcon size={3} color='text-warning'/>}
icon={<CrossIcon size={3} color='clr-text-warning'/>}
noHover
onClick={() => handleClearArgument(props.row.original)}
/>}
/> : null}
</div>
})
], [handleClearArgument]);
@ -139,21 +149,35 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
return (
<div className='flex flex-col gap-3'>
<div className='overflow-y-auto text-sm border select-none max-h-[5.8rem] min-h-[5.8rem]'>
<div className={clsx(
'max-h-[5.8rem] min-h-[5.8rem]',
'overflow-y-auto',
'text-sm',
'border',
'select-none'
)}>
<DataTable dense noFooter
data={state.arguments}
columns={columns}
conditionalRowStyles={conditionalRowStyles}
noDataComponent={
<span className='flex flex-col justify-center p-2 text-center min-h-[3.6rem]'>
<p>Аргументы отсутствуют</p>
</span>
<p className={clsx(
'min-h-[3.6rem] w-full',
'p-2',
'text-center'
)}>
Аргументы отсутствуют
</p>
}
onRowClicked={handleSelectArgument}
/>
</div>
<div className='flex items-center justify-center w-full gap-2 py-1 select-none'>
<div className={clsx(
'py-1 flex gap-2 justify-center items-center',
'w-full',
'select-none'
)}>
<span title='Выберите аргумент из списка сверху и значение из списка снизу'
className='font-semibold text-center'
>
@ -168,7 +192,7 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
<div className='flex'>
<MiniButton
tooltip='Подставить значение аргумента'
icon={<CheckIcon size={5} color={!argumentValue || !selectedArgument ? 'text-disabled' : 'text-success'} />}
icon={<CheckIcon size={5} color={!argumentValue || !selectedArgument ? 'text-disabled' : 'clr-text-success'} />}
disabled={!argumentValue || !selectedArgument}
onClick={() => handleAssignArgument(selectedArgument!, argumentValue)}
/>
@ -176,12 +200,12 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
tooltip='Откатить значение'
disabled={!isModified}
onClick={handleReset}
icon={<ArrowsRotateIcon size={5} color={isModified ? 'text-primary' : ''} />}
icon={<ArrowsRotateIcon size={5} color={isModified ? 'clr-text-primary' : ''} />}
/>
<MiniButton
tooltip='Очистить значение аргумента'
disabled={!selectedClearable}
icon={<CrossIcon size={5} color={!selectedClearable ? 'text-disabled' : 'text-warning'}/>}
icon={<CrossIcon size={5} color={!selectedClearable ? 'text-disabled' : 'clr-text-warning'}/>}
onClick={() => selectedArgument ? handleClearArgument(selectedArgument) : undefined}
/>
</div>

View File

@ -117,7 +117,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
canSubmit={validated}
onSubmit={handleSubmit}
submitText='Создать'
className='max-w-[43rem] min-w-[43rem] min-h-[35rem] px-6'
className='max-w-[43rem] min-w-[43rem] min-h-[36rem] px-6'
>
<Tabs defaultFocus forceRenderTabPanel
selectedTabClassName='clr-selected'
@ -129,16 +129,16 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
</Overlay>
<TabList className='flex justify-center mb-3'>
<div className='flex border w-fit'>
<div className='flex border divide-x rounded-none w-fit'>
<ConceptTab
label='Шаблон'
tooltip='Выбор шаблона выражения'
className='border-r w-[8rem]'
className='w-[8rem]'
/>
<ConceptTab
label='Аргументы'
tooltip='Подстановка аргументов шаблона'
className='border-r w-[8rem]'
className='w-[8rem]'
/>
<ConceptTab
label='Конституента'

View File

@ -1,5 +1,6 @@
'use client';
import clsx from 'clsx';
import { useEffect, useLayoutEffect, useState } from 'react';
import Modal, { ModalProps } from '@/components/Common/Modal';
@ -54,7 +55,10 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
canSubmit={validated}
onSubmit={handleSubmit}
submitText='Создать'
className='h-fit min-w-[35rem] py-2 flex flex-col justify-stretch gap-3 px-6'
className={clsx(
'h-fit min-w-[35rem]',
'py-2 px-6 flex flex-col gap-3 justify-stretch'
)}
>
<div className='flex justify-center w-full gap-6'>
<SelectSingle

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
import { IConstituenta } from '@/models/rsform';
import { labelConstituenta } from '@/utils/labels';
@ -15,7 +17,13 @@ function ConstituentsList({ list, items, title, prefix }: ConstituentsListProps)
<p className='pb-1'>
{title}: <b>{list.length}</b>
</p> : null}
<div className='px-3 border h-[9rem] overflow-y-auto whitespace-nowrap'>
<div className={clsx(
'h-[9rem]',
'px-3',
'overflow-y-auto',
'border',
'whitespace-nowrap'
)}>
{list.map(
(id) => {
const cst = items.find(cst => cst.id === id);

View File

@ -50,7 +50,7 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
hideWindow={hideWindow}
canSubmit={isValid}
onSubmit={handleSubmit}
className='items-center min-w-[40rem] max-w-[40rem] px-6 min-h-[34rem]'
className='min-w-[40rem] max-w-[40rem] px-6 min-h-[34rem]'
>
<Tabs defaultFocus
selectedTabClassName='clr-selected'
@ -62,15 +62,15 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
</Overlay>
<TabList className='flex justify-center mb-3'>
<div className='flex border w-fit'>
<div className='flex border divide-x rounded-none w-fit'>
<ConceptTab
label={labelReferenceType(ReferenceType.ENTITY)}
tooltip='Отсылка на термин в заданной словоформе'
className='w-[12rem] border-r-2'
label={labelReferenceType(ReferenceType.ENTITY)}
className='w-[12rem]'
/>
<ConceptTab
label={labelReferenceType(ReferenceType.SYNTACTIC)}
tooltip='Установление синтаксической связи с отсылкой на термин'
label={labelReferenceType(ReferenceType.SYNTACTIC)}
className='w-[12rem]'
/>
</div>

View File

@ -73,11 +73,11 @@ function EntityTab({ initial, items, setIsValid, setReference }: EntityTabProps)
rows={8}
/>
<div className='flex gap-4 flex-start'>
<div className='flex gap-6 flex-start'>
<TextInput dense
label='Отсылаемая конституента'
placeholder='Имя'
dimensions='max-w-[16rem] min-w-[16rem] whitespace-nowrap'
dimensions='max-w-[17rem] min-w-[17rem]'
value={alias}
onChange={event => setAlias(event.target.value)}
/>

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
import { Grammeme } from '@/models/language';
interface WordformButtonProps {
@ -12,7 +14,14 @@ function WordformButton({ text, example, grams, onSelectGrams, isSelected, ...re
return (
<button type='button' tabIndex={-1}
onClick={() => onSelectGrams(grams)}
className={`min-w-[6rem] p-1 border rounded-none cursor-pointer clr-btn-clear clr-hover ${isSelected ? 'clr-selected': ''}`}
className={clsx(
'min-w-[6rem]',
'p-1',
'border rounded-none',
'cursor-pointer',
'clr-btn-clear clr-hover',
isSelected && 'clr-selected'
)}
{...restProps}
>
<p className='font-semibold'>{text}</p>

View File

@ -1,5 +1,6 @@
'use client';
import clsx from 'clsx';
import { useLayoutEffect, useState } from 'react';
import Label from '@/components/Common/Label';
@ -151,7 +152,7 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
<div className='max-w-min'>
<MiniButton
tooltip='Генерировать словоформу'
icon={<ArrowLeftIcon size={5} color={inputGrams.length == 0 ? 'text-disabled' : 'text-primary'} />}
icon={<ArrowLeftIcon size={5} color={inputGrams.length == 0 ? 'text-disabled' : 'clr-text-primary'} />}
disabled={textProcessor.loading || inputGrams.length == 0}
onClick={handleInflect}
/>
@ -159,7 +160,7 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
tooltip='Определить граммемы'
icon={<ArrowRightIcon
size={5}
color={!inputText ? 'text-disabled' : 'text-primary'}
color={!inputText ? 'text-disabled' : 'clr-text-primary'}
/>}
disabled={textProcessor.loading || !inputText}
onClick={handleParse}
@ -180,7 +181,7 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
tooltip='Внести словоформу'
icon={<CheckIcon
size={5}
color={!inputText || inputGrams.length == 0 ? 'text-disabled' : 'text-success'}
color={!inputText || inputGrams.length == 0 ? 'text-disabled' : 'clr-text-success'}
/>}
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
onClick={handleAddForm}
@ -189,16 +190,26 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
tooltip='Генерировать стандартные словоформы'
icon={<ChevronDoubleDownIcon
size={5}
color={!inputText ? 'text-disabled' : 'text-primary'}
color={!inputText ? 'text-disabled' : 'clr-text-primary'}
/>}
disabled={textProcessor.loading || !inputText}
onClick={handleGenerateLexeme}
/>
</Overlay>
<h1 className='mt-3 mb-2 text-sm'>Заданные вручную словоформы [{forms.length}]</h1>
<div className={clsx(
'mt-3 mb-2',
'text-sm text-center font-semibold'
)}>
Заданные вручную словоформы [{forms.length}]
</div>
<div className='border overflow-y-auto max-h-[17.4rem] min-h-[17.4rem] mb-2'>
<div className={clsx(
'mb-2',
'max-h-[17.4rem] min-h-[17.4rem]',
'border',
'overflow-y-auto'
)}>
<WordFormsTable
forms={forms}
setForms={setForms}

View File

@ -89,7 +89,7 @@ function WordFormsTable({ forms, setForms, onFormSelect, loading }: WordFormsTab
columns={columns}
headPosition='0'
noDataComponent={
<span className='flex flex-col justify-center p-2 text-center min-h-[2rem]'>
<span className='p-2 text-center min-h-[2rem]'>
<p>Список пуст</p>
<p>Добавьте словоформу</p>
</span>

View File

@ -1,5 +1,6 @@
'use client';
import clsx from 'clsx';
import { useLayoutEffect, useState } from 'react';
import Modal, { ModalProps } from '@/components/Common/Modal';
@ -49,7 +50,10 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
hideWindow={hideWindow}
canSubmit={validated}
onSubmit={handleSubmit}
className='flex justify-center items-center gap-6 w-full min-w-[24rem] py-6 px-6'
className={clsx(
'w-full min-w-[24rem]',
'py-6 px-6 flex gap-6 justify-center items-center'
)}
>
<SelectSingle
placeholder='Выберите тип'
@ -72,4 +76,4 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
</Modal>);
}
export default DlgRenameCst;
export default DlgRenameCst;

View File

@ -68,7 +68,7 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
<span>{expression.slice(hoverNode.finish)}</span>
</div> : null}
</div>
<div className='flex-wrap w-full h-full overflow-auto'>
<div className='w-full h-full overflow-auto'>
<div
className='relative'
style={{

View File

@ -63,4 +63,4 @@ function DlgUploadRSForm({ hideWindow }: DlgUploadRSFormProps) {
</Modal>);
}
export default DlgUploadRSForm;
export default DlgUploadRSForm;

View File

@ -167,6 +167,7 @@
.divide-x, .divide-y, .divide-x-2, .divide-y-2
) {
border-color: var(--cl-bg-40);
@apply divide-inherit;
.dark & {
border-color: var(--cd-bg-40);
}
@ -264,8 +265,8 @@
}
}
:is(.text-primary,
.text-url
:is(.clr-text-primary,
.clr-text-url
) {
color: var(--cl-prim-fg-80);
.dark & {
@ -280,7 +281,7 @@
}
}
:is(.text-controls,
:is(.clr-text-controls,
.clr-btn-nav,
.clr-btn-clear
) {
@ -303,21 +304,21 @@
}
}
.text-default {
.clr-text-default {
color: var(--cl-fg-100);
.dark & {
color: var(--cd-fg-100);
}
}
.text-warning {
.clr-text-warning {
color: var(--cl-red-fg-100);
.dark & {
color: var(--cd-red-fg-100);
}
}
.text-success {
.clr-text-success {
color: var(--cl-green-fg-100);
.dark & {
color: var(--cd-green-fg-100);
@ -341,7 +342,7 @@
border-color: var(--cd-bg-40);
outline-color: var(--cd-prim-bg-100);
}
@apply border rounded outline-2 outline
@apply outline-2 outline
}
.cm-editor .cm-placeholder {

View File

@ -11,4 +11,4 @@ createRoot(document.getElementById('root')!).render(
<App />
</GlobalProviders>
)
)

View File

@ -136,4 +136,4 @@ describe('Testing reference parsing', () => {
expect(parseSyntacticReference('@{-1|test test}')).toStrictEqual({offset: -1, nominal: 'test test'});
expect(parseSyntacticReference('@{-99|test test}')).toStrictEqual({offset: -99, nominal: 'test test'});
});
});
});

View File

@ -45,4 +45,4 @@ describe('Testing matching LibraryItem', () => {
expect(matchLibraryItem(item1, item1.time_create)).toEqual(false);
expect(matchLibraryItem(item1, item1.comment)).toEqual(false);
});
});
});

View File

@ -195,19 +195,17 @@ export function inferClass(type: CstType, isTemplate: boolean): CstClass {
* Creates a mock {@link IConstituenta} object with the provided parameters and default values for other properties.
*/
export function createMockConstituenta(
schema: number,
id: number,
alias: string,
type: CstType,
comment: string
): IConstituenta {
return {
id: id,
order: -1,
schema: schema,
schema: -1,
alias: alias,
convention: comment,
cst_type: type,
cst_type: CstType.BASE,
term_raw: '',
term_resolved: '',
term_forms: [],

View File

@ -1,5 +1,6 @@
'use client';
import clsx from 'clsx';
import { useEffect, useRef, useState } from 'react';
import { toast } from 'react-toastify';
@ -79,7 +80,10 @@ function CreateRSFormPage() {
return (
<RequireAuth>
<form
className='flex flex-col w-full max-w-lg gap-3 px-6 py-3'
className={clsx(
'w-full max-w-lg',
'px-6 py-3 flex flex-col gap-3'
)}
onSubmit={handleSubmit}
>
<h1>Создание концептуальной схемы</h1>
@ -91,7 +95,7 @@ function CreateRSFormPage() {
/>
<MiniButton
tooltip='Загрузить из Экстеор'
icon={<DownloadIcon size={5} color='text-primary'/>}
icon={<DownloadIcon size={5} color='clr-text-primary'/>}
onClick={() => inputRef.current?.click()}
/>
</Overlay>

View File

@ -1,3 +1,4 @@
import clsx from 'clsx';
import { useLayoutEffect } from 'react';
import { useAuth } from '@/context/AuthContext';
@ -21,7 +22,10 @@ function HomePage() {
}, [router, user])
return (
<div className='flex flex-col items-center justify-center w-full px-4 py-2'>
<div className={clsx(
'w-full',
'px-4 py-2 flex flex-col justify-center items-center'
)}>
{user?.is_staff ?
<p>
Лендинг находится в разработке. Данная страница видна только пользователям с правами администратора.

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
import { EducationIcon, GroupIcon, SubscribedIcon } from '@/components/Icons';
import { ICurrentUser, ILibraryItem } from '@/models/library';
import { prefixes } from '@/utils/constants';
@ -10,7 +12,10 @@ interface ItemIconsProps {
function ItemIcons({ user, item }: ItemIconsProps) {
return (
<div
className='inline-flex items-center justify-start gap-1 min-w-[2.75rem]'
className={clsx(
'min-w-[2.75rem]',
'inline-flex gap-1'
)}
id={`${prefixes.library_list}${item.id}`}
>
{(user && user.subscriptions.includes(item.id)) ?

View File

@ -1,5 +1,6 @@
'use client';
import clsx from 'clsx';
import { useCallback } from 'react';
import ConceptSearch from '@/components/Common/ConceptSearch';
@ -41,18 +42,32 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setFilter }:
}, [strategy, router]);
return (
<div className='sticky top-0 left-0 right-0 flex items-stretch justify-start w-full border-b clr-input max-h-[2.3rem] pr-40'>
<div className='px-2 py-1 select-none whitespace-nowrap min-w-[10rem]'>
<div className={clsx(
'sticky top-0',
'w-full max-h-[2.3rem]',
'pr-40 flex justify-start items-stretch',
'border-b',
'clr-input'
)}>
<div className={clsx(
'min-w-[10rem]',
'px-2 self-center',
'select-none',
'whitespace-nowrap',
)}>
Фильтр
<span className='ml-2'>
{filtered} из {total}
</span>
</div>
<div className='flex items-center justify-center w-full gap-1'>
<div className={clsx(
'w-full',
'flex gap-1 justify-center items-center'
)}>
<ConceptSearch noBorder
dimensions='min-w-[10rem]'
value={query}
onChange={handleChangeQuery}
dimensions='min-w-[10rem] '
/>
<PickerStrategy
value={strategy}

View File

@ -1,5 +1,6 @@
'use client';
import clsx from 'clsx';
import { useMemo } from 'react';
import { useIntl } from 'react-intl';
@ -92,7 +93,12 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
return (
<>
<div className='sticky top-[2.3rem] w-full'>
<div className='absolute top-[0.125rem] left-[0.25rem] flex gap-1 ml-3 z-pop'>
<div className={clsx(
'z-pop',
'absolute top-[0.125rem] left-[0.25rem]',
'ml-3',
'flex gap-1'
)}>
<HelpButton
topic={HelpTopic.LIBRARY}
dimensions='max-w-[35rem]'

View File

@ -1,6 +1,7 @@
'use client';
import axios from 'axios';
import clsx from 'clsx';
import { useEffect, useState } from 'react';
import SubmitButton from '@/components/Common/SubmitButton';
@ -18,7 +19,7 @@ import { resources } from '@/utils/constants';
function ProcessError({error}: { error: ErrorData }): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
return (
<div className='text-sm select-text text-warning'>
<div className='text-sm select-text clr-text-warning'>
На Портале отсутствует такое сочетание имени пользователя и пароля
</div>);
} else {
@ -65,7 +66,10 @@ function LoginPage() {
}
return (
<form
className='pt-12 pb-6 px-6 flex flex-col gap-3 w-[24rem]'
className={clsx(
'w-[24rem]',
'pt-12 pb-6 px-6 flex flex-col gap-3'
)}
onSubmit={handleSubmit}
>
<img alt='Концепт Портал'

View File

@ -1,3 +1,5 @@
import clsx from 'clsx';
import { HelpTopic } from '@/models/miscelanious';
import { prefixes } from '@/utils/constants';
import { describeHelpTopic, labelHelpTopic } from '@/utils/labels';
@ -9,20 +11,32 @@ interface TopicsListProps {
function TopicsList({ activeTopic, onChangeTopic }: TopicsListProps) {
return (
<div className='sticky top-0 left-0 border-x min-w-[13rem] select-none flex flex-col clr-controls small-caps h-fit'>
<div className='my-2 text-lg text-center'>Справка</div>
<div className={clsx(
'sticky top-0 left-0',
'min-w-[13rem] h-fit',
'flex flex-col',
'border-x',
'clr-controls',
'small-caps',
'select-none'
)}>
<h1 className='mt-2 mb-1'>Справка</h1>
{Object.values(HelpTopic).map(
(topic, index) => {
const isActive = activeTopic === topic;
return (
(topic, index) =>
<div key={`${prefixes.topic_list}${index}`}
className={`px-3 py-1 border-y cursor-pointer clr-hover ${isActive ? 'clr-selected ' : ''}`}
className={clsx(
'px-3 py-1',
'border-y',
'clr-hover',
'cursor-pointer',
activeTopic === topic && 'clr-selected'
)}
title={describeHelpTopic(topic)}
onClick={() => onChangeTopic(topic)}
>
{labelHelpTopic(topic)}
</div>);
})}
</div>
)}
</div>);
}

View File

@ -34,30 +34,30 @@ function ConstituentaToolbar({
<MiniButton
tooltip='Сохранить изменения [Ctrl + S]'
disabled={!canSave}
icon={<SaveIcon size={5} color={canSave ? 'text-primary' : ''}/>}
icon={<SaveIcon size={5} color={canSave ? 'clr-text-primary' : ''}/>}
onClick={onSubmit}
/>
<MiniButton
tooltip='Сбросить несохраненные изменения'
disabled={!canSave}
onClick={onReset}
icon={<ArrowsRotateIcon size={5} color={canSave ? 'text-primary' : ''} />}
icon={<ArrowsRotateIcon size={5} color={canSave ? 'clr-text-primary' : ''} />}
/>
<MiniButton
tooltip='Создать конституенту после данной'
disabled={!isMutable}
onClick={onCreate}
icon={<SmallPlusIcon size={5} color={isMutable ? 'text-success' : ''} />}
icon={<SmallPlusIcon size={5} color={isMutable ? 'clr-text-success' : ''} />}
/>
<MiniButton
tooltip='Клонировать конституенту [Alt + V]'
disabled={!isMutable}
onClick={onClone}
icon={<CloneIcon size={5} color={isMutable ? 'text-success' : ''} />}
icon={<CloneIcon size={5} color={isMutable ? 'clr-text-success' : ''} />}
/>
<MiniButton
tooltip='Создать конституенту из шаблона [Alt + E]'
icon={<DiamondIcon color={isMutable ? 'text-primary': ''} size={5}/>}
icon={<DiamondIcon color={isMutable ? 'clr-text-primary': ''} size={5}/>}
disabled={!isMutable}
onClick={onTemplates}
/>
@ -65,7 +65,7 @@ function ConstituentaToolbar({
tooltip='Удалить редактируемую конституенту'
disabled={!isMutable}
onClick={onDelete}
icon={<DumpBinIcon size={5} color={isMutable ? 'text-warning' : ''} />}
icon={<DumpBinIcon size={5} color={isMutable ? 'clr-text-warning' : ''} />}
/>
<HelpButton topic={HelpTopic.CONSTITUENTA} offset={4} />
</Overlay>);

View File

@ -109,9 +109,9 @@ function FormConstituenta({
disabled={!readyForEdit}
noHover
onClick={onEditTerm}
icon={<EditIcon size={4} color={readyForEdit ? 'text-primary' : ''} />}
icon={<EditIcon size={4} color={readyForEdit ? 'clr-text-primary' : ''} />}
/>
<div className='pt-1 pl-6 text-sm font-semibold w-fit'>
<div className='pt-1 pl-[1.375rem] text-sm font-semibold w-fit'>
<span>Имя </span>
<span className='ml-1'>{constituenta?.alias ?? ''}</span>
</div>
@ -119,7 +119,7 @@ function FormConstituenta({
tooltip='Переименовать конституенту'
disabled={!readyForEdit}
onClick={handleRename}
icon={<EditIcon size={4} color={readyForEdit ? 'text-primary' : ''} />}
icon={<EditIcon size={4} color={readyForEdit ? 'clr-text-primary' : ''} />}
/>
</Overlay>
<form id={id}

View File

@ -135,7 +135,7 @@ function EditorRSExpression({
<MiniButton noHover
tooltip='Дерево разбора выражения'
onClick={handleShowAST}
icon={<ASTNetworkIcon size={5} color='text-primary' />}
icon={<ASTNetworkIcon size={5} color='clr-text-primary' />}
/>
</Overlay>

View File

@ -22,7 +22,7 @@ function ParsingResult({ data, disabled, onShowError }: ParsingResultProps) {
return (
<p
key={`error-${index}`}
className={`text-warning ${disabled ? '' : 'cursor-pointer'}`}
className={`clr-text-warning ${disabled ? '' : 'cursor-pointer'}`}
onClick={disabled ? undefined : () => onShowError(error)}
>
<span className='mr-1 font-semibold underline'>

View File

@ -27,7 +27,7 @@ function RSAnalyzer({
}: RSAnalyzerProps) {
return (
<div className='w-full max-h-[4.5rem] min-h-[4.5rem] flex'>
<div className='flex flex-col text-sm'>
<div className='flex flex-col'>
<Button noOutline
text='Проверить'
tooltip='Проверить формальное определение'

View File

@ -1,5 +1,6 @@
'use client';
import clsx from 'clsx';
import { useMemo } from 'react';
import { useConceptTheme } from '@/context/ThemeContext';
@ -31,7 +32,12 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
return (
<div title={describeExpressionStatus(status)}
className='inline-flex items-center justify-center w-full h-full text-sm font-semibold align-middle border rounded-none select-none small-caps'
className={clsx(
'w-full h-full',
'border rounded-none',
'text-sm font-semibold small-caps text-center',
'select-none'
)}
style={{backgroundColor: colorbgCstStatus(status, colors)}}
>
{labelExpressionStatus(status)}

View File

@ -1,5 +1,5 @@
import Divider from '@/components/Common/Divider';
import LabeledText from '@/components/Common/LabeledText';
import LabeledValue from '@/components/Common/LabeledValue';
import { type IRSFormStats } from '@/models/rsform';
interface RSFormStatsProps {
@ -12,36 +12,36 @@ function RSFormStats({ stats }: RSFormStatsProps) {
}
return (
<div className='flex flex-col gap-1 px-4 mt-8 min-w-[16rem]'>
<LabeledText id='count_all'
<LabeledValue id='count_all'
label='Всего конституент '
text={stats.count_all}
/>
<LabeledText id='count_errors'
<LabeledValue id='count_errors'
label='Некорректных'
text={stats.count_errors}
/>
{stats.count_property !== 0 ?
<LabeledText id='count_property'
<LabeledValue id='count_property'
label='Неразмерных'
text={stats.count_property}
/> : null}
{stats.count_incalc !== 0 ?
<LabeledText id='count_incalc'
<LabeledValue id='count_incalc'
label='Невычислимых'
text={stats.count_incalc}
/> : null}
<Divider margins='my-2' />
<LabeledText id='count_termin'
<LabeledValue id='count_termin'
label='Термины'
text={stats.count_termin}
/>
<LabeledText id='count_definition'
<LabeledValue id='count_definition'
label='Определения'
text={stats.count_definition}
/>
<LabeledText id='count_convention'
<LabeledValue id='count_convention'
label='Конвенции'
text={stats.count_convention}
/>
@ -49,42 +49,42 @@ function RSFormStats({ stats }: RSFormStatsProps) {
<Divider margins='my-2' />
{stats.count_base !== 0 ?
<LabeledText id='count_base'
<LabeledValue id='count_base'
label='Базисные множества '
text={stats.count_base}
/> : null}
{ stats.count_constant !== 0 ?
<LabeledText id='count_constant'
<LabeledValue id='count_constant'
label='Константные множества '
text={stats.count_constant}
/> : null}
{stats.count_structured !== 0 ?
<LabeledText id='count_structured'
<LabeledValue id='count_structured'
label='Родовые структуры '
text={stats.count_structured}
/> : null}
{stats.count_axiom !== 0 ?
<LabeledText id='count_axiom'
<LabeledValue id='count_axiom'
label='Аксиомы '
text={stats.count_axiom}
/> : null}
{stats.count_term !== 0 ?
<LabeledText id='count_term'
<LabeledValue id='count_term'
label='Термы '
text={stats.count_term}
/> : null}
{stats.count_function !== 0 ?
<LabeledText id='count_function'
<LabeledValue id='count_function'
label='Терм-функции '
text={stats.count_function}
/> : null}
{stats.count_predicate !== 0 ?
<LabeledText id='count_predicate'
<LabeledValue id='count_predicate'
label='Предикат-функции '
text={stats.count_predicate}
/> : null}
{stats.count_theorem !== 0 ?
<LabeledText id='count_theorem'
<LabeledValue id='count_theorem'
label='Теоремы '
text={stats.count_theorem}
/> : null}

View File

@ -32,22 +32,22 @@ function RSFormToolbar({
<MiniButton
tooltip='Сохранить изменения [Ctrl + S]'
disabled={!canSave}
icon={<SaveIcon size={5} color={canSave ? 'text-primary' : ''}/>}
icon={<SaveIcon size={5} color={canSave ? 'clr-text-primary' : ''}/>}
onClick={onSubmit}
/>
<MiniButton
tooltip='Поделиться схемой'
icon={<ShareIcon size={5} color='text-primary'/>}
icon={<ShareIcon size={5} color='clr-text-primary'/>}
onClick={onShare}
/>
<MiniButton
tooltip='Скачать TRS файл'
icon={<DownloadIcon size={5} color='text-primary'/>}
icon={<DownloadIcon size={5} color='clr-text-primary'/>}
onClick={onDownload}
/>
<MiniButton
tooltip={claimable ? 'Стать владельцем' : 'Невозможно стать владельцем' }
icon={<OwnerIcon size={5} color={!claimable ? '' : 'text-success'}/>}
icon={<OwnerIcon size={5} color={!claimable ? '' : 'clr-text-success'}/>}
disabled={!claimable || anonymous}
onClick={onClaim}
/>
@ -55,7 +55,7 @@ function RSFormToolbar({
tooltip='Удалить схему'
disabled={!isMutable}
onClick={onDestroy}
icon={<DumpBinIcon size={5} color={isMutable ? 'text-warning' : ''} />}
icon={<DumpBinIcon size={5} color={isMutable ? 'clr-text-warning' : ''} />}
/>
<HelpButton topic={HelpTopic.RSFORM} offset={4} />
</Overlay>);

View File

@ -52,13 +52,13 @@ function RSListToolbar({
/>
<MiniButton
tooltip='Клонировать конституенту [Alt + V]'
icon={<CloneIcon color={isMutable && selectedCount === 1 ? 'text-success': ''} size={5}/>}
icon={<CloneIcon color={isMutable && selectedCount === 1 ? 'clr-text-success': ''} size={5}/>}
disabled={!isMutable || selectedCount !== 1}
onClick={onClone}
/>
<MiniButton
tooltip='Добавить новую конституенту... [Alt + `]'
icon={<SmallPlusIcon color={isMutable ? 'text-success': ''} size={5}/>}
icon={<SmallPlusIcon color={isMutable ? 'clr-text-success': ''} size={5}/>}
disabled={!isMutable}
onClick={() => onCreate()}
/>
@ -66,7 +66,7 @@ function RSListToolbar({
<div>
<MiniButton
tooltip='Добавить пустую конституенту'
icon={<ArrowDropdownIcon color={isMutable ? 'text-success': ''} size={5}/>}
icon={<ArrowDropdownIcon color={isMutable ? 'clr-text-success': ''} size={5}/>}
disabled={!isMutable}
onClick={insertMenu.toggle}
/>
@ -89,19 +89,19 @@ function RSListToolbar({
</div>
<MiniButton
tooltip='Создать конституенту из шаблона [Alt + E]'
icon={<DiamondIcon color={isMutable ? 'text-primary': ''} size={5}/>}
icon={<DiamondIcon color={isMutable ? 'clr-text-primary': ''} size={5}/>}
disabled={!isMutable}
onClick={onTemplates}
/>
<MiniButton
tooltip='Сброс имен: присвоить порядковые имена [Alt + R]'
icon={<UpdateIcon color={isMutable ? 'text-primary': ''} size={5}/>}
icon={<UpdateIcon color={isMutable ? 'clr-text-primary': ''} size={5}/>}
disabled={!isMutable}
onClick={onReindex}
/>
<MiniButton
tooltip='Удалить выбранные [Delete]'
icon={<DumpBinIcon color={isMutable && !nothingSelected ? 'text-warning' : ''} size={5}/>}
icon={<DumpBinIcon color={isMutable && !nothingSelected ? 'clr-text-warning' : ''} size={5}/>}
disabled={!isMutable || nothingSelected}
onClick={onDelete}
/>

View File

@ -146,7 +146,7 @@ function RSTable({
<span className='flex flex-col justify-center p-2 text-center'>
<p>Список пуст</p>
<p
className='cursor-pointer text-primary hover:underline'
className='cursor-pointer clr-text-primary hover:underline'
onClick={() => onCreateNew()}
>
Создать новую конституенту

View File

@ -1,5 +1,6 @@
'use client';
import clsx from 'clsx';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { GraphEdge, GraphNode, LayoutTypes } from 'reagraph';
@ -230,16 +231,28 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
{hoverCst ?
<Overlay
position='top-[1.6rem] left-[2.6rem] px-3 w-[25rem] h-fit min-h-[11rem] overflow-y-auto'
layer='z-tooltip'
className='border shadow-md clr-app'
position='top-[1.6rem] left-[2.6rem]'
className={clsx(
'w-[25rem] h-fit min-h-[11rem]',
'px-3',
'overflow-y-auto',
'border shadow-md',
'clr-app'
)}
>
<InfoConstituenta
data={hoverCst}
/>
</Overlay> : null}
<Overlay position='top-0 left-0 max-w-[13.5rem] min-w-[13.5rem]' className='flex flex-col gap-3'>
<Overlay
position='top-0 left-0'
className={clsx(
'max-w-[13.5rem] min-w-[13.5rem]',
'flex flex-col gap-3'
)}
>
<GraphSidebar
coloring={coloringScheme}
layout={layout}

View File

@ -34,37 +34,37 @@ function GraphToolbar({
<Overlay position='w-full top-1 right-0 flex items-start justify-center'>
<MiniButton
tooltip='Настройки фильтрации узлов и связей'
icon={<FilterIcon color='text-primary' size={5} />}
icon={<FilterIcon color='clr-text-primary' size={5} />}
onClick={showParamsDialog}
/>
<MiniButton
tooltip={!noText ? 'Скрыть текст' : 'Отобразить текст'}
icon={
!noText
? <LetterALinesIcon color='text-success' size={5} />
: <LetterAIcon color='text-primary' size={5} />
? <LetterALinesIcon color='clr-text-success' size={5} />
: <LetterAIcon color='clr-text-primary' size={5} />
}
onClick={toggleNoText}
/>
<MiniButton
tooltip='Новая конституента'
icon={<SmallPlusIcon color={isMutable ? 'text-success' : ''} size={5} />}
icon={<SmallPlusIcon color={isMutable ? 'clr-text-success' : ''} size={5} />}
disabled={!isMutable}
onClick={onCreate}
/>
<MiniButton
tooltip='Удалить выбранные'
icon={<DumpBinIcon color={isMutable && !nothingSelected ? 'text-warning' : ''} size={5} />}
icon={<DumpBinIcon color={isMutable && !nothingSelected ? 'clr-text-warning' : ''} size={5} />}
disabled={!isMutable || nothingSelected}
onClick={onDelete}
/>
<MiniButton
icon={<ArrowsFocusIcon color='text-primary' size={5} />}
icon={<ArrowsFocusIcon color='clr-text-primary' size={5} />}
tooltip='Восстановить камеру'
onClick={onResetViewpoint}
/>
<MiniButton
icon={<PlanetIcon color={!is3D ? '' : orbit ? 'text-success' : 'text-primary'} size={5} />}
icon={<PlanetIcon color={!is3D ? '' : orbit ? 'clr-text-success' : 'clr-text-primary'} size={5} />}
tooltip='Анимация вращения'
disabled={!is3D}
onClick={toggleOrbit}

View File

@ -14,4 +14,4 @@ function RSFormPage() {
</RSFormState>);
}
export default RSFormPage;
export default RSFormPage;

View File

@ -1,6 +1,7 @@
'use client';
import axios from 'axios';
import clsx from 'clsx';
import fileDownload from 'js-file-download';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs';
@ -377,7 +378,11 @@ function RSTabs() {
className='flex flex-col w-full'
>
<div className='flex justify-center w-[100vw]'>
<TabList className='flex border-b-2 border-x-2 justify-stretch w-fit h-[1.9rem]'>
<TabList className={clsx(
'w-fit h-[1.9rem]',
'flex justify-stretch',
'border-b-2 border-x-2 divide-x-2'
)}>
<RSTabsMenu
onDownload={onDownloadSchema}
onDestroy={onDestroySchema}
@ -389,25 +394,28 @@ function RSTabs() {
/>
<ConceptTab
label='Карточка'
className='border-x-2'
tooltip={`Название схемы: ${schema.title ?? ''}`}
/>
<ConceptTab
label='Содержание'
className='border-r-2'
tooltip={`Всего конституент: ${schema.stats?.count_all ?? 0}\nКоличество ошибок: ${schema.stats?.count_errors ?? 0}`}
/>
<ConceptTab
label='Редактор'
className='border-r-2'
/>
<ConceptTab
label='Граф термов'
tooltip={[
`Всего конституент: ${schema.stats?.count_all ?? 0}`,
`Количество ошибок: ${schema.stats?.count_errors ?? 0}`
].join('\n')}
/>
<ConceptTab label='Редактор' />
<ConceptTab label='Граф термов' />
</TabList>
</div>
<div className='overflow-y-auto min-w-[48rem] w-[100vw] flex justify-center' style={{ maxHeight: panelHeight}}>
<div
className={clsx(
'min-w-[48rem] w-[100vw]',
'flex justify-center',
'overflow-y-auto'
)}
style={{ maxHeight: panelHeight}}
>
<TabPanel forceRender style={{ display: activeTab === RSTabID.CARD ? '': 'none' }}>
<EditorRSForm
isModified={isModified}

Some files were not shown because too many files have changed in this diff Show More