F: Refactor z-index and stacking

This commit is contained in:
Ivan 2025-03-07 20:38:40 +03:00
parent 293f1cad6f
commit 81fa0c5796
39 changed files with 317 additions and 352 deletions

View File

@ -15,6 +15,7 @@ interface OverlayProps extends Styling {
/**
* Displays a transparent overlay over the main content.
* Note: Overlay should be inside a relative container.
*/
export function Overlay({
children,
@ -24,10 +25,8 @@ export function Overlay({
...restProps
}: React.PropsWithChildren<OverlayProps>) {
return (
<div className='relative'>
<div className={clsx('absolute', className, position, layer)} {...restProps}>
{children}
</div>
<div className={clsx('absolute', className, position, layer)} {...restProps}>
{children}
</div>
);
}

View File

@ -39,6 +39,7 @@ export function Tooltip({
delayHide={100}
opacity={1}
className={clsx(
'relative',
'max-h-[calc(100svh-6rem)]',
'overflow-y-auto overflow-x-hidden sm:overflow-hidden overscroll-contain',
'border shadow-md',

View File

@ -23,6 +23,7 @@ interface DropdownProps extends Styling {
/**
* Animated list of children with optional positioning and visibility control.
* Note: Dropdown should be inside a relative container.
*/
export function Dropdown({
isOpen,

View File

@ -1,6 +1,5 @@
import clsx from 'clsx';
import { Overlay } from '@/components/Container';
import { IconSearch } from '@/components/Icons';
import { type Styling } from '@/components/props';
@ -35,15 +34,17 @@ export function SearchBar({
noIcon,
onChangeQuery,
noBorder,
className,
placeholder = 'Поиск',
...restProps
}: SearchBarProps) {
return (
<div {...restProps}>
<div className={clsx('relative', className)} {...restProps}>
{!noIcon ? (
<Overlay position='top-[-0.125rem] left-3 translate-y-1/2' className='pointer-events-none clr-text-controls'>
<IconSearch size='1.25rem' />
</Overlay>
<IconSearch
className='absolute top-[-0.125rem] left-3 translate-y-1/2 pointer-events-none clr-text-controls'
size='1.25rem'
/>
) : null}
<TextInput
id={id}

View File

@ -3,7 +3,6 @@ import clsx from 'clsx';
import { globalIDs, PARAMETER } from '@/utils/constants';
import { Overlay } from '../Container';
import { MiniButton } from '../Control';
import { IconDropArrow, IconPageRight } from '../Icons';
import { type Styling } from '../props';
@ -86,6 +85,7 @@ export function SelectTree<ItemType>({
<div
key={`${prefix}${index}`}
className={clsx(
'relative',
'pr-3 pl-6 border-b',
'cc-scroll-row',
'bg-prim-200 clr-hover cc-animate-color',
@ -108,14 +108,13 @@ export function SelectTree<ItemType>({
}}
>
{foldable.has(item) ? (
<Overlay position='left-[-1.3rem]' className={clsx(!folded.includes(item) && 'top-[0.1rem]')}>
<MiniButton
noPadding
noHover
icon={!folded.includes(item) ? <IconDropArrow size='1rem' /> : <IconPageRight size='1.25rem' />}
onClick={event => handleClickFold(event, item, folded.includes(item))}
/>
</Overlay>
<MiniButton
className={clsx('absolute left-[0.3rem]', !folded.includes(item) ? 'top-[0.4rem]' : 'top-1')}
noPadding
noHover
icon={!folded.includes(item) ? <IconDropArrow size='1rem' /> : <IconPageRight size='1.25rem' />}
onClick={event => handleClickFold(event, item, folded.includes(item))}
/>
) : null}
{getParent(item) === item ? getLabel(item) : `- ${getLabel(item).toLowerCase()}`}
</div>

View File

@ -9,9 +9,9 @@ interface ModalBackdropProps {
export function ModalBackdrop({ onHide }: ModalBackdropProps) {
return (
<>
<div className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'backdrop-blur-[3px] opacity-50')} />
<div className={clsx('z-bottom', 'fixed top-0 left-0', 'w-full h-full', 'backdrop-blur-[3px] opacity-50')} />
<div
className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'bg-prim-0 opacity-25')}
className={clsx('z-bottom', 'fixed top-0 left-0', 'w-full h-full', 'bg-prim-0 opacity-25')}
onClick={onHide}
/>
</>

View File

@ -10,7 +10,6 @@ import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
import { Overlay } from '../Container';
import { Button, MiniButton, SubmitButton } from '../Control';
import { IconClose } from '../Icons';
import { type Styling } from '../props';
@ -89,12 +88,12 @@ export function ModalForm({
}
return (
<div className='fixed top-0 left-0 w-full h-full z-modal cursor-default'>
<div className='fixed top-0 left-0 w-full h-full z-modal isolate cursor-default'>
<ModalBackdrop onHide={handleCancel} />
<form
className={clsx(
'cc-animate-modal',
'z-modal absolute bottom-1/2 left-1/2 -translate-x-1/2 translate-y-1/2',
'absolute bottom-1/2 left-1/2 -translate-x-1/2 translate-y-1/2',
'border rounded-xl bg-prim-100'
)}
onSubmit={handleSubmit}
@ -105,15 +104,13 @@ export function ModalForm({
</div>
) : null}
<Overlay className='z-modalOverlay'>
<MiniButton
noPadding
titleHtml={prepareTooltip('Закрыть диалоговое окно', 'ESC')}
icon={<IconClose size='1.25rem' />}
className='float-right mt-2 mr-2'
onClick={handleCancel}
/>
</Overlay>
<MiniButton
noPadding
titleHtml={prepareTooltip('Закрыть диалоговое окно', 'ESC')}
icon={<IconClose size='1.25rem' />}
className='float-right mt-2 mr-2'
onClick={handleCancel}
/>
{header ? <h1 className='px-12 py-2 select-none'>{header}</h1> : null}
@ -133,7 +130,7 @@ export function ModalForm({
{children}
</div>
<div className='z-modal-controls my-2 flex gap-12 justify-center text-sm'>
<div className='z-pop my-2 flex gap-12 justify-center text-sm'>
<SubmitButton
autoFocus
text={submitText}

View File

@ -4,9 +4,9 @@ import { Loader } from '@/components/Loader';
export function ModalLoader() {
return (
<div className='fixed top-0 left-0 w-full h-full z-modal cursor-default'>
<div className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'backdrop-blur-[3px] opacity-50')} />
<div className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'bg-prim-0 opacity-25')} />
<div className='fixed top-0 left-0 w-full h-full z-modal isolate cursor-default'>
<div className={clsx('z-bottom fixed top-0 left-0', 'w-full h-full', 'backdrop-blur-[3px] opacity-50')} />
<div className={clsx('z-bottom fixed top-0 left-0', 'w-full h-full', 'bg-prim-0 opacity-25')} />
<div
className={clsx(
'cc-animate-modal p-20',

View File

@ -9,7 +9,6 @@ import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
import { Overlay } from '../Container';
import { Button, MiniButton } from '../Control';
import { IconClose } from '../Icons';
@ -34,7 +33,7 @@ export function ModalView({
useEscapeKey(hideDialog);
return (
<div className='fixed top-0 left-0 w-full h-full z-modal cursor-default'>
<div className='fixed top-0 left-0 w-full h-full z-modal isolate cursor-default'>
<ModalBackdrop onHide={hideDialog} />
<div
className={clsx(
@ -49,15 +48,13 @@ export function ModalView({
</div>
) : null}
<Overlay className='z-modalOverlay'>
<MiniButton
noPadding
titleHtml={prepareTooltip('Закрыть диалоговое окно', 'ESC')}
icon={<IconClose size='1.25rem' />}
className='float-right mt-2 mr-2'
onClick={hideDialog}
/>
</Overlay>
<MiniButton
noPadding
titleHtml={prepareTooltip('Закрыть диалоговое окно', 'ESC')}
icon={<IconClose size='1.25rem' />}
className='float-right mt-2 mr-2'
onClick={hideDialog}
/>
{header ? <h1 className='px-12 py-2 select-none'>{header}</h1> : null}
@ -77,7 +74,7 @@ export function ModalView({
{children}
</div>
<div className='z-modal-controls my-2 flex gap-12 justify-center text-sm'>
<div className='z-pop my-2 flex gap-12 justify-center text-sm'>
<Button text='Закрыть' className='min-w-[7rem]' onClick={hideDialog} />
</div>
</div>

View File

@ -39,12 +39,10 @@ export function BadgeHelp({ topic, padding = 'p-1', ...restProps }: BadgeHelpPro
return (
<div tabIndex={-1} id={`help-${topic}`} className={padding}>
<IconHelp size='1.25rem' className='icon-primary' />
<Tooltip clickable anchorSelect={`#help-${topic}`} layer='z-modal-tooltip' {...restProps}>
<Tooltip clickable anchorSelect={`#help-${topic}`} layer='z-topmost' {...restProps}>
<Suspense fallback={<Loader />}>
<div className='relative' onClick={event => event.stopPropagation()}>
<div className='absolute right-0 text-sm top-[0.4rem] clr-input'>
<TextURL text='Справка...' href={`/manuals?topic=${topic}`} />
</div>
<div className='absolute right-1 text-sm top-[0.4rem] clr-input' onClick={event => event.stopPropagation()}>
<TextURL text='Справка...' href={`/manuals?topic=${topic}`} />
</div>
<TopicPage topic={topic} />
</Suspense>

View File

@ -33,7 +33,7 @@ export function TopicsDropdown({ activeTopic, onChangeTopic }: TopicsDropdownPro
className={clsx(
'absolute left-0 w-[13.5rem]', // prettier: split-lines
'flex flex-col',
'z-modal-tooltip',
'z-topmost',
'text-xs sm:text-sm',
'select-none',
{

View File

@ -5,7 +5,7 @@ import { urls, useConceptNavigation } from '@/app';
import { useLabelUser, useRoleStore, UserRole } from '@/features/users';
import { InfoUsers, SelectUser } from '@/features/users/components';
import { Overlay, Tooltip } from '@/components/Container';
import { Tooltip } from '@/components/Container';
import { MiniButton } from '@/components/Control';
import { useDropdown } from '@/components/Dropdown';
import {
@ -83,7 +83,7 @@ export function EditorLibraryItem({ schema, isAttachedToOSS }: EditorLibraryItem
return (
<div className='flex flex-col'>
<div className='flex justify-stretch sm:mb-1 max-w-[30rem] gap-3'>
<div className='relative flex justify-stretch sm:mb-1 max-w-[30rem] gap-3'>
<MiniButton
noHover
noPadding
@ -101,21 +101,21 @@ export function EditorLibraryItem({ schema, isAttachedToOSS }: EditorLibraryItem
/>
</div>
{ownerSelector.isOpen ? (
<Overlay position='top-[-0.5rem] left-[4rem] cc-icons'>
{ownerSelector.isOpen ? (
<div className='relative'>
{ownerSelector.isOpen ? (
<div className='absolute top-[-0.5rem] left-[4rem]'>
<SelectUser className='w-[25rem] sm:w-[26rem] text-sm' value={schema.owner} onChange={onSelectUser} />
) : null}
</Overlay>
) : null}
<ValueIcon
className='sm:mb-1'
icon={<IconOwner size='1.25rem' className='icon-primary' />}
value={getUserLabel(schema.owner)}
title={isAttachedToOSS ? 'Владелец наследуется от ОСС' : 'Владелец'}
onClick={ownerSelector.toggle}
disabled={isModified || isProcessing || isAttachedToOSS || role < UserRole.OWNER}
/>
</div>
) : null}
<ValueIcon
className='sm:mb-1'
icon={<IconOwner size='1.25rem' className='icon-primary' />}
value={getUserLabel(schema.owner)}
title={isAttachedToOSS ? 'Владелец наследуется от ОСС' : 'Владелец'}
onClick={ownerSelector.toggle}
disabled={isModified || isProcessing || isAttachedToOSS || role < UserRole.OWNER}
/>
</div>
<div className='sm:mb-1 flex justify-between items-center'>
<ValueIcon
@ -126,7 +126,7 @@ export function EditorLibraryItem({ schema, isAttachedToOSS }: EditorLibraryItem
onClick={handleEditEditors}
disabled={isModified || isProcessing || role < UserRole.OWNER}
/>
<Tooltip anchorSelect='#editor_stats' layer='z-modal-tooltip'>
<Tooltip anchorSelect='#editor_stats'>
<Suspense fallback={<Loader scale={2} />}>
<InfoUsers items={schema.editors} prefix={prefixes.user_editors} header='Редакторы' />
</Suspense>

View File

@ -120,7 +120,7 @@ export function PickSchema({
className='mt-1'
onClick={() => locationMenu.toggle()}
/>
<Dropdown isOpen={locationMenu.isOpen} stretchLeft className='w-[20rem] h-[12.5rem] z-modal-tooltip'>
<Dropdown isOpen={locationMenu.isOpen} stretchLeft className='w-[20rem] h-[12.5rem]'>
<SelectLocation
value={filterLocation}
prefix={prefixes.folders_list}

View File

@ -48,7 +48,7 @@ export function SelectLocationContext({
/>
<Dropdown
isOpen={menu.isOpen}
className={clsx('w-[20rem] h-[12.5rem] z-modal-tooltip', dropdownHeight)}
className={clsx('w-[20rem] h-[12.5rem] z-tooltip', dropdownHeight)}
margin='mt-[-0.25rem]'
>
<SelectLocation

View File

@ -45,7 +45,7 @@ export function SelectLocationHead({
onClick={menu.toggle}
/>
<Dropdown isOpen={menu.isOpen} className='z-modal-tooltip' margin='mt-2'>
<Dropdown isOpen={menu.isOpen} margin='mt-2'>
{Object.values(LocationHead)
.filter(head => !excluded.includes(head))
.map((head, index) => {

View File

@ -42,7 +42,7 @@ export function ToolbarItemAccess({
}
return (
<Overlay position='top-[4.5rem] right-0 w-[12rem] pr-2' className='flex' layer='z-bottom'>
<Overlay position='top-[4.5rem] right-0' className='w-[12rem] flex pr-2' layer='z-bottom'>
<Label text='Доступ' className='self-center select-none' />
<div className='ml-auto cc-icons'>
<SelectAccessPolicy

View File

@ -155,7 +155,7 @@ export function DlgCloneLibraryItem() {
/>
</div>
<TextArea id='dlg_comment' {...register('comment')} label='Описание' error={errors.comment} />
<TextArea id='dlg_comment' {...register('comment')} label='Описание' rows={4} error={errors.comment} />
{selected.length > 0 ? (
<Controller

View File

@ -8,7 +8,6 @@ import clsx from 'clsx';
import { urls, useConceptNavigation } from '@/app';
import { useAuthSuspense } from '@/features/auth';
import { Overlay } from '@/components/Container';
import { Button, MiniButton, SubmitButton } from '@/components/Control';
import { IconDownload } from '@/components/Icons';
import { InfoError } from '@/components/InfoError';
@ -108,9 +107,9 @@ export function FormCreateItem() {
onSubmit={event => void handleSubmit(onSubmit)(event)}
onChange={resetErrors}
>
<h1 className='select-none'>
<h1 className='select-none relative'>
{itemType == LibraryItemType.RSFORM ? (
<Overlay position='top-0 right-[0.5rem]'>
<>
<Controller
control={control}
name='file'
@ -127,10 +126,11 @@ export function FormCreateItem() {
/>
<MiniButton
title='Загрузить из Экстеор'
className='absolute top-0 right-0'
icon={<IconDownload size='1.25rem' className='icon-primary' />}
onClick={() => inputRef.current?.click()}
/>
</Overlay>
</>
) : null}
Создание схемы
</h1>

View File

@ -3,10 +3,8 @@
import { toast } from 'react-toastify';
import fileDownload from 'js-file-download';
import { Overlay } from '@/components/Container';
import { MiniButton } from '@/components/Control';
import { IconCSV } from '@/components/Icons';
import { useAppLayoutStore } from '@/stores/appLayout';
import { useDialogsStore } from '@/stores/dialogs';
import { infoMsg } from '@/utils/labels';
import { convertToCSV } from '@/utils/utils';
@ -24,8 +22,6 @@ export function LibraryPage() {
const { items: libraryItems } = useLibrarySuspense();
const { renameLocation } = useRenameLocation();
const noNavigation = useAppLayoutStore(state => state.noNavigation);
const folderMode = useLibrarySearchStore(state => state.folderMode);
const location = useLibrarySearchStore(state => state.location);
const setLocation = useLibrarySearchStore(state => state.setLocation);
@ -57,20 +53,15 @@ export function LibraryPage() {
return (
<>
<Overlay
position={noNavigation ? 'top-[0.25rem] right-[3rem]' : 'top-[0.25rem] right-0'}
layer='z-tooltip'
className='cc-animate-position'
>
<ToolbarSearch total={libraryItems.length} filtered={filtered.length} />
<div className='relative cc-fade-in flex'>
<MiniButton
className='absolute z-tooltip top-[0.25rem] right-0 cc-animate-position'
title='Выгрузить в формате CSV'
icon={<IconCSV size='1.25rem' className='icon-green' />}
onClick={handleDownloadCSV}
/>
</Overlay>
<ToolbarSearch total={libraryItems.length} filtered={filtered.length} />
<div className='cc-fade-in flex'>
<ViewSideLocation
isVisible={folderMode}
onRenameLocation={() => showChangeLocation({ initial: location, onChangeLocation: handleRenameLocation })}

View File

@ -166,7 +166,7 @@ export function ToolbarSearch({ total, filtered }: ToolbarSearchProps) {
text={head ?? '//'}
/>
<Dropdown isOpen={headMenu.isOpen} stretchLeft className='z-modal-tooltip'>
<Dropdown isOpen={headMenu.isOpen} stretchLeft>
<DropdownButton title='Переключение в режим Проводник' onClick={handleToggleFolder}>
<div className='inline-flex items-center gap-3'>
<IconFolderTree size='1rem' className='clr-text-controls' />

View File

@ -69,7 +69,7 @@ export function FormOSS() {
disabled={!isMutable}
error={errors.title}
/>
<div className='flex justify-between gap-3 mb-3'>
<div className='relative flex justify-between gap-3 mb-3'>
<TextInput
id='schema_alias'
{...register('alias')}

View File

@ -190,7 +190,7 @@ export function OssFlow() {
}
return (
<div tabIndex={-1} onKeyDown={handleKeyDown}>
<div tabIndex={-1} className='relative' onKeyDown={handleKeyDown}>
<Overlay
position='top-[1.9rem] pt-1 right-1/2 translate-x-1/2'
className='rounded-b-2xl cc-blur hover:bg-prim-100 hover:bg-opacity-50'

View File

@ -23,7 +23,12 @@ export function NodeCore({ node }: NodeCoreProps) {
const longLabel = node.data.label.length > LONG_LABEL_CHARS;
return (
<>
<div
className='relative h-[34px] w-[144px] flex items-center justify-center'
data-tooltip-id={globalIDs.operation_tooltip}
data-tooltip-hidden={node.dragging}
onMouseEnter={() => setHover(node.data.operation)}
>
<Overlay position='top-0 right-0' className='flex flex-col gap-1 p-[2px]'>
<Indicator
noPadding
@ -40,35 +45,24 @@ export function NodeCore({ node }: NodeCoreProps) {
</Overlay>
{node.data.operation.operation_type === OperationType.INPUT ? (
<Overlay position='top-[1px] right-1/2 translate-x-1/2' className='flex'>
<div className='border-t w-[30px]'></div>
</Overlay>
<div className='absolute top-[1px] right-1/2 translate-x-1/2 border-t w-[30px]' />
) : null}
{!node.data.operation.is_owned ? (
<Overlay position='left-[2px] top-[6px]'>
<div className='border-r rounded-none clr-input h-[22px]'></div>
</Overlay>
<div className='absolute left-[2px] top-[6px] border-r rounded-none clr-input h-[22px]' />
) : null}
<div
className='h-[34px] w-[144px] flex items-center justify-center'
data-tooltip-id={globalIDs.operation_tooltip}
data-tooltip-hidden={node.dragging}
onMouseEnter={() => setHover(node.data.operation)}
className='text-center line-clamp-2'
style={{
fontSize: longLabel ? '12px' : '14px',
lineHeight: longLabel ? '16px' : '20px',
paddingLeft: '4px',
paddingRight: longLabel ? '10px' : '4px'
}}
>
<div
className='text-center line-clamp-2'
style={{
fontSize: longLabel ? '12px' : '14px',
lineHeight: longLabel ? '16px' : '20px',
paddingLeft: '4px',
paddingRight: longLabel ? '10px' : '4px'
}}
>
{node.data.label}
</div>
{node.data.label}
</div>
</>
</div>
);
}

View File

@ -5,7 +5,6 @@ import clsx from 'clsx';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { Overlay } from '@/components/Container';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/Tabs';
import { useAppLayoutStore } from '@/stores/appLayout';
@ -61,16 +60,22 @@ export function OssTabs({ activeTab }: OssTabsProps) {
onSelect={onSelectTab}
defaultFocus
selectedTabClassName='clr-selected'
className='flex flex-col mx-auto min-w-fit items-center'
className='relative flex flex-col mx-auto min-w-fit items-center'
>
<Overlay position='top-0 right-1/2 translate-x-1/2' layer='z-sticky'>
<TabList className={clsx('w-fit', 'flex items-stretch', 'border-b-2 border-x-2 divide-x-2', 'bg-prim-200')}>
<MenuOssTabs />
<TabList
className={clsx(
'absolute z-sticky',
'top-0 right-1/2 w-fit translate-x-1/2',
'flex items-stretch',
'border-b-2 border-x-2 divide-x-2',
'bg-prim-200'
)}
>
<MenuOssTabs />
<TabLabel label='Карточка' title={schema.title ?? ''} />
<TabLabel label='Граф' />
</TabList>
</Overlay>
<TabLabel label='Карточка' title={schema.title ?? ''} />
<TabLabel label='Граф' />
</TabList>
<div className='overflow-x-hidden'>
<TabPanel>

View File

@ -26,13 +26,10 @@ export function DlgShowAST() {
const [isDragging, setIsDragging] = useState(false);
return (
<ModalView
className='flex flex-col justify-stretch w-[calc(100dvw-3rem)] h-[calc(100dvh-6rem)]'
helpTopic={HelpTopic.UI_FORMULA_TREE}
>
<ModalView className='relative w-[calc(100dvw-3rem)] h-[calc(100dvh-6rem)]' helpTopic={HelpTopic.UI_FORMULA_TREE}>
<Overlay
position='top-2 right-1/2 translate-x-1/2'
className='px-2 py-1 rounded-2xl cc-blur bg-prim-100 max-w-[60ch] text-lg text-center'
position='top-0 -mt-1 right-1/2 translate-x-1/2'
className='px-2 rounded-2xl cc-blur bg-prim-100 max-w-[60ch] text-lg text-center'
>
{!hoverNode || isDragging ? expression : null}
{!isDragging && hoverNode ? (
@ -43,6 +40,7 @@ export function DlgShowAST() {
</div>
) : null}
</Overlay>
<ReactFlowProvider>
<ASTFlow
data={syntaxTree}

View File

@ -5,17 +5,14 @@ import clsx from 'clsx';
import { useWindowSize } from '@/hooks/useWindowSize';
import { useMainHeight } from '@/stores/appLayout';
import { useDialogsStore } from '@/stores/dialogs';
import { useModificationStore } from '@/stores/modification';
import { usePreferencesStore } from '@/stores/preferences';
import { globalIDs } from '@/utils/constants';
import { promptUnsaved } from '@/utils/utils';
import { useMutatingRSForm } from '../../../backend/useMutatingRSForm';
import { useRSEdit } from '../RSEditContext';
import { ViewConstituents } from '../ViewConstituents';
import { EditorControls } from './EditorControls';
import { FormConstituenta } from './FormConstituenta';
import { ToolbarConstituenta } from './ToolbarConstituenta';
@ -28,7 +25,6 @@ export function EditorConstituenta() {
const mainHeight = useMainHeight();
const showList = usePreferencesStore(state => state.showCstSideList);
const showEditTerm = useDialogsStore(state => state.showEditWordForms);
const { isModified } = useModificationStore();
const [toggleReset, setToggleReset] = useState(false);
@ -57,16 +53,6 @@ export function EditorConstituenta() {
}
}
function handleEditTermForms() {
if (!activeCst) {
return;
}
if (isModified && !promptUnsaved()) {
return;
}
showEditTerm({ itemID: schema.id, target: activeCst });
}
function initiateSubmit() {
const element = document.getElementById(globalIDs.constituenta_editor) as HTMLFormElement;
if (element) {
@ -85,46 +71,38 @@ export function EditorConstituenta() {
}
return (
<>
<div
tabIndex={-1}
className={clsx(
'relative',
'cc-fade-in',
'min-h-[20rem] max-w-[calc(min(100vw,95rem))] mx-auto',
'flex pt-[1.9rem]',
'overflow-y-auto overflow-x-clip',
{ 'flex-col md:items-center': isNarrow }
)}
style={{ maxHeight: mainHeight }}
onKeyDown={handleInput}
>
<ToolbarConstituenta
activeCst={activeCst}
disabled={disabled}
onSubmit={initiateSubmit}
onReset={() => setToggleReset(prev => !prev)}
/>
<div
tabIndex={-1}
className={clsx(
'cc-fade-in',
'min-h-[20rem] max-w-[calc(min(100vw,95rem))] mx-auto',
'flex pt-[1.9rem]',
'overflow-y-auto overflow-x-clip',
{ 'flex-col md:items-center': isNarrow }
)}
style={{ maxHeight: mainHeight }}
onKeyDown={handleInput}
>
<div className='mx-0 md:mx-auto pt-[2rem] md:w-[48.8rem] shrink-0 xs:pt-0'>
{activeCst ? (
<EditorControls
disabled={disabled} //
constituenta={activeCst}
onEditTerm={handleEditTermForms}
/>
) : null}
{activeCst ? (
<FormConstituenta
id={globalIDs.constituenta_editor} //
disabled={disabled}
toggleReset={toggleReset}
activeCst={activeCst}
schema={schema}
onOpenEdit={navigateCst}
/>
) : null}
</div>
<ViewConstituents isMounted={showList} isBottom={isNarrow} />
<div className='mx-0 md:mx-auto pt-[2rem] md:w-[48.8rem] shrink-0 xs:pt-0'>
{activeCst ? (
<FormConstituenta
id={globalIDs.constituenta_editor}
disabled={disabled}
toggleReset={toggleReset}
activeCst={activeCst}
schema={schema}
onOpenEdit={navigateCst}
/>
) : null}
</div>
</>
<ViewConstituents isMounted={showList} isBottom={isNarrow} />
</div>
);
}

View File

@ -29,7 +29,7 @@ export function EditorControls({ constituenta, disabled, onEditTerm }: EditorCon
}
return (
<Overlay position='top-1 left-[4.7rem]' className='flex select-none'>
<Overlay position='top-0 left-[4.7rem]' className='flex select-none'>
{!disabled || isProcessing ? (
<MiniButton
title={isModified ? tooltipText.unsaved : `Редактировать словоформы термина`}

View File

@ -14,6 +14,7 @@ import { Indicator } from '@/components/View';
import { useDialogsStore } from '@/stores/dialogs';
import { useModificationStore } from '@/stores/modification';
import { errorMsg } from '@/utils/labels';
import { promptUnsaved } from '@/utils/utils';
import {
CstType,
@ -30,6 +31,8 @@ import { type IConstituenta, type IRSForm } from '../../../models/rsform';
import { isBaseSet, isBasicConcept, isFunctional } from '../../../models/rsformAPI';
import { EditorRSExpression } from '../EditorRSExpression';
import { EditorControls } from './EditorControls';
interface FormConstituentaProps {
id?: string;
disabled: boolean;
@ -41,8 +44,6 @@ interface FormConstituentaProps {
}
export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst, onOpenEdit }: FormConstituentaProps) {
const { cstUpdate } = useCstUpdate();
const showTypification = useDialogsStore(state => state.showShowTypeGraph);
const { isModified, setIsModified } = useModificationStore();
const isProcessing = useMutatingRSForm();
@ -54,6 +55,10 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
formState: { isDirty }
} = useForm<ICstUpdateDTO>({ resolver: zodResolver(schemaCstUpdate) });
const { cstUpdate } = useCstUpdate();
const showTypification = useDialogsStore(state => state.showShowTypeGraph);
const showEditTerm = useDialogsStore(state => state.showEditWordForms);
const [localParse, setLocalParse] = useState<IExpressionParseDTO | null>(null);
const typification = useMemo(
@ -115,8 +120,20 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
showTypification({ items: typeInfo ? [typeInfo] : [] });
}
function handleEditTermForms() {
if (!activeCst) {
return;
}
if (isModified && !promptUnsaved()) {
return;
}
showEditTerm({ itemID: schema.id, target: activeCst });
}
return (
<form id={id} className='cc-column mt-1 px-6 py-1' onSubmit={event => void handleSubmit(onSubmit)(event)}>
<form id={id} className='relative cc-column mt-1 px-6 py-1' onSubmit={event => void handleSubmit(onSubmit)(event)}>
<EditorControls disabled={disabled} constituenta={activeCst} onEditTerm={handleEditTermForms} />
<Controller
control={control}
name='item_data.term_raw'
@ -228,13 +245,13 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
) : null}
{!disabled || isProcessing ? (
<div className='mx-auto flex'>
<div className='relative mx-auto flex'>
<SubmitButton
text='Сохранить изменения'
disabled={disabled || !isModified}
icon={<IconSave size='1.25rem' />}
/>
<Overlay position='top-[0.1rem] left-[0.4rem]' className='cc-icons'>
<Overlay position='top-[0.1rem] left-full' className='cc-icons'>
{activeCst.has_inherited_children && !activeCst.is_inherited ? (
<Indicator
icon={<IconPredecessor size='1.25rem' className='text-sec-600' />}

View File

@ -4,10 +4,6 @@ import { useEffect, useRef, useState } from 'react';
import { toast } from 'react-toastify';
import { type ReactCodeMirrorRef } from '@uiw/react-codemirror';
import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components';
import { Overlay } from '@/components/Container';
import { useDialogsStore } from '@/stores/dialogs';
import { usePreferencesStore } from '@/stores/preferences';
import { errorMsg } from '@/utils/labels';
@ -158,23 +154,16 @@ export function EditorRSExpression({
}
return (
<div className='cc-fade-in'>
<div className='relative cc-fade-in'>
<ToolbarRSExpression disabled={disabled} showAST={handleShowAST} showTypeGraph={onShowTypeGraph} />
<Overlay
position='top-[-0.5rem] right-1/2 translate-x-1/2'
layer='z-pop'
className='w-fit pl-[8.5rem] xs:pl-[2rem] flex gap-1'
>
<StatusBar
processing={isPending}
isModified={isModified}
activeCst={activeCst}
parseData={parseData}
onAnalyze={() => handleCheckExpression()}
/>
<BadgeHelp topic={HelpTopic.UI_CST_STATUS} offset={4} />
</Overlay>
<StatusBar
processing={isPending}
isModified={isModified}
activeCst={activeCst}
parseData={parseData}
onAnalyze={() => handleCheckExpression()}
/>
<RSInput
ref={rsInput}

View File

@ -2,6 +2,10 @@
import clsx from 'clsx';
import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components';
import { Overlay } from '@/components/Container';
import { Loader } from '@/components/Loader';
import { APP_COLORS } from '@/styling/colors';
import { globalIDs } from '@/utils/constants';
@ -35,34 +39,40 @@ export function StatusBar({ isModified, processing, activeCst, parseData, onAnal
})();
return (
<div
tabIndex={0}
className={clsx(
'w-[10rem] h-[1.75rem]',
'px-2 flex items-center justify-center',
'border',
'select-none',
'cursor-pointer',
'focus-frame',
'transition-colors duration-500'
)}
style={{ backgroundColor: processing ? APP_COLORS.bgDefault : colorStatusBar(status) }}
data-tooltip-id={globalIDs.tooltip}
data-tooltip-html={prepareTooltip('Проверить определение', 'Ctrl + Q')}
onClick={onAnalyze}
<Overlay
position='top-[-0.5rem] right-1/2 translate-x-1/2'
layer='z-pop'
className='w-fit pl-[8.5rem] xs:pl-[2rem] flex gap-1'
>
{processing ? (
<div className='cc-fade-in'>
{' '}
<Loader scale={3} />
</div>
) : null}
{!processing ? (
<div className='cc-fade-in flex items-center gap-2'>
<IconExpressionStatus size='1rem' value={status} />
<span className='pb-[0.125rem] font-controls pr-2'>{labelExpressionStatus(status)}</span>
</div>
) : null}
</div>
<div
tabIndex={0}
className={clsx(
'w-[10rem] h-[1.75rem]',
'px-2 flex items-center justify-center',
'border',
'select-none',
'cursor-pointer',
'focus-frame outline-none',
'transition-colors duration-500'
)}
style={{ backgroundColor: processing ? APP_COLORS.bgDefault : colorStatusBar(status) }}
data-tooltip-id={globalIDs.tooltip}
data-tooltip-html={prepareTooltip('Проверить определение', 'Ctrl + Q')}
onClick={onAnalyze}
>
{processing ? (
<div className='cc-fade-in'>
<Loader scale={3} />
</div>
) : null}
{!processing ? (
<div className='cc-fade-in flex items-center gap-2'>
<IconExpressionStatus size='1rem' value={status} />
<span className='pb-[0.125rem] font-controls pr-2'>{labelExpressionStatus(status)}</span>
</div>
) : null}
</div>
<BadgeHelp topic={HelpTopic.UI_CST_STATUS} offset={4} />
</Overlay>
);
}

View File

@ -35,23 +35,23 @@ export function EditorRSFormCard() {
}
return (
<>
<div
onKeyDown={handleInput}
className={clsx(
'relative',
'cc-fade-in',
'md:w-fit md:max-w-fit max-w-[32rem]',
'flex flex-row flex-wrap px-6 pt-[1.9rem]'
)}
>
<ToolbarRSFormCard onSubmit={initiateSubmit} schema={schema} isMutable={isMutable} deleteSchema={deleteSchema} />
<div
onKeyDown={handleInput}
className={clsx(
'cc-fade-in',
'md:w-fit md:max-w-fit max-w-[32rem]',
'flex flex-row flex-wrap px-6 pt-[1.9rem]'
)}
>
<FlexColumn className='shrink'>
<FormRSForm />
<EditorLibraryItem schema={schema} isAttachedToOSS={isAttachedToOSS} />
</FlexColumn>
<RSFormStats stats={schema.stats} isArchive={isArchive} />
</div>
</>
<FlexColumn className='shrink'>
<FormRSForm />
<EditorLibraryItem schema={schema} isAttachedToOSS={isAttachedToOSS} />
</FlexColumn>
<RSFormStats stats={schema.stats} isArchive={isArchive} />
</div>
);
}

View File

@ -91,7 +91,7 @@ export function FormRSForm() {
disabled={!isContentEditable}
error={errors.alias}
/>
<div className='flex flex-col'>
<div className='relative flex flex-col'>
<ToolbarVersioning blockReload={schema.oss.length > 0} />
<ToolbarItemAccess
visible={visible}

View File

@ -4,7 +4,6 @@ import { useState } from 'react';
import { toast } from 'react-toastify';
import fileDownload from 'js-file-download';
import { Overlay } from '@/components/Container';
import { MiniButton } from '@/components/Control';
import { type RowSelectionState } from '@/components/DataTable';
import { IconCSV } from '@/components/Icons';
@ -127,42 +126,40 @@ export function EditorRSList() {
const tableHeight = useFitHeight('4.05rem + 5px');
return (
<>
<div tabIndex={-1} onKeyDown={handleKeyDown} className='relative cc-fade-in pt-[1.9rem]'>
{isContentEditable ? <ToolbarRSList /> : null}
<div tabIndex={-1} onKeyDown={handleKeyDown} className='cc-fade-in pt-[1.9rem]'>
{isContentEditable ? (
<div className='flex items-center border-b'>
<div className='px-2'>
Выбор {selected.length} из {schema.stats?.count_all}
</div>
<SearchBar
id='constituents_search'
noBorder
className='w-[8rem]'
query={filterText}
onChangeQuery={setFilterText}
/>
<MiniButton
className='absolute z-tooltip top-[2.15rem] right-[1rem]'
title='Выгрузить в формате CSV'
icon={<IconCSV size='1.25rem' className='icon-green' />}
onClick={handleDownloadCSV}
/>
{isContentEditable ? (
<div className='flex items-center border-b'>
<div className='px-2'>
Выбор {selected.length} из {schema.stats?.count_all}
</div>
) : null}
<Overlay position='top-[0.25rem] right-[1rem]' layer='z-tooltip'>
<MiniButton
title='Выгрузить в формате CSV'
icon={<IconCSV size='1.25rem' className='icon-green' />}
onClick={handleDownloadCSV}
<SearchBar
id='constituents_search'
noBorder
className='w-[8rem]'
query={filterText}
onChangeQuery={setFilterText}
/>
</Overlay>
</div>
) : null}
<TableRSList
items={filtered}
maxHeight={tableHeight}
enableSelection={isContentEditable}
selected={rowSelection}
setSelected={handleRowSelection}
onEdit={navigateCst}
onCreateNew={createCstDefault}
/>
</div>
</>
<TableRSList
items={filtered}
maxHeight={tableHeight}
enableSelection={isContentEditable}
selected={rowSelection}
setSelected={handleRowSelection}
onEdit={navigateCst}
onCreateNew={createCstDefault}
/>
</div>
);
}

View File

@ -20,7 +20,7 @@ export function GraphSelectors() {
const setColoring = useTermGraphStore(state => state.setColoring);
return (
<div className='border rounded-b-none select-none clr-input rounded-t-md pointer-events-auto'>
<div className='relative border rounded-b-none select-none clr-input rounded-t-md pointer-events-auto'>
<Overlay position='right-[2.5rem] top-[0.25rem]'>
{coloring === 'status' ? <BadgeHelp topic={HelpTopic.UI_CST_STATUS} className='min-w-[25rem]' /> : null}
{coloring === 'type' ? <BadgeHelp topic={HelpTopic.UI_CST_CLASS} className='min-w-[25rem]' /> : null}

View File

@ -40,7 +40,6 @@ export function SchemasGuide() {
<IconHelp size='1.25rem' className='icon-primary' />
<Tooltip
anchorSelect={`#${globalIDs.graph_schemas}`}
layer='z-modal-tooltip'
place='right'
className='max-w-[25rem] break-words text-base'
>

View File

@ -2,7 +2,6 @@
import clsx from 'clsx';
import { Overlay } from '@/components/Container';
import { MiniButton } from '@/components/Control';
import { IconDropArrow, IconDropArrowUp } from '@/components/Icons';
import { useWindowSize } from '@/hooks/useWindowSize';
@ -47,16 +46,16 @@ export function ViewHidden({ items }: ViewHiddenProps) {
return null;
}
return (
<div className='flex flex-col'>
<Overlay position='right-[calc(0.7rem-2px)] top-2 pointer-events-auto'>
<MiniButton
noPadding
noHover
title={!isFolded ? 'Свернуть' : 'Развернуть'}
icon={!isFolded ? <IconDropArrowUp size='1.25rem' /> : <IconDropArrow size='1.25rem' />}
onClick={toggleFolded}
/>
</Overlay>
<div className='flex flex-col relative'>
<MiniButton
className='absolute right-[calc(0.7rem-2px)] top-2 pointer-events-auto'
noPadding
noHover
title={!isFolded ? 'Свернуть' : 'Развернуть'}
icon={!isFolded ? <IconDropArrowUp size='1.25rem' /> : <IconDropArrow size='1.25rem' />}
onClick={toggleFolded}
/>
<div className={clsx('pt-2 clr-input border-x pb-2', { 'border-b rounded-b-md': isFolded })}>
<div
className='w-fit select-none'

View File

@ -5,7 +5,6 @@ import clsx from 'clsx';
import { useConceptNavigation } from '@/app/Navigation/NavigationContext';
import { Overlay } from '@/components/Container';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/Tabs';
import { useAppLayoutStore } from '@/stores/appLayout';
import { useModificationStore } from '@/stores/modification';
@ -72,51 +71,53 @@ export function RSTabs({ activeID, activeTab }: RSTabsProps) {
}
return (
<>
<Tabs
selectedIndex={activeTab}
onSelect={onSelectTab}
defaultFocus
selectedTabClassName='clr-selected'
className='flex flex-col mx-auto min-w-fit items-center'
<Tabs
selectedIndex={activeTab}
onSelect={onSelectTab}
defaultFocus
selectedTabClassName='clr-selected'
className='relative flex flex-col mx-auto min-w-fit items-center'
>
<TabList
className={clsx(
'absolute z-sticky',
'mx-auto w-fit top-0 right-1/2 translate-x-1/2',
'flex items-stretch',
'border-b-2 border-x-2 divide-x-2',
'bg-prim-200'
)}
>
<Overlay position='top-0 right-1/2 translate-x-1/2' layer='z-sticky'>
<TabList
className={clsx('mx-auto w-fit', 'flex items-stretch', 'border-b-2 border-x-2 divide-x-2', 'bg-prim-200')}
>
<MenuRSTabs />
<MenuRSTabs />
<TabLabel
label='Карточка'
titleHtml={`${schema.title ?? ''}<br />Версия: ${labelVersion(schema.version, schema.versions)}`}
/>
<TabLabel
label='Содержание'
titleHtml={`Конституент: ${schema.stats?.count_all ?? 0}<br />Ошибок: ${schema.stats?.count_errors ?? 0}`}
/>
<TabLabel label='Редактор' />
<TabLabel label='Граф термов' />
</TabList>
</Overlay>
<TabLabel
label='Карточка'
titleHtml={`${schema.title ?? ''}<br />Версия: ${labelVersion(schema.version, schema.versions)}`}
/>
<TabLabel
label='Содержание'
titleHtml={`Конституент: ${schema.stats?.count_all ?? 0}<br />Ошибок: ${schema.stats?.count_errors ?? 0}`}
/>
<TabLabel label='Редактор' />
<TabLabel label='Граф термов' />
</TabList>
<div className='overflow-x-hidden'>
<TabPanel>
<EditorRSFormCard />
</TabPanel>
<div className='overflow-x-hidden'>
<TabPanel>
<EditorRSFormCard />
</TabPanel>
<TabPanel>
<EditorRSList />
</TabPanel>
<TabPanel>
<EditorRSList />
</TabPanel>
<TabPanel>
<EditorConstituenta />
</TabPanel>
<TabPanel>
<EditorConstituenta />
</TabPanel>
<TabPanel>
<EditorTermGraph />
</TabPanel>
</div>
</Tabs>
</>
<TabPanel>
<EditorTermGraph />
</TabPanel>
</div>
</Tabs>
);
}

View File

@ -9,7 +9,7 @@ import { urls, useConceptNavigation } from '@/app';
import { HelpTopic } from '@/features/help';
import { isAxiosError } from '@/backend/apiTransport';
import { FlexColumn, Overlay, Tooltip } from '@/components/Container';
import { FlexColumn, Tooltip } from '@/components/Container';
import { Button, SubmitButton, TextURL } from '@/components/Control';
import { IconHelp } from '@/components/Icons';
import { type ErrorData } from '@/components/InfoError';
@ -60,15 +60,7 @@ export function FormSignup() {
onSubmit={event => void handleSubmit(onSubmit)(event)}
onChange={resetErrors}
>
<h1>
<span>Новый пользователь</span>
<Overlay id={globalIDs.email_tooltip} position='top-[0.5rem] right-[1.75rem]'>
<IconHelp size='1.25rem' className='icon-primary' />
</Overlay>
<Tooltip anchorSelect={`#${globalIDs.email_tooltip}`} offset={6}>
электронная почта используется для восстановления пароля
</Tooltip>
</h1>
<h1>Новый пользователь</h1>
<div className='flex gap-12'>
<FlexColumn>
<TextInput
@ -102,7 +94,11 @@ export function FormSignup() {
/>
</FlexColumn>
<FlexColumn className='w-[15rem]'>
<FlexColumn className='w-[15rem] relative'>
<IconHelp id={globalIDs.email_tooltip} className='absolute top-0 right-0 icon-primary' size='1.25rem' />
<Tooltip anchorSelect={`#${globalIDs.email_tooltip}`} offset={6}>
электронная почта используется для восстановления пароля
</Tooltip>
<TextInput
id='email'
{...register('email')}

View File

@ -42,8 +42,6 @@
--z-index-tooltip: 30;
--z-index-navigation: 50;
--z-index-modal: 60;
--z-index-modal-controls: 70;
--z-index-modal-tooltip: 90;
--breakpoint-*: initial;
--breakpoint-xs: 475px;