F: Improve folders UI

This commit is contained in:
Ivan 2025-07-03 11:39:51 +03:00
parent cd62ad574f
commit 161a51fc45
5 changed files with 30 additions and 144 deletions

View File

@ -5,8 +5,7 @@ import {
IconFolderEdit,
IconFolderEmpty,
IconFolderOpened,
IconFolderSearch,
IconFolderTree,
IconLeftClose,
IconOSS,
IconRSForm,
IconSearch,
@ -45,28 +44,25 @@ export function HelpLibrary() {
<li>
<IconShow size='1rem' className='inline-icon' /> фильтры атрибутов применяются по клику
</li>
<li>
<IconSortAsc size='1rem' className='inline-icon' />
<IconSortDesc size='1rem' className='inline-icon' /> сортировка по клику на заголовок таблицы
</li>
<li>
<IconUserSearch size='1rem' className='inline-icon' /> фильтр по пользователю
</li>
<li>
<IconSearch size='1rem' className='inline-icon' /> фильтр по названию и шифру
</li>
<li>
<IconFolderSearch size='1rem' className='inline-icon' /> фильтр по расположению
</li>
<li>
<IconFilterReset size='1rem' className='inline-icon' /> сбросить фильтры
</li>
<li>
<IconFolderTree size='1rem' className='inline-icon' /> переключение между Проводник и Таблица
<IconLeftClose size='1rem' className='inline-icon' /> отображение Проводника
</li>
<li>
<IconSortAsc size='1rem' className='inline-icon' />
<IconSortDesc size='1rem' className='inline-icon' /> сортировка по клику на заголовок таблицы
</li>
</ul>
<h2>Режим: Проводник</h2>
<h2>Проводник</h2>
<ul>
<li>
<IconFolderEdit size='1rem' className='inline-icon' /> переименовать выбранную

View File

@ -6,7 +6,7 @@ export function IconShowSidebar({
size = '1.25rem',
className,
isBottom
}: DomIconProps<boolean> & { isBottom: boolean }) {
}: DomIconProps<boolean> & { isBottom?: boolean }) {
if (isBottom) {
if (value) {
return <IconBottomClose size={size} className={className ?? 'icon-primary'} />;

View File

@ -4,27 +4,16 @@ import clsx from 'clsx';
import { SelectUser } from '@/features/users/components/select-user';
import { MiniButton, SelectorButton } from '@/components/control';
import { MiniButton } from '@/components/control';
import { Dropdown, DropdownButton, useDropdown } from '@/components/dropdown';
import {
IconEditor,
IconFilterReset,
IconFolder,
IconFolderSearch,
IconFolderTree,
IconOwner,
IconUserSearch
} from '@/components/icons';
import { IconEditor, IconFilterReset, IconOwner, IconUserSearch } from '@/components/icons';
import { SearchBar } from '@/components/input';
import { cn } from '@/components/utils';
import { prefixes } from '@/utils/constants';
import { tripleToggleColor } from '@/utils/utils';
import { useLibrarySuspense } from '../../backend/use-library';
import { IconItemVisibility } from '../../components/icon-item-visibility';
import { IconLocationHead } from '../../components/icon-location-head';
import { describeLocationHead, labelLocationHead } from '../../labels';
import { LocationHead } from '../../models/library';
import { IconShowSidebar } from '../../components/icon-show-sidebar';
import { useHasCustomFilter, useLibrarySearchStore } from '../../stores/library-search';
interface ToolbarSearchProps {
@ -36,14 +25,9 @@ interface ToolbarSearchProps {
export function ToolbarSearch({ className, total, filtered }: ToolbarSearchProps) {
const { items } = useLibrarySuspense();
const userMenu = useDropdown();
const headMenu = useDropdown();
const query = useLibrarySearchStore(state => state.query);
const setQuery = useLibrarySearchStore(state => state.setQuery);
const path = useLibrarySearchStore(state => state.path);
const setPath = useLibrarySearchStore(state => state.setPath);
const head = useLibrarySearchStore(state => state.head);
const setHead = useLibrarySearchStore(state => state.setHead);
const folderMode = useLibrarySearchStore(state => state.folderMode);
const toggleFolderMode = useLibrarySearchStore(state => state.toggleFolderMode);
const isOwned = useLibrarySearchStore(state => state.isOwned);
@ -64,24 +48,6 @@ export function ToolbarSearch({ className, total, filtered }: ToolbarSearchProps
return items.some(item => item.owner === userID);
}
function handleChange(newValue: LocationHead | null) {
headMenu.hide();
setHead(newValue);
}
function handleToggleFolder() {
headMenu.hide();
toggleFolderMode();
}
function handleFolderClick(event: React.MouseEvent<Element>) {
if (event.ctrlKey || event.metaKey) {
toggleFolderMode();
} else {
headMenu.toggle();
}
}
return (
<div className={cn('flex gap-3 border-b text-sm bg-input items-center', className)}>
<div className='ml-3 min-w-18 sm:min-w-30 select-none whitespace-nowrap'>
@ -89,6 +55,11 @@ export function ToolbarSearch({ className, total, filtered }: ToolbarSearchProps
</div>
<div className='cc-icons h-full items-center'>
<MiniButton
title='Отображение проводника'
icon={<IconShowSidebar value={!folderMode} size='1.25rem' />}
onClick={toggleFolderMode}
/>
<MiniButton
title='Видимость'
icon={<IconItemVisibility value={true} className={tripleToggleColor(isVisible)} />}
@ -119,7 +90,7 @@ export function ToolbarSearch({ className, total, filtered }: ToolbarSearchProps
aria-label='Выбор пользователя для фильтра по владельцу'
placeholder='Выберите владельца'
noBorder
className='min-w-60 mx-1 mb-1'
className='min-w-60 mx-1 mb-1 cc-hover-bg'
filter={filterNonEmptyUsers}
value={filterUser}
onChange={setFilterUser}
@ -144,60 +115,6 @@ export function ToolbarSearch({ className, total, filtered }: ToolbarSearchProps
query={query}
onChangeQuery={setQuery}
/>
{!folderMode ? (
<div
ref={headMenu.ref}
onBlur={headMenu.handleBlur}
className='relative flex items-center h-full select-none'
>
<SelectorButton
className='rounded-lg py-1'
titleHtml={
(head ? describeLocationHead(head) : 'Выберите каталог') + '<br/><kbd>Ctrl + клик</kbd> - Проводник'
}
hideTitle={headMenu.isOpen}
icon={head ? <IconLocationHead value={head} size='1.25rem' /> : <IconFolderSearch size='1.25rem' />}
onClick={handleFolderClick}
/>
<Dropdown isOpen={headMenu.isOpen} stretchLeft>
<DropdownButton
text='проводник...'
title='Переключение в режим Проводник'
icon={<IconFolderTree size='1rem' className='icon-primary' />}
onClick={handleToggleFolder}
/>
<DropdownButton
text='отображать все'
title='Очистить фильтр по расположению'
icon={<IconFolder size='1rem' className='icon-primary' />}
onClick={() => handleChange(null)}
/>
{Object.values(LocationHead).map((head, index) => {
return (
<DropdownButton
key={`${prefixes.location_head_list}${index}`}
text={labelLocationHead(head)}
title={describeLocationHead(head)}
onClick={() => handleChange(head)}
icon={<IconLocationHead value={head} size='1rem' />}
/>
);
})}
</Dropdown>
</div>
) : null}
{!folderMode ? (
<SearchBar
id='path_search'
placeholder='Путь'
noIcon
noBorder
className='w-18 sm:w-20 grow ml-1'
query={path}
onChangeQuery={setPath}
/>
) : null}
</div>
</div>
);

View File

@ -2,9 +2,7 @@ import { useIntl } from 'react-intl';
import { useLabelUser } from '@/features/users';
import { MiniButton } from '@/components/control';
import { createColumnHelper } from '@/components/data-table';
import { IconFolderTree } from '@/components/icons';
import { useWindowSize } from '@/hooks/use-window-size';
import { type RO } from '@/utils/meta';
@ -20,13 +18,6 @@ export function useLibraryColumns() {
const getUserLabel = useLabelUser();
const folderMode = useLibrarySearchStore(state => state.folderMode);
const toggleFolderMode = useLibrarySearchStore(state => state.toggleFolderMode);
function handleToggleFolder(event: React.MouseEvent<Element>) {
event.preventDefault();
event.stopPropagation();
toggleFolderMode();
}
return [
...(folderMode
@ -34,16 +25,7 @@ export function useLibraryColumns() {
: [
columnHelper.accessor('location', {
id: 'location',
header: () => (
<MiniButton
titleHtml='Переключение в режим Проводник'
aria-label='Переключатель режима Проводник'
noPadding
className='ml-2 max-h-4 -translate-y-0.5'
onClick={handleToggleFolder}
icon={<IconFolderTree size='1.25rem' className='text-primary' />}
/>
),
header: 'Путь',
size: 50,
minSize: 50,
maxSize: 50,

View File

@ -6,7 +6,7 @@ import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components/badge-help';
import { MiniButton } from '@/components/control';
import { IconFolderEdit, IconFolderTree } from '@/components/icons';
import { IconFolderEdit } from '@/components/icons';
import { useFitHeight } from '@/stores/app-layout';
import { prefixes } from '@/utils/constants';
import { infoMsg } from '@/utils/labels';
@ -28,7 +28,6 @@ export function ViewSideLocation({ isVisible, onRenameLocation }: ViewSideLocati
const location = useLibrarySearchStore(state => state.location);
const setLocation = useLibrarySearchStore(state => state.setLocation);
const toggleFolderMode = useLibrarySearchStore(state => state.toggleFolderMode);
const subfolders = useLibrarySearchStore(state => state.subfolders);
const toggleSubfolders = useLibrarySearchStore(state => state.toggleSubfolders);
@ -69,27 +68,19 @@ export function ViewSideLocation({ isVisible, onRenameLocation }: ViewSideLocati
<div className='h-8 flex justify-between items-center pr-1 pl-0.5'>
<BadgeHelp topic={HelpTopic.UI_LIBRARY} contentClass='text-sm' offset={5} place='right-start' />
<div className='cc-icons'>
{canRename ? (
<MiniButton
titleHtml='<b>Редактирование пути</b><br/>Перемещаются только Ваши схемы<br/>в указанной папке (и подпапках)'
aria-label='Редактирование расположения'
icon={<IconFolderEdit size='1.25rem' className='icon-primary' />}
onClick={onRenameLocation}
disabled={!canRename}
/>
) : null}
{!!location ? (
<MiniButton
title={subfolders ? 'Вложенные папки: Вкл' : 'Вложенные папки: Выкл'}
aria-label='Переключатель отображения вложенных папок'
icon={<IconShowSubfolders value={subfolders} />}
onClick={toggleSubfolders}
/>
) : null}
<MiniButton
title='Переключение в режим Таблица'
icon={<IconFolderTree size='1.25rem' className='text-primary' />}
onClick={toggleFolderMode}
/>
</div>
</div>
<SelectLocation