Minor UI improvements and CSS refactoring

This commit is contained in:
IRBorisov 2024-01-06 03:15:02 +03:00
parent d64e4a9894
commit ead0418564
68 changed files with 816 additions and 653 deletions

View File

@ -108,6 +108,7 @@
"signup", "signup",
"Slng", "Slng",
"SMALLPR", "SMALLPR",
"Stylesheet",
"tagset", "tagset",
"tailwindcss", "tailwindcss",
"tanstack", "tanstack",

View File

@ -6,7 +6,7 @@ import { IoLibrary } from 'react-icons/io5';
import { EducationIcon } from '@/components/Icons'; import { EducationIcon } from '@/components/Icons';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { animateNavigation } from '@/utils/animations'; import { animateNavigation } from '@/styling/animations';
import Logo from './Logo'; import Logo from './Logo';
import NavigationButton from './NavigationButton'; import NavigationButton from './NavigationButton';
@ -23,7 +23,14 @@ function Navigation() {
const navigateCreateNew = () => router.push('/library/create'); const navigateCreateNew = () => router.push('/library/create');
return ( return (
<nav className={clsx('z-navigation', 'sticky top-0 left-0 right-0', 'clr-app', 'select-none')}> <nav
className={clsx(
'z-navigation', // prettier: split lines
'sticky top-0 left-0 right-0',
'clr-app',
'select-none'
)}
>
<ToggleNavigationButton /> <ToggleNavigationButton />
<motion.div <motion.div
className={clsx('pl-2 pr-[0.9rem] h-[3rem]', 'flex justify-between', 'shadow-border')} className={clsx('pl-2 pr-[0.9rem] h-[3rem]', 'flex justify-between', 'shadow-border')}

View File

@ -3,7 +3,7 @@ import { motion } from 'framer-motion';
import { RiPushpinFill, RiUnpinLine } from 'react-icons/ri'; import { RiPushpinFill, RiUnpinLine } from 'react-icons/ri';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { animateNavigationToggle } from '@/utils/animations'; import { animateNavigationToggle } from '@/styling/animations';
function ToggleNavigationButton() { function ToggleNavigationButton() {
const { noNavigationAnimation, toggleNoNavigation } = useConceptTheme(); const { noNavigationAnimation, toggleNoNavigation } = useConceptTheme();

View File

@ -0,0 +1,22 @@
import { motion } from 'framer-motion';
import { animateFadeIn } from '@/styling/animations';
import { CProps } from './props';
interface AnimateFadeInProps extends CProps.AnimatedDiv {}
function AnimateFadeIn({ children, ...restProps }: AnimateFadeInProps) {
return (
<motion.div
initial={{ ...animateFadeIn.initial }}
animate={{ ...animateFadeIn.animate }}
exit={{ ...animateFadeIn.exit }}
{...restProps}
>
{children}
</motion.div>
);
}
export default AnimateFadeIn;

View File

@ -1,9 +1,9 @@
import clsx from 'clsx'; import clsx from 'clsx';
import ConstituentaTooltip from '@/components/Help/ConstituentaTooltip'; import ConstituentaTooltip from '@/components/ConstituentaTooltip';
import { IConstituenta } from '@/models/rsform'; import { IConstituenta } from '@/models/rsform';
import { isMockCst } from '@/models/rsformAPI'; import { isMockCst } from '@/models/rsformAPI';
import { colorFgCstStatus, IColorTheme } from '@/utils/color'; import { colorFgCstStatus, IColorTheme } from '@/styling/color';
interface ConstituentaBadgeProps { interface ConstituentaBadgeProps {
prefixID?: string; prefixID?: string;

View File

@ -10,6 +10,7 @@ import { prefixes } from '@/utils/constants';
import { describeConstituenta } from '@/utils/labels'; import { describeConstituenta } from '@/utils/labels';
import ConstituentaBadge from './ConstituentaBadge'; import ConstituentaBadge from './ConstituentaBadge';
import FlexColumn from './ui/FlexColumn';
interface ConstituentaPickerProps { interface ConstituentaPickerProps {
prefixID?: string; prefixID?: string;
@ -94,10 +95,10 @@ function ConstituentaPicker({
columns={columns} columns={columns}
conditionalRowStyles={conditionalRowStyles} conditionalRowStyles={conditionalRowStyles}
noDataComponent={ noDataComponent={
<span className='p-2 min-h-[5rem] flex flex-col justify-center text-center'> <FlexColumn className='p-3 items-center min-h-[6rem]'>
<p>Список конституент пуст</p> <p>Список конституент пуст</p>
<p>Измените параметры фильтра</p> <p>Измените параметры фильтра</p>
</span> </FlexColumn>
} }
onRowClicked={onSelectValue} onRowClicked={onSelectValue}
/> />

View File

@ -14,7 +14,6 @@ import {
useReactTable, useReactTable,
type VisibilityState type VisibilityState
} from '@tanstack/react-table'; } from '@tanstack/react-table';
import clsx from 'clsx';
import { useState } from 'react'; import { useState } from 'react';
import { CProps } from '../props'; import { CProps } from '../props';
@ -122,7 +121,7 @@ function DataTable<TData extends RowData>({
const isEmpty = tableImpl.getRowModel().rows.length === 0; const isEmpty = tableImpl.getRowModel().rows.length === 0;
return ( return (
<div className={clsx(className)} style={style}> <div className={className} style={style}>
<table className='w-full'> <table className='w-full'>
{!noHeader ? ( {!noHeader ? (
<TableHeader <TableHeader

View File

@ -52,14 +52,14 @@ function TableBody<TData>({
style={conditionalRowStyles && getRowStyles(row)} style={conditionalRowStyles && getRowStyles(row)}
> >
{enableRowSelection ? ( {enableRowSelection ? (
<td key={`select-${row.id}`} className='pl-3 pr-1 border-y'> <td key={`select-${row.id}`} className='pl-3 pr-1 border-y align-middle'>
<SelectRow row={row} /> <SelectRow row={row} />
</td> </td>
) : null} ) : null}
{row.getVisibleCells().map((cell: Cell<TData, unknown>) => ( {row.getVisibleCells().map((cell: Cell<TData, unknown>) => (
<td <td
key={cell.id} key={cell.id}
className='px-2 border-y' className='px-2 border-y align-middle'
style={{ style={{
cursor: onRowClicked || onRowDoubleClicked ? 'pointer' : 'auto', cursor: onRowClicked || onRowDoubleClicked ? 'pointer' : 'auto',
paddingBottom: dense ? '0.25rem' : '0.5rem', paddingBottom: dense ? '0.25rem' : '0.5rem',

View File

@ -13,7 +13,7 @@ interface TableHeaderProps<TData> {
function TableHeader<TData>({ table, headPosition, enableRowSelection, enableSorting }: TableHeaderProps<TData>) { function TableHeader<TData>({ table, headPosition, enableRowSelection, enableSorting }: TableHeaderProps<TData>) {
return ( return (
<thead <thead
className={`clr-app shadow-border`} className='clr-app shadow-border'
style={{ style={{
top: headPosition, top: headPosition,
position: 'sticky' position: 'sticky'
@ -22,7 +22,7 @@ function TableHeader<TData>({ table, headPosition, enableRowSelection, enableSor
{table.getHeaderGroups().map((headerGroup: HeaderGroup<TData>) => ( {table.getHeaderGroups().map((headerGroup: HeaderGroup<TData>) => (
<tr key={headerGroup.id}> <tr key={headerGroup.id}>
{enableRowSelection ? ( {enableRowSelection ? (
<th className='pl-3 pr-1'> <th className='pl-3 pr-1 align-middle'>
<SelectAll table={table} /> <SelectAll table={table} />
</th> </th>
) : null} ) : null}

View File

@ -2,7 +2,7 @@ import clsx from 'clsx';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { GramData } from '@/models/language'; import { GramData } from '@/models/language';
import { colorFgGrammeme } from '@/utils/color'; import { colorFgGrammeme } from '@/styling/color';
import { labelGrammeme } from '@/utils/labels'; import { labelGrammeme } from '@/utils/labels';
interface GrammemeBadgeProps { interface GrammemeBadgeProps {

View File

@ -1,15 +1,16 @@
import { BiInfoCircle } from 'react-icons/bi'; import { BiInfoCircle } from 'react-icons/bi';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
import Tooltip from '@/components/ui/Tooltip'; import Tooltip, { PlacesType } from '@/components/ui/Tooltip';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import InfoTopic from '../InfoTopic';
import { CProps } from '../props'; import { CProps } from '../props';
import InfoTopic from './InfoTopic';
interface HelpButtonProps extends CProps.Styling { interface HelpButtonProps extends CProps.Styling {
topic: HelpTopic; topic: HelpTopic;
offset?: number; offset?: number;
place?: PlacesType;
} }
function HelpButton({ topic, ...restProps }: HelpButtonProps) { function HelpButton({ topic, ...restProps }: HelpButtonProps) {

View File

@ -4,7 +4,7 @@ import { urls } from '@/utils/constants';
function HelpExteor() { function HelpExteor() {
// prettier-ignore // prettier-ignore
return ( return (
<div> <div className='flex flex-col gap-2'>
<h1>Экстеор</h1> <h1>Экстеор</h1>
<p>Экстеор 4.9 редактор текстов систем понятий эксплицированных в родах структур, используемых для создания проектов систем организационного управления. Экстеор является идейным предком Портала.</p> <p>Экстеор 4.9 редактор текстов систем понятий эксплицированных в родах структур, используемых для создания проектов систем организационного управления. Экстеор является идейным предком Портала.</p>
<p>Портал превосходит Экстеор в части редактирования экспликаций, но функции синтеза и вычисления интерпретации пока доступны только в Экстеоре. Также следует использовать Экстеор для выгрузки экспликаций в Word для последующей печати.</p> <p>Портал превосходит Экстеор в части редактирования экспликаций, но функции синтеза и вычисления интерпретации пока доступны только в Экстеоре. Также следует использовать Экстеор для выгрузки экспликаций в Word для последующей печати.</p>

View File

@ -4,7 +4,7 @@ import { FiBell } from 'react-icons/fi';
function HelpLibrary() { function HelpLibrary() {
// prettier-ignore // prettier-ignore
return ( return (
<div className='max-w-[80rem]'> <div>
<h1>Библиотека концептуальных схем</h1> <h1>Библиотека концептуальных схем</h1>
<p>В библиотеки собраны различные концептуальные схемы.</p> <p>В библиотеки собраны различные концептуальные схемы.</p>
<p>Группировка и классификации схем на данный момент не проводится.</p> <p>Группировка и классификации схем на данный момент не проводится.</p>

View File

@ -4,7 +4,7 @@ import { urls } from '@/utils/constants';
function HelpMain() { function HelpMain() {
// prettier-ignore // prettier-ignore
return ( return (
<div className='max-w-[60rem]'> <div>
<h1>Портал</h1> <h1>Портал</h1>
<p className=''>Портал позволяет анализировать предметные области, формально записывать системы определений (концептуальные схемы) и синтезировать их с помощью математического аппарата родов структур.</p> <p className=''>Портал позволяет анализировать предметные области, формально записывать системы определений (концептуальные схемы) и синтезировать их с помощью математического аппарата родов структур.</p>
<p className='mt-4 mb-1 text-center'><b>Основные разделы</b></p> <p className='mt-4 mb-1 text-center'><b>Основные разделы</b></p>

View File

@ -1,7 +1,7 @@
function HelpRSTemplates() { function HelpRSTemplates() {
// prettier-ignore // prettier-ignore
return ( return (
<div className='flex flex-col gap-2 pb-2 max-w-[80rem]'> <div className='flex flex-col gap-2 pb-2'>
<h1>Банк выражений</h1> <h1>Банк выражений</h1>
<p>Портал предоставляет быстрый доступ к часто используемым выражениям с помощью функции создания конституенты из шаблона.</p> <p>Портал предоставляет быстрый доступ к часто используемым выражениям с помощью функции создания конституенты из шаблона.</p>
<p>Источником шаблонов является <b>Банк выражений</b>, содержащий параметризованные понятия и утверждения, сгруппированные по разделам.</p> <p>Источником шаблонов является <b>Банк выражений</b>, содержащий параметризованные понятия и утверждения, сгруппированные по разделам.</p>

View File

@ -1,7 +1,7 @@
function HelpTerminologyControl() { function HelpTerminologyControl() {
// prettier-ignore // prettier-ignore
return ( return (
<div className='flex flex-col gap-1 max-w-[80rem]'> <div className='flex flex-col gap-2'>
<h1>Терминологизация</h1> <h1>Терминологизация</h1>
<p>Портал позволяет контролировать употребление терминов, привязанных к сущностям в концептуальных схемах.</p> <p>Портал позволяет контролировать употребление терминов, привязанных к сущностям в концептуальных схемах.</p>
<p>Для этого используется механизм текстовых отсылок: <i>использование термина</i> и <i>связывание слов.</i></p> <p>Для этого используется механизм текстовых отсылок: <i>использование термина</i> и <i>связывание слов.</i></p>

View File

@ -1,7 +1,9 @@
import { IConstituenta } from '@/models/rsform'; import { IConstituenta } from '@/models/rsform';
import { labelCstTypification } from '@/utils/labels'; import { labelCstTypification } from '@/utils/labels';
interface InfoConstituentaProps extends React.HTMLAttributes<HTMLDivElement> { import { CProps } from './props';
interface InfoConstituentaProps extends CProps.Div {
data: IConstituenta; data: IConstituenta;
} }

View File

@ -2,7 +2,7 @@ import clsx from 'clsx';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { CstClass } from '@/models/rsform'; import { CstClass } from '@/models/rsform';
import { colorBgCstClass } from '@/utils/color'; import { colorBgCstClass } from '@/styling/color';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
import { describeCstClass, labelCstClass } from '@/utils/labels'; import { describeCstClass, labelCstClass } from '@/utils/labels';

View File

@ -2,7 +2,7 @@ import clsx from 'clsx';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { ExpressionStatus } from '@/models/rsform'; import { ExpressionStatus } from '@/models/rsform';
import { colorBgCstStatus } from '@/utils/color'; import { colorBgCstStatus } from '@/styling/color';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
import { describeExpressionStatus, labelExpressionStatus } from '@/utils/labels'; import { describeExpressionStatus, labelExpressionStatus } from '@/utils/labels';

View File

@ -1,17 +1,17 @@
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import HelpAPI from './HelpAPI'; import HelpAPI from './Help/HelpAPI';
import HelpConstituenta from './HelpConstituenta'; import HelpConstituenta from './Help/HelpConstituenta';
import HelpExteor from './HelpExteor'; import HelpExteor from './Help/HelpExteor';
import HelpLibrary from './HelpLibrary'; import HelpLibrary from './Help/HelpLibrary';
import HelpMain from './HelpMain'; import HelpMain from './Help/HelpMain';
import HelpPrivacy from './HelpPrivacy'; import HelpPrivacy from './Help/HelpPrivacy';
import HelpRSFormItems from './HelpRSFormItems'; import HelpRSFormItems from './Help/HelpRSFormItems';
import HelpRSFormMeta from './HelpRSFormMeta'; import HelpRSFormMeta from './Help/HelpRSFormMeta';
import HelpRSLang from './HelpRSLang'; import HelpRSLang from './Help/HelpRSLang';
import HelpRSTemplates from './HelpRSTemplates'; import HelpRSTemplates from './Help/HelpRSTemplates';
import HelpTermGraph from './HelpTermGraph'; import HelpTermGraph from './Help/HelpTermGraph';
import HelpTerminologyControl from './HelpTerminologyControl'; import HelpTerminologyControl from './Help/HelpTerminologyControl';
interface InfoTopicProps { interface InfoTopicProps {
topic: HelpTopic; topic: HelpTopic;

View File

@ -5,7 +5,7 @@ import { useMemo, useState } from 'react';
import { Document, Page } from 'react-pdf'; import { Document, Page } from 'react-pdf';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
import { graphLightT } from '@/utils/color'; import { graphLightT } from '@/styling/color';
import Overlay from '../ui/Overlay'; import Overlay from '../ui/Overlay';
import PageControls from './PageControls'; import PageControls from './PageControls';

View File

@ -1,7 +1,7 @@
import { bracketMatching, MatchResult } from '@codemirror/language'; import { bracketMatching, MatchResult } from '@codemirror/language';
import { Decoration, EditorView } from '@codemirror/view'; import { Decoration, EditorView } from '@codemirror/view';
import { bracketsDarkT, bracketsLightT } from '@/utils/color'; import { bracketsDarkT, bracketsLightT } from '@/styling/color';
const matchingMark = Decoration.mark({ class: 'cc-matchingBracket' }); const matchingMark = Decoration.mark({ class: 'cc-matchingBracket' });
const nonMatchingMark = Decoration.mark({ class: 'cc-nonmatchingBracket' }); const nonMatchingMark = Decoration.mark({ class: 'cc-nonmatchingBracket' });

View File

@ -10,7 +10,7 @@ import {
findContainedNodes, findContainedNodes,
findEnvelopingNodes findEnvelopingNodes
} from '@/utils/codemirror'; } from '@/utils/codemirror';
import { IColorTheme } from '@/utils/color'; import { IColorTheme } from '@/styling/color';
import { ReferenceTokens } from './parse'; import { ReferenceTokens } from './parse';
import { RefEntity, RefSyntactic } from './parse/parser.terms'; import { RefEntity, RefSyntactic } from './parse/parser.terms';

View File

@ -38,4 +38,5 @@ export namespace CProps {
export type Input = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>; export type Input = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
export type AnimatedButton = Omit<HTMLMotionProps<'button'>, 'type'>; export type AnimatedButton = Omit<HTMLMotionProps<'button'>, 'type'>;
export type AnimatedDiv = HTMLMotionProps<'div'>;
} }

View File

@ -37,7 +37,13 @@ function Checkbox({ id, disabled, label, title, className, value, setValue, ...r
<button <button
type='button' type='button'
id={id} id={id}
className={clsx('flex items-center gap-2', 'outline-none', 'text-start', cursor, className)} className={clsx(
'flex items-center gap-2', // prettier: split lines
'outline-none',
'text-start',
cursor,
className
)}
disabled={disabled} disabled={disabled}
onClick={handleClick} onClick={handleClick}
data-tooltip-id={title ? globalIDs.tooltip : undefined} data-tooltip-id={title ? globalIDs.tooltip : undefined}
@ -45,10 +51,14 @@ function Checkbox({ id, disabled, label, title, className, value, setValue, ...r
{...restProps} {...restProps}
> >
<div <div
className={clsx('max-w-[1rem] min-w-[1rem] h-4', 'border rounded-sm', { className={clsx(
'clr-primary': value !== false, 'max-w-[1rem] min-w-[1rem] h-4', // prettier: split lines
'clr-app': value === false 'border rounded-sm',
})} {
'clr-primary': value !== false,
'clr-app': value === false
}
)}
> >
{value ? ( {value ? (
<div className='mt-[1px] ml-[1px]'> <div className='mt-[1px] ml-[1px]'>

View File

@ -49,7 +49,12 @@ function CheckboxTristate({
<button <button
type='button' type='button'
id={id} id={id}
className={clsx('flex items-center gap-2 text-start', 'outline-none', cursor, className)} className={clsx(
'flex items-center gap-2 text-start', // prettier: split lines
'outline-none',
cursor,
className
)}
disabled={disabled} disabled={disabled}
onClick={handleClick} onClick={handleClick}
data-tooltip-id={title ? globalIDs.tooltip : undefined} data-tooltip-id={title ? globalIDs.tooltip : undefined}
@ -57,10 +62,14 @@ function CheckboxTristate({
{...restProps} {...restProps}
> >
<div <div
className={clsx('w-4 h-4', 'border rounded-sm', { className={clsx(
'clr-primary': value !== false, 'w-4 h-4', // prettier: split lines
'clr-app': value === false 'border rounded-sm',
})} {
'clr-primary': value !== false,
'clr-app': value === false
}
)}
> >
{value ? ( {value ? (
<div className='mt-[1px] ml-[1px]'> <div className='mt-[1px] ml-[1px]'>

View File

@ -1,7 +1,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { animateDropdown } from '@/utils/animations'; import { animateDropdown } from '@/styling/animations';
import { CProps } from '../props'; import { CProps } from '../props';

View File

@ -1,7 +1,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { animateDropdownItem } from '@/utils/animations'; import { animateDropdownItem } from '@/styling/animations';
import { globalIDs } from '@/utils/constants'; import { globalIDs } from '@/utils/constants';
import { CProps } from '../props'; import { CProps } from '../props';

View File

@ -1,7 +1,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { animateDropdownItem } from '@/utils/animations'; import { animateDropdownItem } from '@/styling/animations';
import Checkbox from './Checkbox'; import Checkbox from './Checkbox';

View File

@ -6,7 +6,7 @@ import { useRef } from 'react';
import { BiX } from 'react-icons/bi'; import { BiX } from 'react-icons/bi';
import useEscapeKey from '@/hooks/useEscapeKey'; import useEscapeKey from '@/hooks/useEscapeKey';
import { animateModal } from '@/utils/animations'; import { animateModal } from '@/styling/animations';
import { CProps } from '../props'; import { CProps } from '../props';
import Button from './Button'; import Button from './Button';

View File

@ -4,7 +4,7 @@ import { useMemo } from 'react';
import Select, { GroupBase, Props, StylesConfig } from 'react-select'; import Select, { GroupBase, Props, StylesConfig } from 'react-select';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { selectDarkT, selectLightT } from '@/utils/color'; import { selectDarkT, selectLightT } from '@/styling/color';
export interface SelectMultiProps<Option, Group extends GroupBase<Option> = GroupBase<Option>> export interface SelectMultiProps<Option, Group extends GroupBase<Option> = GroupBase<Option>>
extends Omit<Props<Option, true, Group>, 'theme' | 'menuPortalTarget'> { extends Omit<Props<Option, true, Group>, 'theme' | 'menuPortalTarget'> {

View File

@ -4,7 +4,7 @@ import { useMemo } from 'react';
import Select, { GroupBase, Props, StylesConfig } from 'react-select'; import Select, { GroupBase, Props, StylesConfig } from 'react-select';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { selectDarkT, selectLightT } from '@/utils/color'; import { selectDarkT, selectLightT } from '@/styling/color';
interface SelectSingleProps<Option, Group extends GroupBase<Option> = GroupBase<Option>> interface SelectSingleProps<Option, Group extends GroupBase<Option> = GroupBase<Option>>
extends Omit<Props<Option, false, Group>, 'theme' | 'menuPortalTarget'> { extends Omit<Props<Option, false, Group>, 'theme' | 'menuPortalTarget'> {

View File

@ -7,6 +7,8 @@ import { ITooltip, Tooltip as TooltipImpl } from 'react-tooltip';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
export type { PlacesType } from 'react-tooltip';
interface TooltipProps extends Omit<ITooltip, 'variant'> { interface TooltipProps extends Omit<ITooltip, 'variant'> {
layer?: string; layer?: string;
text?: string; text?: string;

View File

@ -5,8 +5,8 @@ import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useSt
import Tooltip from '@/components/ui/Tooltip'; import Tooltip from '@/components/ui/Tooltip';
import useLocalStorage from '@/hooks/useLocalStorage'; import useLocalStorage from '@/hooks/useLocalStorage';
import { animationDuration } from '@/utils/animations'; import { animationDuration } from '@/styling/animations';
import { darkT, IColorTheme, lightT } from '@/utils/color'; import { darkT, IColorTheme, lightT } from '@/styling/color';
import { globalIDs } from '@/utils/constants'; import { globalIDs } from '@/utils/constants';
interface IThemeContext { interface IThemeContext {

View File

@ -6,8 +6,8 @@ import GraphUI, { GraphEdge, GraphNode } from '@/components/GraphUI';
import Modal, { ModalProps } from '@/components/ui/Modal'; import Modal, { ModalProps } from '@/components/ui/Modal';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { SyntaxTree } from '@/models/rslang'; import { SyntaxTree } from '@/models/rslang';
import { graphDarkT, graphLightT } from '@/utils/color'; import { graphDarkT, graphLightT } from '@/styling/color';
import { colorBgSyntaxTree } from '@/utils/color'; import { colorBgSyntaxTree } from '@/styling/color';
import { resources } from '@/utils/constants'; import { resources } from '@/utils/constants';
import { labelSyntaxTree } from '@/utils/labels'; import { labelSyntaxTree } from '@/utils/labels';

View File

@ -1,361 +1,4 @@
@import 'react-toastify/dist/ReactToastify.css'; @import 'styling/setup.css';
@import 'styling/styles.css';
@tailwind base; @import 'styling/imports.css';
@tailwind components; @import 'styling/overrides.css';
@tailwind utilities;
/* prettier-ignore */
:root {
--font-ui: 'Geologica', sans-serif;
--font-main: 'Rubik', 'Noto Sans Math', 'Noto Sans Symbols 2', 'Segoe UI Symbol', sans-serif;
--font-math: 'Noto Sans Math', 'Noto Sans Symbols 2', 'Segoe UI Symbol', sans-serif;
/* Light Theme */
--cl-bg-120: hsl(000, 000%, 100%);
--cl-bg-100: hsl(000, 000%, 098%);
--cl-bg-80: hsl(000, 000%, 094%);
--cl-bg-60: hsl(000, 000%, 091%);
--cl-bg-40: hsl(000, 000%, 080%);
--cl-fg-60: hsl(000, 000%, 065%);
--cl-fg-80: hsl(000, 000%, 047%);
--cl-fg-100: hsl(000, 000%, 000%);
--cl-prim-bg-100: hsl(220, 100%, 060%);
--cl-prim-bg-80: hsl(220, 080%, 092%);
--cl-prim-bg-60: hsl(190, 080%, 094%);
--cl-prim-fg-80: hsl(220, 100%, 050%);
--cl-prim-fg-100: hsl(000, 000%, 100%);
--cl-red-bg-100: hsl(000, 100%, 095%);
--cl-red-fg-100: hsl(000, 072%, 051%);
--cl-green-fg-100: hsl(120, 080%, 37%);
/* Dark Theme */
--cd-bg-120: hsl(000, 000%, 005%);
--cd-bg-100: hsl(000, 000%, 009%);
--cd-bg-80: hsl(000, 000%, 015%);
--cd-bg-60: hsl(000, 000%, 022%);
--cd-bg-40: hsl(000, 000%, 035%);
--cd-fg-60: hsl(000, 000%, 055%);
--cd-fg-80: hsl(000, 000%, 080%);
--cd-fg-100: hsl(000, 000%, 093%);
--cd-prim-bg-100: hsl(267, 050%, 050%);
--cd-prim-bg-80: hsl(267, 050%, 032%);
--cd-prim-bg-60: hsl(269, 030%, 028%);
--cd-prim-fg-80: hsl(267, 070%, 070%);
--cd-prim-fg-100: hsl(000, 000%, 100%);
--cd-red-bg-100: hsl(000, 100%, 015%);
--cd-red-fg-100: hsl(000, 080%, 055%);
--cd-green-fg-100: hsl(120, 080%, 042%);
/* Import overrides */
--toastify-color-dark: var(--cd-bg-60);
}
/* Depth layers */
.z-bottom {
z-index: 0;
}
.z-pop {
z-index: 10;
}
:is(.z-sticky, .sticky) {
z-index: 20;
}
.z-tooltip {
z-index: 30;
}
.z-navigation {
z-index: 50;
}
.z-modal {
z-index: 60;
}
.z-modal-controls {
z-index: 70;
}
.z-modal-top {
z-index: 80;
}
.z-modal-tooltip {
z-index: 90;
}
.z-topmost {
z-index: 99;
}
:root {
font-family: var(--font-main);
color: var(--cl-fg-100);
border-color: var(--cl-bg-40);
background-color: var(--cl-bg-100);
&.dark {
color: var(--cd-fg-100);
border-color: var(--cd-bg-40);
background-color: var(--cd-bg-100);
}
}
:focus {
outline-width: 2px;
outline-style: solid;
outline-color: transparent;
.dark & {
outline-color: transparent;
}
}
::selection {
background: var(--cl-prim-bg-60);
.dark & {
background: var(--cd-prim-bg-60);
}
tr :hover& {
background: var(--cl-red-bg-100);
.dark & {
background: var(--cd-red-bg-100);
}
}
}
::placeholder {
color: var(--cl-fg-60);
.dark & {
color: var(--cd-fg-60);
}
}
[data-color-scheme='dark'] {
color-scheme: dark;
}
[data-color-scheme='light'] {
color-scheme: light;
}
@layer utilities {
.font-controls {
font-family: var(--font-ui);
font-weight: 600;
font-variant: small-caps;
}
.font-math {
font-family: var(--font-math);
}
}
@layer components {
h1 {
@apply text-lg font-semibold text-center;
}
h2 {
@apply font-semibold text-center;
}
b {
@apply font-semibold;
}
.border {
@apply rounded;
}
.shadow-border {
@apply shadow-sm shadow-[var(--cl-bg-40)] dark:shadow-[var(--cd-bg-40)];
}
.clr-modal-backdrop {
opacity: 0.75;
}
:is(
.clr-border,
.border,
.border-x,
.border-y,
.border-b,
.border-t,
.border-l,
.border-r,
.border-2,
.border-x-2,
.border-y-2,
.border-b-2,
.border-t-2,
.border-l-2,
.border-r-2,
.divide-x,
.divide-y,
.divide-x-2,
.divide-y-2
) {
border-color: var(--cl-bg-40);
@apply divide-inherit;
.dark & {
border-color: var(--cd-bg-40);
}
}
:is(.clr-app, .clr-footer, .clr-modal-backdrop, .clr-btn-nav, .clr-input:disabled) {
background-color: var(--cl-bg-100);
.dark & {
background-color: var(--cd-bg-100);
}
}
:is(.clr-input) {
background-color: var(--cl-bg-120);
.dark & {
background-color: var(--cd-bg-120);
}
}
:is(.clr-controls, .clr-tab, .clr-btn-default) {
background-color: var(--cl-bg-80);
.dark & {
background-color: var(--cd-bg-80);
}
}
:is(.clr-primary, .clr-btn-primary:hover, .clr-btn-primary:focus) {
@apply transition;
color: var(--cl-prim-fg-100);
background-color: var(--cl-prim-bg-100);
.dark & {
color: var(--cd-prim-fg-100);
background-color: var(--cd-prim-bg-100);
}
}
:is(.clr-selected, .clr-btn-primary) {
color: var(--cl-fg-100);
background-color: var(--cl-prim-bg-80);
.dark & {
color: var(--cd-fg-100);
background-color: var(--cd-prim-bg-80);
}
}
:is(.clr-disabled, .clr-btn-default, .clr-btn-primary):disabled {
@apply transition;
color: var(--cl-fg-80);
background-color: var(--cl-bg-60);
.dark & {
color: var(--cd-fg-80);
background-color: var(--cd-bg-60);
}
}
:is(.clr-hover, .clr-tab, .clr-btn-nav, .clr-btn-default):hover:not(:disabled) {
@apply transition;
color: var(--cl-fg-100);
background-color: var(--cl-prim-bg-60);
.dark & {
color: var(--cd-fg-100);
background-color: var(--cd-prim-bg-60);
}
}
:is(.clr-outline, .clr-btn-primary):focus {
outline-width: 2px;
outline-style: solid;
outline-color: var(--cl-prim-bg-100);
.dark & {
outline-color: var(--cd-prim-bg-100);
}
}
:is(.clr-text-primary, .clr-text-url) {
color: var(--cl-prim-fg-80);
.dark & {
color: var(--cd-prim-fg-80);
}
}
.clr-footer {
color: var(--cl-fg-60);
.dark & {
color: var(--cd-fg-60);
}
}
:is(.clr-text-controls, .clr-btn-nav, .clr-btn-clear) {
color: var(--cl-fg-80);
&:disabled {
color: var(--cl-fg-60);
}
.dark & {
color: var(--cd-fg-80);
&:disabled {
color: var(--cd-fg-60);
}
}
}
.clr-warning {
background-color: var(--cl-red-bg-100);
.dark & {
background-color: var(--cd-red-bg-100);
}
}
.clr-text-default {
color: var(--cl-fg-100);
.dark & {
color: var(--cd-fg-100);
}
}
.clr-text-warning {
color: var(--cl-red-fg-100);
.dark & {
color: var(--cd-red-fg-100);
}
}
.clr-text-success {
color: var(--cl-green-fg-100);
.dark & {
color: var(--cd-green-fg-100);
}
}
}
.cm-editor {
resize: vertical;
overflow-y: auto;
border-color: var(--cl-bg-40);
.dark & {
border-color: var(--cd-bg-40);
}
@apply border rounded px-[0.375rem] py-[0.15rem];
}
.cm-editor.cm-focused {
border-color: var(--cl-bg-40);
outline-color: var(--cl-prim-bg-100);
.dark & {
border-color: var(--cd-bg-40);
outline-color: var(--cd-prim-bg-100);
}
@apply outline-2 outline;
}
.cm-editor .cm-placeholder {
color: var(--cl-fg-60);
.dark & {
color: var(--cd-fg-60);
}
}
.rdt_TableCell {
font-size: 0.875rem;
}

View File

@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from 'react';
import { BiDownload } from 'react-icons/bi'; import { BiDownload } from 'react-icons/bi';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import AnimateFadeIn from '@/components/AnimateFadeIn';
import InfoError from '@/components/InfoError'; import InfoError from '@/components/InfoError';
import RequireAuth from '@/components/RequireAuth'; import RequireAuth from '@/components/RequireAuth';
import Button from '@/components/ui/Button'; import Button from '@/components/ui/Button';
@ -78,56 +79,58 @@ function CreateRSFormPage() {
} }
return ( return (
<RequireAuth> <AnimateFadeIn>
<form className={clsx('px-6 py-3', classnames.flex_col)} onSubmit={handleSubmit}> <RequireAuth>
<h1>Создание концептуальной схемы</h1> <form className={clsx('px-6 py-3', classnames.flex_col)} onSubmit={handleSubmit}>
<Overlay position='top-[-2.4rem] right-[-1rem]'> <h1>Создание концептуальной схемы</h1>
<input <Overlay position='top-[-2.4rem] right-[-1rem]'>
ref={inputRef} <input
type='file' ref={inputRef}
style={{ display: 'none' }} type='file'
accept={EXTEOR_TRS_FILE} style={{ display: 'none' }}
onChange={handleFileChange} accept={EXTEOR_TRS_FILE}
/> onChange={handleFileChange}
<MiniButton />
title='Загрузить из Экстеор' <MiniButton
icon={<BiDownload size='1.25rem' className='clr-text-primary' />} title='Загрузить из Экстеор'
onClick={() => inputRef.current?.click()} icon={<BiDownload size='1.25rem' className='clr-text-primary' />}
/> onClick={() => inputRef.current?.click()}
</Overlay> />
{fileName ? <Label text={`Загружен файл: ${fileName}`} /> : null} </Overlay>
{fileName ? <Label text={`Загружен файл: ${fileName}`} /> : null}
<TextInput <TextInput
required={!file} required={!file}
label='Полное название' label='Полное название'
placeholder={file && 'Загрузить из файла'} placeholder={file && 'Загрузить из файла'}
value={title} value={title}
onChange={event => setTitle(event.target.value)} onChange={event => setTitle(event.target.value)}
/> />
<TextInput <TextInput
required={!file} required={!file}
label='Сокращение' label='Сокращение'
placeholder={file && 'Загрузить из файла'} placeholder={file && 'Загрузить из файла'}
className='w-[14rem]' className='w-[14rem]'
pattern={patterns.library_alias} pattern={patterns.library_alias}
title={`не более ${limits.library_alias_len} символов`} title={`не более ${limits.library_alias_len} символов`}
value={alias} value={alias}
onChange={event => setAlias(event.target.value)} onChange={event => setAlias(event.target.value)}
/> />
<TextArea <TextArea
label='Комментарий' label='Комментарий'
placeholder={file && 'Загрузить из файла'} placeholder={file && 'Загрузить из файла'}
value={comment} value={comment}
onChange={event => setComment(event.target.value)} onChange={event => setComment(event.target.value)}
/> />
<Checkbox label='Общедоступная схема' value={common} setValue={value => setCommon(value ?? false)} /> <Checkbox label='Общедоступная схема' value={common} setValue={value => setCommon(value ?? false)} />
<div className='flex justify-around gap-6 py-3'> <div className='flex justify-around gap-6 py-3'>
<SubmitButton text='Создать схему' loading={processing} className='min-w-[10rem]' /> <SubmitButton text='Создать схему' loading={processing} className='min-w-[10rem]' />
<Button text='Отмена' className='min-w-[10rem]' onClick={() => handleCancel()} /> <Button text='Отмена' className='min-w-[10rem]' onClick={() => handleCancel()} />
</div> </div>
{error ? <InfoError error={error} /> : null} {error ? <InfoError error={error} /> : null}
</form> </form>
</RequireAuth> </RequireAuth>
</AnimateFadeIn>
); );
} }

View File

@ -20,13 +20,7 @@ function HomePage() {
} }
}, [router, user]); }, [router, user]);
return ( return <div />;
<div className='flex flex-col items-center justify-center px-4 py-2'>
{user?.is_staff ? (
<p>Лендинг находится в разработке. Данная страница видна только пользователям с правами администратора.</p>
) : null}
</div>
);
} }
export default HomePage; export default HomePage;

View File

@ -2,6 +2,7 @@
import { useCallback, useLayoutEffect, useState } from 'react'; import { useCallback, useLayoutEffect, useState } from 'react';
import AnimateFadeIn from '@/components/AnimateFadeIn';
import InfoError from '@/components/InfoError'; import InfoError from '@/components/InfoError';
import { Loader } from '@/components/ui/Loader'; import { Loader } from '@/components/ui/Loader';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
@ -68,7 +69,7 @@ function LibraryPage() {
{library.loading ? <Loader /> : null} {library.loading ? <Loader /> : null}
{library.error ? <InfoError error={library.error} /> : null} {library.error ? <InfoError error={library.error} /> : null}
{!library.loading && library.items ? ( {!library.loading && library.items ? (
<> <AnimateFadeIn>
<SearchPanel <SearchPanel
query={query} query={query}
setQuery={setQuery} setQuery={setQuery}
@ -78,7 +79,7 @@ function LibraryPage() {
setFilter={setFilter} setFilter={setFilter}
/> />
<ViewLibrary resetQuery={resetQuery} items={items} /> <ViewLibrary resetQuery={resetQuery} items={items} />
</> </AnimateFadeIn>
) : null} ) : null}
</> </>
); );

View File

@ -6,6 +6,7 @@ import { useIntl } from 'react-intl';
import DataTable, { createColumnHelper } from '@/components/DataTable'; import DataTable, { createColumnHelper } from '@/components/DataTable';
import HelpButton from '@/components/Help/HelpButton'; import HelpButton from '@/components/Help/HelpButton';
import FlexColumn from '@/components/ui/FlexColumn';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
@ -95,7 +96,7 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
'flex gap-1' 'flex gap-1'
)} )}
> >
<HelpButton topic={HelpTopic.LIBRARY} className='max-w-[35rem]' offset={0} /> <HelpButton topic={HelpTopic.LIBRARY} className='max-w-[35rem]' offset={5} place='right-start' />
</div> </div>
</div> </div>
<DataTable <DataTable
@ -103,13 +104,13 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
data={items} data={items}
headPosition='2.3rem' headPosition='2.3rem'
noDataComponent={ noDataComponent={
<div className='p-3 text-center min-h-[6rem]'> <FlexColumn className='p-3 items-center min-h-[6rem]'>
<p>Список схем пуст</p> <p>Список схем пуст</p>
<p className='flex justify-center gap-6 mt-3'> <p className='flex gap-6'>
<TextURL text='Создать схему' href='/library/create' /> <TextURL text='Создать схему' href='/library/create' />
<TextURL text='Очистить фильтр' onClick={cleanQuery} /> <TextURL text='Очистить фильтр' onClick={cleanQuery} />
</p> </p>
</div> </FlexColumn>
} }
onRowClicked={handleOpenItem} onRowClicked={handleOpenItem}
enableSorting enableSorting

View File

@ -4,6 +4,7 @@ import axios from 'axios';
import clsx from 'clsx'; import clsx from 'clsx';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import AnimateFadeIn from '@/components/AnimateFadeIn';
import ExpectedAnonymous from '@/components/ExpectedAnonymous'; import ExpectedAnonymous from '@/components/ExpectedAnonymous';
import InfoError, { ErrorData } from '@/components/InfoError'; import InfoError, { ErrorData } from '@/components/InfoError';
import SubmitButton from '@/components/ui/SubmitButton'; import SubmitButton from '@/components/ui/SubmitButton';
@ -62,39 +63,41 @@ function LoginPage() {
return <ExpectedAnonymous />; return <ExpectedAnonymous />;
} }
return ( return (
<form className={clsx('w-[24rem]', 'pt-12 pb-6 px-6', classnames.flex_col)} onSubmit={handleSubmit}> <AnimateFadeIn>
<img alt='Концепт Портал' src={resources.logo} className='max-h-[2.5rem] min-w-[2.5rem] mb-3' /> <form className={clsx('w-[24rem]', 'pt-12 pb-6 px-6', classnames.flex_col)} onSubmit={handleSubmit}>
<TextInput <img alt='Концепт Портал' src={resources.logo} className='max-h-[2.5rem] min-w-[2.5rem] mb-3' />
id='username' <TextInput
autoFocus id='username'
required autoFocus
allowEnter required
label='Имя пользователя' allowEnter
value={username} label='Имя пользователя'
onChange={event => setUsername(event.target.value)} value={username}
/> onChange={event => setUsername(event.target.value)}
<TextInput />
id='password' <TextInput
type='password' id='password'
required type='password'
allowEnter required
label='Пароль' allowEnter
value={password} label='Пароль'
onChange={event => setPassword(event.target.value)} value={password}
/> onChange={event => setPassword(event.target.value)}
/>
<SubmitButton <SubmitButton
text='Войти' text='Войти'
className='self-center w-[12rem] mt-3' className='self-center w-[12rem] mt-3'
loading={loading} loading={loading}
disabled={!username || !password} disabled={!username || !password}
/> />
<div className='flex flex-col text-sm'> <div className='flex flex-col text-sm'>
<TextURL text='Восстановить пароль...' href='/restore-password' /> <TextURL text='Восстановить пароль...' href='/restore-password' />
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' /> <TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
</div> </div>
{error ? <ProcessError error={error} /> : null} {error ? <ProcessError error={error} /> : null}
</form> </form>
</AnimateFadeIn>
); );
} }

View File

@ -14,7 +14,6 @@ function TopicsList({ activeTopic, onChangeTopic }: TopicsListProps) {
<div <div
className={clsx('sticky top-0 left-0', 'min-w-[13rem] self-start', 'border-x', 'clr-controls', '', 'select-none')} className={clsx('sticky top-0 left-0', 'min-w-[13rem] self-start', 'border-x', 'clr-controls', '', 'select-none')}
> >
<h1 className='my-1'>Справка</h1>
{Object.values(HelpTopic).map((topic, index) => ( {Object.values(HelpTopic).map((topic, index) => (
<div <div
key={`${prefixes.topic_list}${index}`} key={`${prefixes.topic_list}${index}`}

View File

@ -1,4 +1,5 @@
import InfoTopic from '@/components/Help/InfoTopic'; import AnimateFadeIn from '@/components/AnimateFadeIn';
import InfoTopic from '@/components/InfoTopic';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
interface ViewTopicProps { interface ViewTopicProps {
@ -7,9 +8,9 @@ interface ViewTopicProps {
function ViewTopic({ topic }: ViewTopicProps) { function ViewTopic({ topic }: ViewTopicProps) {
return ( return (
<div className='px-2 py-2 mx-auto'> <AnimateFadeIn key={topic} className='px-2 py-2 mx-auto'>
<InfoTopic topic={topic} /> <InfoTopic topic={topic} />
</div> </AnimateFadeIn>
); );
} }

View File

@ -5,7 +5,7 @@ import { motion } from 'framer-motion';
import { IExpressionParse, IRSErrorDescription } from '@/models/rslang'; import { IExpressionParse, IRSErrorDescription } from '@/models/rslang';
import { getRSErrorPrefix } from '@/models/rslangAPI'; import { getRSErrorPrefix } from '@/models/rslangAPI';
import { animateParseResults } from '@/utils/animations'; import { animateParseResults } from '@/styling/animations';
import { describeRSError } from '@/utils/labels'; import { describeRSError } from '@/utils/labels';
interface ParsingResultProps { interface ParsingResultProps {

View File

@ -1,7 +1,7 @@
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { TokenID } from '@/models/rslang'; import { TokenID } from '@/models/rslang';
import { animateRSControl } from '@/utils/animations'; import { animateRSControl } from '@/styling/animations';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
import RSLocalButton from './RSLocalButton'; import RSLocalButton from './RSLocalButton';

View File

@ -9,7 +9,7 @@ import { ExpressionStatus } from '@/models/rsform';
import { type IConstituenta } from '@/models/rsform'; import { type IConstituenta } from '@/models/rsform';
import { inferStatus } from '@/models/rsformAPI'; import { inferStatus } from '@/models/rsformAPI';
import { IExpressionParse, ParsingStatus } from '@/models/rslang'; import { IExpressionParse, ParsingStatus } from '@/models/rslang';
import { colorBgCstStatus } from '@/utils/color'; import { colorBgCstStatus } from '@/styling/color';
import { globalIDs } from '@/utils/constants'; import { globalIDs } from '@/utils/constants';
import { labelExpressionStatus } from '@/utils/labels'; import { labelExpressionStatus } from '@/utils/labels';

View File

@ -195,6 +195,12 @@ function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Edito
return ( return (
<div tabIndex={-1} className='outline-none' onKeyDown={handleTableKey}> <div tabIndex={-1} className='outline-none' onKeyDown={handleTableKey}>
<SelectedCounter
total={schema?.stats?.count_all ?? 0}
selected={selected.length}
position='top-[0.3rem] left-2'
/>
<RSListToolbar <RSListToolbar
selectedCount={selected.length} selectedCount={selected.length}
isMutable={isMutable} isMutable={isMutable}
@ -204,7 +210,6 @@ function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Edito
onCreate={handleCreateCst} onCreate={handleCreateCst}
onDelete={handleDelete} onDelete={handleDelete}
/> />
<SelectedCounter total={schema?.stats?.count_all ?? 0} selected={selected.length} position='left-1 top-2' />
<div className='pt-[2.3rem] border-b' /> <div className='pt-[2.3rem] border-b' />

View File

@ -5,6 +5,7 @@ import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import ConstituentaBadge from '@/components/ConstituentaBadge'; import ConstituentaBadge from '@/components/ConstituentaBadge';
import DataTable, { createColumnHelper, RowSelectionState, VisibilityState } from '@/components/DataTable'; import DataTable, { createColumnHelper, RowSelectionState, VisibilityState } from '@/components/DataTable';
import FlexColumn from '@/components/ui/FlexColumn';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
import { IConstituenta } from '@/models/rsform'; import { IConstituenta } from '@/models/rsform';
@ -135,12 +136,12 @@ function RSTable({ items, selected, setSelected, onEdit, onCreateNew }: RSTableP
rowSelection={selected} rowSelection={selected}
onRowSelectionChange={setSelected} onRowSelectionChange={setSelected}
noDataComponent={ noDataComponent={
<span className='flex flex-col justify-center p-2 text-center'> <FlexColumn className='p-3 items-center'>
<p>Список пуст</p> <p>Список пуст</p>
<p className='cursor-pointer clr-text-primary hover:underline' onClick={() => onCreateNew()}> <p className='cursor-pointer clr-text-primary hover:underline' onClick={() => onCreateNew()}>
Создать новую конституенту Создать новую конституенту
</p> </p>
</span> </FlexColumn>
} }
/> />
); );

View File

@ -14,7 +14,7 @@ import DlgGraphParams from '@/dialogs/DlgGraphParams';
import useLocalStorage from '@/hooks/useLocalStorage'; import useLocalStorage from '@/hooks/useLocalStorage';
import { GraphColoringScheme, GraphFilterParams } from '@/models/miscellaneous'; import { GraphColoringScheme, GraphFilterParams } from '@/models/miscellaneous';
import { CstType, ICstCreateData } from '@/models/rsform'; import { CstType, ICstCreateData } from '@/models/rsform';
import { colorBgGraphNode } from '@/utils/color'; import { colorBgGraphNode } from '@/styling/color';
import { classnames, TIMEOUT_GRAPH_REFRESH } from '@/utils/constants'; import { classnames, TIMEOUT_GRAPH_REFRESH } from '@/utils/constants';
import GraphSidebar from './GraphSidebar'; import GraphSidebar from './GraphSidebar';

View File

@ -4,7 +4,7 @@ import { useCallback, useLayoutEffect, useMemo, useRef } from 'react';
import GraphUI, { GraphCanvasRef, GraphEdge, GraphNode, LayoutTypes, Sphere, useSelection } from '@/components/GraphUI'; import GraphUI, { GraphCanvasRef, GraphEdge, GraphNode, LayoutTypes, Sphere, useSelection } from '@/components/GraphUI';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { graphDarkT, graphLightT } from '@/utils/color'; import { graphDarkT, graphLightT } from '@/styling/color';
import { resources } from '@/utils/constants'; import { resources } from '@/utils/constants';
interface TermGraphProps { interface TermGraphProps {

View File

@ -2,11 +2,11 @@
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import ConstituentaTooltip from '@/components/Help/ConstituentaTooltip'; import ConstituentaTooltip from '@/components/ConstituentaTooltip';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { GraphColoringScheme } from '@/models/miscellaneous'; import { GraphColoringScheme } from '@/models/miscellaneous';
import { IRSForm } from '@/models/rsform'; import { IRSForm } from '@/models/rsform';
import { colorBgGraphNode } from '@/utils/color'; import { colorBgGraphNode } from '@/styling/color';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
interface ViewHiddenProps { interface ViewHiddenProps {

View File

@ -8,6 +8,7 @@ import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import AnimateFadeIn from '@/components/AnimateFadeIn';
import InfoError, { ErrorData } from '@/components/InfoError'; import InfoError, { ErrorData } from '@/components/InfoError';
import { Loader } from '@/components/ui/Loader'; import { Loader } from '@/components/ui/Loader';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
@ -434,51 +435,53 @@ function RSTabs() {
<TabLabel label='Граф термов' /> <TabLabel label='Граф термов' />
</TabList> </TabList>
<TabPanel forceRender style={{ display: activeTab === RSTabID.CARD ? '' : 'none' }}> <AnimateFadeIn>
<EditorRSForm <TabPanel forceRender style={{ display: activeTab === RSTabID.CARD ? '' : 'none' }}>
isMutable={isMutable} <EditorRSForm
isModified={isModified} isMutable={isMutable}
setIsModified={setIsModified} isModified={isModified}
onToggleSubscribe={handleToggleSubscribe} setIsModified={setIsModified}
onDownload={onDownloadSchema} onToggleSubscribe={handleToggleSubscribe}
onDestroy={onDestroySchema} onDownload={onDownloadSchema}
onClaim={onClaimSchema} onDestroy={onDestroySchema}
onShare={onShareSchema} onClaim={onClaimSchema}
/> onShare={onShareSchema}
</TabPanel> />
</TabPanel>
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_LIST ? '' : 'none' }}> <TabPanel forceRender style={{ display: activeTab === RSTabID.CST_LIST ? '' : 'none' }}>
<EditorRSList <EditorRSList
isMutable={isMutable} isMutable={isMutable}
onOpenEdit={onOpenCst} onOpenEdit={onOpenCst}
onCreateCst={promptCreateCst} onCreateCst={promptCreateCst}
onDeleteCst={promptDeleteCst} onDeleteCst={promptDeleteCst}
/> />
</TabPanel> </TabPanel>
<TabPanel forceRender style={{ display: activeTab === RSTabID.CST_EDIT ? '' : 'none' }}> <TabPanel forceRender style={{ display: activeTab === RSTabID.CST_EDIT ? '' : 'none' }}>
<EditorConstituenta <EditorConstituenta
isMutable={isMutable} isMutable={isMutable}
isModified={isModified} isModified={isModified}
setIsModified={setIsModified} setIsModified={setIsModified}
activeID={activeID} activeID={activeID}
activeCst={activeCst} activeCst={activeCst}
onOpenEdit={onOpenCst} onOpenEdit={onOpenCst}
onCreateCst={promptCreateCst} onCreateCst={promptCreateCst}
onDeleteCst={promptDeleteCst} onDeleteCst={promptDeleteCst}
onRenameCst={promptRenameCst} onRenameCst={promptRenameCst}
onEditTerm={promptShowEditTerm} onEditTerm={promptShowEditTerm}
/> />
</TabPanel> </TabPanel>
<TabPanel style={{ display: activeTab === RSTabID.TERM_GRAPH ? '' : 'none' }}> <TabPanel style={{ display: activeTab === RSTabID.TERM_GRAPH ? '' : 'none' }}>
<EditorTermGraph <EditorTermGraph
isMutable={isMutable} isMutable={isMutable}
onOpenEdit={onOpenCst} onOpenEdit={onOpenCst}
onCreateCst={promptCreateCst} onCreateCst={promptCreateCst}
onDeleteCst={promptDeleteCst} onDeleteCst={promptDeleteCst}
/> />
</TabPanel> </TabPanel>
</AnimateFadeIn>
</Tabs> </Tabs>
) : null} ) : null}
</> </>

View File

@ -125,7 +125,7 @@ function ConstituentsTable({ items, activeID, onOpenEdit, maxHeight, denseThresh
columnVisibility={columnVisibility} columnVisibility={columnVisibility}
onColumnVisibilityChange={setColumnVisibility} onColumnVisibilityChange={setColumnVisibility}
noDataComponent={ noDataComponent={
<div className={clsx('min-h-[5rem]', 'p-2', 'text-center', 'select-none')}> <div className={clsx('min-h-[5rem]', 'p-3', 'text-center', 'select-none')}>
<p>Список конституент пуст</p> <p>Список конституент пуст</p>
<p>Измените параметры фильтра</p> <p>Измените параметры фильтра</p>
</div> </div>

View File

@ -5,7 +5,7 @@ import { useMemo, useState } from 'react';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { IConstituenta, IRSForm } from '@/models/rsform'; import { IConstituenta, IRSForm } from '@/models/rsform';
import { animateSideView } from '@/utils/animations'; import { animateSideView } from '@/styling/animations';
import ConstituentsSearch from './ConstituentsSearch'; import ConstituentsSearch from './ConstituentsSearch';
import ConstituentsTable from './ConstituentsTable'; import ConstituentsTable from './ConstituentsTable';

View File

@ -5,6 +5,7 @@ import { useEffect, useState } from 'react';
import { BiInfoCircle } from 'react-icons/bi'; import { BiInfoCircle } from 'react-icons/bi';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import AnimateFadeIn from '@/components/AnimateFadeIn';
import ExpectedAnonymous from '@/components/ExpectedAnonymous'; import ExpectedAnonymous from '@/components/ExpectedAnonymous';
import InfoError from '@/components/InfoError'; import InfoError from '@/components/InfoError';
import Button from '@/components/ui/Button'; import Button from '@/components/ui/Button';
@ -67,85 +68,87 @@ function RegisterPage() {
return <ExpectedAnonymous />; return <ExpectedAnonymous />;
} }
return ( return (
<form className={clsx('px-6 py-3', classnames.flex_col)} onSubmit={handleSubmit}> <AnimateFadeIn>
<h1>Новый пользователь</h1> <form className={clsx('px-6 py-3', classnames.flex_col)} onSubmit={handleSubmit}>
<div className='flex gap-12'> <h1>Новый пользователь</h1>
<FlexColumn> <div className='flex gap-12'>
<div className='absolute'> <FlexColumn>
<Overlay id={globalIDs.password_tooltip} position='top-[4.8rem] left-[3.4rem] absolute'> <div className='absolute'>
<BiInfoCircle size='1.25rem' className='clr-text-primary' /> <Overlay id={globalIDs.password_tooltip} position='top-[4.8rem] left-[3.4rem] absolute'>
</Overlay> <BiInfoCircle size='1.25rem' className='clr-text-primary' />
<Tooltip anchorSelect={`#${globalIDs.password_tooltip}`} offset={6}> </Overlay>
<p>- используйте уникальный пароль</p> <Tooltip anchorSelect={`#${globalIDs.password_tooltip}`} offset={6}>
<p>- портал функционирует в тестовом режиме</p> <p>- используйте уникальный пароль</p>
</Tooltip> <p>- портал функционирует в тестовом режиме</p>
</div> </Tooltip>
</div>
<TextInput <TextInput
id='username' id='username'
required required
label='Имя пользователя (логин)' label='Имя пользователя (логин)'
pattern={patterns.login} pattern={patterns.login}
title='Минимум 3 знака. Латинские буквы и цифры. Не может начинаться с цифры' title='Минимум 3 знака. Латинские буквы и цифры. Не может начинаться с цифры'
value={username} value={username}
className='w-[15rem]' className='w-[15rem]'
onChange={event => setUsername(event.target.value)} onChange={event => setUsername(event.target.value)}
/> />
<TextInput <TextInput
id='password' id='password'
type='password' type='password'
required required
label='Пароль' label='Пароль'
className='w-[15rem]' className='w-[15rem]'
value={password} value={password}
onChange={event => setPassword(event.target.value)} onChange={event => setPassword(event.target.value)}
/> />
<TextInput <TextInput
id='password2' id='password2'
required required
type='password' type='password'
label='Повторите пароль' label='Повторите пароль'
className='w-[15rem]' className='w-[15rem]'
value={password2} value={password2}
onChange={event => setPassword2(event.target.value)} onChange={event => setPassword2(event.target.value)}
/> />
</FlexColumn> </FlexColumn>
<FlexColumn className='w-[15rem]'> <FlexColumn className='w-[15rem]'>
<TextInput <TextInput
id='email' id='email'
required required
label='Электронная почта (email)' label='Электронная почта (email)'
title='электронная почта в корректном формате, например: i.petrov@mycompany.ru.com' title='электронная почта в корректном формате, например: i.petrov@mycompany.ru.com'
value={email} value={email}
onChange={event => setEmail(event.target.value)} onChange={event => setEmail(event.target.value)}
/> />
<TextInput <TextInput
id='first_name' id='first_name'
label='Отображаемое имя' label='Отображаемое имя'
value={firstName} value={firstName}
onChange={event => setFirstName(event.target.value)} onChange={event => setFirstName(event.target.value)}
/> />
<TextInput <TextInput
id='last_name' id='last_name'
label='Отображаемая фамилия' label='Отображаемая фамилия'
value={lastName} value={lastName}
onChange={event => setLastName(event.target.value)} onChange={event => setLastName(event.target.value)}
/> />
</FlexColumn> </FlexColumn>
</div> </div>
<div className='flex gap-1 text-sm'> <div className='flex gap-1 text-sm'>
<Checkbox label='Принимаю условия' value={acceptPrivacy} setValue={setAcceptPrivacy} /> <Checkbox label='Принимаю условия' value={acceptPrivacy} setValue={setAcceptPrivacy} />
<TextURL text='обработки персональных данных...' href={'/manuals?topic=privacy'} /> <TextURL text='обработки персональных данных...' href={'/manuals?topic=privacy'} />
</div> </div>
<div className='flex justify-around my-3'> <div className='flex justify-around my-3'>
<SubmitButton text='Регистрировать' className='min-w-[10rem]' loading={loading} disabled={!acceptPrivacy} /> <SubmitButton text='Регистрировать' className='min-w-[10rem]' loading={loading} disabled={!acceptPrivacy} />
<Button text='Назад' className='min-w-[10rem]' onClick={() => handleCancel()} /> <Button text='Назад' className='min-w-[10rem]' onClick={() => handleCancel()} />
</div> </div>
{error ? <InfoError error={error} /> : null} {error ? <InfoError error={error} /> : null}
</form> </form>
</AnimateFadeIn>
); );
} }

View File

@ -1,14 +1,15 @@
import AnimateFadeIn from '@/components/AnimateFadeIn';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
import { urls } from '@/utils/constants'; import { urls } from '@/utils/constants';
function RestorePasswordPage() { function RestorePasswordPage() {
return ( return (
<div className='py-3'> <AnimateFadeIn className='py-3'>
<p>Автоматическое восстановление пароля не доступно.</p> <p>Автоматическое восстановление пароля не доступно.</p>
<p> <p>
Возможно восстановление пароля через обращение на <TextURL href={urls.mail_portal} text='portal@acconcept.ru' /> Возможно восстановление пароля через обращение на <TextURL href={urls.mail_portal} text='portal@acconcept.ru' />
</p> </p>
</div> </AnimateFadeIn>
); );
} }

View File

@ -4,6 +4,7 @@ import { AnimatePresence } from 'framer-motion';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { FiBell, FiBellOff } from 'react-icons/fi'; import { FiBell, FiBellOff } from 'react-icons/fi';
import AnimateFadeIn from '@/components/AnimateFadeIn';
import InfoError from '@/components/InfoError'; import InfoError from '@/components/InfoError';
import { Loader } from '@/components/ui/Loader'; import { Loader } from '@/components/ui/Loader';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
@ -32,7 +33,7 @@ function UserTabs() {
{loading ? <Loader /> : null} {loading ? <Loader /> : null}
{error ? <InfoError error={error} /> : null} {error ? <InfoError error={error} /> : null}
{user ? ( {user ? (
<div className='flex gap-6 py-2'> <AnimateFadeIn className='flex gap-6 py-2'>
<div> <div>
<Overlay position='top-0 right-0'> <Overlay position='top-0 right-0'>
<MiniButton <MiniButton
@ -56,7 +57,7 @@ function UserTabs() {
<AnimatePresence> <AnimatePresence>
{subscriptions.length > 0 && showSubs ? <ViewSubscriptions items={subscriptions} /> : null} {subscriptions.length > 0 && showSubs ? <ViewSubscriptions items={subscriptions} /> : null}
</AnimatePresence> </AnimatePresence>
</div> </AnimateFadeIn>
) : null} ) : null}
</> </>
); );

View File

@ -7,7 +7,7 @@ import { useIntl } from 'react-intl';
import DataTable, { createColumnHelper } from '@/components/DataTable'; import DataTable, { createColumnHelper } from '@/components/DataTable';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { ILibraryItem } from '@/models/library'; import { ILibraryItem } from '@/models/library';
import { animateSideView } from '@/utils/animations'; import { animateSideView } from '@/styling/animations';
interface ViewSubscriptionsProps { interface ViewSubscriptionsProps {
items: ILibraryItem[]; items: ILibraryItem[];

View File

@ -190,3 +190,25 @@ export const animateModal = {
} }
} }
}; };
export const animateFadeIn = {
initial: {
opacity: 0
},
animate: {
opacity: 1,
transition: {
type: 'tween',
ease: 'linear',
duration: 0.3
}
},
exit: {
opacity: 0,
transition: {
type: 'tween',
ease: 'linear',
duration: 2
}
}
};

View File

@ -0,0 +1,54 @@
/**
* Module: Define CSS variables.
*/
/* prettier-ignore */
:root {
--font-ui: 'Geologica', sans-serif;
--font-main: 'Rubik', 'Noto Sans Math', 'Noto Sans Symbols 2', 'Segoe UI Symbol', sans-serif;
--font-math: 'Noto Sans Math', 'Noto Sans Symbols 2', 'Segoe UI Symbol', sans-serif;
/* Light Theme */
--cl-bg-120: hsl(000, 000%, 100%);
--cl-bg-100: hsl(000, 000%, 098%);
--cl-bg-80: hsl(000, 000%, 094%);
--cl-bg-60: hsl(000, 000%, 091%);
--cl-bg-40: hsl(000, 000%, 080%);
--cl-fg-60: hsl(000, 000%, 065%);
--cl-fg-80: hsl(000, 000%, 047%);
--cl-fg-100: hsl(000, 000%, 000%);
--cl-prim-bg-100: hsl(220, 100%, 060%);
--cl-prim-bg-80: hsl(220, 080%, 092%);
--cl-prim-bg-60: hsl(190, 080%, 094%);
--cl-prim-fg-80: hsl(220, 100%, 050%);
--cl-prim-fg-100: hsl(000, 000%, 100%);
--cl-red-bg-100: hsl(000, 100%, 095%);
--cl-red-fg-100: hsl(000, 072%, 051%);
--cl-green-fg-100: hsl(120, 080%, 37%);
/* Dark Theme */
--cd-bg-120: hsl(000, 000%, 005%);
--cd-bg-100: hsl(000, 000%, 009%);
--cd-bg-80: hsl(000, 000%, 015%);
--cd-bg-60: hsl(000, 000%, 022%);
--cd-bg-40: hsl(000, 000%, 035%);
--cd-fg-60: hsl(000, 000%, 055%);
--cd-fg-80: hsl(000, 000%, 080%);
--cd-fg-100: hsl(000, 000%, 093%);
--cd-prim-bg-100: hsl(267, 050%, 050%);
--cd-prim-bg-80: hsl(267, 050%, 032%);
--cd-prim-bg-60: hsl(269, 030%, 028%);
--cd-prim-fg-80: hsl(267, 070%, 070%);
--cd-prim-fg-100: hsl(000, 000%, 100%);
--cd-red-bg-100: hsl(000, 100%, 015%);
--cd-red-fg-100: hsl(000, 080%, 055%);
--cd-green-fg-100: hsl(120, 080%, 042%);
}

View File

@ -0,0 +1,5 @@
/**
* Module: List external styling imports.
*/
@import 'react-toastify/dist/ReactToastify.css';

View File

@ -0,0 +1,40 @@
/* Depth layers */
.z-bottom {
z-index: 0;
}
.z-pop {
z-index: 10;
}
:is(.z-sticky, .sticky) {
z-index: 20;
}
.z-tooltip {
z-index: 30;
}
.z-navigation {
z-index: 50;
}
.z-modal {
z-index: 60;
}
.z-modal-controls {
z-index: 70;
}
.z-modal-top {
z-index: 80;
}
.z-modal-tooltip {
z-index: 90;
}
.z-topmost {
z-index: 99;
}

View File

@ -0,0 +1,38 @@
/**
* Module: Override imported components CSS styling.
*/
:root {
/* Import overrides */
--toastify-color-dark: var(--cd-bg-60);
}
.cm-editor {
resize: vertical;
overflow-y: auto;
border-color: var(--cl-bg-40);
.dark & {
border-color: var(--cd-bg-40);
}
@apply border rounded px-[0.375rem] py-[0.15rem];
}
.cm-editor.cm-focused {
border-color: var(--cl-bg-40);
outline-color: var(--cl-prim-bg-100);
.dark & {
border-color: var(--cd-bg-40);
outline-color: var(--cd-prim-bg-100);
}
@apply outline-2 outline;
}
.cm-editor .cm-placeholder {
color: var(--cl-fg-60);
.dark & {
color: var(--cd-fg-60);
}
}
.rdt_TableCell {
font-size: 0.875rem;
}

View File

@ -0,0 +1,113 @@
/**
* Module: Basic styling setup.
*/
@import './constants.css';
@import './layers.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
html {
hanging-punctuation: first last;
color-scheme: dark light;
}
[data-color-scheme='dark'] {
color-scheme: dark;
}
[data-color-scheme='light'] {
color-scheme: light;
}
/* Default scroll behavior */
@media (prefers-reduced-motion: no-preference) {
:has(:target) {
scroll-behavior: smooth;
scroll-padding-top: 3rem;
}
}
:root {
font-family: var(--font-main);
color: var(--cl-fg-100);
border-color: var(--cl-bg-40);
background-color: var(--cl-bg-100);
&.dark {
color: var(--cd-fg-100);
border-color: var(--cd-bg-40);
background-color: var(--cd-bg-100);
}
}
:focus {
outline-width: 2px;
outline-style: solid;
outline-color: transparent;
.dark & {
outline-color: transparent;
}
}
::selection {
background: var(--cl-prim-bg-60);
.dark & {
background: var(--cd-prim-bg-60);
}
tr :hover& {
background: var(--cl-red-bg-100);
.dark & {
background: var(--cd-red-bg-100);
}
}
}
::placeholder {
color: var(--cl-fg-60);
.dark & {
color: var(--cd-fg-60);
}
}
/* Wrapping headers */
h1,
h2,
h3,
h4,
h5,
h6 {
text-wrap: balance;
}
/* Limit text lines and setup wrapping */
p,
li {
max-width: 90ch;
text-wrap: pretty;
}
@layer components {
h1 {
@apply text-lg font-semibold text-center;
}
h2 {
@apply font-semibold text-center;
}
b {
@apply font-semibold;
}
.border {
@apply rounded;
}
.shadow-border {
@apply shadow-sm shadow-[var(--cl-bg-40)] dark:shadow-[var(--cd-bg-40)];
}
}

View File

@ -0,0 +1,177 @@
/**
* Module: Custom styling.
*/
@import './constants.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
.font-controls {
font-family: var(--font-ui);
font-weight: 600;
font-variant: small-caps;
}
.font-math {
font-family: var(--font-math);
}
}
@layer components {
.clr-modal-backdrop {
opacity: 0.75;
}
:is(
.clr-border,
.border,
.border-x,
.border-y,
.border-b,
.border-t,
.border-l,
.border-r,
.border-2,
.border-x-2,
.border-y-2,
.border-b-2,
.border-t-2,
.border-l-2,
.border-r-2,
.divide-x,
.divide-y,
.divide-x-2,
.divide-y-2
) {
border-color: var(--cl-bg-40);
@apply divide-inherit;
.dark & {
border-color: var(--cd-bg-40);
}
}
:is(.clr-app, .clr-footer, .clr-modal-backdrop, .clr-btn-nav, .clr-input:disabled) {
background-color: var(--cl-bg-100);
.dark & {
background-color: var(--cd-bg-100);
}
}
:is(.clr-input) {
background-color: var(--cl-bg-120);
.dark & {
background-color: var(--cd-bg-120);
}
}
:is(.clr-controls, .clr-tab, .clr-btn-default) {
background-color: var(--cl-bg-80);
.dark & {
background-color: var(--cd-bg-80);
}
}
:is(.clr-primary, .clr-btn-primary:hover, .clr-btn-primary:focus) {
@apply transition;
color: var(--cl-prim-fg-100);
background-color: var(--cl-prim-bg-100);
.dark & {
color: var(--cd-prim-fg-100);
background-color: var(--cd-prim-bg-100);
}
}
:is(.clr-selected, .clr-btn-primary) {
color: var(--cl-fg-100);
background-color: var(--cl-prim-bg-80);
.dark & {
color: var(--cd-fg-100);
background-color: var(--cd-prim-bg-80);
}
}
:is(.clr-disabled, .clr-btn-default, .clr-btn-primary):disabled {
@apply transition;
color: var(--cl-fg-80);
background-color: var(--cl-bg-60);
.dark & {
color: var(--cd-fg-80);
background-color: var(--cd-bg-60);
}
}
:is(.clr-hover, .clr-tab, .clr-btn-nav, .clr-btn-default):hover:not(:disabled) {
@apply transition;
color: var(--cl-fg-100);
background-color: var(--cl-prim-bg-60);
.dark & {
color: var(--cd-fg-100);
background-color: var(--cd-prim-bg-60);
}
}
:is(.clr-outline, .clr-btn-primary):focus {
outline-width: 2px;
outline-style: solid;
outline-color: var(--cl-prim-bg-100);
.dark & {
outline-color: var(--cd-prim-bg-100);
}
}
:is(.clr-text-primary, .clr-text-url) {
color: var(--cl-prim-fg-80);
.dark & {
color: var(--cd-prim-fg-80);
}
}
.clr-footer {
color: var(--cl-fg-60);
.dark & {
color: var(--cd-fg-60);
}
}
:is(.clr-text-controls, .clr-btn-nav, .clr-btn-clear) {
color: var(--cl-fg-80);
&:disabled {
color: var(--cl-fg-60);
}
.dark & {
color: var(--cd-fg-80);
&:disabled {
color: var(--cd-fg-60);
}
}
}
.clr-warning {
background-color: var(--cl-red-bg-100);
.dark & {
background-color: var(--cd-red-bg-100);
}
}
.clr-text-default {
color: var(--cl-fg-100);
.dark & {
color: var(--cd-fg-100);
}
}
.clr-text-warning {
color: var(--cl-red-fg-100);
.dark & {
color: var(--cd-red-fg-100);
}
}
.clr-text-success {
color: var(--cl-green-fg-100);
.dark & {
color: var(--cd-green-fg-100);
}
}
}

View File

@ -10,7 +10,7 @@ import { IEntityReference, ISyntacticReference } from '@/models/language';
import { parseGrammemes } from '@/models/languageAPI'; import { parseGrammemes } from '@/models/languageAPI';
import { IConstituenta } from '@/models/rsform'; import { IConstituenta } from '@/models/rsform';
import { colorFgGrammeme, IColorTheme } from './color'; import { colorFgGrammeme, IColorTheme } from '../styling/color';
import { describeConstituentaTerm, labelCstTypification, labelGrammeme } from './labels'; import { describeConstituentaTerm, labelCstTypification, labelGrammeme } from './labels';
/** /**