F: Animation rework

This commit is contained in:
Ivan 2024-12-12 13:17:24 +03:00
parent ce8f2584db
commit 162dfa4bff
56 changed files with 825 additions and 875 deletions

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,6 @@
"@uiw/react-codemirror": "^4.23.6",
"axios": "^1.7.8",
"clsx": "^2.1.1",
"framer-motion": "^11.13.1",
"html-to-image": "^1.11.11",
"js-file-download": "^0.4.12",
"react": "^18.3.1",

View File

@ -1,5 +1,3 @@
import clsx from 'clsx';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import useWindowSize from '@/hooks/useWindowSize';
@ -10,7 +8,7 @@ function Logo() {
return (
<img
alt=''
className={clsx('max-h-[1.6rem] w-fit max-w-[11.4rem]')}
className='max-h-[1.6rem] w-fit max-w-[11.4rem]'
src={size.isSmall ? '/logo_sign.svg' : !darkMode ? '/logo_full.svg' : '/logo_full_dark.svg'}
/>
);

View File

@ -3,13 +3,22 @@ import clsx from 'clsx';
import { CProps } from '@/components/props';
import { globals } from '@/utils/constants';
interface NavigationButtonProps extends CProps.Titled {
interface NavigationButtonProps extends CProps.Titled, CProps.Styling {
text?: string;
icon: React.ReactNode;
onClick?: (event: CProps.EventMouse) => void;
}
function NavigationButton({ icon, title, titleHtml, hideTitle, onClick, text }: NavigationButtonProps) {
function NavigationButton({
icon,
title,
className,
style,
titleHtml,
hideTitle,
onClick,
text
}: NavigationButtonProps) {
return (
<button
type='button'
@ -29,8 +38,10 @@ function NavigationButton({ icon, title, titleHtml, hideTitle, onClick, text }:
{
'px-2': text,
'px-4': !text
}
},
className
)}
style={style}
>
{icon ? <span>{icon}</span> : null}
{text ? <span className='hidden sm:inline'>{text}</span> : null}

View File

@ -1,6 +1,5 @@
import { IconLogin, IconUser2 } from '@/components/Icons';
import Loader from '@/components/ui/Loader';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useAuth } from '@/context/AuthContext';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useConceptNavigation } from '@/context/NavigationContext';
@ -19,27 +18,21 @@ function UserMenu() {
const navigateLogin = () => router.push(urls.login);
return (
<div ref={menu.ref} className='h-full w-[4rem] flex items-center justify-center'>
{loading ? (
<AnimateFade key='nav_user_badge_loader'>
<Loader circular scale={1.5} />
</AnimateFade>
) : null}
{loading ? <Loader circular scale={1.5} /> : null}
{!user && !loading ? (
<AnimateFade key='nav_user_badge_login' className='h-full'>
<NavigationButton
title='Перейти на страницу логина'
icon={<IconLogin size='1.5rem' className='icon-primary' />}
onClick={navigateLogin}
/>
</AnimateFade>
<NavigationButton
className='cc-fade-in'
title='Перейти на страницу логина'
icon={<IconLogin size='1.5rem' className='icon-primary' />}
onClick={navigateLogin}
/>
) : null}
{user ? (
<AnimateFade key='nav_user_badge_profile' className='h-full'>
<NavigationButton
icon={<IconUser2 size='1.5rem' className={adminMode && user.is_staff ? 'icon-primary' : ''} />}
onClick={menu.toggle}
/>
</AnimateFade>
<NavigationButton
className='cc-fade-in'
icon={<IconUser2 size='1.5rem' className={adminMode && user.is_staff ? 'icon-primary' : ''} />}
onClick={menu.toggle}
/>
) : null}
<UserDropdown isOpen={!!user && menu.isOpen} hideDropdown={() => menu.hide()} />
</div>

View File

@ -4,7 +4,6 @@ import clsx from 'clsx';
import { isResponseHtml } from '@/utils/utils';
import PrettyJson from '../ui/PrettyJSON';
import AnimateFade from '../wrap/AnimateFade';
export type ErrorData = string | Error | AxiosError | undefined;
@ -59,8 +58,9 @@ function DescribeError({ error }: { error: ErrorData }) {
function InfoError({ error }: InfoErrorProps) {
return (
<AnimateFade
<div
className={clsx(
'cc-fade-in',
'min-w-[25rem]',
'px-3 py-2 flex flex-col',
'clr-text-red',
@ -75,7 +75,7 @@ function InfoError({ error }: InfoErrorProps) {
</div>
<DescribeError error={error} />
</AnimateFade>
</div>
);
}

View File

@ -1,6 +1,5 @@
// =========== Module contains interfaces for common UI elements. ==========
import { animated } from '@react-spring/web';
import { HTMLMotionProps } from 'framer-motion';
import React from 'react';
export namespace CProps {
@ -90,11 +89,6 @@ export namespace CProps {
*/
export type Input = Titled & React.ComponentProps<'input'>;
/**
* Represents `button` component with optional title and animation properties.
*/
export type AnimatedButton = Titled & Omit<HTMLMotionProps<'button'>, 'type'>;
/**
* Represents `div` component with animation properties.
*/

View File

@ -1,7 +1,6 @@
import clsx from 'clsx';
import { motion } from 'framer-motion';
import { animateDropdown } from '@/styling/animations';
import { PARAMETER } from '@/utils/constants';
import { CProps } from '../props';
@ -25,11 +24,12 @@ function Dropdown({
stretchTop,
className,
children,
style,
...restProps
}: React.PropsWithChildren<DropdownProps>) {
return (
<div className='relative'>
<motion.div
<div
tabIndex={-1}
className={clsx(
'z-topmost',
@ -45,13 +45,18 @@ function Dropdown({
},
className
)}
initial={false}
animate={isOpen ? 'open' : 'closed'}
variants={animateDropdown}
style={{
transitionProperty: 'clip-path, transform',
transitionDuration: `${PARAMETER.dropdownDuration}ms`,
transitionTimingFunction: 'ease-in-out',
transform: isOpen ? 'translateY(0)' : 'translateY(-10%)',
clipPath: isOpen ? 'inset(0% 0% 0% 0%)' : 'inset(10% 0% 90% 0%)',
...style
}}
{...restProps}
>
{children}
</motion.div>
</div>
</div>
);
}

View File

@ -1,12 +1,10 @@
import clsx from 'clsx';
import { motion } from 'framer-motion';
import { animateDropdownItem } from '@/styling/animations';
import { globals } from '@/utils/constants';
import { CProps } from '../props';
interface DropdownButtonProps extends CProps.AnimatedButton {
interface DropdownButtonProps extends CProps.Button {
/** Icon to display first (not used if children are provided). */
icon?: React.ReactNode;
@ -18,7 +16,7 @@ interface DropdownButtonProps extends CProps.AnimatedButton {
}
/**
* Animated `button` with optional text, icon, and click functionality.
* `button` with optional text, icon, and click functionality styled for use in a {@link Dropdown}.
* It supports optional children for custom content or the default text/icon display.
*/
function DropdownButton({
@ -33,7 +31,7 @@ function DropdownButton({
...restProps
}: DropdownButtonProps) {
return (
<motion.button
<button
tabIndex={-1}
type='button'
onClick={onClick}
@ -48,7 +46,6 @@ function DropdownButton({
},
className
)}
variants={animateDropdownItem}
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined}
data-tooltip-html={titleHtml}
data-tooltip-content={title}
@ -58,7 +55,7 @@ function DropdownButton({
{children ? children : null}
{!children && icon ? icon : null}
{!children && text ? <span>{text}</span> : null}
</motion.button>
</button>
);
}

View File

@ -1,15 +1,11 @@
import clsx from 'clsx';
import { motion } from 'framer-motion';
import { animateDropdownItem } from '@/styling/animations';
import Checkbox, { CheckboxProps } from './Checkbox';
/** Animated {@link Checkbox} inside a {@link Dropdown} item. */
function DropdownCheckbox({ setValue, disabled, ...restProps }: CheckboxProps) {
return (
<motion.div
variants={animateDropdownItem}
<div
className={clsx(
'px-3 py-1',
'text-left overflow-ellipsis whitespace-nowrap',
@ -18,7 +14,7 @@ function DropdownCheckbox({ setValue, disabled, ...restProps }: CheckboxProps) {
)}
>
<Checkbox tabIndex={-1} disabled={disabled} setValue={setValue} {...restProps} />
</motion.div>
</div>
);
}

View File

@ -1,28 +0,0 @@
import clsx from 'clsx';
import { motion } from 'framer-motion';
import { animateDropdownItem } from '@/styling/animations';
import { DividerProps } from './Divider';
/**
* {@link Divider} with animation inside {@link Dropdown}.
*/
function DropdownDivider({ vertical, margins = 'mx-2', className, ...restProps }: DividerProps) {
return (
<motion.div
variants={animateDropdownItem}
className={clsx(
margins, //prettier: split-lines
className,
{
'border-x': vertical,
'border-y': !vertical
}
)}
{...restProps}
/>
);
}
export default DropdownDivider;

View File

@ -1,11 +1,9 @@
'use client';
import clsx from 'clsx';
import { motion } from 'framer-motion';
import useEscapeKey from '@/hooks/useEscapeKey';
import { HelpTopic } from '@/models/miscellaneous';
import { animateModal } from '@/styling/animations';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/labels';
@ -100,17 +98,12 @@ function Modal({
className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'cc-modal-backdrop')}
onClick={hideWindow}
/>
<motion.div
<div
className={clsx(
'z-modal',
'absolute bottom-1/2 left-1/2 -translate-x-1/2 translate-y-1/2',
'border rounded-xl',
'clr-app'
'cc-animate-modal',
'z-modal absolute bottom-1/2 left-1/2 -translate-x-1/2 translate-y-1/2',
'border rounded-xl clr-app'
)}
initial={{ ...animateModal.initial }}
animate={{ ...animateModal.animate }}
exit={{ ...animateModal.exit }}
{...restProps}
>
<Overlay position='right-2 top-2'>
<MiniButton
@ -137,6 +130,7 @@ function Modal({
},
className
)}
{...restProps}
>
{children}
</div>
@ -155,7 +149,7 @@ function Modal({
) : null}
<Button text={readonly ? 'Закрыть' : 'Отмена'} className='min-w-[7rem]' onClick={handleCancel} />
</div>
</motion.div>
</div>
</div>
);
}

View File

@ -1,41 +1,25 @@
import InfoError, { ErrorData } from '../info/InfoError';
import { CProps } from '../props';
import Loader from '../ui/Loader';
import AnimateFade from './AnimateFade';
interface DataLoaderProps extends CProps.AnimatedDiv {
id: string;
interface DataLoaderProps {
isLoading?: boolean;
error?: ErrorData;
hasNoData?: boolean;
}
function DataLoader({
id,
isLoading,
hasNoData,
error,
className,
children,
...restProps
}: React.PropsWithChildren<DataLoaderProps>) {
return (
<>
{!isLoading && !error && !hasNoData ? (
<AnimateFade id={id} key={`${id}-data`} className={className} {...restProps}>
{children}
</AnimateFade>
) : null}
{!isLoading && !error && hasNoData ? (
<AnimateFade key={`${id}-no-data`} className='w-full text-center p-1' {...restProps}>
Данные не загружены
</AnimateFade>
) : null}
{isLoading ? <Loader key={`${id}-loader`} /> : null}
{error ? <InfoError key={`${id}-error`} error={error} /> : null}
</>
);
function DataLoader({ isLoading, hasNoData, error, children }: React.PropsWithChildren<DataLoaderProps>) {
if (isLoading) {
return <Loader />;
}
if (error) {
return <InfoError error={error} />;
}
if (hasNoData) {
return <div className='cc-fade-in w-full text-center p-1'>Данные не загружены</div>;
} else {
return <>{children}</>;
}
}
export default DataLoader;

View File

@ -13,7 +13,7 @@ function ExpectedAnonymous() {
}
return (
<div className='flex flex-col items-center gap-3 py-6'>
<div className='cc-fade-in flex flex-col items-center gap-3 py-6'>
<p className='font-semibold'>{`Вы вошли в систему как ${user?.username ?? ''}`}</p>
<div className='flex gap-3'>
<TextURL text='Новая схема' href='/library/create' />

View File

@ -4,25 +4,25 @@ import { useAuth } from '@/context/AuthContext';
import Loader from '../ui/Loader';
import TextURL from '../ui/TextURL';
import AnimateFade from './AnimateFade';
function RequireAuth({ children }: React.PropsWithChildren) {
const { user, loading } = useAuth();
return (
<>
{loading ? <Loader key='auth-loader' /> : null}
{!loading && user ? <AnimateFade key='auth-data'>{children}</AnimateFade> : null}
{!loading && !user ? (
<AnimateFade key='auth-no-user' className='flex flex-col items-center gap-1 mt-2'>
<p className='mb-2'>Пожалуйста войдите в систему</p>
<TextURL text='Войти в Портал' href='/login' />
<TextURL text='Зарегистрироваться' href='/signup' />
<TextURL text='Начальная страница' href='/' />
</AnimateFade>
) : null}
</>
);
if (loading) {
return <Loader key='auth-loader' />;
}
if (user) {
return <>{children}</>;
} else {
return (
<div key='auth-no-user' className='flex flex-col items-center gap-1 mt-2'>
<p className='mb-2'>Пожалуйста войдите в систему</p>
<TextURL text='Войти в Портал' href='/login' />
<TextURL text='Зарегистрироваться' href='/signup' />
<TextURL text='Начальная страница' href='/' />
</div>
);
}
}
export default RequireAuth;

View File

@ -6,7 +6,6 @@ import { TabList, TabPanel, Tabs } from 'react-tabs';
import Modal, { ModalProps } from '@/components/ui/Modal';
import TabLabel from '@/components/ui/TabLabel';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useLibrary } from '@/context/LibraryContext';
import usePartialUpdate from '@/hooks/usePartialUpdate';
import { HelpTopic } from '@/models/miscellaneous';
@ -145,9 +144,9 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
const editorPanel = useMemo(
() => (
<TabPanel>
<AnimateFade className='cc-column'>
<div className='cc-fade-in cc-column'>
<FormCreateCst state={constituenta} partialUpdate={updateConstituenta} schema={schema} />
</AnimateFade>
</div>
</TabPanel>
),
[constituenta, updateConstituenta, schema]

View File

@ -10,7 +10,6 @@ import PickConstituenta from '@/components/select/PickConstituenta';
import DataTable, { IConditionalStyle } from '@/components/ui/DataTable';
import MiniButton from '@/components/ui/MiniButton';
import NoData from '@/components/ui/NoData';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { IConstituenta, IRSForm } from '@/models/rsform';
import { IArgumentValue } from '@/models/rslang';
@ -147,7 +146,7 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
);
return (
<AnimateFade>
<div className='cc-fade-in'>
<DataTable
dense
noFooter
@ -224,7 +223,7 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
height='5.1rem'
value={state.definition}
/>
</AnimateFade>
</div>
);
}

View File

@ -6,7 +6,6 @@ import RSInput from '@/components/RSInput';
import PickConstituenta from '@/components/select/PickConstituenta';
import SelectSingle from '@/components/ui/SelectSingle';
import TextArea from '@/components/ui/TextArea';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useLibrary } from '@/context/LibraryContext';
import { CATEGORY_CST_TYPE, IConstituenta, IRSForm } from '@/models/rsform';
import { applyFilterCategory } from '@/models/rsformAPI';
@ -78,7 +77,7 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
}, [state.filterCategory, templateSchema]);
return (
<AnimateFade>
<div className='cc-fade-in'>
<div className='flex border-t border-x rounded-t-md clr-input'>
<SelectSingle
noBorder
@ -138,7 +137,7 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
height='5.1rem'
value={state.prototype?.definition_formal}
/>
</AnimateFade>
</div>
);
}

View File

@ -9,7 +9,6 @@ import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton';
import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useLibrary } from '@/context/LibraryContext';
import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/models/library';
import { IOperationSchema } from '@/models/oss';
@ -53,7 +52,7 @@ function TabInputOperation({
}, [createSchema, onChangeAttachedID]);
return (
<AnimateFade className='cc-column'>
<div className='cc-fade-in cc-column'>
<TextInput
id='operation_title'
label='Полное название'
@ -111,7 +110,7 @@ function TabInputOperation({
baseFilter={baseFilter}
/>
) : null}
</AnimateFade>
</div>
);
}

View File

@ -2,7 +2,6 @@ import FlexColumn from '@/components/ui/FlexColumn';
import Label from '@/components/ui/Label';
import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
import AnimateFade from '@/components/wrap/AnimateFade';
import { IOperationSchema, OperationID } from '@/models/oss';
import PickMultiOperation from '../../components/select/PickMultiOperation';
@ -31,7 +30,7 @@ function TabSynthesisOperation({
setInputs
}: TabSynthesisOperationProps) {
return (
<AnimateFade className='cc-column'>
<div className='cc-fade-in cc-column'>
<TextInput
id='operation_title'
label='Полное название'
@ -61,7 +60,7 @@ function TabSynthesisOperation({
<Label text={`Выбор аргументов: [ ${inputs.length} ]`} />
<PickMultiOperation items={oss.items} selected={inputs} setSelected={setInputs} rows={6} />
</FlexColumn>
</AnimateFade>
</div>
);
}

View File

@ -5,7 +5,6 @@ import { useMemo } from 'react';
import PickMultiOperation from '@/components/select/PickMultiOperation';
import FlexColumn from '@/components/ui/FlexColumn';
import Label from '@/components/ui/Label';
import AnimateFade from '@/components/wrap/AnimateFade';
import { IOperationSchema, OperationID } from '@/models/oss';
interface TabArgumentsProps {
@ -22,12 +21,12 @@ function TabArguments({ oss, inputs, target, setInputs }: TabArgumentsProps) {
[oss.items, potentialCycle]
);
return (
<AnimateFade className='cc-column'>
<div className='cc-fade-in cc-column'>
<FlexColumn>
<Label text={`Выбор аргументов: [ ${inputs.length} ]`} />
<PickMultiOperation items={filtered} selected={inputs} setSelected={setInputs} rows={8} />
</FlexColumn>
</AnimateFade>
</div>
);
}

View File

@ -1,6 +1,5 @@
import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
import AnimateFade from '@/components/wrap/AnimateFade';
interface TabOperationProps {
alias: string;
@ -13,7 +12,7 @@ interface TabOperationProps {
function TabOperation({ alias, onChangeAlias, title, onChangeTitle, comment, onChangeComment }: TabOperationProps) {
return (
<AnimateFade className='cc-column'>
<div className='cc-fade-in cc-column'>
<TextInput
id='operation_title'
label='Полное название'
@ -38,7 +37,7 @@ function TabOperation({ alias, onChangeAlias, title, onChangeTitle, comment, onC
onChange={event => onChangeComment(event.target.value)}
/>
</div>
</AnimateFade>
</div>
);
}

View File

@ -31,21 +31,23 @@ function TabSynthesis({
}: TabSynthesisProps) {
const { colors } = useConceptOptions();
return (
<DataLoader id='dlg-synthesis-tab' className='cc-column mt-3' isLoading={loading} error={error}>
<PickSubstitutions
schemas={schemas}
prefixID={prefixes.dlg_cst_substitutes_list}
rows={8}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
suggestions={suggestions}
/>
<TextArea
disabled
value={validationText}
rows={4}
style={{ borderColor: isCorrect ? undefined : colors.fgRed }}
/>
<DataLoader isLoading={loading} error={error}>
<div className='cc-fade-in cc-column mt-3'>
<PickSubstitutions
schemas={schemas}
prefixID={prefixes.dlg_cst_substitutes_list}
rows={8}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
suggestions={suggestions}
/>
<TextArea
disabled
value={validationText}
rows={4}
style={{ borderColor: isCorrect ? undefined : colors.fgRed }}
/>
</div>
</DataLoader>
);
}

View File

@ -6,7 +6,6 @@ import PickConstituenta from '@/components/select/PickConstituenta';
import SelectMultiGrammeme from '@/components/select/SelectMultiGrammeme';
import Label from '@/components/ui/Label';
import TextInput from '@/components/ui/TextInput';
import AnimateFade from '@/components/wrap/AnimateFade';
import { ReferenceType } from '@/models/language';
import { parseEntityReference, parseGrammemes } from '@/models/languageAPI';
import { CstMatchMode } from '@/models/miscellaneous';
@ -59,7 +58,7 @@ function TabEntityReference({ initial, schema, onChangeValid, onChangeReference
}
return (
<AnimateFade className='cc-column'>
<div className='cc-fade-in cc-column'>
<PickConstituenta
id='dlg_reference_entity_picker'
initialFilter={initial.text}
@ -108,7 +107,7 @@ function TabEntityReference({ initial, schema, onChangeValid, onChangeReference
onChangeValue={setSelectedGrams}
/>
</div>
</AnimateFade>
</div>
);
}

View File

@ -3,7 +3,6 @@
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
import TextInput from '@/components/ui/TextInput';
import AnimateFade from '@/components/wrap/AnimateFade';
import { ReferenceType } from '@/models/language';
import { parseSyntacticReference } from '@/models/languageAPI';
@ -44,7 +43,7 @@ function TabSyntacticReference({ initial, onChangeValid, onChangeReference }: Ta
}, [nominal, offset, onChangeValid, onChangeReference]);
return (
<AnimateFade className='flex flex-col gap-2'>
<div className='cc-fade-in flex flex-col gap-2'>
<TextInput
id='dlg_reference_offset'
type='number'
@ -70,7 +69,7 @@ function TabSyntacticReference({ initial, onChangeValid, onChangeReference }: Ta
value={nominal}
onChange={event => setNominal(event.target.value)}
/>
</AnimateFade>
</div>
);
}

View File

@ -16,7 +16,7 @@ interface TabConstituentsProps {
function TabConstituents({ schema, error, loading, selected, setSelected }: TabConstituentsProps) {
return (
<DataLoader id='dlg-constituents-tab' isLoading={loading} error={error} hasNoData={!schema}>
<DataLoader isLoading={loading} error={error} hasNoData={!schema}>
{schema ? (
<PickMultiConstituenta
schema={schema}

View File

@ -4,7 +4,6 @@ import { useMemo } from 'react';
import PickSchema from '@/components/select/PickSchema';
import TextInput from '@/components/ui/TextInput';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useLibrary } from '@/context/LibraryContext';
import { LibraryItemID, LibraryItemType } from '@/models/library';
import { IRSForm } from '@/models/rsform';
@ -22,7 +21,7 @@ function TabSchema({ selected, receiver, setSelected }: TabSchemaProps) {
const sortedItems = useMemo(() => sortItemsForInlineSynthesis(receiver, library.items), [receiver, library.items]);
return (
<AnimateFade className='flex flex-col'>
<div className='cc-fade-in flex flex-col'>
<PickSchema
id='dlg_schema_picker' // prettier: split lines
items={sortedItems}
@ -43,7 +42,7 @@ function TabSchema({ selected, receiver, setSelected }: TabSchemaProps) {
dense
/>
</div>
</AnimateFade>
</div>
);
}

View File

@ -40,7 +40,7 @@ function TabSubstitutions({
const schemas = useMemo(() => [...(source ? [source] : []), ...(receiver ? [receiver] : [])], [source, receiver]);
return (
<DataLoader id='dlg-substitutions-tab' className='cc-column' isLoading={loading} error={error} hasNoData={!source}>
<DataLoader isLoading={loading} error={error} hasNoData={!source}>
<PickSubstitutions
substitutions={substitutions}
setSubstitutions={setSubstitutions}

View File

@ -118,7 +118,7 @@ function DlgRelocateConstituents({ oss, hideWindow, initialTarget, onSubmit }: D
onSelectValue={handleSelectDestination}
/>
</div>
<DataLoader id='dlg-relocate-constituents' isLoading={sourceData.loading} error={sourceData.error}>
<DataLoader isLoading={sourceData.loading} error={sourceData.error}>
{sourceData.schema ? (
<PickMultiConstituenta
noBorder

View File

@ -1,15 +1,12 @@
import AnimateFade from '@/components/wrap/AnimateFade';
import RequireAuth from '@/components/wrap/RequireAuth';
import FormCreateItem from './FormCreateItem';
function CreateItemPage() {
return (
<AnimateFade>
<RequireAuth>
<FormCreateItem />
</RequireAuth>
</AnimateFade>
<RequireAuth>
<FormCreateItem />
</RequireAuth>
);
}

View File

@ -122,7 +122,10 @@ function FormCreateItem() {
}, [itemType]);
return (
<form className={clsx('cc-column', 'min-w-[30rem] max-w-[30rem] mx-auto', 'px-6 py-3')} onSubmit={handleSubmit}>
<form
className={clsx('cc-fade-in cc-column', 'min-w-[30rem] max-w-[30rem] mx-auto', 'px-6 py-3')}
onSubmit={handleSubmit}
>
<h1 className='select-none'>
{itemType == LibraryItemType.RSFORM ? (
<Overlay position='top-0 right-[0.5rem]'>

View File

@ -3,7 +3,6 @@
import { useLayoutEffect, useMemo } from 'react';
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { resources } from '@/utils/constants';
@ -18,13 +17,13 @@ function DatabaseSchemaPage() {
}, [setNoFooter]);
return (
<AnimateFade className='flex justify-center overflow-hidden' style={{ maxHeight: panelHeight }}>
<div className='cc-fade-in flex justify-center overflow-hidden' style={{ maxHeight: panelHeight }}>
<TransformWrapper>
<TransformComponent>
<img alt='Схема базы данных' src={resources.db_schema} className='w-fit h-fit' />
</TransformComponent>
</TransformWrapper>
</AnimateFade>
</div>
);
}

View File

@ -172,12 +172,7 @@ function LibraryPage() {
);
return (
<DataLoader
id='library-page' // prettier: split lines
isLoading={library.loading}
error={library.loadingError}
hasNoData={library.items.length === 0}
>
<DataLoader isLoading={library.loading} error={library.loadingError} hasNoData={library.items.length === 0}>
{showRenameLocation ? (
<DlgChangeLocation
initial={options.location}
@ -219,7 +214,7 @@ function LibraryPage() {
toggleFolderMode={toggleFolderMode}
/>
<div className='flex'>
<div className='cc-fade-in flex'>
{viewLocations}
{viewLibrary}
</div>

View File

@ -1,7 +1,6 @@
'use client';
import clsx from 'clsx';
import { motion } from 'framer-motion';
import { useCallback, useMemo } from 'react';
import { LocationIcon, VisibilityIcon } from '@/components/DomainIcons';
@ -25,7 +24,6 @@ import { useUsers } from '@/context/UsersContext';
import useDropdown from '@/hooks/useDropdown';
import { LocationHead } from '@/models/library';
import { UserID } from '@/models/user';
import { animateDropdownItem } from '@/styling/animations';
import { prefixes } from '@/utils/constants';
import { describeLocationHead, labelLocationHead } from '@/utils/labels';
import { tripleToggleColor } from '@/utils/utils';
@ -163,16 +161,14 @@ function ToolbarSearch({
icon={<IconEditor size='1.25rem' className={tripleToggleColor(isEditor)} />}
onClick={toggleEditor}
/>
<motion.div className='px-1 pb-1' variants={animateDropdownItem}>
<SelectUser
noBorder
placeholder='Выберите владельца'
className='min-w-[15rem] text-sm'
items={users}
value={filterUser}
onSelectValue={onChangeFilterUser}
/>
</motion.div>
<SelectUser
noBorder
placeholder='Выберите владельца'
className='min-w-[15rem] text-sm mx-1 mb-1'
items={users}
value={filterUser}
onSelectValue={onChangeFilterUser}
/>
</Dropdown>
</div>

View File

@ -9,7 +9,6 @@ import InfoError, { ErrorData } from '@/components/info/InfoError';
import SubmitButton from '@/components/ui/SubmitButton';
import TextInput from '@/components/ui/TextInput';
import TextURL from '@/components/ui/TextURL';
import AnimateFade from '@/components/wrap/AnimateFade';
import ExpectedAnonymous from '@/components/wrap/ExpectedAnonymous';
import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext';
@ -52,44 +51,42 @@ function LoginPage() {
return <ExpectedAnonymous />;
}
return (
<AnimateFade>
<form className={clsx('cc-column', 'w-[24rem] mx-auto', 'pt-12 pb-6 px-6')} onSubmit={handleSubmit}>
<img alt='Концепт Портал' src={resources.logo} className='max-h-[2.5rem] min-w-[2.5rem] mb-3' />
<TextInput
id='username'
label='Логин или email'
autoComplete='username'
autoFocus
required
allowEnter
spellCheck={false}
value={username}
onChange={event => setUsername(event.target.value)}
/>
<TextInput
id='password'
type='password'
label='Пароль'
autoComplete='current-password'
required
allowEnter
value={password}
onChange={event => setPassword(event.target.value)}
/>
<form className={clsx('cc-column cc-fade-in', 'w-[24rem] mx-auto', 'pt-12 pb-6 px-6')} onSubmit={handleSubmit}>
<img alt='Концепт Портал' src={resources.logo} className='max-h-[2.5rem] min-w-[2.5rem] mb-3' />
<TextInput
id='username'
label='Логин или email'
autoComplete='username'
autoFocus
required
allowEnter
spellCheck={false}
value={username}
onChange={event => setUsername(event.target.value)}
/>
<TextInput
id='password'
type='password'
label='Пароль'
autoComplete='current-password'
required
allowEnter
value={password}
onChange={event => setPassword(event.target.value)}
/>
<SubmitButton
text='Войти'
className='self-center w-[12rem] mt-3'
loading={loading}
disabled={!username || !password}
/>
<div className='flex flex-col text-sm'>
<TextURL text='Восстановить пароль...' href='/restore-password' />
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
</div>
{error ? <ProcessError error={error} /> : null}
</form>
</AnimateFade>
<SubmitButton
text='Войти'
className='self-center w-[12rem] mt-3'
loading={loading}
disabled={!username || !password}
/>
<div className='flex flex-col text-sm'>
<TextURL text='Восстановить пароль...' href='/restore-password' />
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
</div>
{error ? <ProcessError error={error} /> : null}
</form>
);
}

View File

@ -1,6 +1,5 @@
'use client';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { HelpTopic } from '@/models/miscellaneous';
import TopicPage from '@/pages/ManualsPage/TopicPage';
@ -12,13 +11,13 @@ interface ViewTopicProps {
function ViewTopic({ topic }: ViewTopicProps) {
const { mainHeight } = useConceptOptions();
return (
<AnimateFade
<div
key={topic}
className='py-2 px-6 mx-auto sm:mx-0 lg:px-12 overflow-y-auto'
className='cc-fade-in py-2 px-6 mx-auto sm:mx-0 lg:px-12 overflow-y-auto'
style={{ maxHeight: mainHeight }}
>
<TopicPage topic={topic} />
</AnimateFade>
</div>
);
}

View File

@ -3,7 +3,6 @@
import clsx from 'clsx';
import FlexColumn from '@/components/ui/FlexColumn';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useOSS } from '@/context/OssContext';
import EditorLibraryItem from '@/pages/RSFormPage/EditorRSFormCard/EditorLibraryItem';
import ToolbarRSFormCard from '@/pages/RSFormPage/EditorRSFormCard/ToolbarRSFormCard';
@ -47,9 +46,14 @@ function EditorOssCard({ isModified, onDestroy, setIsModified }: EditorOssCardPr
onDestroy={onDestroy}
controller={controller}
/>
<AnimateFade
<div
onKeyDown={handleInput}
className={clsx('md:w-fit md:max-w-fit max-w-[32rem]', 'mx-auto pt-[1.9rem]', 'flex flex-row flex-wrap px-6')}
className={clsx(
'cc-fade-in',
'md:w-fit md:max-w-fit max-w-[32rem]',
'mx-auto pt-[1.9rem]',
'flex flex-row flex-wrap px-6'
)}
>
<FlexColumn className='px-3'>
<FormOSS id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} />
@ -57,7 +61,7 @@ function EditorOssCard({ isModified, onDestroy, setIsModified }: EditorOssCardPr
</FlexColumn>
{schema ? <OssStats stats={schema.stats} /> : null}
</AnimateFade>
</div>
</>
);
}

View File

@ -18,7 +18,6 @@ import {
import { CProps } from '@/components/props';
import Overlay from '@/components/ui/Overlay';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useOSS } from '@/context/OssContext';
import useLocalStorage from '@/hooks/useLocalStorage';
@ -349,7 +348,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
);
return (
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}>
<div tabIndex={-1} onKeyDown={handleKeyDown}>
<Overlay position='top-[1.9rem] pt-1 right-1/2 translate-x-1/2' className='rounded-b-2xl cc-blur'>
<ToolbarOssGraph
isModified={isModified}
@ -381,10 +380,10 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
{...menuProps}
/>
) : null}
<div className='relative w-[100vw]' style={{ height: mainHeight, fontFamily: 'Rubik' }}>
<div className='cc-fade-in relative w-[100vw]' style={{ height: mainHeight, fontFamily: 'Rubik' }}>
{graph}
</div>
</AnimateFade>
</div>
);
}

View File

@ -16,9 +16,9 @@ import {
IconShare
} from '@/components/Icons';
import Button from '@/components/ui/Button';
import Divider from '@/components/ui/Divider';
import Dropdown from '@/components/ui/Dropdown';
import DropdownButton from '@/components/ui/DropdownButton';
import DropdownDivider from '@/components/ui/DropdownDivider';
import { useAccessMode } from '@/context/AccessModeContext';
import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext';
@ -102,7 +102,7 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) {
/>
) : null}
<DropdownDivider margins='mx-3 my-1' />
<Divider margins='mx-3 my-1' />
{user ? (
<DropdownButton

View File

@ -12,7 +12,6 @@ import Loader from '@/components/ui/Loader';
import Overlay from '@/components/ui/Overlay';
import TabLabel from '@/components/ui/TabLabel';
import TextURL from '@/components/ui/TextURL';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useAuth } from '@/context/AuthContext';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useLibrary } from '@/context/LibraryContext';
@ -151,10 +150,10 @@ function OssTabs() {
</TabList>
</Overlay>
<AnimateFade>
<div className='overflow-x-hidden'>
{cardPanel}
{graphPanel}
</AnimateFade>
</div>
</Tabs>
) : null}
</OssEditState>

View File

@ -65,12 +65,8 @@ function PasswordChangePage() {
return <ProcessError error={error} />;
}
return (
<DataLoader
id='password-change-page' //
isLoading={loading}
hasNoData={!isTokenValid}
>
<form className={clsx('cc-column', 'w-[24rem] mx-auto', 'px-6 mt-3')} onSubmit={handleSubmit}>
<DataLoader isLoading={loading} hasNoData={!isTokenValid}>
<form className={clsx('cc-fade-in cc-column', 'w-[24rem] mx-auto', 'px-6 mt-3')} onSubmit={handleSubmit}>
<TextInput
id='new_password'
type='password'

View File

@ -87,36 +87,37 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
onReset={() => setToggleReset(prev => !prev)}
onToggleList={() => setShowList(prev => !prev)}
/>
<div className='pt-[1.9rem] overflow-y-auto overflow-x-clip min-h-[20rem]' style={{ maxHeight: mainHeight }}>
<div
tabIndex={-1}
className={clsx(
'max-w-[95rem] mx-auto', // prettier: split lines
'flex',
{ 'flex-col md:items-center': isNarrow }
)}
onKeyDown={handleInput}
>
<FormConstituenta
disabled={disabled}
id={globals.constituenta_editor}
state={activeCst}
isModified={isModified}
toggleReset={toggleReset}
setIsModified={setIsModified}
onEditTerm={controller.editTermForms}
onRename={controller.renameCst}
onOpenEdit={onOpenEdit}
/>
<ViewConstituents
isMounted={showList}
schema={controller.schema}
expression={activeCst?.definition_formal ?? ''}
isBottom={isNarrow}
activeCst={activeCst}
onOpenEdit={onOpenEdit}
/>
</div>
<div
tabIndex={-1}
className={clsx(
'cc-fade-in',
'min-h-[20rem] max-w-[95rem] mx-auto',
'flex pt-[1.9rem]',
'overflow-y-auto overflow-x-clip',
{ 'flex-col md:items-center': isNarrow }
)}
style={{ maxHeight: mainHeight }}
onKeyDown={handleInput}
>
<FormConstituenta
disabled={disabled}
id={globals.constituenta_editor}
state={activeCst}
isModified={isModified}
toggleReset={toggleReset}
setIsModified={setIsModified}
onEditTerm={controller.editTermForms}
onRename={controller.renameCst}
onOpenEdit={onOpenEdit}
/>
<ViewConstituents
isMounted={showList}
schema={controller.schema}
expression={activeCst?.definition_formal ?? ''}
isBottom={isNarrow}
activeCst={activeCst}
onOpenEdit={onOpenEdit}
/>
</div>
</>
);

View File

@ -154,7 +154,7 @@ function FormConstituenta({
}
return (
<AnimateFade className='mx-0 md:mx-auto pt-[2rem] xs:pt-0'>
<div className='mx-0 md:mx-auto pt-[2rem] xs:pt-0'>
{showTypification && state ? (
<DlgShowTypeGraph items={typeInfo ? [typeInfo] : []} hideWindow={() => setShowTypification(false)} />
) : null}
@ -293,7 +293,7 @@ function FormConstituenta({
</>
) : null}
</form>
</AnimateFade>
</div>
);
}

View File

@ -3,7 +3,6 @@
import clsx from 'clsx';
import FlexColumn from '@/components/ui/FlexColumn';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useRSForm } from '@/context/RSFormContext';
import { globals } from '@/utils/constants';
@ -47,9 +46,13 @@ function EditorRSFormCard({ isModified, onDestroy, setIsModified }: EditorRSForm
onDestroy={onDestroy}
controller={controller}
/>
<AnimateFade
<div
onKeyDown={handleInput}
className={clsx('md:w-fit md:max-w-fit max-w-[32rem] mx-auto', 'flex flex-row flex-wrap px-6 pt-[1.9rem]')}
className={clsx(
'cc-fade-in',
'md:w-fit md:max-w-fit max-w-[32rem] mx-auto',
'flex flex-row flex-wrap px-6 pt-[1.9rem]'
)}
>
<FlexColumn className='flex-shrink'>
<FormRSForm id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} />
@ -57,7 +60,7 @@ function EditorRSFormCard({ isModified, onDestroy, setIsModified }: EditorRSForm
</FlexColumn>
{model.schema ? <RSFormStats stats={model.schema.stats} isArchive={model.isArchive} /> : null}
</AnimateFade>
</div>
</>
);
}

View File

@ -9,7 +9,6 @@ import { type RowSelectionState } from '@/components/ui/DataTable';
import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay';
import SearchBar from '@/components/ui/SearchBar';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { CstMatchMode } from '@/models/miscellaneous';
import { ConstituentaID, CstType, IConstituenta } from '@/models/rsform';
@ -142,7 +141,7 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
return (
<>
{controller.isContentEditable ? <ToolbarRSList /> : null}
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown} className='pt-[1.9rem]'>
<div tabIndex={-1} onKeyDown={handleKeyDown} className='cc-fade-in pt-[1.9rem]'>
{controller.isContentEditable ? (
<div className='flex items-center border-b'>
<div className='px-2'>
@ -175,7 +174,7 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
onEdit={onOpenEdit}
onCreateNew={() => controller.createCst(undefined, false)}
/>
</AnimateFade>
</div>
</>
);
}

View File

@ -24,7 +24,6 @@ import SelectedCounter from '@/components/info/SelectedCounter';
import { CProps } from '@/components/props';
import ToolbarGraphSelection from '@/components/select/ToolbarGraphSelection';
import Overlay from '@/components/ui/Overlay';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import DlgGraphParams from '@/dialogs/DlgGraphParams';
import useLocalStorage from '@/hooks/useLocalStorage';
@ -383,60 +382,60 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
/>
) : null}
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}>
<Overlay position='cc-tab-tools' className='flex flex-col items-center rounded-b-2xl cc-blur'>
<ToolbarTermGraph
noText={filterParams.noText}
foldDerived={filterParams.foldDerived}
showParamsDialog={() => setShowParamsDialog(true)}
onCreate={handleCreateCst}
onDelete={handleDeleteCst}
onFitView={() => setToggleResetView(prev => !prev)}
onSaveImage={handleSaveImage}
toggleFoldDerived={handleFoldDerived}
toggleNoText={() =>
<Overlay position='cc-tab-tools' className='flex flex-col items-center rounded-b-2xl cc-blur'>
<ToolbarTermGraph
noText={filterParams.noText}
foldDerived={filterParams.foldDerived}
showParamsDialog={() => setShowParamsDialog(true)}
onCreate={handleCreateCst}
onDelete={handleDeleteCst}
onFitView={() => setToggleResetView(prev => !prev)}
onSaveImage={handleSaveImage}
toggleFoldDerived={handleFoldDerived}
toggleNoText={() =>
setFilterParams(prev => ({
...prev,
noText: !prev.noText
}))
}
/>
{!focusCst ? (
<ToolbarGraphSelection
graph={controller.schema!.graph}
isCore={cstID => isBasicConcept(controller.schema?.cstByID.get(cstID)?.cst_type)}
isOwned={
controller.schema && controller.schema.inheritance.length > 0
? cstID => !controller.schema!.cstByID.get(cstID)?.is_inherited
: undefined
}
selected={controller.selected}
setSelected={handleSetSelected}
emptySelection={controller.selected.length === 0}
/>
) : null}
{focusCst ? (
<ToolbarFocusedCst
center={focusCst}
reset={() => handleSetFocus(undefined)}
showInputs={filterParams.focusShowInputs}
showOutputs={filterParams.focusShowOutputs}
toggleShowInputs={() =>
setFilterParams(prev => ({
...prev,
noText: !prev.noText
focusShowInputs: !prev.focusShowInputs
}))
}
toggleShowOutputs={() =>
setFilterParams(prev => ({
...prev,
focusShowOutputs: !prev.focusShowOutputs
}))
}
/>
{!focusCst ? (
<ToolbarGraphSelection
graph={controller.schema!.graph}
isCore={cstID => isBasicConcept(controller.schema?.cstByID.get(cstID)?.cst_type)}
isOwned={
controller.schema && controller.schema.inheritance.length > 0
? cstID => !controller.schema!.cstByID.get(cstID)?.is_inherited
: undefined
}
selected={controller.selected}
setSelected={handleSetSelected}
emptySelection={controller.selected.length === 0}
/>
) : null}
{focusCst ? (
<ToolbarFocusedCst
center={focusCst}
reset={() => handleSetFocus(undefined)}
showInputs={filterParams.focusShowInputs}
showOutputs={filterParams.focusShowOutputs}
toggleShowInputs={() =>
setFilterParams(prev => ({
...prev,
focusShowInputs: !prev.focusShowInputs
}))
}
toggleShowOutputs={() =>
setFilterParams(prev => ({
...prev,
focusShowOutputs: !prev.focusShowOutputs
}))
}
/>
) : null}
</Overlay>
) : null}
</Overlay>
<div className='cc-fade-in' tabIndex={-1} onKeyDown={handleKeyDown}>
<SelectedCounter
hideZero
totalCount={controller.schema?.stats?.count_all ?? 0}
@ -467,8 +466,9 @@ function TGFlow({ onOpenEdit }: TGFlowProps) {
{viewHidden}
</div>
</Overlay>
{graph}
</AnimateFade>
</div>
</>
);
}

View File

@ -27,9 +27,9 @@ import {
IconUpload
} from '@/components/Icons';
import Button from '@/components/ui/Button';
import Divider from '@/components/ui/Divider';
import Dropdown from '@/components/ui/Dropdown';
import DropdownButton from '@/components/ui/DropdownButton';
import DropdownDivider from '@/components/ui/DropdownDivider';
import { useAccessMode } from '@/context/AccessModeContext';
import { useAuth } from '@/context/AuthContext';
import { useGlobalOss } from '@/context/GlobalOssContext';
@ -191,7 +191,7 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
/>
) : null}
<DropdownDivider margins='mx-3 my-1' />
<Divider margins='mx-3 my-1' />
{user ? (
<DropdownButton
@ -244,7 +244,7 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
onClick={handleInlineSynthesis}
/>
<DropdownDivider margins='mx-3 my-1' />
<Divider margins='mx-3 my-1' />
<DropdownButton
text='Упорядочить список'

View File

@ -13,7 +13,6 @@ import Loader from '@/components/ui/Loader';
import Overlay from '@/components/ui/Overlay';
import TabLabel from '@/components/ui/TabLabel';
import TextURL from '@/components/ui/TextURL';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useGlobalOss } from '@/context/GlobalOssContext';
import { useLibrary } from '@/context/LibraryContext';
@ -270,12 +269,12 @@ function RSTabs() {
</TabList>
</Overlay>
<AnimateFade className='overflow-x-hidden'>
<div className='overflow-x-hidden'>
{cardPanel}
{listPanel}
{editorPanel}
{graphPanel}
</AnimateFade>
</div>
</Tabs>
) : null}
</RSEditState>

View File

@ -72,7 +72,7 @@ function FormSignup() {
}
}
return (
<form className={clsx('cc-column', 'mx-auto w-[36rem]', 'px-6 py-3')} onSubmit={handleSubmit}>
<form className={clsx('cc-fade-in cc-column', 'mx-auto w-[36rem]', 'px-6 py-3')} onSubmit={handleSubmit}>
<h1>
<span>Новый пользователь</span>
<Overlay id={globals.password_tooltip} position='top-[5.4rem] left-[3.5rem]'>

View File

@ -1,5 +1,4 @@
import Loader from '@/components/ui/Loader';
import AnimateFade from '@/components/wrap/AnimateFade';
import ExpectedAnonymous from '@/components/wrap/ExpectedAnonymous';
import { useAuth } from '@/context/AuthContext';
@ -12,17 +11,10 @@ function RegisterPage() {
return <Loader />;
}
if (user) {
return (
<AnimateFade>
<ExpectedAnonymous />
</AnimateFade>
);
return <ExpectedAnonymous />;
} else {
return <FormSignup />;
}
return (
<AnimateFade key='signup-no-user'>
<FormSignup />
</AnimateFade>
);
}
export default RegisterPage;

View File

@ -8,7 +8,6 @@ import InfoError, { ErrorData } from '@/components/info/InfoError';
import SubmitButton from '@/components/ui/SubmitButton';
import TextInput from '@/components/ui/TextInput';
import TextURL from '@/components/ui/TextURL';
import AnimateFade from '@/components/wrap/AnimateFade';
import { useAuth } from '@/context/AuthContext';
import { IRequestPasswordData } from '@/models/user';
@ -32,38 +31,37 @@ function RestorePasswordPage() {
setError(undefined);
}, [email, setError]);
return (
<AnimateFade>
{!isCompleted ? (
<form className={clsx('cc-column', 'w-[24rem] mx-auto', 'px-6 mt-3')} onSubmit={handleSubmit}>
<TextInput
id='email'
autoComplete='email'
required
allowEnter
label='Электронная почта'
value={email}
onChange={event => setEmail(event.target.value)}
/>
if (isCompleted) {
return (
<div className='cc-fade-in flex flex-col items-center gap-1 mt-3'>
<p>На указанную почту отправлены инструкции по восстановлению пароля.</p>
<TextURL text='Войти в Портал' href='/login' />
<TextURL text='Начальная страница' href='/' />
</div>
);
} else {
return (
<form className={clsx('cc-fade-in cc-column', 'w-[24rem] mx-auto', 'px-6 mt-3')} onSubmit={handleSubmit}>
<TextInput
id='email'
autoComplete='email'
required
allowEnter
label='Электронная почта'
value={email}
onChange={event => setEmail(event.target.value)}
/>
<SubmitButton
text='Запросить пароль'
className='self-center w-[12rem] mt-3'
loading={loading}
disabled={!email}
/>
{error ? <ProcessError error={error} /> : null}
</form>
) : null}
{isCompleted ? (
<div className='flex flex-col items-center gap-1 mt-3'>
<p>На указанную почту отправлены инструкции по восстановлению пароля.</p>
<TextURL text='Войти в Портал' href='/login' />
<TextURL text='Начальная страница' href='/' />
</div>
) : null}
</AnimateFade>
);
<SubmitButton
text='Запросить пароль'
className='self-center w-[12rem] mt-3'
loading={loading}
disabled={!email}
/>
{error ? <ProcessError error={error} /> : null}
</form>
);
}
}
export default RestorePasswordPage;

View File

@ -1,6 +1,5 @@
'use client';
import AnimateFade from '@/components/wrap/AnimateFade';
import DataLoader from '@/components/wrap/DataLoader';
import { useUserProfile } from '@/context/UserProfileContext';
@ -11,21 +10,14 @@ function UserContents() {
const { user, error, loading } = useUserProfile();
return (
<DataLoader
id='profile-page' // prettier: split lines
isLoading={loading}
error={error}
hasNoData={!user}
>
<AnimateFade className='flex gap-6 py-2 mx-auto w-fit'>
<div className='w-fit'>
<h1 className='mb-4 select-none'>Учетные данные пользователя</h1>
<div className='flex py-2'>
<EditorProfile />
<EditorPassword />
</div>
<DataLoader isLoading={loading} error={error} hasNoData={!user}>
<div className='cc-fade-in flex gap-6 py-2 mx-auto w-fit'>
<h1 className='mb-4 select-none'>Учетные данные пользователя</h1>
<div className='flex py-2'>
<EditorProfile />
<EditorPassword />
</div>
</AnimateFade>
</div>
</DataLoader>
);
}

View File

@ -1,71 +0,0 @@
/**
* Module: animations parameters.
*/
import { Variants } from 'framer-motion';
export const animateDropdown: Variants = {
open: {
clipPath: 'inset(0% 0% 0% 0%)',
transition: {
type: 'spring',
bounce: 0,
duration: 0.4,
delayChildren: 0.2,
staggerChildren: 0.05
}
},
closed: {
clipPath: 'inset(10% 0% 90% 0%)',
transition: {
type: 'spring',
bounce: 0,
duration: 0.3
}
}
};
export const animateDropdownItem: Variants = {
open: {
opacity: 1,
y: 0,
transition: {
type: 'spring',
duration: 0.1,
stiffness: 300,
damping: 24
}
},
closed: {
opacity: 0,
y: 10,
transition: {
duration: 0.1
}
}
};
export const animateModal = {
initial: {
clipPath: 'inset(50% 50% 50% 50%)',
opacity: 0
},
animate: {
clipPath: 'inset(0% 0% 0% 0%)',
opacity: 1,
transition: {
type: 'spring',
bounce: 0,
duration: 0.3
}
},
exit: {
opacity: 0,
clipPath: 'inset(50% 50% 50% 50%)',
transition: {
type: 'spring',
bounce: 0,
duration: 0.2
}
}
};

View File

@ -16,7 +16,9 @@
--text-max-width: 75ch;
--scroll-padding: 3rem;
--duration-move: 400ms;
--duration-move: 500ms;
--duration-modal: 300ms;
--duration-fade: 300ms;
/* Light Theme */
--cl-bg-120: hsl(000, 000%, 100%);

View File

@ -214,11 +214,14 @@
}
.cc-column {
@apply flex flex-col gap-3;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.cc-icons {
@apply flex gap-1;
display: flex;
gap: 0.25rem;
}
.cc-fit-content {
@ -254,4 +257,30 @@
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: var(--duration-move);
}
.cc-fade-in {
opacity: 1;
transition-property: opacity;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: var(--duration-fade);
@starting-style {
opacity: 0;
}
}
.cc-animate-modal {
clip-path: inset(0% 0% 0% 0%);
opacity: 1;
transition-property: clip-path, opacity;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: var(--duration-modal);
@starting-style {
clip-path: inset(50% 50% 50% 50%);
opacity: 0;
}
}
}

View File

@ -23,6 +23,7 @@ export const PARAMETER = {
fastAnimation: 200, // milliseconds - duration of fast animation
fadeDuration: 300, // milliseconds - duration of fade animation
dropdownDuration: 300, // milliseconds - duration of dropdown animation
moveDuration: 500, // milliseconds - duration of move animation
graphHandleSize: 3, // pixels - size of graph connection handle