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

View File

@ -2,16 +2,16 @@
For more specific TODOs see comments in code For more specific TODOs see comments in code
[Bugs - PENDING] [Bugs - PENDING]
- Tab index still selecting background elements when modal is active -
[Functionality - PENDING] [Functionality - PENDING]
- Landing page - Landing page
- Design first user experience - Design first user experience
- Demo sandbox for anonymous users - Demo sandbox for anonymous users
- User profile: Settings + settings server persistency User profile:
- Settings + settings server persistency
- Profile pictures - Profile pictures
- Integrate socials and feedback
- Custom LibraryItem lists - Custom LibraryItem lists
- Custom user filters and sharing filters - Custom user filters and sharing filters
@ -31,6 +31,7 @@ For more specific TODOs see comments in code
[Functionality - CANCELED] [Functionality - CANCELED]
- User notifications on edit - consider spam prevention and change aggregation - User notifications on edit - consider spam prevention and change aggregation
- Integrate socials and feedback
- Content based search in Library - Content based search in Library
- Private projects. Consider cooperative editing - Private projects. Consider cooperative editing
- OSS: synthesis table: auto substitution for diamond synthesis - OSS: synthesis table: auto substitution for diamond synthesis
@ -38,10 +39,7 @@ For more specific TODOs see comments in code
[Tech] [Tech]
- duplicate syntax parsing and type info calculations to client. Consider moving backend to Nodejs or embedding c++ lib - 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 - Testing E2E playwright
- move autopep8 and isort settings from vscode settings to pyproject.toml
- Testing: frontend react components, testplane / playwright?
[Deployment] [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 https://stackoverflow.com/questions/28838170/multilevel-json-diff-in-python
- Documentation platform. Consider diplodoc - 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.recommendedTypeChecked,
...typescriptPlugin.configs.stylisticTypeChecked, ...typescriptPlugin.configs.stylisticTypeChecked,
{ {
ignores: [ ignores: ['**/parser.ts', '**/node_modules/**', '**/public/**', '**/dist/**', 'eslint.config.js']
'**/parser.ts',
'**/node_modules/**',
'**/public/**',
'**/dist/**',
'eslint.config.js',
'tailwind.config.js',
'postcss.config.js'
]
}, },
{ {
languageOptions: { languageOptions: {
@ -43,6 +35,12 @@ export default [
settings: { react: { version: 'detect' } }, settings: { react: { version: 'detect' } },
rules: { rules: {
'react-compiler/react-compiler': 'error', '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/no-empty-object-type': ['error', { allowInterfaces: 'with-single-extends' }],
'@typescript-eslint/prefer-nullish-coalescing': 'off', '@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/no-inferrable-types': '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", "@dagrejs/dagre": "^1.1.4",
"@hookform/resolvers": "^4.1.0", "@hookform/resolvers": "^4.1.0",
"@lezer/lr": "^1.4.2", "@lezer/lr": "^1.4.2",
"@tanstack/react-query": "^5.66.7", "@tanstack/react-query": "^5.66.8",
"@tanstack/react-query-devtools": "^5.66.7", "@tanstack/react-query-devtools": "^5.66.8",
"@tanstack/react-table": "^8.21.2", "@tanstack/react-table": "^8.21.2",
"@uiw/codemirror-themes": "^4.23.8", "@uiw/codemirror-themes": "^4.23.8",
"@uiw/react-codemirror": "^4.23.8", "@uiw/react-codemirror": "^4.23.8",
"axios": "^1.7.9", "axios": "^1.7.9",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"global": "^4.4.0",
"html-to-image": "^1.11.13", "html-to-image": "^1.11.13",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"qrcode.react": "^4.2.0", "qrcode.react": "^4.2.0",
@ -30,9 +31,10 @@
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-error-boundary": "^5.0.0", "react-error-boundary": "^5.0.0",
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
"react-icons": "^5.4.0", "react-icons": "^5.5.0",
"react-intl": "^7.1.6", "react-intl": "^7.1.6",
"react-router": "^7.2.0", "react-router": "^7.2.0",
"react-scan": "^0.1.3",
"react-select": "^5.10.0", "react-select": "^5.10.0",
"react-tabs": "^6.1.0", "react-tabs": "^6.1.0",
"react-toastify": "^11.0.3", "react-toastify": "^11.0.3",
@ -46,6 +48,7 @@
"devDependencies": { "devDependencies": {
"@lezer/generator": "^1.7.2", "@lezer/generator": "^1.7.2",
"@playwright/test": "^1.50.1", "@playwright/test": "^1.50.1",
"@tailwindcss/vite": "^4.0.7",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"@types/node": "^22.13.4", "@types/node": "^22.13.4",
"@types/react": "^19.0.10", "@types/react": "^19.0.10",
@ -53,22 +56,20 @@
"@typescript-eslint/eslint-plugin": "^8.0.1", "@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1", "@typescript-eslint/parser": "^8.0.1",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20", "babel-plugin-react-compiler": "^19.0.0-beta-21e868a-20250216",
"babel-plugin-react-compiler": "^19.0.0-beta-30d8a17-20250209",
"eslint": "^9.20.1", "eslint": "^9.20.1",
"eslint-plugin-import": "^2.31.0", "eslint-plugin-import": "^2.31.0",
"eslint-plugin-react": "^7.37.4", "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-react-hooks": "^5.1.0",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.15.0", "globals": "^16.0.0",
"jest": "^29.7.0", "jest": "^29.7.0",
"postcss": "^8.5.2", "tailwindcss": "^4.0.7",
"tailwindcss": "^3.4.17",
"ts-jest": "^29.2.5", "ts-jest": "^29.2.5",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"typescript-eslint": "^8.24.1", "typescript-eslint": "^8.24.1",
"vite": "^6.1.0" "vite": "^6.1.1"
}, },
"overrides": { "overrides": {
"react": "^19.0.0" "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 { ModalLoader } from '@/components/Modal';
import { useAppLayoutStore, useMainHeight, useViewportHeight } from '@/stores/appLayout'; import { useAppLayoutStore, useMainHeight, useViewportHeight } from '@/stores/appLayout';
import { globals } from '@/utils/constants'; import { useDialogsStore } from '@/stores/dialogs';
import { NavigationState } from './Navigation/NavigationContext'; import { NavigationState } from './Navigation/NavigationContext';
import { Footer } from './Footer'; import { Footer } from './Footer';
@ -14,13 +14,14 @@ import { GlobalTooltips } from './GlobalTooltips';
import { MutationErrors } from './MutationErrors'; import { MutationErrors } from './MutationErrors';
import { Navigation } from './Navigation'; import { Navigation } from './Navigation';
function ApplicationLayout() { export function ApplicationLayout() {
const mainHeight = useMainHeight(); const mainHeight = useMainHeight();
const viewportHeight = useViewportHeight(); const viewportHeight = useViewportHeight();
const showScroll = useAppLayoutStore(state => !state.noScroll); const showScroll = useAppLayoutStore(state => !state.noScroll);
const noNavigationAnimation = useAppLayoutStore(state => state.noNavigationAnimation); const noNavigationAnimation = useAppLayoutStore(state => state.noNavigationAnimation);
const noNavigation = useAppLayoutStore(state => state.noNavigation); const noNavigation = useAppLayoutStore(state => state.noNavigation);
const noFooter = useAppLayoutStore(state => state.noFooter); const noFooter = useAppLayoutStore(state => state.noFooter);
const activeDialog = useDialogsStore(state => state.active);
return ( return (
<NavigationState> <NavigationState>
@ -41,11 +42,9 @@ function ApplicationLayout() {
<Navigation /> <Navigation />
<div <div
id={globals.main_scroll}
className='overflow-x-auto max-w-[100vw]' className='overflow-x-auto max-w-[100vw]'
style={{ style={{ maxHeight: viewportHeight }}
maxHeight: viewportHeight inert={activeDialog !== null}
}}
> >
<main className='cc-scroll-y' style={{ overflowY: showScroll ? 'scroll' : 'auto', minHeight: mainHeight }}> <main className='cc-scroll-y' style={{ overflowY: showScroll ? 'scroll' : 'auto', minHeight: mainHeight }}>
<GlobalLoader /> <GlobalLoader />
@ -58,5 +57,3 @@ function ApplicationLayout() {
</NavigationState> </NavigationState>
); );
} }
export default ApplicationLayout;

View File

@ -8,7 +8,7 @@ export function ErrorFallback() {
const router = useNavigate(); const router = useNavigate();
function resetErrorBoundary() { function resetErrorBoundary() {
Promise.resolve(router('/')).catch(console.log); Promise.resolve(router('/')).catch(console.error);
} }
return ( return (
<div className='flex flex-col gap-3 my-3 items-center antialiased' role='alert'> <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'; import { DialogType, useDialogsStore } from '@/stores/dialogs';
const DlgChangeInputSchema = React.lazy(() => import('@/features/oss/dialogs/DlgChangeInputSchema')); const DlgChangeInputSchema = React.lazy(() =>
const DlgChangeLocation = React.lazy(() => import('@/features/library/dialogs/DlgChangeLocation')); import('@/features/oss/dialogs/DlgChangeInputSchema').then(module => ({ default: module.DlgChangeInputSchema }))
const DlgCloneLibraryItem = React.lazy(() => import('@/features/library/dialogs/DlgCloneLibraryItem')); );
const DlgCreateCst = React.lazy(() => import('@/features/rsform/dialogs/DlgCreateCst')); const DlgChangeLocation = React.lazy(() =>
const DlgCreateOperation = React.lazy(() => import('@/features/oss/dialogs/DlgCreateOperation')); import('@/features/library/dialogs/DlgChangeLocation').then(module => ({
const DlgCreateVersion = React.lazy(() => import('@/features/library/dialogs/DlgCreateVersion')); default: module.DlgChangeLocation
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 DlgCloneLibraryItem = React.lazy(() =>
const DlgEditEditors = React.lazy(() => import('@/features/library/dialogs/DlgEditEditors')); import('@/features/library/dialogs/DlgCloneLibraryItem').then(module => ({
const DlgEditOperation = React.lazy(() => import('@/features/oss/dialogs/DlgEditOperation')); default: module.DlgCloneLibraryItem
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 DlgCreateCst = React.lazy(() =>
const DlgGraphParams = React.lazy(() => import('@/features/rsform/dialogs/DlgGraphParams')); import('@/features/rsform/dialogs/DlgCreateCst').then(module => ({ default: module.DlgCreateCst }))
const DlgInlineSynthesis = React.lazy(() => import('@/features/rsform/dialogs/DlgInlineSynthesis')); );
const DlgRelocateConstituents = React.lazy(() => import('@/features/oss/dialogs/DlgRelocateConstituents')); const DlgCreateOperation = React.lazy(() =>
const DlgRenameCst = React.lazy(() => import('@/features/rsform/dialogs/DlgRenameCst')); import('@/features/oss/dialogs/DlgCreateOperation').then(module => ({
const DlgShowAST = React.lazy(() => import('@/features/rsform/dialogs/DlgShowAST')); default: module.DlgCreateOperation
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 DlgCreateVersion = React.lazy(() =>
const DlgUploadRSForm = React.lazy(() => import('@/features/rsform/dialogs/DlgUploadRSForm')); 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 = () => { export const GlobalDialogs = () => {
const active = useDialogsStore(state => state.active); const active = useDialogsStore(state => state.active);
if (active === undefined) { if (active === null) {
return null; return null;
} }
switch (active) { switch (active) {

View File

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

View File

@ -1,32 +1,59 @@
'use client'; 'use client';
import InfoConstituenta from '@/features/rsform/components/InfoConstituenta'; import React, { Suspense } from 'react';
import { Tooltip } from '@/components/Container'; import { Tooltip } from '@/components/Container';
import { Loader } from '@/components/Loader'; import { Loader } from '@/components/Loader';
import { useTooltipsStore } from '@/stores/tooltips'; 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 = () => { export const GlobalTooltips = () => {
const hoverCst = useTooltipsStore(state => state.activeCst); const hoverCst = useTooltipsStore(state => state.activeCst);
const hoverOperation = useTooltipsStore(state => state.activeOperation);
return ( return (
<> <>
<Tooltip <Tooltip
float float
id={globals.tooltip} id={globalIDs.tooltip}
layer='z-topmost' layer='z-topmost'
place='right-start' place='right-start'
className='mt-8 max-w-[20rem] break-words' className='mt-8 max-w-[20rem] break-words'
/> />
<Tooltip <Tooltip
float float
id={globals.value_tooltip} id={globalIDs.value_tooltip}
layer='z-topmost' layer='z-topmost'
className='max-w-[calc(min(40rem,100dvw-2rem))] text-justify' className='max-w-[calc(min(40rem,100dvw-2rem))] text-justify'
/> />
<Tooltip clickable id={globals.constituenta_tooltip} layer='z-modalTooltip' className='max-w-[30rem]'> <Tooltip
{hoverCst ? <InfoConstituenta data={hoverCst} onClick={event => event.stopPropagation()} /> : <Loader />} 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> </Tooltip>
</> </>
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { QueryClient } from '@tanstack/react-query'; 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'; import { DELAYS } from './configuration';
declare module '@tanstack/react-query' { declare module '@tanstack/react-query' {

View File

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

View File

@ -1,8 +1,8 @@
import clsx from 'clsx'; 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. */ /** Indicates whether the divider is vertical. */
vertical?: boolean; vertical?: boolean;

View File

@ -1,12 +1,10 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { CProps } from '../props';
/** /**
* `flex` column container. * `flex` column container.
* This component is useful for creating vertical layouts with flexbox. * 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 ( return (
<div className={clsx('cc-column', className)} {...restProps}> <div className={clsx('cc-column', className)} {...restProps}>
{children} {children}

View File

@ -1,8 +1,8 @@
import clsx from 'clsx'; 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 of the overlay. */
id?: string; id?: string;

View File

@ -1,8 +1,8 @@
'use client'; 'use client';
import { ReactNode } from 'react'; import { type ReactNode } from 'react';
import { createPortal } from 'react-dom'; 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 clsx from 'clsx';
import { usePreferencesStore } from '@/stores/preferences'; import { usePreferencesStore } from '@/stores/preferences';

View File

@ -1,10 +1,10 @@
import clsx from 'clsx'; 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 to display first. */
icon?: React.ReactNode; icon?: React.ReactNode;
@ -32,7 +32,6 @@ export function Button({
disabled, disabled,
noBorder, noBorder,
noOutline, noOutline,
colors = 'clr-btn-default',
className, className,
...restProps ...restProps
}: ButtonProps) { }: ButtonProps) {
@ -43,20 +42,19 @@ export function Button({
className={clsx( className={clsx(
'inline-flex gap-2 items-center justify-center', 'inline-flex gap-2 items-center justify-center',
'select-none disabled:cursor-auto', '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-1': dense,
'px-3 py-1': !dense, 'px-3 py-1': !dense,
'cursor-progress': loading, 'cursor-progress': loading,
'cursor-pointer': !loading, 'cursor-pointer': !loading,
'outline-none': noOutline, 'outline-hidden': noOutline,
'clr-outline': !noOutline 'clr-outline': !noOutline
}, },
className, className
colors
)} )}
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined} data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml} data-tooltip-html={titleHtml}
data-tooltip-content={title} data-tooltip-content={title}
data-tooltip-hidden={hideTitle} data-tooltip-hidden={hideTitle}

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import clsx from 'clsx'; 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 to display in the button. */
text?: string; text?: string;

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
'use no memo'; 'use no memo';
import { Table } from '@tanstack/react-table'; import { type Table } from '@tanstack/react-table';
import { CheckboxTristate } from '../Input'; import { CheckboxTristate } from '../Input';
@ -9,7 +9,7 @@ interface SelectAllProps<TData> {
resetLastSelected: () => void; resetLastSelected: () => void;
} }
function SelectAll<TData>({ table, resetLastSelected }: SelectAllProps<TData>) { export function SelectAll<TData>({ table, resetLastSelected }: SelectAllProps<TData>) {
function handleChange(value: boolean | null) { function handleChange(value: boolean | null) {
resetLastSelected(); resetLastSelected();
table.toggleAllPageRowsSelected(value !== false); 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'; 'use no memo';
import { Row } from '@tanstack/react-table'; import { type Row } from '@tanstack/react-table';
import { Checkbox } from '../Input'; import { Checkbox } from '../Input';
interface SelectRowProps<TData> { interface SelectRowProps<TData> {
row: Row<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) { function handleChange(value: boolean) {
onChangeLastSelected(row.id); onChangeLastSelected(row.id);
row.toggleSelected(value); row.toggleSelected(value);
@ -17,5 +17,3 @@ function SelectRow<TData>({ row, onChangeLastSelected }: SelectRowProps<TData>)
return <Checkbox tabIndex={-1} value={row.getIsSelected()} onChange={handleChange} />; return <Checkbox tabIndex={-1} value={row.getIsSelected()} onChange={handleChange} />;
} }
export default SelectRow;

View File

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

View File

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

View File

@ -1,12 +1,12 @@
'use no memo'; '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> { interface TableFooterProps<TData> {
table: Table<TData>; table: Table<TData>;
} }
function TableFooter<TData>({ table }: TableFooterProps<TData>) { export function TableFooter<TData>({ table }: TableFooterProps<TData>) {
return ( return (
<tfoot> <tfoot>
{table.getFooterGroups().map((footerGroup: HeaderGroup<TData>) => ( {table.getFooterGroups().map((footerGroup: HeaderGroup<TData>) => (
@ -21,5 +21,3 @@ function TableFooter<TData>({ table }: TableFooterProps<TData>) {
</tfoot> </tfoot>
); );
} }
export default TableFooter;

View File

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

View File

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

View File

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

View File

@ -2,9 +2,9 @@ import clsx from 'clsx';
import { PARAMETER } from '@/utils/constants'; 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. */ /** Indicates whether the dropdown should stretch to the left. */
stretchLeft?: boolean; stretchLeft?: boolean;

View File

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

View File

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

View File

@ -2,7 +2,7 @@
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import useClickedOutside from '@/hooks/useClickedOutside'; import { useClickedOutside } from '@/hooks/useClickedOutside';
export function useDropdown() { export function useDropdown() {
const [isOpen, setIsOpen] = useState(false); 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'; import { PARAMETER } from '@/utils/constants';
const RADIUS = PARAMETER.graphNodeRadius + PARAMETER.graphNodePadding; 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 sourceY = props.sourceY - PARAMETER.graphNodeRadius - PARAMETER.graphHandleSize;
const targetY = props.targetY + 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 clsx from 'clsx';
import { ZodError } from 'zod'; import { ZodError } from 'zod';
import { AxiosError, isAxiosError } from '@/backend/apiTransport'; import { type AxiosError, isAxiosError } from '@/backend/apiTransport';
import { isResponseHtml } from '@/utils/utils'; import { isResponseHtml } from '@/utils/utils';
import { PrettyJson } from './View'; import { PrettyJson } from './View';
export type ErrorData = string | Error | AxiosError | ZodError | undefined | null; export type ErrorData = string | Error | AxiosError | ZodError;
interface InfoErrorProps { interface InfoErrorProps {
error: ErrorData; error: ErrorData;

View File

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

View File

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

View File

@ -5,11 +5,11 @@ import clsx from 'clsx';
import { Button } from '../Control'; import { Button } from '../Control';
import { IconUpload } from '../Icons'; import { IconUpload } from '../Icons';
import { CProps } from '../props'; import { type Titled } from '../props';
import { Label } from './Label'; 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 to display in file upload button. */
label: string; label: string;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,20 +2,20 @@
import clsx from 'clsx'; 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 { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils'; import { prepareTooltip } from '@/utils/utils';
import { Button, MiniButton, SubmitButton } from '../Control'; import { Button, MiniButton, SubmitButton } from '../Control';
import { IconClose } from '../Icons'; import { IconClose } from '../Icons';
import { CProps } from '../props'; import { type Styling } from '../props';
import { ModalBackdrop } from './ModalBackdrop'; import { ModalBackdrop } from './ModalBackdrop';
export interface ModalProps extends CProps.Styling { export interface ModalProps extends Styling {
/** Title of the modal window. */ /** Title of the modal window. */
header?: string; header?: string;
@ -115,7 +115,7 @@ export function ModalForm({
<div <div
className={clsx( 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-auto': !overflowVisible,
'overflow-visible': overflowVisible 'overflow-visible': overflowVisible

View File

@ -4,7 +4,7 @@ import clsx from 'clsx';
import { BadgeHelp } from '@/features/help'; import { BadgeHelp } from '@/features/help';
import useEscapeKey from '@/hooks/useEscapeKey'; import { useEscapeKey } from '@/hooks/useEscapeKey';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils'; import { prepareTooltip } from '@/utils/utils';
@ -13,7 +13,7 @@ import { Button, MiniButton } from '../Control';
import { IconClose } from '../Icons'; import { IconClose } from '../Icons';
import { ModalBackdrop } from './ModalBackdrop'; import { ModalBackdrop } from './ModalBackdrop';
import { ModalProps } from './ModalForm'; import { type ModalProps } from './ModalForm';
interface ModalViewProps extends ModalProps {} interface ModalViewProps extends ModalProps {}
@ -60,7 +60,7 @@ export function ModalView({
<div <div
className={clsx( 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-auto': !overflowVisible,
'overflow-visible': 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 { Tab as TabImpl } from 'react-tabs';
import clsx from 'clsx'; 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 to display in the tab. */
label?: string; label?: string;
} }
@ -23,11 +23,11 @@ export function TabLabel({ label, title, titleHtml, hideTitle, className, ...oth
'clr-hover cc-animate-color duration-150', 'clr-hover cc-animate-color duration-150',
'text-sm whitespace-nowrap font-controls', 'text-sm whitespace-nowrap font-controls',
'select-none hover:cursor-pointer', 'select-none hover:cursor-pointer',
'outline-none', 'outline-hidden',
className className
)} )}
tabIndex='-1' tabIndex='-1'
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined} data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml} data-tooltip-html={titleHtml}
data-tooltip-content={title} data-tooltip-content={title}
data-tooltip-hidden={hideTitle} data-tooltip-hidden={hideTitle}

View File

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

View File

@ -1,11 +1,9 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { CProps } from '@/components/props';
/** /**
* Wraps content in a div with a centered text. * 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 ( return (
<div className={clsx('p-3 flex flex-col items-center text-center select-none w-full', className)} {...restProps}> <div className={clsx('p-3 flex flex-col items-center text-center select-none w-full', className)} {...restProps}>
{children} {children}

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import clsx from 'clsx'; 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 of the component. */
id?: string; 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 { PARAMETER } from '@/utils/constants';
import { ValueIcon } from './ValueIcon'; import { ValueIcon } from './ValueIcon';
interface ValueStatsProps extends CProps.Styling, CProps.Titled { interface ValueStatsProps extends Styling, Titled {
/** Id of the component. */ /** Id of the component. */
id: string; id: string;

View File

@ -1,8 +1,7 @@
// =========== Module contains interfaces for common UI elements. ========== // =========== Module contains interfaces for common UI elements. ==========
import React from 'react'; import type React from 'react';
import { FieldError } from 'react-hook-form'; import { type FieldError } from 'react-hook-form';
export namespace CProps {
/** /**
* Represents an object that can have inline styles and CSS class names for styling. * Represents an object that can have inline styles and CSS class names for styling.
*/ */
@ -14,14 +13,6 @@ export namespace CProps {
className?: string; 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. * 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; 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. * Represents `button` component with optional title and HTML attributes.
*/ */
export type Button = Titled & Omit<React.ComponentProps<'button'>, 'children' | 'type'>; 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 { infoMsg } from '@/utils/labels';
import { import {
IChangePasswordDTO, type IChangePasswordDTO,
ICurrentUser, type ICurrentUser,
IPasswordTokenDTO, type IPasswordTokenDTO,
IRequestPasswordDTO, type IRequestPasswordDTO,
IResetPasswordDTO, type IResetPasswordDTO,
IUserLoginDTO type IUserLoginDTO
} from './types'; } from './types';
/** /**

View File

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

View File

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

View File

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

View File

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

View File

@ -8,17 +8,17 @@ import { urls, useConceptNavigation } from '@/app';
import { isAxiosError } from '@/backend/apiTransport'; import { isAxiosError } from '@/backend/apiTransport';
import { SubmitButton, TextURL } from '@/components/Control'; import { SubmitButton, TextURL } from '@/components/Control';
import { ErrorData } from '@/components/InfoError'; import { type ErrorData } from '@/components/InfoError';
import { TextInput } from '@/components/Input'; import { TextInput } from '@/components/Input';
import useQueryStrings from '@/hooks/useQueryStrings'; import { useQueryStrings } from '@/hooks/useQueryStrings';
import { resources } from '@/utils/constants'; import { resources } from '@/utils/constants';
import { IUserLoginDTO, schemaUserLogin } from '../backend/types'; import { type IUserLoginDTO, schemaUserLogin } from '../backend/types';
import { useAuthSuspense } from '../backend/useAuth'; import { useAuthSuspense } from '../backend/useAuth';
import { useLogin } from '../backend/useLogin'; import { useLogin } from '../backend/useLogin';
import { ExpectedAnonymous } from '../components/ExpectedAnonymous'; import { ExpectedAnonymous } from '../components/ExpectedAnonymous';
function LoginPage() { export function LoginPage() {
const router = useConceptNavigation(); const router = useConceptNavigation();
const query = useQueryStrings(); const query = useQueryStrings();
const initialName = query.get('username') ?? ''; const initialName = query.get('username') ?? '';
@ -92,8 +92,6 @@ function LoginPage() {
); );
} }
export default LoginPage;
// ====== Internals ========= // ====== Internals =========
function ServerError({ error }: { error: ErrorData }): React.ReactElement | null { function ServerError({ error }: { error: ErrorData }): React.ReactElement | null {
if (isAxiosError(error) && error.response && error.response.status === 400) { 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 { isAxiosError } from '@/backend/apiTransport';
import { SubmitButton } from '@/components/Control'; import { SubmitButton } from '@/components/Control';
import { ErrorData, InfoError } from '@/components/InfoError'; import { type ErrorData, InfoError } from '@/components/InfoError';
import { TextInput } from '@/components/Input'; import { TextInput } from '@/components/Input';
import { Loader } from '@/components/Loader'; import { Loader } from '@/components/Loader';
import useQueryStrings from '@/hooks/useQueryStrings'; import { useQueryStrings } from '@/hooks/useQueryStrings';
import { useResetPassword } from '../backend/useResetPassword'; import { useResetPassword } from '../backend/useResetPassword';
@ -24,9 +24,6 @@ export function Component() {
const [newPassword, setNewPassword] = useState(''); const [newPassword, setNewPassword] = useState('');
const [newPasswordRepeat, setNewPasswordRepeat] = useState(''); const [newPasswordRepeat, setNewPasswordRepeat] = useState('');
const passwordColor =
!!newPassword && !!newPasswordRepeat && newPassword !== newPasswordRepeat ? 'bg-warn-100' : 'clr-input';
const canSubmit = !!newPassword && !!newPasswordRepeat && newPassword === newPasswordRepeat; const canSubmit = !!newPassword && !!newPasswordRepeat && newPassword === newPasswordRepeat;
function handleSubmit(event: React.FormEvent<HTMLFormElement>) { function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
@ -61,7 +58,6 @@ export function Component() {
label='Новый пароль' label='Новый пароль'
autoComplete='new-password' autoComplete='new-password'
allowEnter allowEnter
colors={passwordColor}
value={newPassword} value={newPassword}
onChange={event => { onChange={event => {
setNewPassword(event.target.value); setNewPassword(event.target.value);
@ -73,7 +69,6 @@ export function Component() {
label='Повторите новый' label='Повторите новый'
autoComplete='new-password' autoComplete='new-password'
allowEnter allowEnter
colors={passwordColor}
value={newPasswordRepeat} value={newPasswordRepeat}
onChange={event => { onChange={event => {
setNewPasswordRepeat(event.target.value); setNewPasswordRepeat(event.target.value);

View File

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

View File

@ -1,17 +1,19 @@
import React, { Suspense } from 'react'; import React, { Suspense } from 'react';
import { PlacesType, Tooltip } from '@/components/Container'; import { type PlacesType, Tooltip } from '@/components/Container';
import { TextURL } from '@/components/Control'; import { TextURL } from '@/components/Control';
import { IconHelp } from '@/components/Icons'; import { IconHelp } from '@/components/Icons';
import { Loader } from '@/components/Loader'; import { Loader } from '@/components/Loader';
import { CProps } from '@/components/props'; import { type Styling } from '@/components/props';
import { usePreferencesStore } from '@/stores/preferences'; 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 to display in a tooltip. */
topic: HelpTopic; topic: HelpTopic;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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