Compare commits

..

18 Commits

Author SHA1 Message Date
Ivan
6e1d99122e R: Remove react-scan from prod
Some checks failed
Frontend CI / build (22.x) (push) Has been cancelled
2025-02-22 14:27:10 +03:00
Ivan
ad5a97b844 R: Remove redundant types and styles 2025-02-22 14:03:13 +03:00
Ivan
2569cc521c M: Fix semantic tag 2025-02-22 12:21:06 +03:00
Ivan
012ee27db6 R: Remove redundant type 2025-02-22 12:20:22 +03:00
Ivan
25f2768445 F: Fix tabIndex for modals 2025-02-22 11:22:34 +03:00
Ivan
ea0c4565bf B: Fix tailwind and other minor bug 2025-02-21 21:13:40 +03:00
Ivan
39006a70bc F: move to tailwind 4 pt1 2025-02-20 20:22:05 +03:00
Ivan
0694c45c08 npm update + fix name collision 2025-02-20 18:10:34 +03:00
Ivan
b6a888140a M: Remove modficiation tracking 2025-02-20 18:01:52 +03:00
Ivan
6e5e932af6 M: Change focus keybind 2025-02-20 17:17:45 +03:00
Ivan
d1e4edc326 M: Fix centering for editor 2025-02-20 17:07:54 +03:00
Ivan
44ea7f7944 F: Improve version selection 2025-02-20 16:39:37 +03:00
Ivan
32b34ff0ed F: Disable react-scan for production 2025-02-20 14:59:37 +03:00
Ivan
12ddc007ac F: Add react-scan to tooling and fix some rerenders 2025-02-20 14:45:12 +03:00
Ivan
45dbe16444 M: Add timestamp verification 2025-02-20 00:02:52 +03:00
Ivan
4b19fc57a2 Actualize TODOs 2025-02-19 23:48:11 +03:00
Ivan
3e4d67c9a0 R: Remove all default exports 2025-02-19 23:29:45 +03:00
Ivan
13dd7638e4 R: Distinguish undefined semantics vs null 2025-02-19 22:32:50 +03:00
390 changed files with 3747 additions and 3621 deletions

View File

@ -40,6 +40,7 @@ This readme file is used mostly to document project dependencies and conventions
- react-tooltip
- react-zoom-pan-pinch
- react-hook-form
- react-scan
- reactflow
- js-file-download
- use-debounce
@ -61,8 +62,6 @@ This readme file is used mostly to document project dependencies and conventions
<summary>npm install -D</summary>
<pre>
- tailwindcss
- postcss
- autoprefixer
- eslint-plugin-import
- eslint-plugin-react-compiler
- eslint-plugin-simple-import-sort
@ -72,6 +71,7 @@ This readme file is used mostly to document project dependencies and conventions
- vite
- jest
- ts-jest
- @vitejs/plugin-react
- @types/jest
- @lezer/generator
- @playwright/test

View File

@ -2,16 +2,16 @@
For more specific TODOs see comments in code
[Bugs - PENDING]
- Tab index still selecting background elements when modal is active
-
[Functionality - PENDING]
- Landing page
- Design first user experience
- Demo sandbox for anonymous users
- User profile: Settings + settings server persistency
User profile:
- Settings + settings server persistency
- Profile pictures
- Integrate socials and feedback
- Custom LibraryItem lists
- Custom user filters and sharing filters
@ -31,6 +31,7 @@ For more specific TODOs see comments in code
[Functionality - CANCELED]
- User notifications on edit - consider spam prevention and change aggregation
- Integrate socials and feedback
- Content based search in Library
- Private projects. Consider cooperative editing
- OSS: synthesis table: auto substitution for diamond synthesis
@ -38,10 +39,7 @@ For more specific TODOs see comments in code
[Tech]
- duplicate syntax parsing and type info calculations to client. Consider moving backend to Nodejs or embedding c++ lib
- add debounce to some search fields. Consider pagination and dynamic loading
- move autopep8 and isort settings from vscode settings to pyproject.toml
- Testing: frontend react components, testplane / playwright?
- Testing E2E playwright
[Deployment]
@ -70,11 +68,3 @@ https://drf-standardized-errors.readthedocs.io/en/latest/error_response.html
https://stackoverflow.com/questions/28838170/multilevel-json-diff-in-python
- Documentation platform. Consider diplodoc
- radix-ui
- shadcn-ui
- Zod
- react-query
- react-hook-form

View File

@ -11,15 +11,7 @@ export default [
...typescriptPlugin.configs.recommendedTypeChecked,
...typescriptPlugin.configs.stylisticTypeChecked,
{
ignores: [
'**/parser.ts',
'**/node_modules/**',
'**/public/**',
'**/dist/**',
'eslint.config.js',
'tailwind.config.js',
'postcss.config.js'
]
ignores: ['**/parser.ts', '**/node_modules/**', '**/public/**', '**/dist/**', 'eslint.config.js']
},
{
languageOptions: {
@ -43,6 +35,12 @@ export default [
settings: { react: { version: 'detect' } },
rules: {
'react-compiler/react-compiler': 'error',
'@typescript-eslint/consistent-type-imports': [
'warn',
{
fixStyle: 'inline-type-imports'
}
],
'@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'with-single-extends' }],
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/no-inferrable-types': 'off',

File diff suppressed because it is too large Load Diff

View File

@ -16,13 +16,14 @@
"@dagrejs/dagre": "^1.1.4",
"@hookform/resolvers": "^4.1.0",
"@lezer/lr": "^1.4.2",
"@tanstack/react-query": "^5.66.7",
"@tanstack/react-query-devtools": "^5.66.7",
"@tanstack/react-query": "^5.66.8",
"@tanstack/react-query-devtools": "^5.66.8",
"@tanstack/react-table": "^8.21.2",
"@uiw/codemirror-themes": "^4.23.8",
"@uiw/react-codemirror": "^4.23.8",
"axios": "^1.7.9",
"clsx": "^2.1.1",
"global": "^4.4.0",
"html-to-image": "^1.11.13",
"js-file-download": "^0.4.12",
"qrcode.react": "^4.2.0",
@ -30,9 +31,10 @@
"react-dom": "^19.0.0",
"react-error-boundary": "^5.0.0",
"react-hook-form": "^7.54.2",
"react-icons": "^5.4.0",
"react-icons": "^5.5.0",
"react-intl": "^7.1.6",
"react-router": "^7.2.0",
"react-scan": "^0.1.3",
"react-select": "^5.10.0",
"react-tabs": "^6.1.0",
"react-toastify": "^11.0.3",
@ -46,6 +48,7 @@
"devDependencies": {
"@lezer/generator": "^1.7.2",
"@playwright/test": "^1.50.1",
"@tailwindcss/vite": "^4.0.7",
"@types/jest": "^29.5.14",
"@types/node": "^22.13.4",
"@types/react": "^19.0.10",
@ -53,22 +56,20 @@
"@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"babel-plugin-react-compiler": "^19.0.0-beta-30d8a17-20250209",
"babel-plugin-react-compiler": "^19.0.0-beta-21e868a-20250216",
"eslint": "^9.20.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-compiler": "^19.0.0-beta-30d8a17-20250209",
"eslint-plugin-react-compiler": "^19.0.0-beta-21e868a-20250216",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.15.0",
"globals": "^16.0.0",
"jest": "^29.7.0",
"postcss": "^8.5.2",
"tailwindcss": "^3.4.17",
"tailwindcss": "^4.0.7",
"ts-jest": "^29.2.5",
"typescript": "^5.7.3",
"typescript-eslint": "^8.24.1",
"vite": "^6.1.0"
"vite": "^6.1.1"
},
"overrides": {
"react": "^19.0.0"

View File

@ -1,8 +0,0 @@
export default {
plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
'tailwindcss': {},
'autoprefixer': {}
}
};

View File

@ -3,7 +3,7 @@ import { Outlet } from 'react-router';
import { ModalLoader } from '@/components/Modal';
import { useAppLayoutStore, useMainHeight, useViewportHeight } from '@/stores/appLayout';
import { globals } from '@/utils/constants';
import { useDialogsStore } from '@/stores/dialogs';
import { NavigationState } from './Navigation/NavigationContext';
import { Footer } from './Footer';
@ -14,13 +14,14 @@ import { GlobalTooltips } from './GlobalTooltips';
import { MutationErrors } from './MutationErrors';
import { Navigation } from './Navigation';
function ApplicationLayout() {
export function ApplicationLayout() {
const mainHeight = useMainHeight();
const viewportHeight = useViewportHeight();
const showScroll = useAppLayoutStore(state => !state.noScroll);
const noNavigationAnimation = useAppLayoutStore(state => state.noNavigationAnimation);
const noNavigation = useAppLayoutStore(state => state.noNavigation);
const noFooter = useAppLayoutStore(state => state.noFooter);
const activeDialog = useDialogsStore(state => state.active);
return (
<NavigationState>
@ -41,11 +42,9 @@ function ApplicationLayout() {
<Navigation />
<div
id={globals.main_scroll}
className='overflow-x-auto max-w-[100vw]'
style={{
maxHeight: viewportHeight
}}
style={{ maxHeight: viewportHeight }}
inert={activeDialog !== null}
>
<main className='cc-scroll-y' style={{ overflowY: showScroll ? 'scroll' : 'auto', minHeight: mainHeight }}>
<GlobalLoader />
@ -58,5 +57,3 @@ function ApplicationLayout() {
</NavigationState>
);
}
export default ApplicationLayout;

View File

@ -8,7 +8,7 @@ export function ErrorFallback() {
const router = useNavigate();
function resetErrorBoundary() {
Promise.resolve(router('/')).catch(console.log);
Promise.resolve(router('/')).catch(console.error);
}
return (
<div className='flex flex-col gap-3 my-3 items-center antialiased' role='alert'>

View File

@ -4,34 +4,120 @@ import React from 'react';
import { DialogType, useDialogsStore } from '@/stores/dialogs';
const DlgChangeInputSchema = React.lazy(() => import('@/features/oss/dialogs/DlgChangeInputSchema'));
const DlgChangeLocation = React.lazy(() => import('@/features/library/dialogs/DlgChangeLocation'));
const DlgCloneLibraryItem = React.lazy(() => import('@/features/library/dialogs/DlgCloneLibraryItem'));
const DlgCreateCst = React.lazy(() => import('@/features/rsform/dialogs/DlgCreateCst'));
const DlgCreateOperation = React.lazy(() => import('@/features/oss/dialogs/DlgCreateOperation'));
const DlgCreateVersion = React.lazy(() => import('@/features/library/dialogs/DlgCreateVersion'));
const DlgCstTemplate = React.lazy(() => import('@/features/rsform/dialogs/DlgCstTemplate'));
const DlgDeleteCst = React.lazy(() => import('@/features/rsform/dialogs/DlgDeleteCst'));
const DlgDeleteOperation = React.lazy(() => import('@/features/oss/dialogs/DlgDeleteOperation'));
const DlgEditEditors = React.lazy(() => import('@/features/library/dialogs/DlgEditEditors'));
const DlgEditOperation = React.lazy(() => import('@/features/oss/dialogs/DlgEditOperation'));
const DlgEditReference = React.lazy(() => import('@/features/rsform/dialogs/DlgEditReference'));
const DlgEditVersions = React.lazy(() => import('@/features/library/dialogs/DlgEditVersions'));
const DlgEditWordForms = React.lazy(() => import('@/features/rsform/dialogs/DlgEditWordForms'));
const DlgGraphParams = React.lazy(() => import('@/features/rsform/dialogs/DlgGraphParams'));
const DlgInlineSynthesis = React.lazy(() => import('@/features/rsform/dialogs/DlgInlineSynthesis'));
const DlgRelocateConstituents = React.lazy(() => import('@/features/oss/dialogs/DlgRelocateConstituents'));
const DlgRenameCst = React.lazy(() => import('@/features/rsform/dialogs/DlgRenameCst'));
const DlgShowAST = React.lazy(() => import('@/features/rsform/dialogs/DlgShowAST'));
const DlgShowQR = React.lazy(() => import('@/features/rsform/dialogs/DlgShowQR'));
const DlgShowTypeGraph = React.lazy(() => import('@/features/rsform/dialogs/DlgShowTypeGraph'));
const DlgSubstituteCst = React.lazy(() => import('@/features/rsform/dialogs/DlgSubstituteCst'));
const DlgUploadRSForm = React.lazy(() => import('@/features/rsform/dialogs/DlgUploadRSForm'));
const DlgChangeInputSchema = React.lazy(() =>
import('@/features/oss/dialogs/DlgChangeInputSchema').then(module => ({ default: module.DlgChangeInputSchema }))
);
const DlgChangeLocation = React.lazy(() =>
import('@/features/library/dialogs/DlgChangeLocation').then(module => ({
default: module.DlgChangeLocation
}))
);
const DlgCloneLibraryItem = React.lazy(() =>
import('@/features/library/dialogs/DlgCloneLibraryItem').then(module => ({
default: module.DlgCloneLibraryItem
}))
);
const DlgCreateCst = React.lazy(() =>
import('@/features/rsform/dialogs/DlgCreateCst').then(module => ({ default: module.DlgCreateCst }))
);
const DlgCreateOperation = React.lazy(() =>
import('@/features/oss/dialogs/DlgCreateOperation').then(module => ({
default: module.DlgCreateOperation
}))
);
const DlgCreateVersion = React.lazy(() =>
import('@/features/library/dialogs/DlgCreateVersion').then(module => ({
default: module.DlgCreateVersion
}))
);
const DlgCstTemplate = React.lazy(() =>
import('@/features/rsform/dialogs/DlgCstTemplate').then(module => ({
default: module.DlgCstTemplate
}))
);
const DlgDeleteCst = React.lazy(() =>
import('@/features/rsform/dialogs/DlgDeleteCst').then(module => ({
default: module.DlgDeleteCst
}))
);
const DlgDeleteOperation = React.lazy(() =>
import('@/features/oss/dialogs/DlgDeleteOperation').then(module => ({
default: module.DlgDeleteOperation
}))
);
const DlgEditEditors = React.lazy(() =>
import('@/features/library/dialogs/DlgEditEditors').then(module => ({
default: module.DlgEditEditors
}))
);
const DlgEditOperation = React.lazy(() =>
import('@/features/oss/dialogs/DlgEditOperation').then(module => ({
default: module.DlgEditOperation
}))
);
const DlgEditReference = React.lazy(() =>
import('@/features/rsform/dialogs/DlgEditReference').then(module => ({
default: module.DlgEditReference
}))
);
const DlgEditVersions = React.lazy(() =>
import('@/features/library/dialogs/DlgEditVersions').then(module => ({
default: module.DlgEditVersions
}))
);
const DlgEditWordForms = React.lazy(() =>
import('@/features/rsform/dialogs/DlgEditWordForms').then(module => ({
default: module.DlgEditWordForms
}))
);
const DlgInlineSynthesis = React.lazy(() =>
import('@/features/rsform/dialogs/DlgInlineSynthesis').then(module => ({
default: module.DlgInlineSynthesis
}))
);
const DlgRelocateConstituents = React.lazy(() =>
import('@/features/oss/dialogs/DlgRelocateConstituents').then(module => ({
default: module.DlgRelocateConstituents
}))
);
const DlgRenameCst = React.lazy(() =>
import('@/features/rsform/dialogs/DlgRenameCst').then(module => ({
default: module.DlgRenameCst
}))
);
const DlgShowAST = React.lazy(() =>
import('@/features/rsform/dialogs/DlgShowAST').then(module => ({
default: module.DlgShowAST
}))
);
const DlgShowQR = React.lazy(() =>
import('@/features/rsform/dialogs/DlgShowQR').then(module => ({
default: module.DlgShowQR
}))
);
const DlgShowTypeGraph = React.lazy(() =>
import('@/features/rsform/dialogs/DlgShowTypeGraph').then(module => ({
default: module.DlgShowTypeGraph
}))
);
const DlgSubstituteCst = React.lazy(() =>
import('@/features/rsform/dialogs/DlgSubstituteCst').then(module => ({
default: module.DlgSubstituteCst
}))
);
const DlgUploadRSForm = React.lazy(() =>
import('@/features/rsform/dialogs/DlgUploadRSForm').then(module => ({
default: module.DlgUploadRSForm
}))
);
const DlgGraphParams = React.lazy(() =>
import('@/features/rsform/dialogs/DlgGraphParams').then(module => ({ default: module.DlgGraphParams }))
);
export const GlobalDialogs = () => {
const active = useDialogsStore(state => state.active);
if (active === undefined) {
if (active === null) {
return null;
}
switch (active) {

View File

@ -7,7 +7,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { queryClient } from '@/backend/queryClient';
// prettier-ignore
function GlobalProviders({ children }: React.PropsWithChildren) {
export function GlobalProviders({ children }: React.PropsWithChildren) {
return (
<IntlProvider locale='ru' defaultLocale='ru'>
<QueryClientProvider client={queryClient}>
@ -18,5 +18,3 @@ function GlobalProviders({ children }: React.PropsWithChildren) {
</QueryClientProvider>
</IntlProvider>);
}
export default GlobalProviders;

View File

@ -1,32 +1,59 @@
'use client';
import InfoConstituenta from '@/features/rsform/components/InfoConstituenta';
import React, { Suspense } from 'react';
import { Tooltip } from '@/components/Container';
import { Loader } from '@/components/Loader';
import { useTooltipsStore } from '@/stores/tooltips';
import { globals } from '@/utils/constants';
import { globalIDs } from '@/utils/constants';
const InfoConstituenta = React.lazy(() =>
import('@/features/rsform/components/InfoConstituenta').then(module => ({ default: module.InfoConstituenta }))
);
const InfoOperation = React.lazy(() =>
import('@/features/oss/components/InfoOperation').then(module => ({ default: module.InfoOperation }))
);
export const GlobalTooltips = () => {
const hoverCst = useTooltipsStore(state => state.activeCst);
const hoverOperation = useTooltipsStore(state => state.activeOperation);
return (
<>
<Tooltip
float
id={globals.tooltip}
id={globalIDs.tooltip}
layer='z-topmost'
place='right-start'
className='mt-8 max-w-[20rem] break-words'
/>
<Tooltip
float
id={globals.value_tooltip}
id={globalIDs.value_tooltip}
layer='z-topmost'
className='max-w-[calc(min(40rem,100dvw-2rem))] text-justify'
/>
<Tooltip clickable id={globals.constituenta_tooltip} layer='z-modalTooltip' className='max-w-[30rem]'>
{hoverCst ? <InfoConstituenta data={hoverCst} onClick={event => event.stopPropagation()} /> : <Loader />}
<Tooltip
clickable
id={globalIDs.constituenta_tooltip}
layer='z-modalTooltip'
className='max-w-[30rem]'
hidden={!hoverCst}
>
<Suspense fallback={<Loader />}>
{hoverCst ? <InfoConstituenta data={hoverCst} onClick={event => event.stopPropagation()} /> : null}
</Suspense>
</Tooltip>
<Tooltip
id={globalIDs.operation_tooltip}
layer='z-modalTooltip'
className='max-w-[35rem] max-h-[40rem] dense'
hidden={!hoverOperation}
>
<Suspense fallback={<Loader />}>
{hoverOperation ? <InfoOperation operation={hoverOperation} /> : null}
</Suspense>
</Tooltip>
</>
);

View File

@ -3,7 +3,7 @@ import clsx from 'clsx';
import { useMutationErrors } from '@/backend/useMutationErrors';
import { Button } from '@/components/Control';
import { DescribeError } from '@/components/InfoError';
import useEscapeKey from '@/hooks/useEscapeKey';
import { useEscapeKey } from '@/hooks/useEscapeKey';
import { useDialogsStore } from '@/stores/dialogs';
export function MutationErrors() {

View File

@ -1,7 +1,7 @@
import useWindowSize from '@/hooks/useWindowSize';
import { useWindowSize } from '@/hooks/useWindowSize';
import { usePreferencesStore } from '@/stores/preferences';
function Logo() {
export function Logo() {
const darkMode = usePreferencesStore(state => state.darkMode);
const size = useWindowSize();
@ -13,4 +13,3 @@ function Logo() {
/>
);
}
export default Logo;

View File

@ -1,28 +1,28 @@
import clsx from 'clsx';
import { IconLibrary2, IconManuals, IconNewItem2 } from '@/components/Icons';
import { CProps } from '@/components/props';
import useWindowSize from '@/hooks/useWindowSize';
import { useWindowSize } from '@/hooks/useWindowSize';
import { useAppLayoutStore } from '@/stores/appLayout';
import { PARAMETER } from '@/utils/constants';
import { urls } from '../urls';
import Logo from './Logo';
import NavigationButton from './NavigationButton';
import { Logo } from './Logo';
import { NavigationButton } from './NavigationButton';
import { useConceptNavigation } from './NavigationContext';
import ToggleNavigation from './ToggleNavigation';
import UserMenu from './UserMenu';
import { ToggleNavigation } from './ToggleNavigation';
import { UserMenu } from './UserMenu';
export function Navigation() {
const router = useConceptNavigation();
const size = useWindowSize();
const noNavigationAnimation = useAppLayoutStore(state => state.noNavigationAnimation);
const navigateHome = (event: CProps.EventMouse) => router.push(urls.home, event.ctrlKey || event.metaKey);
const navigateLibrary = (event: CProps.EventMouse) => router.push(urls.library, event.ctrlKey || event.metaKey);
const navigateHelp = (event: CProps.EventMouse) => router.push(urls.manuals, event.ctrlKey || event.metaKey);
const navigateCreateNew = (event: CProps.EventMouse) =>
const navigateHome = (event: React.MouseEvent<Element>) => router.push(urls.home, event.ctrlKey || event.metaKey);
const navigateLibrary = (event: React.MouseEvent<Element>) =>
router.push(urls.library, event.ctrlKey || event.metaKey);
const navigateHelp = (event: React.MouseEvent<Element>) => router.push(urls.manuals, event.ctrlKey || event.metaKey);
const navigateCreateNew = (event: React.MouseEvent<Element>) =>
router.push(urls.create_schema, event.ctrlKey || event.metaKey);
return (

View File

@ -1,15 +1,15 @@
import clsx from 'clsx';
import { CProps } from '@/components/props';
import { globals } from '@/utils/constants';
import { type Styling, type Titled } from '@/components/props';
import { globalIDs } from '@/utils/constants';
interface NavigationButtonProps extends CProps.Titled, CProps.Styling {
interface NavigationButtonProps extends Titled, Styling {
text?: string;
icon: React.ReactNode;
onClick?: (event: CProps.EventMouse) => void;
onClick?: (event: React.MouseEvent<Element>) => void;
}
function NavigationButton({
export function NavigationButton({
icon,
title,
className,
@ -23,14 +23,15 @@ function NavigationButton({
<button
type='button'
tabIndex={-1}
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined}
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml}
data-tooltip-content={title}
data-tooltip-hidden={hideTitle}
onClick={onClick}
className={clsx(
'mr-1 h-full', //
'mr-1 h-full',
'flex items-center gap-1',
'cursor-pointer',
'clr-btn-nav cc-animate-color duration-500',
'rounded-xl',
'font-controls whitespace-nowrap',
@ -47,5 +48,3 @@ function NavigationButton({
</button>
);
}
export default NavigationButton;

View File

@ -1,9 +1,8 @@
'use client';
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router';
import { createContext, useContext, useEffect, useState } from 'react';
import { useNavigate } from 'react-router';
import { globals } from '@/utils/constants';
import { contextOutsideScope } from '@/utils/labels';
interface INavigationContext {
@ -29,68 +28,48 @@ export const useConceptNavigation = () => {
export const NavigationState = ({ children }: React.PropsWithChildren) => {
const router = useNavigate();
const { pathname } = useLocation();
const [isBlocked, setIsBlocked] = useState(false);
const validate = useCallback(() => {
function validate() {
return !isBlocked || confirm('Изменения не сохранены. Вы уверены что хотите совершить переход?');
}, [isBlocked]);
const canBack = useCallback(() => !!window.history && window.history?.length !== 0, []);
const scrollTop = useCallback(() => {
window.scrollTo(0, 0);
const mainScroll = document.getElementById(globals.main_scroll);
if (mainScroll) {
mainScroll.scroll(0, 0);
}
}, []);
const push = useCallback(
(path: string, newTab?: boolean) => {
function canBack() {
return !!window.history && window.history?.length !== 0;
}
function push(path: string, newTab?: boolean) {
if (newTab) {
window.open(`${path}`, '_blank');
return;
}
if (validate()) {
scrollTop();
Promise.resolve(router(path, { viewTransition: true })).catch(console.log);
Promise.resolve(router(path, { viewTransition: true })).catch(console.error);
setIsBlocked(false);
}
},
[router, validate, scrollTop]
);
}
const replace = useCallback(
(path: string) => {
function replace(path: string) {
if (validate()) {
scrollTop();
Promise.resolve(router(path, { replace: true, viewTransition: true })).catch(console.log);
Promise.resolve(router(path, { replace: true, viewTransition: true })).catch(console.error);
setIsBlocked(false);
}
},
[router, validate, scrollTop]
);
}
const back = useCallback(() => {
function back() {
if (validate()) {
scrollTop();
Promise.resolve(router(-1)).catch(console.log);
Promise.resolve(router(-1)).catch(console.error);
setIsBlocked(false);
}
}, [router, validate, scrollTop]);
}
const forward = useCallback(() => {
function forward() {
if (validate()) {
scrollTop();
Promise.resolve(router(1)).catch(console.log);
Promise.resolve(router(1)).catch(console.error);
setIsBlocked(false);
}
}, [router, validate, scrollTop]);
useEffect(() => {
scrollTop();
}, [pathname, scrollTop]);
}
return (
<NavigationContext

View File

@ -3,9 +3,9 @@ import clsx from 'clsx';
import { IconDarkTheme, IconLightTheme, IconPin, IconUnpin } from '@/components/Icons';
import { useAppLayoutStore } from '@/stores/appLayout';
import { usePreferencesStore } from '@/stores/preferences';
import { globals, PARAMETER } from '@/utils/constants';
import { globalIDs, PARAMETER } from '@/utils/constants';
function ToggleNavigation() {
export function ToggleNavigation() {
const darkMode = usePreferencesStore(state => state.darkMode);
const toggleDarkMode = usePreferencesStore(state => state.toggleDarkMode);
const noNavigation = useAppLayoutStore(state => state.noNavigation);
@ -34,7 +34,7 @@ function ToggleNavigation() {
type='button'
className='p-1'
onClick={toggleDarkMode}
data-tooltip-id={globals.tooltip}
data-tooltip-id={globalIDs.tooltip}
data-tooltip-content={darkMode ? 'Тема: Темная' : 'Тема: Светлая'}
>
{darkMode ? <IconDarkTheme size='0.75rem' /> : null}
@ -46,7 +46,7 @@ function ToggleNavigation() {
type='button'
className='p-1'
onClick={toggleNoNavigation}
data-tooltip-id={globals.tooltip}
data-tooltip-id={globalIDs.tooltip}
data-tooltip-content={noNavigationAnimation ? 'Показать навигацию' : 'Скрыть навигацию'}
>
{!noNavigationAnimation ? <IconPin size={iconSize} /> : null}
@ -55,5 +55,3 @@ function ToggleNavigation() {
</div>
);
}
export default ToggleNavigation;

View File

@ -3,14 +3,14 @@ import { useAuthSuspense } from '@/features/auth';
import { IconLogin, IconUser2 } from '@/components/Icons';
import { usePreferencesStore } from '@/stores/preferences';
import NavigationButton from './NavigationButton';
import { NavigationButton } from './NavigationButton';
interface UserButtonProps {
onLogin: () => void;
onClickUser: () => void;
}
function UserButton({ onLogin, onClickUser }: UserButtonProps) {
export function UserButton({ onLogin, onClickUser }: UserButtonProps) {
const { user, isAnonymous } = useAuthSuspense();
const adminMode = usePreferencesStore(state => state.adminMode);
if (isAnonymous) {
@ -32,5 +32,3 @@ function UserButton({ onLogin, onClickUser }: UserButtonProps) {
);
}
}
export default UserButton;

View File

@ -15,7 +15,6 @@ import {
IconRESTapi,
IconUser
} from '@/components/Icons';
import { CProps } from '@/components/props';
import { usePreferencesStore } from '@/stores/preferences';
import { urls } from '../urls';
@ -27,7 +26,7 @@ interface UserDropdownProps {
hideDropdown: () => void;
}
function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
export function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
const router = useConceptNavigation();
const { user } = useAuthSuspense();
const { logout } = useLogout();
@ -39,7 +38,7 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
const adminMode = usePreferencesStore(state => state.adminMode);
const toggleAdminMode = usePreferencesStore(state => state.toggleAdminMode);
function navigateProfile(event: CProps.EventMouse) {
function navigateProfile(event: React.MouseEvent<Element>) {
hideDropdown();
router.push(urls.profile, event.ctrlKey || event.metaKey);
}
@ -54,7 +53,7 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
void logout().then(() => router.push(urls.admin, true));
}
function gotoIcons(event: CProps.EventMouse) {
function gotoIcons(event: React.MouseEvent<Element>) {
hideDropdown();
router.push(urls.icons, event.ctrlKey || event.metaKey);
}
@ -64,7 +63,7 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
router.push(urls.rest_api, true);
}
function gotoDatabaseSchema(event: CProps.EventMouse) {
function gotoDatabaseSchema(event: React.MouseEvent<Element>) {
hideDropdown();
router.push(urls.database_schema, event.ctrlKey || event.metaKey);
}
@ -141,5 +140,3 @@ function UserDropdown({ isOpen, hideDropdown }: UserDropdownProps) {
</Dropdown>
);
}
export default UserDropdown;

View File

@ -6,10 +6,10 @@ import { Loader } from '@/components/Loader';
import { urls } from '../urls';
import { useConceptNavigation } from './NavigationContext';
import UserButton from './UserButton';
import UserDropdown from './UserDropdown';
import { UserButton } from './UserButton';
import { UserDropdown } from './UserDropdown';
function UserMenu() {
export function UserMenu() {
const router = useConceptNavigation();
const menu = useDropdown();
return (
@ -21,5 +21,3 @@ function UserMenu() {
</div>
);
}
export default UserMenu;

View File

@ -1,11 +1,11 @@
import { createBrowserRouter } from 'react-router';
import { prefetchAuth } from '@/features/auth/backend/useAuth';
import LoginPage from '@/features/auth/pages/LoginPage';
import HomePage from '@/features/home/HomePage';
import NotFoundPage from '@/features/home/NotFoundPage';
import { LoginPage } from '@/features/auth/pages/LoginPage';
import { HomePage } from '@/features/home/HomePage';
import { NotFoundPage } from '@/features/home/NotFoundPage';
import { prefetchLibrary } from '@/features/library/backend/useLibrary';
import CreateItemPage from '@/features/library/pages/CreateItemPage';
import { CreateItemPage } from '@/features/library/pages/CreateItemPage';
import { prefetchOSS } from '@/features/oss/backend/useOSS';
import { prefetchRSForm } from '@/features/rsform/backend/useRSForm';
import { prefetchProfile } from '@/features/users/backend/useProfile';
@ -13,7 +13,7 @@ import { prefetchUsers } from '@/features/users/backend/useUsers';
import { Loader } from '@/components/Loader';
import ApplicationLayout from './ApplicationLayout';
import { ApplicationLayout } from './ApplicationLayout';
import { ErrorFallback } from './ErrorFallback';
import { routes } from './urls';

View File

@ -5,8 +5,6 @@ import { RouterProvider } from 'react-router';
import { Router } from './Router';
function App() {
export function App() {
return <RouterProvider router={Router} />;
}
export default App;

View File

@ -2,8 +2,8 @@
* Module: generic API for backend REST communications using axios library.
*/
import { toast } from 'react-toastify';
import axios, { AxiosError, AxiosRequestConfig } from 'axios';
import { z, ZodError } from 'zod';
import axios, { type AxiosError, type AxiosRequestConfig } from 'axios';
import { type z, ZodError } from 'zod';
import { buildConstants } from '@/utils/buildConstants';
import { errorMsg } from '@/utils/labels';
@ -32,8 +32,6 @@ axiosInstance.interceptors.request.use(config => {
});
// ================ Data transfer types ================
export type DataCallback<ResponseData = undefined> = (data: ResponseData) => void;
export interface IFrontRequest<RequestData, ResponseData> {
data?: RequestData;
successMessage?: string | ((data: ResponseData) => string);

View File

@ -1,7 +1,7 @@
import { QueryClient } from '@tanstack/react-query';
import { ZodError } from 'zod';
import { type ZodError } from 'zod';
import { AxiosError } from './apiTransport';
import { type AxiosError } from './apiTransport';
import { DELAYS } from './configuration';
declare module '@tanstack/react-query' {

View File

@ -1,18 +1,15 @@
import { useState } from 'react';
import { useMutationState, useQueryClient } from '@tanstack/react-query';
import { useMutationState } from '@tanstack/react-query';
import { KEYS } from './configuration';
export const useMutationErrors = () => {
const queryClient = useQueryClient();
const [ignored, setIgnored] = useState<(Error | null)[]>([]);
const [ignored, setIgnored] = useState<Error[]>([]);
const mutationErrors = useMutationState({
filters: { mutationKey: [KEYS.global_mutation], status: 'error' },
select: mutation => mutation.state.error
select: mutation => mutation.state.error!
});
console.log(queryClient.getMutationCache().getAll());
function resetErrors() {
setIgnored(mutationErrors);
}

View File

@ -1,8 +1,8 @@
import clsx from 'clsx';
import { CProps } from '../props';
import { type Styling } from '../props';
interface DividerProps extends CProps.Styling {
interface DividerProps extends Styling {
/** Indicates whether the divider is vertical. */
vertical?: boolean;

View File

@ -1,12 +1,10 @@
import clsx from 'clsx';
import { CProps } from '../props';
/**
* `flex` column container.
* This component is useful for creating vertical layouts with flexbox.
*/
export function FlexColumn({ className, children, ...restProps }: CProps.Div) {
export function FlexColumn({ className, children, ...restProps }: React.ComponentProps<'div'>) {
return (
<div className={clsx('cc-column', className)} {...restProps}>
{children}

View File

@ -1,8 +1,8 @@
import clsx from 'clsx';
import { CProps } from '../props';
import { type Styling } from '../props';
interface OverlayProps extends CProps.Styling {
interface OverlayProps extends Styling {
/** Id of the overlay. */
id?: string;

View File

@ -1,8 +1,8 @@
'use client';
import { ReactNode } from 'react';
import { type ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { ITooltip, Tooltip as TooltipImpl } from 'react-tooltip';
import { type ITooltip, Tooltip as TooltipImpl } from 'react-tooltip';
import clsx from 'clsx';
import { usePreferencesStore } from '@/stores/preferences';

View File

@ -1,10 +1,10 @@
import clsx from 'clsx';
import { globals } from '@/utils/constants';
import { globalIDs } from '@/utils/constants';
import { CProps } from '../props';
import { type Button as ButtonStyle, type Control } from '../props';
interface ButtonProps extends CProps.Control, CProps.Colors, CProps.Button {
interface ButtonProps extends Control, ButtonStyle {
/** Icon to display first. */
icon?: React.ReactNode;
@ -32,7 +32,6 @@ export function Button({
disabled,
noBorder,
noOutline,
colors = 'clr-btn-default',
className,
...restProps
}: ButtonProps) {
@ -43,20 +42,19 @@ export function Button({
className={clsx(
'inline-flex gap-2 items-center justify-center',
'select-none disabled:cursor-auto',
'cc-animate-color',
'clr-btn-default cc-animate-color',
{
'border rounded': !noBorder,
'border rounded-sm': !noBorder,
'px-1': dense,
'px-3 py-1': !dense,
'cursor-progress': loading,
'cursor-pointer': !loading,
'outline-none': noOutline,
'outline-hidden': noOutline,
'clr-outline': !noOutline
},
className,
colors
className
)}
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined}
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml}
data-tooltip-content={title}
data-tooltip-hidden={hideTitle}

View File

@ -1,10 +1,10 @@
import clsx from 'clsx';
import { globals } from '@/utils/constants';
import { globalIDs } from '@/utils/constants';
import { CProps } from '../props';
import { type Button } from '../props';
interface MiniButtonProps extends CProps.Button {
interface MiniButtonProps extends Button {
/** Button type. */
type?: 'button' | 'submit';
@ -43,12 +43,12 @@ export function MiniButton({
'cursor-pointer disabled:cursor-auto',
{
'px-1 py-1': !noPadding,
'outline-none': noHover,
'outline-hidden': noHover,
'clr-hover': !noHover
},
className
)}
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined}
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml}
data-tooltip-content={title}
data-tooltip-hidden={hideTitle}

View File

@ -1,19 +1,16 @@
import clsx from 'clsx';
import { globals } from '@/utils/constants';
import { globalIDs } from '@/utils/constants';
import { CProps } from '../props';
import { type Button } from '../props';
interface SelectorButtonProps extends CProps.Button {
interface SelectorButtonProps extends Button {
/** Text to display in the button. */
text?: string;
/** Icon to display in the button. */
icon?: React.ReactNode;
/** Classnames for the colors of the button. */
colors?: string;
/** Indicates if button background should be transparent. */
transparent?: boolean;
}
@ -26,7 +23,6 @@ export function SelectorButton({
icon,
title,
titleHtml,
colors = 'clr-btn-default',
className,
transparent,
hideTitle,
@ -44,12 +40,11 @@ export function SelectorButton({
'cc-animate-color',
{
'clr-hover': transparent,
'border': !transparent
'clr-btn-default border': !transparent
},
className,
!transparent && colors
className
)}
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined}
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml}
data-tooltip-content={title}
data-tooltip-hidden={hideTitle}

View File

@ -1,8 +1,8 @@
import clsx from 'clsx';
import { CProps } from '../props';
import { type Button } from '../props';
interface SubmitButtonProps extends CProps.Button {
interface SubmitButtonProps extends Button {
/** Text to display in the button. */
text?: string;

View File

@ -3,27 +3,27 @@
import { useMemo, useState } from 'react';
import {
ColumnSort,
type ColumnSort,
createColumnHelper,
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
PaginationState,
RowData,
type PaginationState,
type RowData,
type RowSelectionState,
SortingState,
TableOptions,
type SortingState,
type TableOptions,
useReactTable,
type VisibilityState
} from '@tanstack/react-table';
import { CProps } from '../props';
import { type Styling } from '../props';
import DefaultNoData from './DefaultNoData';
import PaginationTools from './PaginationTools';
import TableBody from './TableBody';
import TableFooter from './TableFooter';
import TableHeader from './TableHeader';
import { DefaultNoData } from './DefaultNoData';
import { PaginationTools } from './PaginationTools';
import { TableBody } from './TableBody';
import { TableFooter } from './TableFooter';
import { TableHeader } from './TableHeader';
export { type ColumnSort, createColumnHelper, type RowSelectionState, type VisibilityState };
@ -37,7 +37,7 @@ export interface IConditionalStyle<TData> {
}
export interface DataTableProps<TData extends RowData>
extends CProps.Styling,
extends Styling,
Pick<TableOptions<TData>, 'data' | 'columns' | 'onRowSelectionChange' | 'onColumnVisibilityChange'> {
/** Id of the component. */
id?: string;
@ -67,10 +67,10 @@ export interface DataTableProps<TData extends RowData>
noDataComponent?: React.ReactNode;
/** Callback to be called when a row is clicked. */
onRowClicked?: (rowData: TData, event: CProps.EventMouse) => void;
onRowClicked?: (rowData: TData, event: React.MouseEvent<Element>) => void;
/** Callback to be called when a row is double clicked. */
onRowDoubleClicked?: (rowData: TData, event: CProps.EventMouse) => void;
onRowDoubleClicked?: (rowData: TData, event: React.MouseEvent<Element>) => void;
/** Enable row selection. */
enableRowSelection?: boolean;
@ -109,7 +109,7 @@ export interface DataTableProps<TData extends RowData>
* @param headPosition - Top position of sticky header (0 if no other sticky elements are present).
* No sticky header if omitted
*/
function DataTable<TData extends RowData>({
export function DataTable<TData extends RowData>({
id,
style,
className,
@ -141,7 +141,7 @@ function DataTable<TData extends RowData>({
...restProps
}: DataTableProps<TData>) {
const [sorting, setSorting] = useState<SortingState>(initialSorting ? [initialSorting] : []);
const [lastSelected, setLastSelected] = useState<string | undefined>(undefined);
const [lastSelected, setLastSelected] = useState<string | null>(null);
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
@ -198,7 +198,7 @@ function DataTable<TData extends RowData>({
enableRowSelection={enableRowSelection}
enableSorting={enableSorting}
headPosition={headPosition}
resetLastSelected={() => setLastSelected(undefined)}
resetLastSelected={() => setLastSelected(null)}
/>
) : null}
@ -229,5 +229,3 @@ function DataTable<TData extends RowData>({
</div>
);
}
export default DataTable;

View File

@ -1,5 +1,3 @@
function defaultNoDataComponent() {
export function DefaultNoData() {
return <div className='p-2 text-center'>Данные отсутствуют</div>;
}
export default defaultNoDataComponent;

View File

@ -2,7 +2,7 @@
'use no memo';
import { useCallback } from 'react';
import { Table } from '@tanstack/react-table';
import { type Table } from '@tanstack/react-table';
import clsx from 'clsx';
import { prefixes } from '@/utils/constants';
@ -16,7 +16,7 @@ interface PaginationToolsProps<TData> {
onChangePaginationOption?: (newValue: number) => void;
}
function PaginationTools<TData>({
export function PaginationTools<TData>({
id,
table,
paginationOptions,
@ -106,5 +106,3 @@ function PaginationTools<TData>({
</div>
);
}
export default PaginationTools;

View File

@ -1,6 +1,6 @@
'use no memo';
import { Table } from '@tanstack/react-table';
import { type Table } from '@tanstack/react-table';
import { CheckboxTristate } from '../Input';
@ -9,7 +9,7 @@ interface SelectAllProps<TData> {
resetLastSelected: () => void;
}
function SelectAll<TData>({ table, resetLastSelected }: SelectAllProps<TData>) {
export function SelectAll<TData>({ table, resetLastSelected }: SelectAllProps<TData>) {
function handleChange(value: boolean | null) {
resetLastSelected();
table.toggleAllPageRowsSelected(value !== false);
@ -26,5 +26,3 @@ function SelectAll<TData>({ table, resetLastSelected }: SelectAllProps<TData>) {
/>
);
}
export default SelectAll;

View File

@ -1,15 +1,15 @@
'use no memo';
import { Row } from '@tanstack/react-table';
import { type Row } from '@tanstack/react-table';
import { Checkbox } from '../Input';
interface SelectRowProps<TData> {
row: Row<TData>;
onChangeLastSelected: (newValue: string | undefined) => void;
onChangeLastSelected: (newValue: string) => void;
}
function SelectRow<TData>({ row, onChangeLastSelected }: SelectRowProps<TData>) {
export function SelectRow<TData>({ row, onChangeLastSelected }: SelectRowProps<TData>) {
function handleChange(value: boolean) {
onChangeLastSelected(row.id);
row.toggleSelected(value);
@ -17,5 +17,3 @@ function SelectRow<TData>({ row, onChangeLastSelected }: SelectRowProps<TData>)
return <Checkbox tabIndex={-1} value={row.getIsSelected()} onChange={handleChange} />;
}
export default SelectRow;

View File

@ -1,6 +1,6 @@
'use no memo';
import { Column } from '@tanstack/react-table';
import { type Column } from '@tanstack/react-table';
import { IconSortAsc, IconSortDesc } from '../Icons';
@ -8,7 +8,7 @@ interface SortingIconProps<TData> {
column: Column<TData>;
}
function SortingIcon<TData>({ column }: SortingIconProps<TData>) {
export function SortingIcon<TData>({ column }: SortingIconProps<TData>) {
return (
<>
{{
@ -18,5 +18,3 @@ function SortingIcon<TData>({ column }: SortingIconProps<TData>) {
</>
);
}
export default SortingIcon;

View File

@ -1,12 +1,10 @@
'use no memo';
import { Cell, flexRender, Row, Table } from '@tanstack/react-table';
import { type Cell, flexRender, type Row, type Table } from '@tanstack/react-table';
import clsx from 'clsx';
import { CProps } from '../props';
import SelectRow from './SelectRow';
import { IConditionalStyle } from '.';
import { SelectRow } from './SelectRow';
import { type IConditionalStyle } from '.';
interface TableBodyProps<TData> {
table: Table<TData>;
@ -15,14 +13,14 @@ interface TableBodyProps<TData> {
enableRowSelection?: boolean;
conditionalRowStyles?: IConditionalStyle<TData>[];
lastSelected: string | undefined;
onChangeLastSelected: (newValue: string | undefined) => void;
lastSelected: string | null;
onChangeLastSelected: (newValue: string | null) => void;
onRowClicked?: (rowData: TData, event: CProps.EventMouse) => void;
onRowDoubleClicked?: (rowData: TData, event: CProps.EventMouse) => void;
onRowClicked?: (rowData: TData, event: React.MouseEvent<Element>) => void;
onRowDoubleClicked?: (rowData: TData, event: React.MouseEvent<Element>) => void;
}
function TableBody<TData>({
export function TableBody<TData>({
table,
dense,
noHeader,
@ -33,7 +31,7 @@ function TableBody<TData>({
onRowClicked,
onRowDoubleClicked
}: TableBodyProps<TData>) {
function handleRowClicked(target: Row<TData>, event: CProps.EventMouse) {
function handleRowClicked(target: Row<TData>, event: React.MouseEvent<Element>) {
onRowClicked?.(target.original, event);
if (enableRowSelection && target.getCanSelect()) {
if (event.shiftKey && !!lastSelected && lastSelected !== target.id) {
@ -49,7 +47,7 @@ function TableBody<TData>({
newSelection[row.id] = !target.getIsSelected();
});
table.setRowSelection(prev => ({ ...prev, ...newSelection }));
onChangeLastSelected(undefined);
onChangeLastSelected(null);
} else {
onChangeLastSelected(target.id);
target.toggleSelected(!target.getIsSelected());
@ -94,7 +92,7 @@ function TableBody<TData>({
width: noHeader && index === 0 ? `calc(var(--col-${cell.column.id}-size) * 1px)` : 'auto'
}}
onClick={event => handleRowClicked(row, event)}
onDoubleClick={event => (onRowDoubleClicked ? onRowDoubleClicked(row.original, event) : undefined)}
onDoubleClick={event => onRowDoubleClicked?.(row.original, event)}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
@ -104,5 +102,3 @@ function TableBody<TData>({
</tbody>
);
}
export default TableBody;

View File

@ -1,12 +1,12 @@
'use no memo';
import { flexRender, Header, HeaderGroup, Table } from '@tanstack/react-table';
import { flexRender, type Header, type HeaderGroup, type Table } from '@tanstack/react-table';
interface TableFooterProps<TData> {
table: Table<TData>;
}
function TableFooter<TData>({ table }: TableFooterProps<TData>) {
export function TableFooter<TData>({ table }: TableFooterProps<TData>) {
return (
<tfoot>
{table.getFooterGroups().map((footerGroup: HeaderGroup<TData>) => (
@ -21,5 +21,3 @@ function TableFooter<TData>({ table }: TableFooterProps<TData>) {
</tfoot>
);
}
export default TableFooter;

View File

@ -1,9 +1,9 @@
'use no memo';
import { flexRender, Header, HeaderGroup, Table } from '@tanstack/react-table';
import { flexRender, type Header, type HeaderGroup, type Table } from '@tanstack/react-table';
import SelectAll from './SelectAll';
import SortingIcon from './SortingIcon';
import { SelectAll } from './SelectAll';
import { SortingIcon } from './SortingIcon';
interface TableHeaderProps<TData> {
table: Table<TData>;
@ -13,7 +13,7 @@ interface TableHeaderProps<TData> {
resetLastSelected: () => void;
}
function TableHeader<TData>({
export function TableHeader<TData>({
table,
headPosition,
enableRowSelection,
@ -61,5 +61,3 @@ function TableHeader<TData>({
</thead>
);
}
export default TableHeader;

View File

@ -1,6 +1,6 @@
export {
createColumnHelper,
default,
DataTable,
type IConditionalStyle,
type RowSelectionState,
type VisibilityState

View File

@ -14,7 +14,7 @@ import {
IconHide,
IconMoveDown,
IconMoveUp,
IconProps,
type IconProps,
IconPublic,
IconSettings,
IconShow,

View File

@ -2,9 +2,9 @@ import clsx from 'clsx';
import { PARAMETER } from '@/utils/constants';
import { CProps } from '../props';
import { type Styling } from '../props';
interface DropdownProps extends CProps.Styling {
interface DropdownProps extends Styling {
/** Indicates whether the dropdown should stretch to the left. */
stretchLeft?: boolean;

View File

@ -1,9 +1,9 @@
import clsx from 'clsx';
import { CProps } from '@/components/props';
import { globals } from '@/utils/constants';
import { type Button } from '@/components/props';
import { globalIDs } from '@/utils/constants';
interface DropdownButtonProps extends CProps.Button {
interface DropdownButtonProps extends Button {
/** Icon to display first (not used if children are provided). */
icon?: React.ReactNode;
@ -36,7 +36,7 @@ export function DropdownButton({
onClick={onClick}
className={clsx(
'px-3 py-1 inline-flex items-center gap-2',
'text-left text-sm overflow-ellipsis whitespace-nowrap',
'text-left text-sm text-ellipsis whitespace-nowrap',
'disabled:clr-text-controls',
'cc-animate-color',
{
@ -46,7 +46,7 @@ export function DropdownButton({
},
className
)}
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined}
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml}
data-tooltip-content={title}
data-tooltip-hidden={hideTitle}

View File

@ -1,6 +1,6 @@
import clsx from 'clsx';
import { Checkbox, CheckboxProps } from '../Input';
import { Checkbox, type CheckboxProps } from '../Input';
/** Animated {@link Checkbox} inside a {@link Dropdown} item. */
export function DropdownCheckbox({ onChange: setValue, disabled, ...restProps }: CheckboxProps) {
@ -8,7 +8,7 @@ export function DropdownCheckbox({ onChange: setValue, disabled, ...restProps }:
<div
className={clsx(
'px-3 py-1',
'text-left overflow-ellipsis whitespace-nowrap',
'text-left text-ellipsis whitespace-nowrap',
'disabled:clr-text-controls cc-animate-color',
!!setValue && !disabled && 'clr-hover'
)}

View File

@ -2,7 +2,7 @@
import { useRef, useState } from 'react';
import useClickedOutside from '@/hooks/useClickedOutside';
import { useClickedOutside } from '@/hooks/useClickedOutside';
export function useDropdown() {
const [isOpen, setIsOpen] = useState(false);

View File

@ -1,10 +1,10 @@
import { EdgeProps, getStraightPath } from 'reactflow';
import { type EdgeProps, getStraightPath } from 'reactflow';
import { PARAMETER } from '@/utils/constants';
const RADIUS = PARAMETER.graphNodeRadius + PARAMETER.graphNodePadding;
function DynamicEdge({ id, markerEnd, style, ...props }: EdgeProps) {
export function DynamicEdge({ id, markerEnd, style, ...props }: EdgeProps) {
const sourceY = props.sourceY - PARAMETER.graphNodeRadius - PARAMETER.graphHandleSize;
const targetY = props.targetY + PARAMETER.graphNodeRadius + PARAMETER.graphHandleSize;
@ -32,5 +32,3 @@ function DynamicEdge({ id, markerEnd, style, ...props }: EdgeProps) {
</>
);
}
export default DynamicEdge;

View File

@ -1,12 +1,12 @@
import clsx from 'clsx';
import { ZodError } from 'zod';
import { AxiosError, isAxiosError } from '@/backend/apiTransport';
import { type AxiosError, isAxiosError } from '@/backend/apiTransport';
import { isResponseHtml } from '@/utils/utils';
import { PrettyJson } from './View';
export type ErrorData = string | Error | AxiosError | ZodError | undefined | null;
export type ErrorData = string | Error | AxiosError | ZodError;
interface InfoErrorProps {
error: ErrorData;

View File

@ -1,11 +1,11 @@
import clsx from 'clsx';
import { globals } from '@/utils/constants';
import { globalIDs } from '@/utils/constants';
import { CheckboxChecked } from '../Icons';
import { CProps } from '../props';
import { type Button } from '../props';
export interface CheckboxProps extends Omit<CProps.Button, 'value' | 'onClick' | 'onChange'> {
export interface CheckboxProps extends Omit<Button, 'value' | 'onClick' | 'onChange'> {
/** Label to display next to the checkbox. */
label?: string;
@ -35,7 +35,7 @@ export function Checkbox({
}: CheckboxProps) {
const cursor = disabled ? 'cursor-arrow' : onChange ? 'cursor-pointer' : '';
function handleClick(event: CProps.EventMouse): void {
function handleClick(event: React.MouseEvent<Element>): void {
event.preventDefault();
event.stopPropagation();
if (disabled || !onChange) {
@ -49,14 +49,14 @@ export function Checkbox({
type='button'
className={clsx(
'flex items-center gap-2', //
'outline-none',
'outline-hidden',
'focus-frame',
cursor,
className
)}
disabled={disabled}
onClick={handleClick}
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined}
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml}
data-tooltip-content={title}
data-tooltip-hidden={hideTitle}
@ -66,7 +66,7 @@ export function Checkbox({
className={clsx(
'max-w-[1rem] min-w-[1rem] h-4', //
'pt-[0.05rem] pl-[0.05rem]',
'border rounded-sm',
'border rounded-xs',
'cc-animate-color',
{
'bg-sec-600 text-sec-0': value !== false,

View File

@ -1,11 +1,10 @@
import clsx from 'clsx';
import { globals } from '@/utils/constants';
import { globalIDs } from '@/utils/constants';
import { CheckboxChecked, CheckboxNull } from '../Icons';
import { CProps } from '../props';
import { CheckboxProps } from './Checkbox';
import { type CheckboxProps } from './Checkbox';
export interface CheckboxTristateProps extends Omit<CheckboxProps, 'value' | 'onChange'> {
/** Current value - `null`, `true` or `false`. */
@ -31,7 +30,7 @@ export function CheckboxTristate({
}: CheckboxTristateProps) {
const cursor = disabled ? 'cursor-arrow' : onChange ? 'cursor-pointer' : '';
function handleClick(event: CProps.EventMouse): void {
function handleClick(event: React.MouseEvent<Element>): void {
event.preventDefault();
event.stopPropagation();
if (disabled || !onChange) {
@ -51,14 +50,14 @@ export function CheckboxTristate({
type='button'
className={clsx(
'flex items-center gap-2', //
'outline-none',
'outline-hidden',
'focus-frame',
cursor,
className
)}
disabled={disabled}
onClick={handleClick}
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined}
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml}
data-tooltip-content={title}
data-tooltip-hidden={hideTitle}
@ -68,7 +67,7 @@ export function CheckboxTristate({
className={clsx(
'w-4 h-4', //
'pt-[0.05rem] pl-[0.05rem]',
'border rounded-sm',
'border rounded-xs',
'cc-animate-color',
{
'bg-sec-600 text-sec-0': value !== false,

View File

@ -1,9 +1,9 @@
import { FieldError, GlobalError } from 'react-hook-form';
import { type FieldError, type GlobalError } from 'react-hook-form';
import clsx from 'clsx';
import { CProps } from '../props';
import { type Styling } from '../props';
interface ErrorFieldProps extends CProps.Styling {
interface ErrorFieldProps extends Styling {
error?: FieldError | GlobalError;
}

View File

@ -5,11 +5,11 @@ import clsx from 'clsx';
import { Button } from '../Control';
import { IconUpload } from '../Icons';
import { CProps } from '../props';
import { type Titled } from '../props';
import { Label } from './Label';
interface FileInputProps extends Omit<CProps.Input, 'accept' | 'type'> {
interface FileInputProps extends Titled, Omit<React.ComponentProps<'input'>, 'accept' | 'type'> {
/** Label to display in file upload button. */
label: string;

View File

@ -1,8 +1,6 @@
import clsx from 'clsx';
import { CProps } from '../props';
interface LabelProps extends CProps.Label {
interface LabelProps extends Omit<React.ComponentProps<'label'>, 'children'> {
/** Text to display. */
text?: string;
}

View File

@ -2,11 +2,11 @@ import clsx from 'clsx';
import { Overlay } from '@/components/Container';
import { IconSearch } from '@/components/Icons';
import { CProps } from '@/components/props';
import { type Styling } from '@/components/props';
import { TextInput } from './TextInput';
interface SearchBarProps extends CProps.Styling {
interface SearchBarProps extends Styling {
/** Id of the search bar. */
id?: string;
@ -48,12 +48,13 @@ export function SearchBar({
<TextInput
id={id}
noOutline
transparent
placeholder={placeholder}
type='search'
className={clsx('outline-none bg-transparent', !noIcon && 'pl-10')}
className={clsx('bg-transparent', !noIcon && 'pl-10')}
noBorder={noBorder}
value={query}
onChange={event => (onChangeQuery ? onChangeQuery(event.target.value) : undefined)}
onChange={event => onChangeQuery?.(event.target.value)}
/>
</div>
);

View File

@ -1,15 +1,15 @@
'use client';
import Select, {
ClearIndicatorProps,
type ClearIndicatorProps,
components,
DropdownIndicatorProps,
GroupBase,
Props,
StylesConfig
type DropdownIndicatorProps,
type GroupBase,
type Props,
type StylesConfig
} from 'react-select';
import useWindowSize from '@/hooks/useWindowSize';
import { useWindowSize } from '@/hooks/useWindowSize';
import { APP_COLORS, SELECT_THEME } from '@/styling/colors';
import { IconClose, IconDropArrow, IconDropArrowUp } from '../Icons';

View File

@ -1,15 +1,15 @@
'use client';
import Select, {
ClearIndicatorProps,
type ClearIndicatorProps,
components,
DropdownIndicatorProps,
GroupBase,
Props,
StylesConfig
type DropdownIndicatorProps,
type GroupBase,
type Props,
type StylesConfig
} from 'react-select';
import useWindowSize from '@/hooks/useWindowSize';
import { useWindowSize } from '@/hooks/useWindowSize';
import { APP_COLORS, SELECT_THEME } from '@/styling/colors';
import { IconClose, IconDropArrow, IconDropArrowUp } from '../Icons';

View File

@ -1,14 +1,14 @@
import { useEffect, useState } from 'react';
import clsx from 'clsx';
import { globals, PARAMETER } from '@/utils/constants';
import { globalIDs, PARAMETER } from '@/utils/constants';
import { Overlay } from '../Container';
import { MiniButton } from '../Control';
import { IconDropArrow, IconPageRight } from '../Icons';
import { CProps } from '../props';
import { type Styling } from '../props';
interface SelectTreeProps<ItemType> extends CProps.Styling {
interface SelectTreeProps<ItemType> extends Styling {
/** Current value. */
value: ItemType;
@ -66,13 +66,13 @@ export function SelectTree<ItemType>({
);
}
function handleClickFold(event: CProps.EventMouse, target: ItemType, showChildren: boolean) {
function handleClickFold(event: React.MouseEvent<Element>, target: ItemType, showChildren: boolean) {
event.preventDefault();
event.stopPropagation();
onFoldItem(target, showChildren);
}
function handleSetValue(event: CProps.EventMouse, target: ItemType) {
function handleSetValue(event: React.MouseEvent<Element>, target: ItemType) {
event.preventDefault();
event.stopPropagation();
onChange(target);
@ -93,7 +93,7 @@ export function SelectTree<ItemType>({
value === item && 'clr-selected',
!isActive && 'pointer-events-none'
)}
data-tooltip-id={globals.tooltip}
data-tooltip-id={globalIDs.tooltip}
data-tooltip-html={getDescription(item)}
onClick={event => handleSetValue(event, item)}
style={{

View File

@ -1,11 +1,14 @@
import clsx from 'clsx';
import { Label } from '../Input/Label';
import { CProps } from '../props';
import { type Editor, type ErrorProcessing, type Titled } from '../props';
import { ErrorField } from './ErrorField';
export interface TextAreaProps extends CProps.Editor, CProps.ErrorProcessing, CProps.Colors, CProps.TextArea {
export interface TextAreaProps extends Editor, ErrorProcessing, Titled, React.ComponentProps<'textarea'> {
/** Indicates that the input should be transparent. */
transparent?: boolean;
/** Indicates that padding should be minimal. */
dense?: boolean;
@ -23,6 +26,7 @@ export function TextArea({
id,
label,
required,
transparent,
rows,
dense,
noBorder,
@ -31,7 +35,6 @@ export function TextArea({
className,
fitContent,
error,
colors = 'clr-input',
...restProps
}: TextAreaProps) {
return (
@ -40,7 +43,7 @@ export function TextArea({
'w-full',
{
'flex flex-col': !dense,
'flex flex-grow items-center gap-3': dense
'flex grow items-center gap-3': dense
},
dense && className
)}
@ -53,14 +56,15 @@ export function TextArea({
'leading-tight',
'overflow-x-hidden overflow-y-auto',
{
'cc-fit-content': fitContent,
'field-sizing-content': fitContent,
'resize-none': noResize,
'border': !noBorder,
'flex-grow max-w-full': dense,
'grow max-w-full': dense,
'mt-2': !dense && !!label,
'clr-outline': !noOutline
'clr-outline': !noOutline,
'bg-transparent': transparent,
'clr-input': !transparent
},
colors,
!dense && className
)}
rows={rows}

View File

@ -1,11 +1,14 @@
import clsx from 'clsx';
import { Label } from '../Input/Label';
import { CProps } from '../props';
import { type Editor, type ErrorProcessing, type Titled } from '../props';
import { ErrorField } from './ErrorField';
interface TextInputProps extends CProps.Editor, CProps.ErrorProcessing, CProps.Colors, CProps.Input {
interface TextInputProps extends Editor, ErrorProcessing, Titled, React.ComponentProps<'input'> {
/** Indicates that the input should be transparent. */
transparent?: boolean;
/** Indicates that padding should be minimal. */
dense?: boolean;
@ -30,8 +33,8 @@ export function TextInput({
noOutline,
allowEnter,
disabled,
transparent,
className,
colors = 'clr-input',
onKeyDown,
error,
...restProps
@ -54,12 +57,13 @@ export function TextInput({
'leading-tight truncate hover:text-clip',
{
'px-3': !noBorder || !disabled,
'flex-grow max-w-full': dense,
'grow max-w-full': dense,
'mt-2': !dense && !!label,
'border': !noBorder,
'clr-outline': !noOutline
'clr-outline': !noOutline,
'bg-transparent': transparent,
'clr-input': !transparent
},
colors,
!dense && className
)}
onKeyDown={!allowEnter && !onKeyDown ? preventEnterCapture : onKeyDown}

View File

@ -2,20 +2,20 @@
import clsx from 'clsx';
import { BadgeHelp, HelpTopic } from '@/features/help';
import { BadgeHelp, type HelpTopic } from '@/features/help';
import useEscapeKey from '@/hooks/useEscapeKey';
import { useEscapeKey } from '@/hooks/useEscapeKey';
import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
import { Button, MiniButton, SubmitButton } from '../Control';
import { IconClose } from '../Icons';
import { CProps } from '../props';
import { type Styling } from '../props';
import { ModalBackdrop } from './ModalBackdrop';
export interface ModalProps extends CProps.Styling {
export interface ModalProps extends Styling {
/** Title of the modal window. */
header?: string;
@ -115,7 +115,7 @@ export function ModalForm({
<div
className={clsx(
'overscroll-contain max-h-[calc(100svh-8rem)] max-w-[100svw] xs:max-w-[calc(100svw-2rem)] outline-none',
'overscroll-contain max-h-[calc(100svh-8rem)] max-w-[100svw] xs:max-w-[calc(100svw-2rem)] outline-hidden',
{
'overflow-auto': !overflowVisible,
'overflow-visible': overflowVisible

View File

@ -4,7 +4,7 @@ import clsx from 'clsx';
import { BadgeHelp } from '@/features/help';
import useEscapeKey from '@/hooks/useEscapeKey';
import { useEscapeKey } from '@/hooks/useEscapeKey';
import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
@ -13,7 +13,7 @@ import { Button, MiniButton } from '../Control';
import { IconClose } from '../Icons';
import { ModalBackdrop } from './ModalBackdrop';
import { ModalProps } from './ModalForm';
import { type ModalProps } from './ModalForm';
interface ModalViewProps extends ModalProps {}
@ -60,7 +60,7 @@ export function ModalView({
<div
className={clsx(
'overscroll-contain max-h-[calc(100svh-8rem)] max-w-[100svw] xs:max-w-[calc(100svw-2rem)] outline-none',
'overscroll-contain max-h-[calc(100svh-8rem)] max-w-[100svw] xs:max-w-[calc(100svw-2rem)] outline-hidden',
{
'overflow-auto': !overflowVisible,
'overflow-visible': overflowVisible

View File

@ -2,11 +2,11 @@ import type { TabProps as TabPropsImpl } from 'react-tabs';
import { Tab as TabImpl } from 'react-tabs';
import clsx from 'clsx';
import { globals } from '@/utils/constants';
import { globalIDs } from '@/utils/constants';
import { CProps } from '../props';
import { type Titled } from '../props';
interface TabLabelProps extends Omit<TabPropsImpl, 'children'>, CProps.Titled {
interface TabLabelProps extends Omit<TabPropsImpl, 'children'>, Titled {
/** Label to display in the tab. */
label?: string;
}
@ -23,11 +23,11 @@ export function TabLabel({ label, title, titleHtml, hideTitle, className, ...oth
'clr-hover cc-animate-color duration-150',
'text-sm whitespace-nowrap font-controls',
'select-none hover:cursor-pointer',
'outline-none',
'outline-hidden',
className
)}
tabIndex='-1'
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined}
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml}
data-tooltip-content={title}
data-tooltip-hidden={hideTitle}

View File

@ -1,9 +1,9 @@
import clsx from 'clsx';
import { CProps } from '@/components/props';
import { globals } from '@/utils/constants';
import { type Styling, type Titled } from '@/components/props';
import { globalIDs } from '@/utils/constants';
interface IndicatorProps extends CProps.Titled, CProps.Styling {
interface IndicatorProps extends Titled, Styling {
/** Icon to display. */
icon: React.ReactNode;
@ -19,13 +19,13 @@ export function Indicator({ icon, title, titleHtml, hideTitle, noPadding, classN
<div
className={clsx(
'clr-text-controls',
'outline-none',
'outline-hidden',
{
'px-1 py-1': !noPadding
},
className
)}
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined}
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml}
data-tooltip-content={title}
data-tooltip-hidden={hideTitle}

View File

@ -1,11 +1,9 @@
import clsx from 'clsx';
import { CProps } from '@/components/props';
/**
* Wraps content in a div with a centered text.
*/
export function NoData({ className, children, ...restProps }: CProps.Div) {
export function NoData({ className, children, ...restProps }: React.ComponentProps<'div'>) {
return (
<div className={clsx('p-3 flex flex-col items-center text-center select-none w-full', className)} {...restProps}>
{children}

View File

@ -1,6 +1,6 @@
'use client';
import useWindowSize from '@/hooks/useWindowSize';
import { useWindowSize } from '@/hooks/useWindowSize';
import { useFitHeight } from '@/stores/appLayout';
/** Maximum width of the viewer. */

View File

@ -1,10 +1,11 @@
import clsx from 'clsx';
import { CProps } from '@/components/props';
import { globals } from '@/utils/constants';
import { globalIDs } from '@/utils/constants';
import { truncateToLastWord } from '@/utils/utils';
export interface TextContentProps extends CProps.Styling {
import { type Styling } from '../props';
export interface TextContentProps extends Styling {
/** Text to display. */
text: string;
@ -24,7 +25,7 @@ export function TextContent({ className, text, maxLength, noTooltip, ...restProp
return (
<div
className={clsx('text-xs text-pretty', className)}
data-tooltip-id={isTruncated && !noTooltip ? globals.value_tooltip : undefined}
data-tooltip-id={isTruncated && !noTooltip ? globalIDs.value_tooltip : undefined}
data-tooltip-html={isTruncated && !noTooltip ? text : undefined}
{...restProps}
>

View File

@ -1,11 +1,11 @@
import clsx from 'clsx';
import { CProps } from '@/components/props';
import { globals } from '@/utils/constants';
import { globalIDs } from '@/utils/constants';
import { MiniButton } from '../Control';
import { type Styling, type Titled } from '../props';
interface ValueIconProps extends CProps.Styling, CProps.Titled {
interface ValueIconProps extends Styling, Titled {
/** Id of the component. */
id?: string;
@ -19,7 +19,7 @@ interface ValueIconProps extends CProps.Styling, CProps.Titled {
textClassName?: string;
/** Callback to be called when the component is clicked. */
onClick?: (event: CProps.EventMouse) => void;
onClick?: (event: React.MouseEvent<Element>) => void;
/** Number of symbols to display in a small size. */
smallThreshold?: number;
@ -61,7 +61,7 @@ export function ValueIcon({
className
)}
{...restProps}
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined}
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml}
data-tooltip-content={title}
data-tooltip-hidden={hideTitle}

View File

@ -1,8 +1,8 @@
import clsx from 'clsx';
import { CProps } from '@/components/props';
import { type Styling } from '@/components/props';
interface ValueLabeledProps extends CProps.Styling {
interface ValueLabeledProps extends Styling {
/** Id of the component. */
id?: string;

View File

@ -1,9 +1,9 @@
import { CProps } from '@/components/props';
import { type Styling, type Titled } from '@/components/props';
import { PARAMETER } from '@/utils/constants';
import { ValueIcon } from './ValueIcon';
interface ValueStatsProps extends CProps.Styling, CProps.Titled {
interface ValueStatsProps extends Styling, Titled {
/** Id of the component. */
id: string;

View File

@ -1,8 +1,7 @@
// =========== Module contains interfaces for common UI elements. ==========
import React from 'react';
import { FieldError } from 'react-hook-form';
import type React from 'react';
import { type FieldError } from 'react-hook-form';
export namespace CProps {
/**
* Represents an object that can have inline styles and CSS class names for styling.
*/
@ -14,14 +13,6 @@ export namespace CProps {
className?: string;
}
/**
* Represents an object that can have a color or set of colors.
*/
export interface Colors {
/** Optional color or set of colors applied via classNames. */
colors?: string;
}
/**
* Represents an object that can have a title with optional HTML rendering and a flag to hide the title.
*/
@ -71,33 +62,7 @@ export namespace CProps {
label?: string;
};
/**
* Represents `div` component with all standard HTML attributes and React-specific properties.
*/
export type Div = React.ComponentProps<'div'>;
/**
* Represents `button` component with optional title and HTML attributes.
*/
export type Button = Titled & Omit<React.ComponentProps<'button'>, 'children' | 'type'>;
/**
* Represents `label` component with HTML attributes.
*/
export type Label = Omit<React.ComponentProps<'label'>, 'children'>;
/**
* Represents `textarea` component with optional title and HTML attributes.
*/
export type TextArea = Titled & React.ComponentProps<'textarea'>;
/**
* Represents `input` component with optional title and HTML attributes.
*/
export type Input = Titled & React.ComponentProps<'input'>;
/**
* Represents `mouse event` in React.
*/
export type EventMouse = React.MouseEvent<Element, MouseEvent>;
}

View File

@ -5,12 +5,12 @@ import { DELAYS, KEYS } from '@/backend/configuration';
import { infoMsg } from '@/utils/labels';
import {
IChangePasswordDTO,
ICurrentUser,
IPasswordTokenDTO,
IRequestPasswordDTO,
IResetPasswordDTO,
IUserLoginDTO
type IChangePasswordDTO,
type ICurrentUser,
type IPasswordTokenDTO,
type IRequestPasswordDTO,
type IResetPasswordDTO,
type IUserLoginDTO
} from './types';
/**

View File

@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { KEYS } from '@/backend/configuration';
import { authApi } from './api';
import { IChangePasswordDTO } from './types';
import { type IChangePasswordDTO } from './types';
export const useChangePassword = () => {
const client = useQueryClient();

View File

@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { KEYS } from '@/backend/configuration';
import { authApi } from './api';
import { IUserLoginDTO } from './types';
import { type IUserLoginDTO } from './types';
export const useLogin = () => {
const client = useQueryClient();

View File

@ -3,7 +3,7 @@ import { useMutation } from '@tanstack/react-query';
import { KEYS } from '@/backend/configuration';
import { authApi } from './api';
import { IRequestPasswordDTO } from './types';
import { type IRequestPasswordDTO } from './types';
export const useRequestPasswordReset = () => {
const mutation = useMutation({

View File

@ -3,7 +3,7 @@ import { useMutation } from '@tanstack/react-query';
import { KEYS } from '@/backend/configuration';
import { authApi } from './api';
import { IPasswordTokenDTO, IResetPasswordDTO } from './types';
import { type IPasswordTokenDTO, type IResetPasswordDTO } from './types';
export const useResetPassword = () => {
const validateMutation = useMutation({

View File

@ -8,17 +8,17 @@ import { urls, useConceptNavigation } from '@/app';
import { isAxiosError } from '@/backend/apiTransport';
import { SubmitButton, TextURL } from '@/components/Control';
import { ErrorData } from '@/components/InfoError';
import { type ErrorData } from '@/components/InfoError';
import { TextInput } from '@/components/Input';
import useQueryStrings from '@/hooks/useQueryStrings';
import { useQueryStrings } from '@/hooks/useQueryStrings';
import { resources } from '@/utils/constants';
import { IUserLoginDTO, schemaUserLogin } from '../backend/types';
import { type IUserLoginDTO, schemaUserLogin } from '../backend/types';
import { useAuthSuspense } from '../backend/useAuth';
import { useLogin } from '../backend/useLogin';
import { ExpectedAnonymous } from '../components/ExpectedAnonymous';
function LoginPage() {
export function LoginPage() {
const router = useConceptNavigation();
const query = useQueryStrings();
const initialName = query.get('username') ?? '';
@ -92,8 +92,6 @@ function LoginPage() {
);
}
export default LoginPage;
// ====== Internals =========
function ServerError({ error }: { error: ErrorData }): React.ReactElement | null {
if (isAxiosError(error) && error.response && error.response.status === 400) {

View File

@ -7,10 +7,10 @@ import { urls, useConceptNavigation } from '@/app';
import { isAxiosError } from '@/backend/apiTransport';
import { SubmitButton } from '@/components/Control';
import { ErrorData, InfoError } from '@/components/InfoError';
import { type ErrorData, InfoError } from '@/components/InfoError';
import { TextInput } from '@/components/Input';
import { Loader } from '@/components/Loader';
import useQueryStrings from '@/hooks/useQueryStrings';
import { useQueryStrings } from '@/hooks/useQueryStrings';
import { useResetPassword } from '../backend/useResetPassword';
@ -24,9 +24,6 @@ export function Component() {
const [newPassword, setNewPassword] = useState('');
const [newPasswordRepeat, setNewPasswordRepeat] = useState('');
const passwordColor =
!!newPassword && !!newPasswordRepeat && newPassword !== newPasswordRepeat ? 'bg-warn-100' : 'clr-input';
const canSubmit = !!newPassword && !!newPasswordRepeat && newPassword === newPasswordRepeat;
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
@ -61,7 +58,6 @@ export function Component() {
label='Новый пароль'
autoComplete='new-password'
allowEnter
colors={passwordColor}
value={newPassword}
onChange={event => {
setNewPassword(event.target.value);
@ -73,7 +69,6 @@ export function Component() {
label='Повторите новый'
autoComplete='new-password'
allowEnter
colors={passwordColor}
value={newPasswordRepeat}
onChange={event => {
setNewPasswordRepeat(event.target.value);

View File

@ -5,7 +5,7 @@ import clsx from 'clsx';
import { isAxiosError } from '@/backend/apiTransport';
import { SubmitButton, TextURL } from '@/components/Control';
import { ErrorData } from '@/components/InfoError';
import { type ErrorData } from '@/components/InfoError';
import { TextInput } from '@/components/Input';
import { useRequestPasswordReset } from '../backend/useRequestPasswordReset';

View File

@ -1,17 +1,19 @@
import React, { Suspense } from 'react';
import { PlacesType, Tooltip } from '@/components/Container';
import { type PlacesType, Tooltip } from '@/components/Container';
import { TextURL } from '@/components/Control';
import { IconHelp } from '@/components/Icons';
import { Loader } from '@/components/Loader';
import { CProps } from '@/components/props';
import { type Styling } from '@/components/props';
import { usePreferencesStore } from '@/stores/preferences';
import { HelpTopic } from '../models/helpTopic';
import { type HelpTopic } from '../models/helpTopic';
const TopicPage = React.lazy(() => import('@/features/help/pages/ManualsPage/TopicPage'));
const TopicPage = React.lazy(() =>
import('@/features/help/pages/ManualsPage/TopicPage').then(module => ({ default: module.TopicPage }))
);
interface BadgeHelpProps extends CProps.Styling {
interface BadgeHelpProps extends Styling {
/** Topic to display in a tooltip. */
topic: HelpTopic;

View File

@ -10,7 +10,7 @@ interface InfoCstClassProps {
header?: string;
}
function InfoCstClass({ header }: InfoCstClassProps) {
export function InfoCstClass({ header }: InfoCstClassProps) {
return (
<div className='flex flex-col gap-1 mb-2 dense'>
{header ? <h1>{header}</h1> : null}
@ -31,5 +31,3 @@ function InfoCstClass({ header }: InfoCstClassProps) {
</div>
);
}
export default InfoCstClass;

View File

@ -10,7 +10,7 @@ interface InfoCstStatusProps {
title?: string;
}
function InfoCstStatus({ title }: InfoCstStatusProps) {
export function InfoCstStatus({ title }: InfoCstStatusProps) {
return (
<div className='flex flex-col gap-1 mb-2 dense'>
{title ? <h1>{title}</h1> : null}
@ -37,5 +37,3 @@ function InfoCstStatus({ title }: InfoCstStatusProps) {
</div>
);
}
export default InfoCstStatus;

View File

@ -2,7 +2,7 @@ import { urls } from '@/app';
import { TextURL } from '@/components/Control';
import { HelpTopic } from '../models/helpTopic';
import { type HelpTopic } from '../models/helpTopic';
interface TextURLProps {
/** Text to display. */

View File

@ -2,7 +2,7 @@ import { removeTags } from '@/utils/utils';
import { LinkTopic } from '../components/LinkTopic';
import { describeHelpTopic, labelHelpTopic } from '../labels';
import { HelpTopic } from '../models/helpTopic';
import { type HelpTopic } from '../models/helpTopic';
interface TopicItemProps {
topic: HelpTopic;

View File

@ -1,6 +1,6 @@
import { IconHide, IconImmutable, IconPrivate, IconProtected, IconPublic } from '@/components/Icons';
function HelpAccess() {
export function HelpAccess() {
return (
<div>
<h1>Организация доступов</h1>
@ -29,5 +29,3 @@ function HelpAccess() {
</div>
);
}
export default HelpAccess;

View File

@ -4,7 +4,7 @@ import { external_urls } from '@/utils/constants';
import { Subtopics } from '../components/Subtopics';
import { HelpTopic } from '../models/helpTopic';
function HelpConceptSystem() {
export function HelpConcept() {
return (
<div className='text-justify'>
<h1>Концептуализация</h1>
@ -43,5 +43,3 @@ function HelpConceptSystem() {
</div>
);
}
export default HelpConceptSystem;

View File

@ -1,7 +1,7 @@
import { TextURL } from '@/components/Control';
import { external_urls, PARAMETER } from '@/utils/constants';
function HelpExteor() {
export function HelpExteor() {
return (
<div>
<h1>Экстеор</h1>
@ -38,5 +38,3 @@ function HelpExteor() {
</div>
);
}
export default HelpExteor;

View File

@ -1,7 +1,7 @@
import { Subtopics } from '../components/Subtopics';
import { HelpTopic } from '../models/helpTopic';
function HelpInfo() {
export function HelpInfo() {
return (
<div>
<h1>Справочная информация и документы</h1>
@ -14,5 +14,3 @@ function HelpInfo() {
</div>
);
}
export default HelpInfo;

View File

@ -12,7 +12,7 @@ import {
import { Subtopics } from '../components/Subtopics';
import { HelpTopic } from '../models/helpTopic';
function HelpInterface() {
export function HelpInterface() {
return (
<div>
<h1>Пользовательский интерфейс</h1>
@ -60,5 +60,3 @@ function HelpInterface() {
</div>
);
}
export default HelpInterface;

View File

@ -5,7 +5,7 @@ import { LinkTopic } from '../components/LinkTopic';
import { TopicItem } from '../components/TopicItem';
import { HelpTopic } from '../models/helpTopic';
function HelpMain() {
export function HelpMain() {
return (
<div className='text-justify'>
<h1>Портал</h1>
@ -66,5 +66,3 @@ function HelpMain() {
</div>
);
}
export default HelpMain;

View File

@ -1,11 +1,11 @@
import { EmbedYoutube } from '@/components/View';
import useWindowSize from '@/hooks/useWindowSize';
import { useWindowSize } from '@/hooks/useWindowSize';
import { external_urls, youtube } from '@/utils/constants';
import { Subtopics } from '../components/Subtopics';
import { HelpTopic } from '../models/helpTopic';
function HelpRSLang() {
export function HelpRSLang() {
const windowSize = useWindowSize();
const videoHeight = (() => {
@ -35,5 +35,3 @@ function HelpRSLang() {
</div>
</div>);
}
export default HelpRSLang;

View File

@ -1,4 +1,4 @@
function HelpTerminologyControl() {
export function HelpTerminologyControl() {
return (
<div>
<h1>Терминологизация</h1>
@ -24,5 +24,3 @@ function HelpTerminologyControl() {
</div>
);
}
export default HelpTerminologyControl;

View File

@ -30,7 +30,7 @@ import {
import { LinkTopic } from '../components/LinkTopic';
import { HelpTopic } from '../models/helpTopic';
function HelpThesaurus() {
export function HelpThesaurus() {
return (
<div className='text-justify'>
<h1>Тезаурус</h1>
@ -281,5 +281,3 @@ function HelpThesaurus() {
</div>
);
}
export default HelpThesaurus;

View File

@ -1,6 +1,6 @@
import { IconEditor, IconNewVersion, IconShare, IconUpload, IconVersions } from '@/components/Icons';
function HelpVersions() {
export function HelpVersions() {
return (
<div className=''>
<h1>Версионирование схем</h1>
@ -28,5 +28,3 @@ function HelpVersions() {
</div>
);
}
export default HelpVersions;

View File

@ -11,7 +11,7 @@ import {
import { LinkTopic } from '../../components/LinkTopic';
import { HelpTopic } from '../../models/helpTopic';
function HelpConceptOSS() {
export function HelpConceptOSS() {
return (
<div className='text-justify'>
<h1>Операционная схема синтеза</h1>
@ -70,5 +70,3 @@ function HelpConceptOSS() {
</div>
);
}
export default HelpConceptOSS;

View File

@ -3,7 +3,7 @@ import { IconPredecessor } from '@/components/Icons';
import { LinkTopic } from '../../components/LinkTopic';
import { HelpTopic } from '../../models/helpTopic';
function HelpConceptPropagation() {
export function HelpConceptPropagation() {
return (
<div className='text-justify'>
<h1>Сквозные изменения</h1>
@ -44,5 +44,3 @@ function HelpConceptPropagation() {
</div>
);
}
export default HelpConceptPropagation;

View File

@ -1,7 +1,7 @@
import { LinkTopic } from '../../components/LinkTopic';
import { HelpTopic } from '../../models/helpTopic';
function HelpConceptRelations() {
export function HelpConceptRelations() {
return (
<div className='text-justify'>
<h1>Связи между конституентами</h1>
@ -38,5 +38,3 @@ function HelpConceptRelations() {
</div>
);
}
export default HelpConceptRelations;

View File

@ -1,7 +1,7 @@
import { LinkTopic } from '../../components/LinkTopic';
import { HelpTopic } from '../../models/helpTopic';
function HelpConceptSynthesis() {
export function HelpConceptSynthesis() {
return (
<div className='text-justify'>
<h1>Синтез концептуальных схем</h1>
@ -46,5 +46,3 @@ function HelpConceptSynthesis() {
</div>
);
}
export default HelpConceptSynthesis;

View File

@ -1,7 +1,7 @@
import { LinkTopic } from '../../components/LinkTopic';
import { HelpTopic } from '../../models/helpTopic';
function HelpConceptSystem() {
export function HelpConceptSystem() {
return (
<div className='text-justify'>
<h1>Концептуальная схема Система определений</h1>
@ -61,5 +61,3 @@ function HelpConceptSystem() {
</div>
);
}
export default HelpConceptSystem;

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