diff --git a/.vscode/settings.json b/.vscode/settings.json index d409c286..96da4662 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -66,6 +66,7 @@ "ADVB", "Analyse", "Backquote", + "bezier", "BIGPR", "cctext", "Certbot", diff --git a/rsconcept/frontend/src/app/Navigation/Navigation.tsx b/rsconcept/frontend/src/app/Navigation/Navigation.tsx index 432a6109..b223295f 100644 --- a/rsconcept/frontend/src/app/Navigation/Navigation.tsx +++ b/rsconcept/frontend/src/app/Navigation/Navigation.tsx @@ -1,11 +1,11 @@ import clsx from 'clsx'; -import { motion } from 'framer-motion'; import { IconLibrary2, IconManuals, IconNewItem2 } from '@/components/Icons'; import { CProps } from '@/components/props'; import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { useConceptNavigation } from '@/context/NavigationContext'; -import { animateNavigation } from '@/styling/animations'; +import useWindowSize from '@/hooks/useWindowSize'; +import { PARAMETER } from '@/utils/constants'; import { urls } from '../urls'; import Logo from './Logo'; @@ -15,6 +15,7 @@ import UserMenu from './UserMenu'; function Navigation() { const router = useConceptNavigation(); + const size = useWindowSize(); const { noNavigationAnimation } = useConceptOptions(); const navigateHome = (event: CProps.EventMouse) => router.push(urls.home, event.ctrlKey || event.metaKey); @@ -33,17 +34,24 @@ function Navigation() { )} > - -
+
@@ -52,7 +60,7 @@ function Navigation() { } onClick={navigateHelp} />
- +
); } diff --git a/rsconcept/frontend/src/app/Navigation/ToggleNavigation.tsx b/rsconcept/frontend/src/app/Navigation/ToggleNavigation.tsx index 144eebd2..a4d251db 100644 --- a/rsconcept/frontend/src/app/Navigation/ToggleNavigation.tsx +++ b/rsconcept/frontend/src/app/Navigation/ToggleNavigation.tsx @@ -1,15 +1,13 @@ import clsx from 'clsx'; -import { motion } from 'framer-motion'; import { IconPin, IconUnpin } from '@/components/Icons'; import { useConceptOptions } from '@/context/ConceptOptionsContext'; -import { animateNavigationToggle } from '@/styling/animations'; -import { globals } from '@/utils/constants'; +import { globals, PARAMETER } from '@/utils/constants'; function ToggleNavigation() { const { noNavigationAnimation, toggleNoNavigation } = useConceptOptions(); return ( - {!noNavigationAnimation ? : null} {noNavigationAnimation ? : null} - + ); } diff --git a/rsconcept/frontend/src/components/ui/SelectTree.tsx b/rsconcept/frontend/src/components/ui/SelectTree.tsx index 3b4e6545..49563992 100644 --- a/rsconcept/frontend/src/components/ui/SelectTree.tsx +++ b/rsconcept/frontend/src/components/ui/SelectTree.tsx @@ -1,9 +1,7 @@ import clsx from 'clsx'; -import { AnimatePresence, motion } from 'framer-motion'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; -import { animateSideAppear } from '@/styling/animations'; -import { globals } from '@/utils/constants'; +import { globals, PARAMETER } from '@/utils/constants'; import { IconDropArrow, IconPageRight } from '../Icons'; import { CProps } from '../props'; @@ -94,41 +92,46 @@ function SelectTree({ return (
- - {items.map((item, index) => - getParent(item) === item || !folded.includes(getParent(item)) ? ( - handleSetValue(event, item)} - initial={{ ...animateSideAppear.initial }} - animate={{ ...animateSideAppear.animate }} - exit={{ ...animateSideAppear.exit }} - > - {foldable.has(item) ? ( - - : } - onClick={event => handleClickFold(event, item, folded.includes(item))} - /> - - ) : null} - {getParent(item) === item ? getLabel(item) : `- ${getLabel(item).toLowerCase()}`} - - ) : null - )} - + {items.map((item, index) => { + const isActive = getParent(item) === item || !folded.includes(getParent(item)); + return ( +
handleSetValue(event, item) : undefined} + style={{ + borderBottomWidth: isActive ? '1px' : '0px', + transitionProperty: 'height, opacity, padding', + transitionDuration: `${PARAMETER.moveDuration}ms`, + paddingTop: isActive ? '0.25rem' : '0', + paddingBottom: isActive ? '0.25rem' : '0', + height: isActive ? 'min-content' : '0', + opacity: isActive ? '1' : '0' + }} + > + {foldable.has(item) ? ( + + : } + onClick={event => handleClickFold(event, item, folded.includes(item))} + /> + + ) : null} + {getParent(item) === item ? getLabel(item) : `- ${getLabel(item).toLowerCase()}`} +
+ ); + })}
); } diff --git a/rsconcept/frontend/src/context/ConceptOptionsContext.tsx b/rsconcept/frontend/src/context/ConceptOptionsContext.tsx index 6c5ae255..d2cd078f 100644 --- a/rsconcept/frontend/src/context/ConceptOptionsContext.tsx +++ b/rsconcept/frontend/src/context/ConceptOptionsContext.tsx @@ -4,9 +4,8 @@ import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useSt import Tooltip from '@/components/ui/Tooltip'; import useLocalStorage from '@/hooks/useLocalStorage'; -import { animationDuration } from '@/styling/animations'; import { darkT, IColorTheme, lightT } from '@/styling/color'; -import { globals, storage } from '@/utils/constants'; +import { globals, PARAMETER, storage } from '@/utils/constants'; import { contextOutsideScope } from '@/utils/labels'; interface IOptionsContext { @@ -91,7 +90,7 @@ export const OptionsState = ({ children }: React.PropsWithChildren) => { setNoNavigation(false); } else { setNoNavigationAnimation(true); - setTimeout(() => setNoNavigation(true), animationDuration.navigationToggle); + setTimeout(() => setNoNavigation(true), PARAMETER.moveDuration); } }, [noNavigation]); diff --git a/rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx b/rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx index a08cb7d3..0f3b2cfb 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/LibraryPage.tsx @@ -1,6 +1,5 @@ 'use client'; -import { AnimatePresence } from 'framer-motion'; import fileDownload from 'js-file-download'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react'; import { toast } from 'react-toastify'; @@ -150,6 +149,7 @@ function LibraryPage() { const viewLocations = useMemo( () => (
- {options.folderMode ? viewLocations : null} + {viewLocations} {viewLibrary}
diff --git a/rsconcept/frontend/src/pages/LibraryPage/ViewSideLocation.tsx b/rsconcept/frontend/src/pages/LibraryPage/ViewSideLocation.tsx index 127fea25..24d17b87 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/ViewSideLocation.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/ViewSideLocation.tsx @@ -1,5 +1,4 @@ import clsx from 'clsx'; -import { motion } from 'framer-motion'; import { useCallback, useMemo } from 'react'; import { toast } from 'react-toastify'; @@ -15,12 +14,12 @@ import { useLibrary } from '@/context/LibraryContext'; import useWindowSize from '@/hooks/useWindowSize'; import { FolderNode, FolderTree } from '@/models/FolderTree'; import { HelpTopic } from '@/models/miscellaneous'; -import { animateSideMinWidth } from '@/styling/animations'; import { PARAMETER, prefixes } from '@/utils/constants'; import { information } from '@/utils/labels'; interface ViewSideLocationProps { folderTree: FolderTree; + isVisible: boolean; subfolders: boolean; activeLocation: string; onChangeActiveLocation: (newValue: string) => void; @@ -33,6 +32,7 @@ function ViewSideLocation({ folderTree, activeLocation, subfolders, + isVisible, onChangeActiveLocation, toggleFolderMode, toggleSubfolders, @@ -57,7 +57,6 @@ function ViewSideLocation({ return located.length !== 0; }, [activeLocation, user, items]); - const animations = useMemo(() => animateSideMinWidth(windowSize.isSmall ? '10rem' : '15rem'), [windowSize]); const maxHeight = useMemo(() => calculateHeight('4.5rem'), [calculateHeight]); const handleClickFolder = useCallback( @@ -77,11 +76,16 @@ function ViewSideLocation({ ); return ( -
- +
); } diff --git a/rsconcept/frontend/src/pages/ManualsPage/TopicsDropdown.tsx b/rsconcept/frontend/src/pages/ManualsPage/TopicsDropdown.tsx index 4b42cde7..971b6809 100644 --- a/rsconcept/frontend/src/pages/ManualsPage/TopicsDropdown.tsx +++ b/rsconcept/frontend/src/pages/ManualsPage/TopicsDropdown.tsx @@ -1,7 +1,6 @@ 'use client'; import clsx from 'clsx'; -import { motion } from 'framer-motion'; import { useCallback } from 'react'; import { IconMenuFold, IconMenuUnfold } from '@/components/Icons'; @@ -10,8 +9,7 @@ import SelectTree from '@/components/ui/SelectTree'; import { useConceptOptions } from '@/context/ConceptOptionsContext'; import useDropdown from '@/hooks/useDropdown'; import { HelpTopic, topicParent } from '@/models/miscellaneous'; -import { animateSlideLeft } from '@/styling/animations'; -import { prefixes } from '@/utils/constants'; +import { PARAMETER, prefixes } from '@/utils/constants'; import { describeHelpTopic, labelHelpTopic } from '@/utils/labels'; interface TopicsDropdownProps { @@ -55,27 +53,26 @@ function TopicsDropdown({ activeTopic, onChangeTopic }: TopicsDropdownProps) { className={clsx('w-[3rem] h-7 rounded-none border-l-0', menu.isOpen && 'border-b-0')} onClick={menu.toggle} /> - item as HelpTopic)} + value={activeTopic} + onChangeValue={handleSelectTopic} + prefix={prefixes.topic_list} + getParent={item => topicParent.get(item) ?? item} + getLabel={labelHelpTopic} + getDescription={describeHelpTopic} className={clsx( - 'border border-l-0 divide-y rounded-none', // prettier: split-lines + 'border-r border-t rounded-none', // prettier: split-lines 'cc-scroll-y', 'clr-controls' )} - style={{ maxHeight: calculateHeight('4rem + 2px') }} - initial={false} - animate={menu.isOpen ? 'open' : 'closed'} - variants={animateSlideLeft} - > - item as HelpTopic)} - value={activeTopic} - onChangeValue={handleSelectTopic} - prefix={prefixes.topic_list} - getParent={item => topicParent.get(item) ?? item} - getLabel={labelHelpTopic} - getDescription={describeHelpTopic} - /> - + style={{ + maxHeight: calculateHeight('4rem + 2px'), + transitionProperty: 'clip-path', + transitionDuration: `${PARAMETER.moveDuration}ms`, + clipPath: menu.isOpen ? 'inset(0% 0% 0% 0%)' : 'inset(0% 100% 0% 0%)' + }} + /> ); } diff --git a/rsconcept/frontend/src/pages/ManualsPage/TopicsStatic.tsx b/rsconcept/frontend/src/pages/ManualsPage/TopicsStatic.tsx index 8df8b64c..540e3731 100644 --- a/rsconcept/frontend/src/pages/ManualsPage/TopicsStatic.tsx +++ b/rsconcept/frontend/src/pages/ManualsPage/TopicsStatic.tsx @@ -27,7 +27,7 @@ function TopicsStatic({ activeTopic, onChangeTopic }: TopicsStaticProps) { 'min-w-[14.5rem] max-w-[14.5rem] sm:min-w-[12.5rem] sm:max-w-[12.5rem] md:min-w-[14.5rem] md:max-w-[14.5rem]', 'cc-scroll-y', 'self-start', - 'border divide-y rounded-none', + 'border-x border-t rounded-none', 'clr-controls', 'text-xs sm:text-sm', 'select-none' diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ToolbarConstituenta.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ToolbarConstituenta.tsx index 207fc258..9c520f0c 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ToolbarConstituenta.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/ToolbarConstituenta.tsx @@ -51,7 +51,7 @@ function ToolbarConstituenta({ return ( {controller.schema && controller.schema?.oss.length > 0 ? (

Ошибок: {errorCount} | Предупреждений: {warningsCount} @@ -42,7 +43,7 @@ function ParsingResult({ isOpen, data, disabled, onShowError }: ParsingResultPro

); })} -
+ ); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/RSEditControls.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/RSEditControls.tsx index 7270a868..c6666896 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/RSEditControls.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/RSEditControls.tsx @@ -1,9 +1,7 @@ import clsx from 'clsx'; -import { motion } from 'framer-motion'; import { TokenID } from '@/models/rslang'; -import { animateRSControl } from '@/styling/animations'; -import { prefixes } from '@/utils/constants'; +import { PARAMETER, prefixes } from '@/utils/constants'; import RSLocalButton from './RSLocalButton'; import RSTokenButton from './RSTokenButton'; @@ -90,7 +88,7 @@ interface RSEditorControlsProps { function RSEditorControls({ isOpen, disabled, onEdit }: RSEditorControlsProps) { return ( - {MAIN_FIRST_ROW.map(token => ( @@ -143,7 +145,7 @@ function RSEditorControls({ isOpen, disabled, onEdit }: RSEditorControlsProps) { disabled={disabled} /> ))} - + ); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/StatusBar.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/StatusBar.tsx index b9492a29..af9bbe83 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/StatusBar.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/StatusBar.tsx @@ -56,7 +56,7 @@ function StatusBar({ isModified, processing, activeCst, parseData, onAnalyze }: 'select-none', 'cursor-pointer', 'focus-frame', - 'duration-500 transition-colors' + 'transition-colors duration-500' )} style={{ backgroundColor: processing ? colors.bgDefault : colorStatusBar(status, colors) }} data-tooltip-id={globals.tooltip} diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/ToolbarRSList.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/ToolbarRSList.tsx index 7fb4a0dc..fb0fa940 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/ToolbarRSList.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/ToolbarRSList.tsx @@ -29,7 +29,7 @@ function ToolbarRSList() { return ( {controller.schema && controller.schema?.oss.length > 0 ? ( setIsFolded(prev => !prev)} /> -
- +
{`Скрытые [${localSelected.length} | ${items.length}]`} - +
- {items.map(cstID => { const cst = schema.cstByID.get(cstID)!; @@ -124,7 +118,7 @@ function ViewHidden({ items, selected, toggleSelection, setFocus, schema, colori ); })} - + ); } diff --git a/rsconcept/frontend/src/styling/animations.ts b/rsconcept/frontend/src/styling/animations.ts index 418904ab..ff2cd8e9 100644 --- a/rsconcept/frontend/src/styling/animations.ts +++ b/rsconcept/frontend/src/styling/animations.ts @@ -4,97 +4,6 @@ import { Variants } from 'framer-motion'; -/** - * Duration constants in ms. - */ -export const animationDuration = { - navigationToggle: 500 -}; - -export const animateNavigation: Variants = { - open: { - height: '3rem', - translateY: 0, - transition: { - type: 'spring', - bounce: 0, - duration: animationDuration.navigationToggle / 1000 - } - }, - closed: { - height: 0, - translateY: '-1.5rem', - transition: { - type: 'spring', - bounce: 0, - duration: animationDuration.navigationToggle / 1000 - } - } -}; - -export const animateNavigationToggle: Variants = { - on: { - height: '3rem', - width: '1.2rem', - transition: { - type: 'spring', - bounce: 0, - duration: animationDuration.navigationToggle / 1000 - } - }, - off: { - height: '1.2rem', - width: '3rem', - transition: { - type: 'spring', - bounce: 0, - duration: animationDuration.navigationToggle / 1000 - } - } -}; - -export const animateSlideLeft: 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(0% 100% 0% 0%)', - transition: { - type: 'spring', - bounce: 0, - duration: 0.3 - } - } -}; - -export const animateHiddenHeader: Variants = { - open: { - translateX: 'calc(6.5rem - 50%)', - marginLeft: 0, - transition: { - type: 'spring', - bounce: 0, - duration: 0.3 - } - }, - closed: { - translateX: 0, - marginLeft: '0.75rem', - transition: { - type: 'spring', - bounce: 0, - duration: 0.3 - } - } -}; - export const animateDropdown: Variants = { open: { clipPath: 'inset(0% 0% 0% 0%)', @@ -136,120 +45,6 @@ export const animateDropdownItem: Variants = { } }; -export const animateRSControl: Variants = { - open: { - clipPath: 'inset(0% 0% 0% 0%)', - marginTop: '0.25rem', - height: 'max-content', - transition: { - type: 'spring', - bounce: 0, - duration: 0.4 - } - }, - closed: { - clipPath: 'inset(0% 0% 100% 0%)', - marginTop: '0', - height: 0, - transition: { - type: 'spring', - bounce: 0, - duration: 0.3 - } - } -}; - -export const animateParseResults: Variants = { - open: { - clipPath: 'inset(0% 0% 0% 0%)', - marginTop: '0.75rem', - padding: '0.25rem 0.5rem 0.25rem 0.5rem', - borderWidth: '1px', - height: '4.5rem', - transition: { - type: 'spring', - bounce: 0, - duration: 0.4 - } - }, - closed: { - clipPath: 'inset(0% 0% 100% 0%)', - marginTop: '0', - borderWidth: '0', - padding: '0 0 0 0', - height: 0, - transition: { - type: 'spring', - bounce: 0, - duration: 0.3 - } - } -}; - -export const animateSideMinWidth = (width: string) => ({ - initial: { - minWidth: 0, - opacity: 0 - }, - animate: { - minWidth: width, - opacity: 1, - transition: { - width: { - duration: 0.4 - }, - opacity: { - delay: 0.4, - duration: 0 - } - } - }, - exit: { - minWidth: 0, - opacity: 0, - transition: { - width: { - duration: 0.4 - }, - opacity: { - duration: 0 - } - } - } -}); - -export const animateSideAppear = { - initial: { - height: 0, - opacity: 0 - }, - animate: { - height: 'auto', - opacity: 1, - transition: { - height: { - duration: 0.25 - }, - opacity: { - delay: 0.25, - duration: 0 - } - } - }, - exit: { - height: 0, - opacity: 0, - transition: { - height: { - duration: 0.25 - }, - opacity: { - duration: 0 - } - } - } -}; - export const animateModal = { initial: { clipPath: 'inset(50% 50% 50% 50%)', diff --git a/rsconcept/frontend/src/styling/constants.css b/rsconcept/frontend/src/styling/constants.css index 29d35761..8a61ee5e 100644 --- a/rsconcept/frontend/src/styling/constants.css +++ b/rsconcept/frontend/src/styling/constants.css @@ -16,6 +16,8 @@ --text-max-width: 75ch; --scroll-padding: 3rem; + --duration-move: 400ms; + /* Light Theme */ --cl-bg-120: hsl(000, 000%, 100%); --cl-bg-100: hsl(000, 000%, 098%); diff --git a/rsconcept/frontend/src/styling/styles.css b/rsconcept/frontend/src/styling/styles.css index 1ccd77d1..4be32573 100644 --- a/rsconcept/frontend/src/styling/styles.css +++ b/rsconcept/frontend/src/styling/styles.css @@ -248,4 +248,10 @@ .cc-shadow-border { @apply shadow-sm shadow-[var(--cl-bg-40)] dark:shadow-[var(--cd-bg-40)]; } + + .cc-animate-position { + transition-property: transform top left bottom right margin padding; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: var(--duration-move); + } } diff --git a/rsconcept/frontend/src/utils/constants.ts b/rsconcept/frontend/src/utils/constants.ts index 470062e5..a30cd720 100644 --- a/rsconcept/frontend/src/utils/constants.ts +++ b/rsconcept/frontend/src/utils/constants.ts @@ -21,8 +21,9 @@ export const PARAMETER = { ossDistanceX: 180, // pixels - insert x-distance between node centers ossDistanceY: 100, // pixels - insert y-distance between node centers + fastAnimation: 200, // milliseconds - duration of fast animation fadeDuration: 300, // milliseconds - duration of fade animation - moveDuration: 700, // milliseconds - duration of move animation + moveDuration: 500, // milliseconds - duration of move animation graphHandleSize: 3, // pixels - size of graph connection handle graphNodeRadius: 20, // pixels - radius of graph node