This commit is contained in:
2025-11-06 01:46:33 +01:00
parent a16f1501d3
commit 82ae18b4b9
5 changed files with 489 additions and 28 deletions
+196 -21
View File
@@ -27,6 +27,10 @@ import { isTagTransferEvent } from '../documents/tagTransfer';
const ASSET_PRESIGN_TTL_MS = 240 * 1000; // backend issues 5 min tokens; refresh slightly early
const DEFAULT_FOLDER_NAME = 'Documents';
const DEFAULT_SORT_FIELD = 'title';
const DEFAULT_SORT_DIRECTION = 'asc';
const SORT_FIELD_VALUES = ['title', 'issued_at', 'created_at', 'updated_at'];
const TAG_FILTER_UNTAGGED = '__UNTAGGED__';
const ROW_KEY_SEPARATOR = ':';
const DOCUMENT_ROW_PREFIX = 'document';
@@ -204,6 +208,46 @@ const AppLayout = () => {
return 'list';
}
});
const [documentsSortField, setDocumentsSortField] = useState(() => {
if (typeof window === 'undefined') {
return DEFAULT_SORT_FIELD;
}
try {
const stored = window.sessionStorage.getItem('papercrate_sort_field');
return SORT_FIELD_VALUES.includes(stored) ? stored : DEFAULT_SORT_FIELD;
} catch (error) {
console.warn('[sort] failed to read stored sort field', error);
return DEFAULT_SORT_FIELD;
}
});
const [documentsSortDirection, setDocumentsSortDirection] = useState(() => {
if (typeof window === 'undefined') {
return DEFAULT_SORT_DIRECTION;
}
try {
const stored = window.sessionStorage.getItem('papercrate_sort_direction');
return stored === 'desc' || stored === 'asc' ? stored : DEFAULT_SORT_DIRECTION;
} catch (error) {
console.warn('[sort] failed to read stored sort direction', error);
return DEFAULT_SORT_DIRECTION;
}
});
const documentsSortFieldRef = useRef(DEFAULT_SORT_FIELD);
const documentsSortDirectionRef = useRef(DEFAULT_SORT_DIRECTION);
const [searchIncludeDescendants, setSearchIncludeDescendants] = useState(() => {
if (typeof window === 'undefined') {
return true;
}
try {
const stored = window.sessionStorage.getItem('papercrate_include_descendants');
if (stored === 'true') return true;
if (stored === 'false') return false;
} catch (error) {
console.warn('[search] failed to read include_descendants preference', error);
}
return true;
});
const sortRefreshReadyRef = useRef(false);
const [deskHelpOpen, setDeskHelpOpen] = useState(false);
const lastNonDeskViewRef = useRef(documentsViewMode === 'desk' ? 'list' : documentsViewMode);
@@ -218,6 +262,42 @@ const AppLayout = () => {
setDeskHelpOpen(false);
}
}, [documentsViewMode, deskHelpOpen]);
useEffect(() => {
documentsSortFieldRef.current = documentsSortField;
if (typeof window !== 'undefined') {
try {
window.sessionStorage.setItem('papercrate_sort_field', documentsSortField);
} catch (error) {
console.warn('[sort] failed to persist sort field', error);
}
}
}, [documentsSortField, documentsSortFieldRef]);
useEffect(() => {
documentsSortDirectionRef.current = documentsSortDirection;
if (typeof window !== 'undefined') {
try {
window.sessionStorage.setItem('papercrate_sort_direction', documentsSortDirection);
} catch (error) {
console.warn('[sort] failed to persist sort direction', error);
}
}
}, [documentsSortDirection, documentsSortDirectionRef]);
useEffect(() => {
if (typeof window === 'undefined') {
return;
}
try {
window.sessionStorage.setItem(
'papercrate_include_descendants',
searchIncludeDescendants ? 'true' : 'false',
);
} catch (error) {
console.warn('[search] failed to persist include_descendants preference', error);
}
}, [searchIncludeDescendants]);
const tokenRef = useRef(token);
const refreshPromiseRef = useRef(null);
const breadcrumbFetchRef = useRef(new Set());
@@ -281,11 +361,16 @@ const AppLayout = () => {
);
const toggleTagFilter = useCallback((tagId) => {
if (!tagId) return;
setActiveTagFilters((previous) =>
previous.includes(tagId)
? previous.filter((id) => id !== tagId)
: previous.concat([tagId]),
);
setActiveTagFilters((previous) => {
if (tagId === TAG_FILTER_UNTAGGED) {
return previous.includes(TAG_FILTER_UNTAGGED) ? [] : [TAG_FILTER_UNTAGGED];
}
const sanitized = previous.filter((id) => id !== TAG_FILTER_UNTAGGED);
if (sanitized.includes(tagId)) {
return sanitized.filter((id) => id !== tagId);
}
return sanitized.concat([tagId]);
});
}, []);
const toggleCorrespondentFilter = useCallback((correspondentId) => {
@@ -297,6 +382,19 @@ const AppLayout = () => {
});
}, []);
const handleDocumentsSortFieldChange = useCallback((field) => {
const nextField = SORT_FIELD_VALUES.includes(field) ? field : DEFAULT_SORT_FIELD;
setDocumentsSortField((previous) => (previous === nextField ? previous : nextField));
}, []);
const handleDocumentsSortDirectionToggle = useCallback(() => {
setDocumentsSortDirection((previous) => (previous === 'asc' ? 'desc' : 'asc'));
}, []);
const toggleSearchIncludeDescendants = useCallback(() => {
setSearchIncludeDescendants((previous) => !previous);
}, []);
const initialRefreshAttemptedRef = useRef(Boolean(token));
useEffect(() => {
@@ -312,6 +410,7 @@ const AppLayout = () => {
setActiveTagFilters([]);
setActiveCorrespondentFilters([]);
setSearchLoading(false);
setSearchIncludeDescendants(true);
}, []);
const handleSearchChange = useCallback((value) => {
@@ -982,13 +1081,32 @@ const AppLayout = () => {
const ensureFolderData = useCallback(
async (
folderId,
{ force = false, includeDocuments = true, prefetchDepth = 0 } = {},
{
force = false,
includeDocuments = true,
prefetchDepth = 0,
sortField: overrideSortField,
sortDirection: overrideSortDirection,
} = {},
) => {
const requestTenantId = tenantIdRef.current;
const cached = folderContents.get(folderId);
const cachedSortField = cached?.__sortField ?? DEFAULT_SORT_FIELD;
const cachedSortDirection = cached?.__sortDirection ?? DEFAULT_SORT_DIRECTION;
const sortField = includeDocuments
? overrideSortField || documentsSortFieldRef.current || DEFAULT_SORT_FIELD
: null;
const sortDirection = includeDocuments
? overrideSortDirection || documentsSortDirectionRef.current || DEFAULT_SORT_DIRECTION
: null;
const cachedSortMatches =
!includeDocuments
|| !cached
|| (cachedSortField === sortField && cachedSortDirection === sortDirection);
if (!force && cached) {
const includesDocuments = Boolean(cached.__includesDocuments);
if (!includeDocuments || includesDocuments) {
if (!includeDocuments || (includesDocuments && cachedSortMatches)) {
if (prefetchDepth > 0) {
const subfolders = Array.isArray(cached.subfolders) ? cached.subfolders : [];
await Promise.allSettled(
@@ -1006,18 +1124,28 @@ const AppLayout = () => {
}
const path = folderId === 'root' ? 'root' : folderId;
const params = includeDocuments
? undefined
: { include_documents: false };
const { data } = await api.get(`/folders/${path}/contents`, {
params,
});
const params = {};
if (!includeDocuments) {
params.include_documents = false;
} else {
params.sort = sortField;
params.dir = sortDirection;
}
const requestConfig = Object.keys(params).length ? { params } : {};
const { data } = await api.get(`/folders/${path}/contents`, requestConfig);
const hydrated = assetManager.hydrateFolderContents(data);
const childFolders = Array.isArray(data.subfolders) ? data.subfolders : [];
const childIds = childFolders.map((child) => child.id);
const enriched = {
...hydrated,
__includesDocuments: includeDocuments,
__sortField: includeDocuments ? sortField : cachedSortField,
__sortDirection: includeDocuments ? sortDirection : cachedSortDirection,
};
if (tenantIdRef.current !== requestTenantId) {
return { ...hydrated, __includesDocuments: includeDocuments };
return enriched;
}
setFolderNodes((prev) => {
@@ -1089,11 +1217,6 @@ const AppLayout = () => {
);
}
const enriched = {
...hydrated,
__includesDocuments: includeDocuments,
};
if (includeDocuments) {
setFolderContents((prev) => {
if (tenantIdRef.current !== requestTenantId) {
@@ -1118,6 +1241,8 @@ const AppLayout = () => {
? existingEntry.documents
: hydrated.documents,
__includesDocuments: existingEntry.__includesDocuments || false,
__sortField: existingEntry.__sortField ?? enriched.__sortField,
__sortDirection: existingEntry.__sortDirection ?? enriched.__sortDirection,
});
} else {
next.set(folderId, enriched);
@@ -1128,7 +1253,7 @@ const AppLayout = () => {
return enriched;
},
[assetManager, folderContents],
[assetManager, documentsSortDirectionRef, documentsSortFieldRef, folderContents],
);
const isInvalidFolderDrop = useCallback(
@@ -1461,6 +1586,22 @@ const AppLayout = () => {
}
}, [selectedFolder, ensureFolderData, applySelectedFolder, notifyApiError]);
useEffect(() => {
if (!sortRefreshReadyRef.current) {
sortRefreshReadyRef.current = true;
return;
}
if (!isFilterActive && token) {
refreshCurrentFolder();
}
}, [
documentsSortField,
documentsSortDirection,
isFilterActive,
refreshCurrentFolder,
token,
]);
const handleDocumentCorrespondentAttach = useCallback(
async ({ documentId, correspondentId }, { notify = true, refresh = true } = {}) => {
if (!documentId || !correspondentId) {
@@ -3719,7 +3860,16 @@ const AppLayout = () => {
params.query = trimmedQuery;
}
if (activeTagFilters.length) {
params.tags = activeTagFilters.join(',');
const onlyUntagged = activeTagFilters.length === 1
&& activeTagFilters[0] === TAG_FILTER_UNTAGGED;
if (onlyUntagged) {
params.tags = 'none';
} else {
const tagIds = activeTagFilters.filter((id) => id !== TAG_FILTER_UNTAGGED);
if (tagIds.length) {
params.tags = tagIds.join(',');
}
}
}
if (activeCorrespondentFilters.length) {
params.correspondents = activeCorrespondentFilters.join(',');
@@ -3728,6 +3878,15 @@ const AppLayout = () => {
if (folderIdentifier) {
params.folder_id = folderIdentifier;
}
if (!searchIncludeDescendants) {
params.include_descendants = false;
}
if (documentsSortField) {
params.sort = documentsSortField;
}
if (documentsSortDirection) {
params.dir = documentsSortDirection;
}
const { data } = await api.get('/documents', { params });
if (cancelled) return;
@@ -3803,6 +3962,9 @@ const AppLayout = () => {
searchQuery,
activeTagFilters,
activeCorrespondentFilters,
searchIncludeDescendants,
documentsSortField,
documentsSortDirection,
selectedFolder,
notifyApiError,
assetManager,
@@ -4561,6 +4723,12 @@ const AppLayout = () => {
onCorrespondentClick: toggleCorrespondentFilter,
onDocumentTagDrop: handleDocumentTagDrop,
viewMode: documentsViewMode,
sortField: documentsSortField,
sortDirection: documentsSortDirection,
onSortFieldChange: handleDocumentsSortFieldChange,
onSortDirectionToggle: handleDocumentsSortDirectionToggle,
searchIncludeDescendants,
onToggleSearchIncludeDescendants: toggleSearchIncludeDescendants,
onViewModeChange: handleDocumentsViewModeChange,
onClearSelection: clearDocumentSelection,
onDeleteSelection: handleDeleteSelection,
@@ -4586,6 +4754,8 @@ const AppLayout = () => {
currentSubfolders,
documents,
documentsViewMode,
documentsSortField,
documentsSortDirection,
draggedDocumentIds,
draggedFolderId,
focusedRowKey,
@@ -4595,6 +4765,8 @@ const AppLayout = () => {
handleDocumentTagDrop,
handleDocumentTitleUpdate,
handleDocumentsViewModeChange,
handleDocumentsSortFieldChange,
handleDocumentsSortDirectionToggle,
handleFolderDragEnd,
handleFolderDragStart,
handleFolderRename,
@@ -4607,6 +4779,7 @@ const AppLayout = () => {
refreshCurrentFolder,
searchLoading,
searchResults,
searchIncludeDescendants,
selectFolder,
selectedDocumentIds,
selectedEntries,
@@ -4627,6 +4800,7 @@ const AppLayout = () => {
handleBulkSelectionReanalyze,
folderOptions,
moveDocumentsToFolder,
toggleSearchIncludeDescendants,
],
);
@@ -4647,6 +4821,7 @@ const AppLayout = () => {
onCreateFolder: handlePromptCreateFolder,
creatingFolder,
tags,
untaggedFilterId: TAG_FILTER_UNTAGGED,
activeTagIds: activeTagFilters,
onToggleTagFilter: toggleTagFilter,
onCreateTag: (label) => handleTagCreate({ label }),
+147 -6
View File
@@ -6,7 +6,12 @@ import {
RefreshIcon,
MinusVerticalIcon,
InfoIcon,
FoldersIcon,
FoldersOffIcon,
SortAscendingLettersIcon,
SortDescendingLettersIcon,
} from '../ui/icons';
import QuickAddMenu from '../ui/QuickAddMenu';
import BreadcrumbTrail from '../ui/BreadcrumbTrail';
import createWorkspaceSurfaceConfig from './workspaceHeader';
import DetailPanel from '../detail/DetailPanel';
@@ -25,6 +30,19 @@ const EntryType = {
document: 'document',
};
const SORT_OPTIONS = [
{ value: 'title', label: 'Title' },
{ value: 'issued_at', label: 'Issued date' },
{ value: 'created_at', label: 'Added' },
{ value: 'updated_at', label: 'Updated date' },
];
const SORT_LABEL_LOOKUP = SORT_OPTIONS.reduce((accumulator, option) => {
const next = accumulator;
next[option.value] = option.label;
return next;
}, {});
const DocumentsPanel = ({
currentFolderName,
breadcrumbs,
@@ -545,7 +563,6 @@ const DocumentsPanel = ({
const showDefaultEmptyState = !showingSearchResults && !isFilterActive && entries.length === 0;
const showListSearchEmptyState = showingSearchResults && !hasDocumentEntries && !isGridView && !isSearchLoading;
const showGridSearchEmptyState = isGridView && showingSearchResults && !hasDocumentEntries && !isSearchLoading;
const showSearchHint = showingSearchResults && rows.length > 0;
const breadcrumbEntries = useMemo(() => (Array.isArray(breadcrumbs) ? breadcrumbs.filter(Boolean) : []), [breadcrumbs]);
const trailEntries = useMemo(() => {
if (!breadcrumbEntries.length) {
@@ -724,11 +741,6 @@ const DocumentsPanel = ({
/>
)}
</div>
{showSearchHint && (
<div className="search-hint">
Showing {rows.length} document{rows.length === 1 ? '' : 's'} in {currentFolderName} and subfolders.
</div>
)}
</div>
)}
</section>
@@ -743,16 +755,115 @@ const DocumentsPanel = ({
export default DocumentsPanel;
const SortFieldQuickMenu = ({ sortField, onChange }) => {
const currentOption = useMemo(
() => SORT_OPTIONS.find((option) => option.value === sortField) || SORT_OPTIONS[0],
[sortField],
);
const options = useMemo(
() => SORT_OPTIONS.map((option) => ({ id: option.value, label: option.label })),
[],
);
const handleSelect = useCallback(
(value, option) => {
if (typeof onChange !== 'function') {
return;
}
const nextValue = option?.id || option?.original?.id || value;
if (nextValue) {
onChange(nextValue);
}
},
[onChange],
);
const label = currentOption?.label || SORT_LABEL_LOOKUP[currentOption?.value] || 'Title';
return (
<QuickAddMenu
className="documents-sort__quickmenu"
options={options}
onSelectOption={handleSelect}
triggerClassName="view-toggle__button documents-sort__trigger quick-add__trigger"
triggerContent={(
<span className="documents-sort__trigger-content">
<span className="documents-sort__label">{label}</span>
</span>
)}
triggerAriaLabel={`Sort by ${label}`}
triggerTitle={`Sort by ${label}`}
placeholder="Select sort field"
menuMinWidth={200}
align="start"
positionStrategy="absolute"
/>
);
};
export const createDocumentsTableHeaderActions = ({
viewMode,
onViewModeChange,
onRefresh,
onShowDeskHelp = null,
sortField = 'title',
onSortFieldChange = null,
sortDirection = 'asc',
onSortDirectionToggle = null,
isFilterActive = false,
includeDescendants = true,
onToggleIncludeDescendants = null,
}) => {
const isListView = viewMode === 'list';
const isGridView = viewMode === 'grid';
const isDeskView = viewMode === 'desk';
const sortDirectionIsDesc = sortDirection === 'desc';
const sortDirectionTitle = sortDirectionIsDesc
? 'Sorting Z → A. Click to switch to ascending.'
: 'Sorting A → Z. Click to switch to descending.';
const includeDescendantsToggle = isFilterActive && typeof onToggleIncludeDescendants === 'function'
? (
<button
type="button"
className="icon-button documents-toolbar__toggle"
onClick={onToggleIncludeDescendants}
aria-pressed={!includeDescendants}
aria-label={includeDescendants ? 'Include subfolders' : 'Limit to current folder'}
title={includeDescendants
? 'Including subfolders. Click to limit the search to the current folder.'
: 'Limiting to the current folder. Click to include subfolders again.'}
>
{includeDescendants ? <FoldersIcon /> : <FoldersOffIcon />}
</button>
)
: null;
const sortControls = typeof onSortFieldChange === 'function'
? (
<div className="documents-actions__sort-group">
<SortFieldQuickMenu sortField={sortField} onChange={onSortFieldChange} />
{typeof onSortDirectionToggle === 'function' ? (
<button
type="button"
className="icon-button documents-toolbar__toggle documents-sort__direction"
onClick={onSortDirectionToggle}
aria-pressed={sortDirectionIsDesc}
aria-label={sortDirectionIsDesc ? 'Sort descending' : 'Sort ascending'}
title={sortDirectionTitle}
>
{sortDirectionIsDesc ? (
<SortDescendingLettersIcon size={18} />
) : (
<SortAscendingLettersIcon size={18} />
)}
</button>
) : null}
</div>
)
: null;
return (
<>
{isDeskView && typeof onShowDeskHelp === 'function' ? (
@@ -771,6 +882,22 @@ export const createDocumentsTableHeaderActions = ({
</span>
</>
) : null}
{includeDescendantsToggle ? (
<>
{includeDescendantsToggle}
<span className="main-content__actions-divider" aria-hidden="true">
<MinusVerticalIcon />
</span>
</>
) : null}
{sortControls ? (
<>
{sortControls}
<span className="main-content__actions-divider" aria-hidden="true">
<MinusVerticalIcon />
</span>
</>
) : null}
<div className="view-toggle" role="group" aria-label="Change view">
<button
type="button"
@@ -828,9 +955,14 @@ export const createDocumentsSurface = ({
currentFolderName,
breadcrumbs,
searchResults,
isFilterActive,
viewMode,
onViewModeChange,
onRefresh,
sortField,
sortDirection,
onSortFieldChange,
onSortDirectionToggle,
selectedDocumentIds,
selectedFolderIds,
onDeleteSelection,
@@ -846,6 +978,8 @@ export const createDocumentsSurface = ({
onBulkReanalyze,
folderOptions,
onMoveDocumentsToFolder,
searchIncludeDescendants,
onToggleSearchIncludeDescendants,
} = tableProps;
const title = Array.isArray(searchResults) ? 'Search results' : currentFolderName;
@@ -862,6 +996,13 @@ export const createDocumentsSurface = ({
viewMode,
onViewModeChange,
onRefresh,
sortField,
onSortFieldChange,
sortDirection,
onSortDirectionToggle,
isFilterActive,
includeDescendants: searchIncludeDescendants,
onToggleIncludeDescendants: onToggleSearchIncludeDescendants,
});
const floatingActions = selectionCount > 0
+16
View File
@@ -165,6 +165,7 @@ const Sidebar = ({
onCreateFolder,
creatingFolder = false,
tags = [],
untaggedFilterId = null,
activeTagIds = [],
onToggleTagFilter,
correspondents = [],
@@ -215,6 +216,7 @@ const Sidebar = ({
);
const handleToggleTag = onToggleTagFilter || (() => {});
const activeTagSet = new Set(activeTagIds);
const untaggedActive = untaggedFilterId ? activeTagSet.has(untaggedFilterId) : false;
const handleManageTags = onManageTags || (() => {});
const handleManageCorrespondents = onManageCorrespondents || (() => {});
const handleCreateTag = useCallback(async () => {
@@ -619,6 +621,20 @@ const Sidebar = ({
}`}
role="list"
>
{untaggedFilterId ? (
<button
type="button"
role="listitem"
className={`sidebar-tag-pill sidebar-tag-pill--untagged${
untaggedActive ? ' active' : ''
}`}
onClick={() => handleToggleTag(untaggedFilterId)}
aria-pressed={untaggedActive}
draggable={false}
>
No tag assigned
</button>
) : null}
{tags.map((tag) => {
const isActive = activeTagSet.has(tag.id);
const style = getTagColorStyle(tag.color);
+90 -1
View File
@@ -396,7 +396,7 @@ button.danger:hover:not([disabled]) {
.panel-header button,
.panel-header a.icon-button {
display: inline-flex;
align-items: flex-start;
align-items: center;
justify-content: flex-start;
border: none;
background: transparent;
@@ -631,6 +631,80 @@ button.danger:hover:not([disabled]) {
height: 1.4rem;
}
.documents-actions__sort-group {
display: inline-flex;
align-items: center;
gap: 0.35rem;
}
.documents-sort {
display: inline-flex;
align-items: center;
position: relative;
}
.documents-sort__trigger {
display: inline-flex;
align-items: center;
gap: 0.35rem;
font-size: 0.85rem;
white-space: nowrap;
padding: 0.25rem 0.5rem;
min-height: 2.1rem;
}
.documents-sort__label {
display: inline-flex;
align-items: center;
line-height: 1.1;
}
.documents-sort__trigger-content {
display: inline-flex;
align-items: center;
gap: 0.35rem;
}
.documents-sort__quickmenu .menu__item,
.documents-sort__quickmenu .menu__item.active {
font-weight: 400;
}
.documents-toolbar__toggle {
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.3rem;
background: transparent;
color: var(--muted);
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.documents-toolbar__toggle:hover:not([disabled]) {
color: var(--fg);
border-color: var(--border);
}
.documents-toolbar__toggle[aria-pressed='true'] {
border-color: var(--accent);
color: var(--accent);
background: var(--surface-subtle);
}
.documents-sort__direction {
padding: 0.3rem 0.45rem;
}
.documents-sort__direction[aria-pressed='true'] {
border-color: var(--border);
color: var(--muted);
background: transparent;
}
.documents-sort__direction svg {
width: 1.1rem;
height: 1.1rem;
}
.app-shell {
height: 100%;
display: flex;
@@ -1949,6 +2023,16 @@ button.danger:hover:not([disabled]) {
box-shadow: 0 0 0 1.5px var(--sidebar-active-pill-border);
}
.sidebar-tag-pill--untagged {
border: 1px dashed var(--border);
background: var(--surface-subtle);
color: var(--muted);
}
.sidebar-tag-pill--untagged.active {
color: var(--fg);
}
.sidebar-tag-cloud--has-active .sidebar-tag-pill:not(.active) {
opacity: 0.45;
}
@@ -2818,6 +2902,11 @@ button.danger:hover:not([disabled]) {
white-space: nowrap;
}
.documents-sort__trigger.quick-add__trigger {
padding: 0.25rem 0.5rem;
min-height: 2.1rem;
}
.quick-add__chip {
display: inline-flex;
align-items: center;
+40
View File
@@ -15,6 +15,8 @@ import {
IconTextScan2,
IconFolderPlus,
IconFolder,
IconFolders,
IconFoldersOff,
IconRefresh,
IconRestore,
IconMinusVertical,
@@ -34,6 +36,8 @@ import {
IconCircleDashedCheck,
IconFile,
IconLoader,
IconSortAscendingLetters,
IconSortDescendingLetters,
} from '@tabler/icons-react';
import FolderSvg from '../assets/folder.svg';
@@ -190,6 +194,24 @@ export const FolderPlusIcon = ({ className, size = '1em', stroke = 1.6, ...rest
/>
);
export const FoldersIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
<IconFolders
className={composeClassName('icon', className)}
size={size}
stroke={stroke}
{...rest}
/>
);
export const FoldersOffIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
<IconFoldersOff
className={composeClassName('icon', className)}
size={size}
stroke={stroke}
{...rest}
/>
);
export const RefreshIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
<IconRefresh
className={composeClassName('icon', className)}
@@ -262,6 +284,24 @@ export const IconFileStack = ({ className, size = 24, stroke = 160, ...rest }) =
);
};
export const SortAscendingLettersIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
<IconSortAscendingLetters
className={composeClassName('icon', className)}
size={size}
stroke={stroke}
{...rest}
/>
);
export const SortDescendingLettersIcon = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
<IconSortDescendingLetters
className={composeClassName('icon', className)}
size={size}
stroke={stroke}
{...rest}
/>
);
export const IconX = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
<TablerIconX
className={composeClassName('icon', className)}