Optimize frontend for small screens

This commit is contained in:
IRBorisov 2024-01-16 13:47:29 +03:00
parent 46d8d93c4b
commit 5a196b8fa1
13 changed files with 81 additions and 27 deletions

View File

@ -442,6 +442,7 @@ class RSForm:
if transfer_term:
substitution.term_raw = original.term_raw
substitution.term_forms = original.term_forms
substitution.term_resolved = original.term_resolved
substitution.save()
original.delete()
self.on_term_change([substitution.alias])

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="ru">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" />

View File

@ -11,7 +11,7 @@ function ApplicationLayout() {
const { viewportHeight, mainHeight, showScroll } = useConceptTheme();
return (
<NavigationState>
<div className='min-w-[30rem] clr-app antialiased'>
<div className='min-w-[20rem] clr-app antialiased'>
<ConceptToaster
className='mt-[4rem] text-sm' // prettier: split lines
autoClose={3000}

View File

@ -15,8 +15,8 @@ function Footer() {
tabIndex={-1}
className={clsx(
'z-navigation',
'px-4 py-2 flex flex-col items-center gap-1',
'text-sm select-none whitespace-nowrap'
'sm:px-4 sm:py-2 flex flex-col items-center gap-1',
'text-xs sm:text-sm select-none whitespace-nowrap'
)}
>
<div className='flex gap-3'>

View File

@ -1,12 +1,19 @@
import clsx from 'clsx';
import { useConceptTheme } from '@/context/ThemeContext';
import useWindowSize from '@/hooks/useWindowSize';
function Logo() {
const { darkMode } = useConceptTheme();
const size = useWindowSize();
return (
<img
alt='Логотип КонцептПортал'
className='max-h-[1.6rem] min-w-[11.5rem]'
src={!darkMode ? '/logo_full.svg' : '/logo_full_dark.svg'}
className={clsx('max-h-[1.6rem] min-w-fit', 'text-start', {
'min-w-[11.5rem]': size.isSmall
})}
src={size.isSmall ? '/logo_sign.svg' : !darkMode ? '/logo_full.svg' : '/logo_full_dark.svg'}
/>
);
}

View File

@ -29,7 +29,7 @@ function NavigationButton({ icon, title, onClick, text }: NavigationButtonProps)
)}
>
{icon ? <span>{icon}</span> : null}
{text ? <span>{text}</span> : null}
{text ? <span className='hidden sm:inline'>{text}</span> : null}
</button>
);
}

View File

@ -2,13 +2,16 @@
import { useEffect, useState } from 'react';
import { SMALL_SCREEN_WIDTH } from '@/utils/constants';
function useWindowSize() {
const isClient = typeof window === 'object';
function getSize() {
return {
width: isClient ? window.innerWidth : undefined,
height: isClient ? window.innerHeight : undefined
height: isClient ? window.innerHeight : undefined,
isSmall: isClient && window.innerWidth < SMALL_SCREEN_WIDTH
};
}

View File

@ -47,9 +47,10 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setFilter }:
<div
className={clsx(
'sticky top-0', // prettier: split lines
'w-full max-h-[2.3rem]',
'pr-40 flex',
'w-full max-h-[2.2rem]',
'sm:pr-40 flex',
'border-b',
'text-sm',
'clr-input'
)}
>

View File

@ -1,10 +1,10 @@
'use client';
import clsx from 'clsx';
import { useMemo } from 'react';
import { useLayoutEffect, useMemo, useState } from 'react';
import { useIntl } from 'react-intl';
import DataTable, { createColumnHelper } from '@/components/DataTable';
import DataTable, { createColumnHelper, VisibilityState } from '@/components/DataTable';
import HelpButton from '@/components/Help/HelpButton';
import FlexColumn from '@/components/ui/FlexColumn';
import TextURL from '@/components/ui/TextURL';
@ -12,6 +12,7 @@ import { useAuth } from '@/context/AuthContext';
import { useConceptNavigation } from '@/context/NavigationContext';
import { useUsers } from '@/context/UsersContext';
import useLocalStorage from '@/hooks/useLocalStorage';
import useWindowSize from '@/hooks/useWindowSize';
import { ILibraryItem } from '@/models/library';
import { HelpTopic } from '@/models/miscellaneous';
@ -33,6 +34,16 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
const handleOpenItem = (item: ILibraryItem) => router.push(`/rsforms/${item.id}`);
const windowSize = useWindowSize();
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
useLayoutEffect(() => {
setColumnVisibility({
owner: !windowSize.isSmall
});
}, [windowSize]);
const columns = useMemo(
() => [
columnHelper.display({
@ -46,43 +57,53 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
columnHelper.accessor('alias', {
id: 'alias',
header: 'Шифр',
size: 200,
minSize: 200,
maxSize: 200,
size: 150,
minSize: 80,
maxSize: 150,
enableSorting: true,
sortingFn: 'text'
}),
columnHelper.accessor('title', {
id: 'title',
header: 'Название',
size: 2000,
minSize: 400,
maxSize: 2000,
size: 1200,
minSize: 200,
maxSize: 1200,
enableSorting: true,
sortingFn: 'text'
}),
columnHelper.accessor(item => item.owner ?? 0, {
id: 'owner',
header: 'Владелец',
size: 600,
minSize: 200,
maxSize: 600,
size: 400,
minSize: 100,
maxSize: 400,
cell: props => getUserLabel(props.cell.getValue()),
enableSorting: true,
sortingFn: 'text'
}),
columnHelper.accessor('time_update', {
id: 'time_update',
header: 'Обновлена',
header: windowSize.isSmall ? 'Дата' : 'Обновлена',
cell: props => (
<div className='text-sm whitespace-nowrap'>{new Date(props.cell.getValue()).toLocaleString(intl.locale)}</div>
<div className='whitespace-nowrap'>
{new Date(props.cell.getValue()).toLocaleString(intl.locale, {
year: '2-digit',
month: '2-digit',
day: '2-digit',
...(!windowSize.isSmall && {
hour: '2-digit',
minute: '2-digit'
})
})}
</div>
),
enableSorting: true,
sortingFn: 'datetime',
sortDescFirst: true
})
],
[intl, getUserLabel, user]
[intl, getUserLabel, user, windowSize]
);
return (
@ -102,7 +123,8 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
<DataTable
columns={columns}
data={items}
headPosition='2.3rem'
headPosition='2.2rem'
className='text-xs sm:text-sm'
noDataComponent={
<FlexColumn className='p-3 items-center min-h-[6rem]'>
<p>Список схем пуст</p>
@ -112,6 +134,7 @@ function ViewLibrary({ items, resetQuery: cleanQuery }: ViewLibraryProps) {
</p>
</FlexColumn>
}
columnVisibility={columnVisibility}
onRowClicked={handleOpenItem}
enableSorting
initialSorting={{

View File

@ -12,7 +12,14 @@ 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', '', 'select-none')}
className={clsx(
'sticky top-0 left-0',
'self-start',
'border-x',
'clr-controls',
'text-xs sm:text-sm',
'select-none'
)}
>
{Object.values(HelpTopic).map((topic, index) => (
<div

View File

@ -70,7 +70,7 @@ function EditorRSForm({
onDestroy={onDestroy}
onToggleSubscribe={onToggleSubscribe}
/>
<div tabIndex={-1} className='flex' onKeyDown={handleInput}>
<div tabIndex={-1} className='flex flex-col sm:flex-row w-fit' onKeyDown={handleInput}>
<FlexColumn className='px-4 pb-2'>
<FormRSForm
disabled={!isMutable}

View File

@ -31,6 +31,7 @@ html {
}
:root {
font-size: 16px;
font-family: var(--font-main);
color: var(--cl-fg-100);
@ -44,6 +45,12 @@ html {
}
}
@media only screen and (max-width: 640px) {
:root {
font-size: 12px;
}
}
:focus {
outline-width: 2px;
outline-style: solid;

View File

@ -14,6 +14,11 @@ export const buildConstants = {
*/
export const TIMEOUT_UI_REFRESH = 100;
/**
* Threshold for small screen size optimizations.
*/
export const SMALL_SCREEN_WIDTH = 640; // == tailwind:xs
/**
* Timeout [in ms] for graph refresh.
*/