UI update: new fonts and adjustments

This commit is contained in:
IRBorisov 2023-12-30 19:43:24 +03:00
parent 0a4d48f6ef
commit 7414aa0186
33 changed files with 223 additions and 220 deletions

View File

@ -60,6 +60,7 @@
"filterset",
"forceatlas",
"futr",
"Geologica",
"Grammeme",
"Grammemes",
"GRND",

View File

@ -14,16 +14,11 @@
"sourceType": "module",
"project": ["tsconfig.json", "tsconfig.node.json"]
},
"plugins": [
"react-refresh",
"simple-import-sort",
"eslint-plugin-tsdoc"
],
"plugins": ["react-refresh", "simple-import-sort", "eslint-plugin-tsdoc"],
"rules": {
"react-refresh/only-export-components": [
"off",
{ "allowConstantExport": true }
],
"no-console": "off",
"require-jsdoc": "off",
"react-refresh/only-export-components": ["off", { "allowConstantExport": true }],
"simple-import-sort/imports": "warn",
"tsdoc/syntax": "warn"
}

View File

@ -7,6 +7,13 @@
<meta name="theme-color" content="#000000" />
<meta name="description" content="Веб-приложение для работы с концептуальными схемами" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Geologica:wght@100;200;300;400;500;600;700;800;900&family=Noto+Sans+Math&display=swap"
rel="stylesheet"
/>
<title>Концепт Портал</title>
<script>

View File

@ -35,7 +35,7 @@ function Button({
{
'border rounded': !noBorder,
'px-1': dense,
'px-3 py-2': !dense,
'px-3 py-1': !dense,
'cursor-progress': loading,
'cursor-pointer': !loading,
'outline-none': noOutline,
@ -49,7 +49,7 @@ function Button({
{...restProps}
>
{icon ? icon : null}
{text ? <span className='font-semibold'>{text}</span> : null}
{text ? <span className='font-medium'>{text}</span> : null}
</button>
);
}

View File

@ -5,7 +5,6 @@ import { globalIDs } from '@/utils/constants';
import { CheckboxCheckedIcon } from '../Icons';
import { CProps } from '../props';
import Label from './Label';
export interface CheckboxProps extends Omit<CProps.Button, 'value' | 'onClick'> {
label?: string;
@ -57,7 +56,9 @@ function Checkbox({ id, disabled, label, title, className, value, setValue, ...r
</div>
) : null}
</div>
<Label className={cursor} text={label} htmlFor={id} />
<label className={clsx('text-sm whitespace-nowrap', cursor)} htmlFor={id}>
{label}
</label>
</button>
);
}

View File

@ -19,7 +19,7 @@ function ConceptSearch({ value, onChange, noBorder, ...restProps }: ConceptSearc
<TextInput
noOutline
placeholder='Поиск'
className='pl-10'
className='w-full pl-10'
noBorder={noBorder}
value={value}
onChange={event => (onChange ? onChange(event.target.value) : undefined)}

View File

@ -15,7 +15,7 @@ function ConceptTab({ label, title, className, ...otherProps }: ConceptTabProps)
'min-w-[6rem]',
'px-2 py-1 flex justify-center',
'clr-tab',
'text-sm whitespace-nowrap small-caps font-semibold',
'text-sm whitespace-nowrap font-controls',
'select-none hover:cursor-pointer',
className
)}

View File

@ -44,7 +44,7 @@ function FileInput({ label, acceptType, title, className, style, onChange, ...re
onChange={handleFileChange}
{...restProps}
/>
<Button text={label} icon={<BiUpload size='1.5rem' />} onClick={handleUploadClick} title={title} />
<Button text={label} icon={<BiUpload size='1.25rem' />} onClick={handleUploadClick} title={title} />
<Label text={fileName} />
</div>
);

View File

@ -11,7 +11,7 @@ function Label({ text, className, ...restProps }: LabelProps) {
return null;
}
return (
<label className={clsx('text-sm font-semibold whitespace-nowrap', className)} {...restProps}>
<label className={clsx('text-sm font-medium whitespace-nowrap', className)} {...restProps}>
{text}
</label>
);

View File

@ -8,7 +8,7 @@ interface LabeledValueProps {
function LabeledValue({ id, label, text, title }: LabeledValueProps) {
return (
<div className='flex justify-between gap-3'>
<label className='font-semibold' title={title} htmlFor={id}>
<label title={title} htmlFor={id}>
{label}
</label>
<span id={id}>{text}</span>

View File

@ -28,7 +28,7 @@ function SelectorButton({
data-tooltip-content={title}
className={clsx(
'px-1 flex flex-start items-center gap-1',
'text-sm small-caps select-none',
'text-sm font-controls select-none',
'text-btn clr-text-controls',
'disabled:cursor-not-allowed cursor-pointer',
{
@ -41,7 +41,7 @@ function SelectorButton({
{...restProps}
>
{icon ? icon : null}
{text ? <div className={'font-semibold whitespace-nowrap pb-1'}>{text}</div> : null}
{text ? <div className={'whitespace-nowrap pb-1'}>{text}</div> : null}
</button>
);
}

View File

@ -13,9 +13,9 @@ function SubmitButton({ text = 'ОК', icon, disabled, loading, className, ...re
<button
type='submit'
className={clsx(
'px-3 py-2 flex gap-2 items-center justify-center',
'px-3 py-1 flex gap-2 items-center justify-center',
'border',
'font-semibold',
'font-medium',
'clr-btn-primary',
'select-none disabled:cursor-not-allowed',
loading && 'cursor-progress',

View File

@ -30,7 +30,7 @@ function SwitchButton<ValueType>({
className={clsx(
'px-2 py-1',
'border rounded-none',
'font-semibold small-caps',
'font-controls',
'clr-btn-clear clr-hover',
'cursor-pointer',
isSelected && 'clr-selected',

View File

@ -5,7 +5,6 @@ import { globalIDs } from '@/utils/constants';
import { CheckboxCheckedIcon, CheckboxNullIcon } from '../Icons';
import { CheckboxProps } from './Checkbox';
import Label from './Label';
export interface TristateProps extends Omit<CheckboxProps, 'value' | 'setValue'> {
value: boolean | null;
@ -65,7 +64,9 @@ function Tristate({ id, disabled, label, title, className, value, setValue, ...r
</div>
) : null}
</div>
<Label className={cursor} text={label} htmlFor={id} />
<label className={clsx('text-sm whitespace-nowrap', cursor)} htmlFor={id}>
{label}
</label>
</button>
);
}

View File

@ -30,19 +30,19 @@ function TableHeader<TData>({ table, headPosition, enableRowSelection, enableSor
<th
key={header.id}
colSpan={header.colSpan}
className='px-2 py-2 text-xs font-semibold select-none whitespace-nowrap'
className='px-2 py-2 text-xs font-medium select-none whitespace-nowrap'
style={{
textAlign: header.getSize() > 100 ? 'left' : 'center',
textAlign: 'center',
width: header.getSize(),
cursor: enableSorting && header.column.getCanSort() ? 'pointer' : 'auto'
}}
onClick={enableSorting ? header.column.getToggleSortingHandler() : undefined}
>
{!header.isPlaceholder ? (
<div className='flex gap-1'>
<span className='inline-flex gap-1'>
{flexRender(header.column.columnDef.header, header.getContext())}
{enableSorting && header.column.getCanSort() ? <SortingIcon column={header.column} /> : null}
</div>
</span>
) : null}
</th>
))}

View File

@ -21,7 +21,7 @@ function NavigationButton({ icon, title, onClick, text }: NavigationButtonProps)
'mr-1 h-full', //
'flex items-center gap-1',
'clr-btn-nav',
'small-caps whitespace-nowrap',
'font-controls whitespace-nowrap',
{
'px-2': text,
'px-4': !text
@ -29,7 +29,7 @@ function NavigationButton({ icon, title, onClick, text }: NavigationButtonProps)
)}
>
{icon ? <span>{icon}</span> : null}
{text ? <span className='font-semibold'>{text}</span> : null}
{text ? <span>{text}</span> : null}
</button>
);
}

View File

@ -82,9 +82,9 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
{ tag: tags.propertyName, color: colors.fgTeal }, // Radical
{ tag: tags.keyword, color: colors.fgBlue }, // keywords
{ tag: tags.literal, color: colors.fgBlue }, // literals
{ tag: tags.controlKeyword, fontWeight: '500' }, // R | I | D
{ tag: tags.controlKeyword, fontWeight: '400' }, // R | I | D
{ tag: tags.unit, fontSize: '0.75rem' }, // indices
{ tag: tags.brace, color: colors.fgPurple, fontWeight: '700' } // braces (curly brackets)
{ tag: tags.brace, color: colors.fgPurple, fontWeight: '600' } // braces (curly brackets)
]
}),
[disabled, colors, darkMode]
@ -131,6 +131,7 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
<div className={clsx('flex flex-col gap-2', className, cursor)} style={style}>
<Label text={label} htmlFor={id} />
<CodeMirror
className='font-math'
id={id}
ref={thisRef}
basicSetup={editorSetup}

View File

@ -1,28 +1,25 @@
import clsx from 'clsx';
import ConceptTooltip from '@/components/Common/ConceptTooltip';
import ConstituentaTooltip from '@/components/Help/ConstituentaTooltip';
import { IConstituenta } from '@/models/rsform';
import { isMockCst } from '@/models/rsformAPI';
import { colorFgCstStatus, IColorTheme } from '@/utils/color';
import { describeExpressionStatus } from '@/utils/labels';
interface ConstituentaBadgeProps {
prefixID?: string;
shortTooltip?: boolean;
value: IConstituenta;
theme: IColorTheme;
}
function ConstituentaBadge({ value, prefixID, shortTooltip, theme }: ConstituentaBadgeProps) {
function ConstituentaBadge({ value, prefixID, theme }: ConstituentaBadgeProps) {
return (
<div
id={`${prefixID}${value.alias}`}
className={clsx(
'min-w-[3.1rem] max-w-[3.1rem]',
'min-w-[3.1rem] max-w-[3.1rem]', //
'px-1',
'border rounded-md',
'text-center font-semibold whitespace-nowrap'
'text-center font-medium whitespace-nowrap'
)}
style={{
borderColor: colorFgCstStatus(value.status, theme),
@ -31,14 +28,7 @@ function ConstituentaBadge({ value, prefixID, shortTooltip, theme }: Constituent
}}
>
{value.alias}
{!shortTooltip ? <ConstituentaTooltip anchor={`#${prefixID}${value.alias}`} data={value} /> : null}
{shortTooltip ? (
<ConceptTooltip anchorSelect={`#${prefixID}${value.alias}`} place='right'>
<p>
<b>Статус</b>: {describeExpressionStatus(value.status)}
</p>
</ConceptTooltip>
) : null}
<ConstituentaTooltip anchor={`#${prefixID}${value.alias}`} data={value} />
</div>
);
}

View File

@ -16,10 +16,10 @@ function GrammemeBadge({ key, grammeme }: GrammemeBadgeProps) {
<div
key={key}
className={clsx(
'min-w-[3rem]',
'min-w-[3rem]', //
'px-1',
'border rounded-md',
'text-sm font-semibold text-center whitespace-nowrap'
'text-sm font-medium text-center whitespace-nowrap'
)}
style={{
borderColor: colorFgGrammeme(grammeme, colors),

View File

@ -20,13 +20,7 @@ function InfoCstClass({ header }: InfoCstClassProps) {
return (
<p key={`${prefixes.cst_status_list}${index}`}>
<span
className={clsx(
'inline-block',
'min-w-[7rem]',
'px-1',
'border',
'text-center text-sm small-caps font-semibold'
)}
className={clsx('inline-block', 'min-w-[7rem]', 'px-1', 'border', 'text-center text-sm font-controls')}
style={{ backgroundColor: colorBgCstClass(cstClass, colors) }}
>
{labelCstClass(cstClass)}

View File

@ -22,11 +22,11 @@ function InfoCstStatus({ title }: InfoCstStatusProps) {
<p key={`${prefixes.cst_status_list}${index}`}>
<span
className={clsx(
'inline-block',
'inline-block', //
'min-w-[7rem]',
'px-1',
'border',
'text-center text-sm small-caps font-semibold'
'text-center text-sm font-controls'
)}
style={{ backgroundColor: colorBgCstStatus(status, colors) }}
>

View File

@ -3,6 +3,8 @@ import { useIntl } from 'react-intl';
import { useUsers } from '@/context/UsersContext';
import { ILibraryItemEx } from '@/models/library';
import LabeledValue from '../Common/LabeledValue';
interface InfoLibraryItemProps {
item?: ILibraryItemEx;
}
@ -12,26 +14,13 @@ function InfoLibraryItem({ item }: InfoLibraryItemProps) {
const intl = useIntl();
return (
<div className='flex flex-col gap-1'>
<div className='flex'>
<label className='font-semibold'>Владелец:</label>
<span className='min-w-[200px] ml-2 overflow-ellipsis overflow-hidden whitespace-nowrap'>
{getUserLabel(item?.owner ?? null)}
</span>
</div>
<div className='flex'>
<label className='font-semibold'>Отслеживают:</label>
<span id='subscriber-count' className='ml-2'>
{item?.subscribers.length ?? 0}
</span>
</div>
<div className='flex'>
<label className='font-semibold'>Дата обновления:</label>
<span className='ml-2'>{item && new Date(item?.time_update).toLocaleString(intl.locale)}</span>
</div>
<div className='flex'>
<label className='font-semibold'>Дата создания:</label>
<span className='ml-8'>{item && new Date(item?.time_create).toLocaleString(intl.locale)}</span>
</div>
<LabeledValue label='Владелец' text={getUserLabel(item?.owner ?? null)} />
<LabeledValue label='Отслеживают' text={item?.subscribers.length ?? 0} />
<LabeledValue
label='Дата обновления'
text={item ? new Date(item?.time_update).toLocaleString(intl.locale) : ''}
/>
<LabeledValue label='Дата создания' text={item ? new Date(item?.time_create).toLocaleString(intl.locale) : ''} />
</div>
);
}

View File

@ -12,7 +12,7 @@ function SelectedCounter({ total, selected, hideZero, position = 'top-0 left-0'
return null;
}
return (
<Overlay position={`px-2 ${position}`} className='select-none whitespace-nowrap small-caps clr-app'>
<Overlay position={`px-2 ${position}`} className='select-none whitespace-nowrap clr-app'>
Выбор {selected} из {total}
</Overlay>
);

View File

@ -26,7 +26,7 @@ function WordformButton({ text, example, grams, onSelectGrams, isSelected, ...re
)}
{...restProps}
>
<p className='font-semibold'>{text}</p>
<p className='font-medium'>{text}</p>
<p>{example}</p>
</button>
);

View File

@ -6,6 +6,10 @@
/* prettier-ignore */
:root {
--font-ui: 'Geologica', sans-serif;
--font-main: 'Rubik', sans-serif, 'Noto Sans Math', 'Segoe UI Symbol';
--font-math: 'Noto Sans Math', sans-serif, 'Segoe UI Symbol';
/* Light Theme */
--cl-bg-120: hsl(000, 000%, 100%);
--cl-bg-100: hsl(000, 000%, 098%);
@ -87,6 +91,8 @@
}
:root {
font-family: var(--font-main);
color: var(--cl-fg-100);
border-color: var(--cl-bg-40);
background-color: var(--cl-bg-100);
@ -136,9 +142,14 @@
}
@layer utilities {
.small-caps {
.font-controls {
font-family: var(--font-ui);
font-weight: 600;
font-variant: small-caps;
}
.font-math {
font-family: var(--font-math);
}
}
@layer components {

View File

@ -44,8 +44,23 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setFilter }:
);
return (
<div className={clsx('sticky top-0', 'w-full max-h-[2.3rem]', 'pr-40 flex', 'border-b', 'clr-input')}>
<div className={clsx('min-w-[10rem]', 'px-2 self-center', 'select-none', 'whitespace-nowrap')}>
<div
className={clsx(
'sticky top-0', //
'w-full max-h-[2.3rem]',
'pr-40 flex',
'border-b',
'clr-input'
)}
>
<div
className={clsx(
'min-w-[10rem]', //
'px-2 self-center',
'select-none',
'whitespace-nowrap'
)}
>
Фильтр
<span className='ml-2'>
{filtered} из {total}

View File

@ -12,14 +12,7 @@ interface TopicsListProps {
function TopicsList({ activeTopic, onChangeTopic }: TopicsListProps) {
return (
<div
className={clsx(
'sticky top-0 left-0',
'min-w-[13rem] self-start',
'border-x',
'clr-controls',
'small-caps',
'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) => (

View File

@ -123,7 +123,7 @@ function FormConstituenta({
return (
<>
<Overlay position='top-1 left-[4rem]' className='flex select-none'>
<Overlay position='top-1 left-[4.1rem]' className='flex select-none'>
<MiniButton
title={`Редактировать словоформы термина: ${constituenta?.term_forms.length ?? 0}`}
disabled={disabled}
@ -131,7 +131,7 @@ function FormConstituenta({
onClick={onEditTerm}
icon={<LiaEdit size='1rem' className={!disabled ? 'clr-text-primary' : ''} />}
/>
<div className='pt-1 pl-[1.375rem] text-sm font-semibold whitespace-nowrap'>
<div className='pt-1 pl-[1.375rem] text-sm font-medium whitespace-nowrap'>
<span>Имя </span>
<span className='ml-1'>{constituenta?.alias ?? ''}</span>
</div>
@ -205,7 +205,7 @@ function FormConstituenta({
text='Сохранить изменения'
className='self-center'
disabled={!isModified || disabled}
icon={<FiSave size='1.5rem' />}
icon={<FiSave size='1.25rem' />}
/>
</form>
</>

View File

@ -22,7 +22,8 @@ function RSLocalButton({ text, title, disabled, onInsert }: RSLocalButtonProps)
'w-[2rem] h-6',
'cursor-pointer disabled:cursor-default',
'rounded-none',
'clr-hover clr-btn-clear'
'clr-hover clr-btn-clear',
'font-math'
)}
onClick={() => onInsert(TokenID.ID_LOCAL, text)}
>

View File

@ -23,6 +23,7 @@ function RSTokenButton({ token, disabled, onInsert }: RSTokenButtonProps) {
'px-1',
'outline-none',
'clr-hover clr-btn-clear',
'font-math',
'cursor-pointer disabled:cursor-default',
{
'w-[4.5rem]': label.length > 3,

View File

@ -56,7 +56,7 @@ function StatusBar({ isModified, processing, constituenta, parseData, onAnalyze
) : (
<>
<StatusIcon status={status} />
<span className='pb-[0.125rem] font-semibold small-caps pr-2'>{labelExpressionStatus(status)}</span>
<span className='pb-[0.125rem] font-controls pr-2'>{labelExpressionStatus(status)}</span>
</>
)}
</div>

View File

@ -16,15 +16,13 @@ import { IRSFormCreateData } from '@/models/rsform';
import { classnames, limits, patterns } from '@/utils/constants';
interface FormRSFormProps {
id?: string
disabled: boolean
isModified: boolean
setIsModified: Dispatch<SetStateAction<boolean>>
id?: string;
disabled: boolean;
isModified: boolean;
setIsModified: Dispatch<SetStateAction<boolean>>;
}
function FormRSForm({
id, disabled, isModified, setIsModified,
}: FormRSFormProps) {
function FormRSForm({ id, disabled, isModified, setIsModified }: FormRSFormProps) {
const { schema, update, processing } = useRSForm();
const { user } = useAuth();
@ -34,8 +32,7 @@ function FormRSForm({
const [common, setCommon] = useState(false);
const [canonical, setCanonical] = useState(false);
useEffect(
() => {
useEffect(() => {
if (!schema) {
setIsModified(false);
return;
@ -48,12 +45,22 @@ function FormRSForm({
schema.is_canonical !== canonical
);
return () => setIsModified(false);
}, [schema, schema?.title, schema?.alias, schema?.comment,
schema?.is_common, schema?.is_canonical,
title, alias, comment, common, canonical, setIsModified]);
}, [
schema,
schema?.title,
schema?.alias,
schema?.comment,
schema?.is_common,
schema?.is_canonical,
title,
alias,
comment,
common,
canonical,
setIsModified
]);
useLayoutEffect(
() => {
useLayoutEffect(() => {
if (schema) {
setTitle(schema.title);
setAlias(schema.alias);
@ -79,21 +86,16 @@ function FormRSForm({
};
return (
<form id={id}
className={clsx(
'mt-1 min-w-[22rem] w-[30rem]',
'py-1',
classnames.flex_col
)}
onSubmit={handleSubmit}
>
<TextInput required
<form id={id} className={clsx('mt-1 min-w-[22rem] w-[30rem]', 'py-1', classnames.flex_col)} onSubmit={handleSubmit}>
<TextInput
required
label='Полное название'
value={title}
disabled={disabled}
onChange={event => setTitle(event.target.value)}
/>
<TextInput required
<TextInput
required
label='Сокращение'
className='w-[14rem]'
pattern={patterns.alias}
@ -129,9 +131,10 @@ function FormRSForm({
className='self-center'
loading={processing}
disabled={!isModified || disabled}
icon={<FiSave size='1.5rem' />}
icon={<FiSave size='1.25rem' />}
/>
</form>);
</form>
);
}
export default FormRSForm;

View File

@ -204,7 +204,7 @@ function EditorRSList({ isMutable, onOpenEdit, onCreateCst, onDeleteCst }: Edito
onCreate={handleCreateCst}
onDelete={handleDelete}
/>
<SelectedCounter total={schema?.stats?.count_all ?? 0} selected={selected.length} position='left-0 top-1' />
<SelectedCounter total={schema?.stats?.count_all ?? 0} selected={selected.length} position='left-1 top-2' />
<div className='pt-[2.3rem] border-b' />