diff --git a/README.md b/README.md index 9ce3c227..b8cab60c 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ This readme file is used mostly to document project dependencies - react-pdf - react-tooltip - js-file-download + - framer-motion - reagraph - @tanstack/react-table - @uiw/react-codemirror diff --git a/rsconcept/frontend/package-lock.json b/rsconcept/frontend/package-lock.json index 48a32112..572eeb16 100644 --- a/rsconcept/frontend/package-lock.json +++ b/rsconcept/frontend/package-lock.json @@ -14,6 +14,7 @@ "@uiw/react-codemirror": "^4.21.21", "axios": "^1.6.2", "clsx": "^2.0.0", + "framer-motion": "^10.16.16", "js-file-download": "^0.4.12", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -4985,6 +4986,44 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "10.16.16", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-10.16.16.tgz", + "integrity": "sha512-je6j91rd7NmUX7L1XHouwJ4v3R+SO4umso2LUcgOct3rHZ0PajZ80ETYZTajzEXEl9DlKyzjyt4AvGQ+lrebOw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "optionalDependencies": { + "@emotion/is-prop-valid": "^0.8.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/framer-motion/node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "optional": true, + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "node_modules/framer-motion/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "optional": true + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", diff --git a/rsconcept/frontend/package.json b/rsconcept/frontend/package.json index 0f52ec01..73e8c5e3 100644 --- a/rsconcept/frontend/package.json +++ b/rsconcept/frontend/package.json @@ -18,6 +18,7 @@ "@uiw/react-codemirror": "^4.21.21", "axios": "^1.6.2", "clsx": "^2.0.0", + "framer-motion": "^10.16.16", "js-file-download": "^0.4.12", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/rsconcept/frontend/src/components/Common/Button.tsx b/rsconcept/frontend/src/components/Common/Button.tsx index a9239940..4233a3c2 100644 --- a/rsconcept/frontend/src/components/Common/Button.tsx +++ b/rsconcept/frontend/src/components/Common/Button.tsx @@ -38,7 +38,7 @@ function Button({ className, colors )} - data-tooltip-id={title ? (globalIDs.tooltip) : undefined} + data-tooltip-id={title ? globalIDs.tooltip : undefined} data-tooltip-content={title} {...restProps} > diff --git a/rsconcept/frontend/src/components/Common/ConceptTooltip.tsx b/rsconcept/frontend/src/components/Common/ConceptTooltip.tsx index e08ce60d..8d44e18e 100644 --- a/rsconcept/frontend/src/components/Common/ConceptTooltip.tsx +++ b/rsconcept/frontend/src/components/Common/ConceptTooltip.tsx @@ -22,7 +22,6 @@ function ConceptTooltip({ ...restProps }: ConceptTooltipProps) { const { darkMode } = useConceptTheme(); - if (typeof window === 'undefined') { return null; } diff --git a/rsconcept/frontend/src/components/Common/Dropdown.tsx b/rsconcept/frontend/src/components/Common/Dropdown.tsx index 4d018fbb..8e4ebf9c 100644 --- a/rsconcept/frontend/src/components/Common/Dropdown.tsx +++ b/rsconcept/frontend/src/components/Common/Dropdown.tsx @@ -1,25 +1,29 @@ import clsx from 'clsx'; +import { motion } from 'framer-motion'; + +import { animateDropdown } from '@/utils/animations'; import { CProps } from '../props'; -import Overlay from './Overlay'; interface DropdownProps extends CProps.Styling { stretchLeft?: boolean + isOpen: boolean children: React.ReactNode } function Dropdown({ + isOpen, stretchLeft, className, - stretchLeft, children, ...restProps -}: DropdownProps) { +}: DropdownProps) { return ( - + {children} - ); + + ); } export default Dropdown; \ No newline at end of file diff --git a/rsconcept/frontend/src/components/Common/DropdownButton.tsx b/rsconcept/frontend/src/components/Common/DropdownButton.tsx index c0d4b15f..0adeb5c2 100644 --- a/rsconcept/frontend/src/components/Common/DropdownButton.tsx +++ b/rsconcept/frontend/src/components/Common/DropdownButton.tsx @@ -1,11 +1,13 @@ import clsx from 'clsx'; +import { motion } from 'framer-motion'; +import { animateDropdownItem } from '@/utils/animations'; import { globalIDs } from '@/utils/constants'; import { CProps } from '../props'; interface DropdownButtonProps -extends CProps.Button { +extends CProps.AnimatedButton { text?: string icon?: React.ReactNode @@ -20,7 +22,7 @@ function DropdownButton({ ...restProps }: DropdownButtonProps) { return ( - ); + ); } export default DropdownButton; \ No newline at end of file diff --git a/rsconcept/frontend/src/components/Common/DropdownCheckbox.tsx b/rsconcept/frontend/src/components/Common/DropdownCheckbox.tsx index edb917b2..a87e3598 100644 --- a/rsconcept/frontend/src/components/Common/DropdownCheckbox.tsx +++ b/rsconcept/frontend/src/components/Common/DropdownCheckbox.tsx @@ -1,4 +1,7 @@ import clsx from 'clsx'; +import { motion } from 'framer-motion'; + +import { animateDropdownItem } from '@/utils/animations'; import Checkbox from './Checkbox'; @@ -12,7 +15,8 @@ interface DropdownCheckboxProps { function DropdownCheckbox({ title, setValue, disabled, ...restProps }: DropdownCheckboxProps) { return ( -
-
); + ); } export default DropdownCheckbox; \ No newline at end of file diff --git a/rsconcept/frontend/src/components/Common/Modal.tsx b/rsconcept/frontend/src/components/Common/Modal.tsx index e1c0eb73..48d87365 100644 --- a/rsconcept/frontend/src/components/Common/Modal.tsx +++ b/rsconcept/frontend/src/components/Common/Modal.tsx @@ -1,10 +1,12 @@ 'use client'; import clsx from 'clsx'; +import { motion } from 'framer-motion'; import { useRef } from 'react'; import { BiX } from 'react-icons/bi'; import useEscapeKey from '@/hooks/useEscapeKey'; +import { animateModal } from '@/utils/animations'; import { CProps } from '../props'; import Button from './Button'; @@ -56,13 +58,16 @@ function Modal({ 'w-full h-full', 'clr-modal-backdrop' )}/> -
@@ -107,7 +112,7 @@ function Modal({ onClick={handleCancel} />
- + ); } diff --git a/rsconcept/frontend/src/components/Navigation/Navigation.tsx b/rsconcept/frontend/src/components/Navigation/Navigation.tsx index 7c66a494..a348bce9 100644 --- a/rsconcept/frontend/src/components/Navigation/Navigation.tsx +++ b/rsconcept/frontend/src/components/Navigation/Navigation.tsx @@ -1,10 +1,12 @@ import clsx from 'clsx'; +import { motion } from 'framer-motion'; import { FaSquarePlus } from 'react-icons/fa6'; import { IoLibrary } from 'react-icons/io5'; import { EducationIcon } from '@/components/Icons'; import { useConceptNavigation } from '@/context/NagivationContext'; import { useConceptTheme } from '@/context/ThemeContext'; +import { animateNavigation } from '@/utils/animations'; import Logo from './Logo'; import NavigationButton from './NavigationButton'; @@ -13,13 +15,13 @@ import UserMenu from './UserMenu'; function Navigation () { const router = useConceptNavigation(); - const { noNavigation } = useConceptTheme(); + const { noNavigationAnimation } = useConceptTheme(); const navigateHome = () => router.push('/'); const navigateLibrary = () => router.push('/library'); const navigateHelp = () => router.push('/manuals'); const navigateCreateNew = () => router.push('/library/create'); - + return ( ); } diff --git a/rsconcept/frontend/src/components/Navigation/ToggleNavigationButton.tsx b/rsconcept/frontend/src/components/Navigation/ToggleNavigationButton.tsx index e573ae14..14410676 100644 --- a/rsconcept/frontend/src/components/Navigation/ToggleNavigationButton.tsx +++ b/rsconcept/frontend/src/components/Navigation/ToggleNavigationButton.tsx @@ -1,36 +1,28 @@ import clsx from 'clsx'; -import { useMemo } from 'react'; +import { motion } from 'framer-motion'; +import { RiPushpinFill, RiUnpinLine } from 'react-icons/ri'; import { useConceptTheme } from '@/context/ThemeContext'; +import { animateNavigationToggle } from '@/utils/animations'; function ToggleNavigationButton() { - const { noNavigation, toggleNoNavigation } = useConceptTheme(); - const text = useMemo(() => ( - noNavigation ? - '∨∨∨' - : - <> -

{'>'}

-

{'>'}

- - ), [noNavigation] - ); + const { noNavigationAnimation, toggleNoNavigation } = useConceptTheme(); return ( - ); + {!noNavigationAnimation ? : null} + {noNavigationAnimation ? : null} + ); } export default ToggleNavigationButton; \ No newline at end of file diff --git a/rsconcept/frontend/src/components/Navigation/UserDropdown.tsx b/rsconcept/frontend/src/components/Navigation/UserDropdown.tsx index 298da73e..89a7e5dc 100644 --- a/rsconcept/frontend/src/components/Navigation/UserDropdown.tsx +++ b/rsconcept/frontend/src/components/Navigation/UserDropdown.tsx @@ -5,10 +5,11 @@ import { useConceptNavigation } from '@/context/NagivationContext'; import { useConceptTheme } from '@/context/ThemeContext'; interface UserDropdownProps { + isOpen: boolean hideDropdown: () => void } -function UserDropdown({ hideDropdown }: UserDropdownProps) { +function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) { const { darkMode, toggleDarkMode } = useConceptTheme(); const router = useConceptNavigation(); const { user, logout } = useAuth(); @@ -25,7 +26,7 @@ function UserDropdown({ hideDropdown }: UserDropdownProps) { }; return ( - + } onClick={menu.toggle} /> : null} - {(user && menu.isActive) ? menu.hide()} - /> : null} + /> ); } diff --git a/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx b/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx index 060baf87..91d486d7 100644 --- a/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx +++ b/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx @@ -6,6 +6,7 @@ import { createTheme } from '@uiw/codemirror-themes'; import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror'; import clsx from 'clsx'; import { EditorView } from 'codemirror'; +import { AnimatePresence } from 'framer-motion'; import { RefObject, useCallback, useMemo, useRef, useState } from 'react'; import Label from '@/components/Common/Label'; @@ -164,6 +165,7 @@ function RefsInput({ }, [thisRef]); return (<> + {showEditor ? setShowEditor(false)} @@ -177,6 +179,8 @@ function RefsInput({ }} onSave={handleInputReference} /> : null} + +
, HTMLTextAreaElement>; export type Input = React.DetailedHTMLProps, HTMLInputElement>; +export type AnimatedButton = Omit, 'type'>; + } \ No newline at end of file diff --git a/rsconcept/frontend/src/context/ThemeContext.tsx b/rsconcept/frontend/src/context/ThemeContext.tsx index e7b3f9c2..873b41a4 100644 --- a/rsconcept/frontend/src/context/ThemeContext.tsx +++ b/rsconcept/frontend/src/context/ThemeContext.tsx @@ -1,10 +1,11 @@ 'use client'; import clsx from 'clsx'; -import { createContext, useContext, useLayoutEffect, useMemo, useState } from 'react'; +import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react'; import ConceptTooltip from '@/components/Common/ConceptTooltip'; import useLocalStorage from '@/hooks/useLocalStorage'; +import { animationDuration } from '@/utils/animations'; import { darkT, IColorTheme, lightT } from '@/utils/color'; import { globalIDs } from '@/utils/constants'; @@ -17,6 +18,7 @@ interface IThemeContext { darkMode: boolean toggleDarkMode: () => void + noNavigationAnimation: boolean noNavigation: boolean toggleNoNavigation: () => void @@ -44,6 +46,7 @@ export const ThemeState = ({ children }: ThemeStateProps) => { const [darkMode, setDarkMode] = useLocalStorage('darkMode', false); const [colors, setColors] = useState(lightT); const [noNavigation, setNoNavigation] = useState(false); + const [noNavigationAnimation, setNoNavigationAnimation] = useState(false); const [noFooter, setNoFooter] = useState(false); const [showScroll, setShowScroll] = useState(false); @@ -65,6 +68,17 @@ export const ThemeState = ({ children }: ThemeStateProps) => { setColors(darkMode ? darkT : lightT) }, [darkMode, setColors]); + const toggleNoNavigation = useCallback( + () => { + if (noNavigation) { + setNoNavigationAnimation(false); + setNoNavigation(false); + } else { + setNoNavigationAnimation(true); + setTimeout(() => setNoNavigation(true), animationDuration.navigationToggle); + } + }, [noNavigation]); + const mainHeight = useMemo( () => { return !noNavigation ? @@ -82,9 +96,9 @@ export const ThemeState = ({ children }: ThemeStateProps) => { return ( setDarkMode(prev => !prev), - toggleNoNavigation: () => setNoNavigation(prev => !prev), + toggleNoNavigation: toggleNoNavigation, setNoFooter, setShowScroll, viewportHeight, mainHeight }}> diff --git a/rsconcept/frontend/src/hooks/useDropdown.ts b/rsconcept/frontend/src/hooks/useDropdown.ts index 6e52e9be..5797494f 100644 --- a/rsconcept/frontend/src/hooks/useDropdown.ts +++ b/rsconcept/frontend/src/hooks/useDropdown.ts @@ -5,17 +5,17 @@ import { useRef, useState } from 'react'; import useClickedOutside from './useClickedOutside'; function useDropdown() { - const [isActive, setIsActive] = useState(false); + const [isOpen, setIsOpen] = useState(false); const ref = useRef(null); - useClickedOutside({ ref, callback: () => setIsActive(false) }); + useClickedOutside({ ref, callback: () => setIsOpen(false) }); return { ref, - isActive, - setIsActive, - toggle: () => setIsActive(!isActive), - hide: () => setIsActive(false) + isOpen, + setIsOpen, + toggle: () => setIsOpen(!isOpen), + hide: () => setIsOpen(false) }; } diff --git a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx index 636217f9..e702e4cb 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx @@ -48,8 +48,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) { text={labelLibraryFilter(value)} onClick={strategyMenu.toggle} /> - {strategyMenu.isActive ? - + {Object.values(LibraryFilterStrategy).map( (enumValue, index) => { const strategy = enumValue as LibraryFilterStrategy; @@ -63,7 +62,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) { disabled={isStrategyDisabled(strategy)} />); })} - : null} +
); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx index 8c4e5a21..f66e8c10 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/EditorConstituenta.tsx @@ -1,5 +1,6 @@ 'use client'; +import { AnimatePresence } from 'framer-motion'; import { Dispatch, SetStateAction, useMemo, useState } from 'react'; import { useRSForm } from '@/context/RSFormContext'; @@ -149,6 +150,7 @@ function EditorConstituenta({ onEditTerm={onEditTerm} onRenameCst={onRenameCst} /> + {(showList && windowSize.width && windowSize.width >= SIDELIST_HIDE_THRESHOLD) ? : null} + ); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx index edbfc05a..bcd475c0 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/EditorRSExpression.tsx @@ -1,6 +1,7 @@ 'use client'; import { ReactCodeMirrorRef } from '@uiw/react-codemirror'; +import { AnimatePresence } from 'framer-motion'; import { useCallback, useLayoutEffect, useRef, useState } from 'react'; import { BiListUl } from 'react-icons/bi'; import { FaRegKeyboard } from 'react-icons/fa6'; @@ -31,7 +32,7 @@ interface EditorRSExpressionProps { value: string label: string placeholder?: string - + disabled?: boolean toggleReset?: boolean showList: boolean @@ -134,12 +135,14 @@ function EditorRSExpression({ } return (<> + {showAST ? setShowAST(false)} /> : null} +
@@ -179,18 +182,18 @@ function EditorRSExpression({ {...restProps} /> - {showControls ? - : null} + /> - {(parseData && parseData.errors.length > 0) ? 0} data={parseData} disabled={disabled} onShowError={onShowError} - />: null} + />
); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/ParsingResult.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/ParsingResult.tsx index 42e52420..ab365fff 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/ParsingResult.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/ParsingResult.tsx @@ -1,23 +1,39 @@ 'use client'; +import clsx from 'clsx'; +import { motion } from 'framer-motion'; + import { IExpressionParse, IRSErrorDescription } from '@/models/rslang'; +import { animateRSControl } from '@/utils/animations'; import { describeRSError } from '@/utils/labels'; import { getRSErrorPrefix } from '@/utils/misc'; interface ParsingResultProps { - data: IExpressionParse + data: IExpressionParse | undefined disabled?: boolean + isOpen: boolean onShowError: (error: IRSErrorDescription) => void } -function ParsingResult({ data, disabled, onShowError }: ParsingResultProps) { - const errorCount = data.errors.reduce((total, error) => (error.isCritical ? total + 1 : total), 0); - const warningsCount = data.errors.length - errorCount; +function ParsingResult({ isOpen, data, disabled, onShowError }: ParsingResultProps) { + const errorCount = data ? data.errors.reduce((total, error) => (error.isCritical ? total + 1 : total), 0) : 0; + const warningsCount = data ? data.errors.length - errorCount : 0; return ( -
+

Ошибок: {errorCount} | Предупреждений: {warningsCount}

- {data.errors.map( + {data?.errors.map( (error, index) => { return (

{` ${describeRSError(error)}`}

); })} -
); + ); } export default ParsingResult; \ No newline at end of file diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/RSEditControls.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/RSEditControls.tsx index 990e3500..5cf454a8 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/RSEditControls.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSExpression/RSEditControls.tsx @@ -1,4 +1,7 @@ +import { motion } from 'framer-motion'; + import { TokenID } from '@/models/rslang'; +import { animateRSControl } from '@/utils/animations'; import { prefixes } from '@/utils/constants'; import RSLocalButton from './RSLocalButton'; @@ -79,13 +82,19 @@ const SECONDARY_THIRD_ROW = [ ]; interface RSEditorControlsProps { - onEdit: (id: TokenID, key?: string) => void + isOpen: boolean disabled?: boolean + onEdit: (id: TokenID, key?: string) => void } -function RSEditorControls({ onEdit, disabled }: RSEditorControlsProps) { +function RSEditorControls({ isOpen, disabled, onEdit }: RSEditorControlsProps) { return ( -
+ {MAIN_FIRST_ROW.map( (token) => )} -
); + ); } export default RSEditorControls; \ No newline at end of file diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSListToolbar.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSListToolbar.tsx index 4a5f3a09..726c4a4c 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSListToolbar.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSList/RSListToolbar.tsx @@ -70,8 +70,7 @@ function RSListToolbar({ disabled={!isMutable} onClick={insertMenu.toggle} /> - {insertMenu.isActive ? - + {(Object.values(CstType)).map( (typeStr) => )} - : null} + + {showParamsDialog ? setShowParamsDialog(false)} initial={filterParams} onConfirm={handleChangeParams} /> : null} + {loading ? : null} {error ? : null} - - {showUpload ? - setShowUpload(false)} - /> : null} - {showClone ? - setShowClone(false)} - /> : null} - {showCreateCst ? - setShowCreateCst(false)} - onCreate={handleCreateCst} - schema={schema!} - initial={createInitialData} - /> : null} - {showRenameCst ? - setShowRenameCst(false)} - onRename={handleRenameCst} - initial={renameInitialData!} - /> : null} - {showDeleteCst ? - setShowDeleteCst(false)} - onDelete={handleDeleteCst} - selected={toBeDeleted} - /> : null} - {showEditTerm ? - setShowEditTerm(false)} - onSave={handleSaveWordforms} - target={activeCst!} - /> : null} - {showTemplates ? - setShowTemplates(false)} - insertAfter={insertCstID} - onCreate={handleCreateCst} - /> : null} + + {showUpload ? + setShowUpload(false)} + /> : null} + {showClone ? + setShowClone(false)} + /> : null} + {showCreateCst ? + setShowCreateCst(false)} + onCreate={handleCreateCst} + schema={schema!} + initial={createInitialData} + /> : null} + {showRenameCst ? + setShowRenameCst(false)} + onRename={handleRenameCst} + initial={renameInitialData!} + /> : null} + {showDeleteCst ? + setShowDeleteCst(false)} + onDelete={handleDeleteCst} + selected={toBeDeleted} + /> : null} + {showEditTerm ? + setShowEditTerm(false)} + onSave={handleSaveWordforms} + target={activeCst!} + /> : null} + {showTemplates ? + setShowTemplates(false)} + insertAfter={insertCstID} + onCreate={handleCreateCst} + /> : null} + {(schema && !loading) ? - {schemaMenu.isActive ? - - + } + icon={} onClick={(!isOwned && user && isClaimable) ? handleClaimOwner : undefined} /> } onClick={handleCreateNew} /> - : null} +
@@ -153,8 +151,7 @@ function RSTabsMenu({ icon={} onClick={editMenu.toggle} /> - {editMenu.isActive ? - + } onClick={handleTemplates} /> - : null} +
@@ -182,8 +179,7 @@ function RSTabsMenu({ } onClick={accessMenu.toggle} /> - {accessMenu.isActive ? - + } onClick={() => handleChangeMode(UserAccessMode.ADMIN)} /> - : null} +
); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsSearch.tsx b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsSearch.tsx index e6234709..55514633 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsSearch.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsSearch.tsx @@ -90,8 +90,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }: text={labelCstMathchMode(filterMatch)} onClick={matchModeMenu.toggle} /> - {matchModeMenu.isActive ? - + {Object.values(CstMatchMode).filter(value => !isNaN(Number(value))).map( (value, index) => { const matchMode = value as CstMatchMode; @@ -103,7 +102,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:

{labelCstMathchMode(matchMode)}: {describeCstMathchMode(matchMode)}

); })} -
: null} +
@@ -114,8 +113,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }: text={labelCstSource(filterSource)} onClick={sourceMenu.toggle} /> - {sourceMenu.isActive ? - + {Object.values(DependencyMode).filter(value => !isNaN(Number(value))).map( (value, index) => { const source = value as DependencyMode; @@ -127,7 +125,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:

{labelCstSource(source)}: {describeCstSource(source)}

); })} -
: null} +
); } diff --git a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ViewConstituents.tsx b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ViewConstituents.tsx index a44bf5b5..012ae98d 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ViewConstituents.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ViewConstituents.tsx @@ -1,9 +1,11 @@ 'use client'; +import { motion } from 'framer-motion'; import { useMemo, useState } from 'react'; import { useConceptTheme } from '@/context/ThemeContext'; import { IConstituenta, IRSForm } from '@/models/rsform'; +import { animateSideView } from '@/utils/animations'; import ConstituentsSearch from './ConstituentsSearch'; import ConstituentsTable from './ConstituentsTable'; @@ -36,7 +38,12 @@ function ViewConstituents({ expression, baseHeight, schema, activeID, onOpenEdit }, [noNavigation, baseHeight]); return ( -
+ -
); + ); } export default ViewConstituents; \ No newline at end of file diff --git a/rsconcept/frontend/src/pages/UserProfilePage/UserTabs.tsx b/rsconcept/frontend/src/pages/UserProfilePage/UserTabs.tsx index c95cd3dd..2f4cba72 100644 --- a/rsconcept/frontend/src/pages/UserProfilePage/UserTabs.tsx +++ b/rsconcept/frontend/src/pages/UserProfilePage/UserTabs.tsx @@ -1,5 +1,6 @@ 'use client'; +import { AnimatePresence } from 'framer-motion'; import { useMemo, useState } from 'react'; import { FiBell, FiBellOff } from 'react-icons/fi'; @@ -50,11 +51,12 @@ function UserTabs() { + {(subscriptions.length > 0 && showSubs) ? -
-

Отслеживаемые схемы

- -
: null} + : null} +
: null} ); } diff --git a/rsconcept/frontend/src/pages/UserProfilePage/ViewSubscriptions.tsx b/rsconcept/frontend/src/pages/UserProfilePage/ViewSubscriptions.tsx index 695e6dcb..a57cbd18 100644 --- a/rsconcept/frontend/src/pages/UserProfilePage/ViewSubscriptions.tsx +++ b/rsconcept/frontend/src/pages/UserProfilePage/ViewSubscriptions.tsx @@ -1,11 +1,13 @@ 'use client'; +import { motion } from 'framer-motion'; import { useMemo } from 'react'; import { useIntl } from 'react-intl'; import DataTable, { createColumnHelper } from '@/components/DataTable'; import { useConceptNavigation } from '@/context/NagivationContext'; import { ILibraryItem } from '@/models/library'; +import { animateSideView } from '@/utils/animations'; interface ViewSubscriptionsProps { items: ILibraryItem[] @@ -52,25 +54,32 @@ function ViewSubscriptions({items}: ViewSubscriptionsProps) { ], [intl]); return ( - +

Отслеживаемые схемы

+ - Отслеживаемые схемы отсутствуют - - } + enableSorting + initialSorting={{ + id: 'time_update', + desc: true + }} + noDataComponent={ +
+ Отслеживаемые схемы отсутствуют +
+ } - onRowClicked={openRSForm} - />); + onRowClicked={openRSForm} + /> + ); } export default ViewSubscriptions; \ No newline at end of file diff --git a/rsconcept/frontend/src/utils/animations.ts b/rsconcept/frontend/src/utils/animations.ts new file mode 100644 index 00000000..3a97a3fe --- /dev/null +++ b/rsconcept/frontend/src/utils/animations.ts @@ -0,0 +1,163 @@ +/** + * Module: animations parameters. + */ + +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 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 animateRSControl: Variants = { + open: { + clipPath: 'inset(0% 0% 0% 0%)', + height: 'max-content', + transition: { + type: 'spring', + bounce: 0, + duration: 0.4 + } + }, + closed: { + clipPath: 'inset(0% 0% 100% 0%)', + height: 0, + transition: { + type: 'spring', + bounce: 0, + duration: 0.3 + } + } +}; + +export const animateSideView = { + initial: { + clipPath: 'inset(0% 100% 0% 0%)', + }, + animate: { + clipPath: 'inset(0% 0% 0% 0%)', + transition: { + type: 'spring', + bounce: 0, + duration: 1 + } + }, + exit: { + clipPath: 'inset(0% 100% 0% 0%)', + transition: { + type: 'spring', + bounce: 0, + duration: 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 + } + } +}; \ No newline at end of file