feat: add category banned and update ui for sorting

This commit is contained in:
Tautvydas Derzinskas committed 2026-09-09 00:02:09 +02:00
1 parent 312c6be706
commit b4e4c2aa9b
4 files changed
+99 -53

No files matched your search

@@ -139,7 +139,9 @@ export default function CollectionDetailPage({ theme, previewMode, onUnauthorize
{collection.description && (
<Typography variant="body2" color="text.secondary">{collection.description}</Typography>
)}
<SortTabs value={sortMode} onChange={setSortMode} />
<Box sx={{ display: "flex", justifyContent: "flex-end" }}>
<SortTabs value={sortMode} onChange={setSortMode} />
</Box>
{items.length ? (
<Stack spacing={2}>
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(5, 1fr)", columnGap: "20px", rowGap: "20px" }}>
@@ -0,0 +1,35 @@
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { alpha } from "@mui/material/styles";
import { type Folder } from "../../api/folders";
type Props = {
folder: Folder;
};
/** The rounded title+description banner shown above the grid when the selected category has
* admin-set meta text (Folder.metaTitle/metaDescription, edited via CategoryMetaDialog) --
* mirrors MakerWorld's category banner, a light rounded box with the category's display title
* and blurb sitting above its model grid. Models page only: a plain Collection has no
* analogous meta text today. Renders nothing without a title -- the description alone isn't
* worth a banner. */
export default function CategoryBanner({ folder }: Props) {
if (!folder.meta_title) return null;
return (
<Box
sx={{
borderRadius: 2,
px: 2.5,
py: 2,
bgcolor: (theme) => alpha(theme.palette.primary.main, 0.08),
}}
>
<Typography variant="h6">{folder.meta_title}</Typography>
{folder.meta_description && (
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
{folder.meta_description}
</Typography>
)}
</Box>
);
}
+2 -1
View File
@@ -25,7 +25,8 @@ export default function SortTabs({ value, onChange }: Props) {
onClick={() => onChange(mode)}
sx={{
cursor: "pointer",
fontWeight: value === mode ? 700 : 400,
fontSize: 14,
fontWeight: value === mode ? 700 : 500,
color: value === mode ? "primary.main" : "text.secondary",
"&:hover": { color: "primary.main" },
}}
+59 -51
View File
@@ -13,6 +13,7 @@ import { type ResolvedTheme } from "../../constants/settingsOptions";
import { usePageHeader } from "../../components/Layout/PageHeaderContext";
import { useInfiniteScroll } from "../../hooks/useInfiniteScroll";
import CategoriesPanel from "./CategoriesPanel";
import CategoryBanner from "./CategoryBanner";
import ModelCard from "./ModelCard";
import SortTabs from "./SortTabs";
@@ -185,58 +186,65 @@ export default function ModelsPage({ folderId, onSelectFolder, foldersVersion, o
};
return (
<Stack direction="row" spacing={2} alignItems="flex-start">
<CategoriesPanel
folders={folders}
loading={foldersLoading}
selectedId={folderId}
onSelect={onSelectFolder}
onCreate={createCategory}
onRename={renameCategory}
onDelete={deleteCategory}
onReorder={reorderCategories}
onUpdateMeta={updateCategoryMeta}
/>
<Box sx={{ flex: 1, minWidth: 0 }}>
<Box sx={{ mb: 2 }}>
<SortTabs value={sortMode} onChange={setSortMode} />
</Box>
{loading ? (
<Stack alignItems="center" sx={{ py: 8 }}>
<CircularProgress size={22} />
</Stack>
) : items.length ? (
<Stack spacing={2}>
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(5, 1fr)", columnGap: "20px", rowGap: "20px" }}>
{items.map(item => (
<ModelCard
key={item.id}
item={item}
theme={theme}
previewMode={previewMode}
onDeleted={deletedId => setItems(prev => prev.filter(i => i.id !== deletedId))}
onFavoriteChange={updated => setItems(prev => prev.map(i => (i.id === updated.id ? updated : i)))}
onUnauthorized={onUnauthorized}
/>
))}
</Box>
{hasMore && (
<Stack ref={loadMoreSentinelRef} direction="row" justifyContent="center" sx={{ py: 1 }}>
{loadingMore && (
<Stack direction="row" alignItems="center" spacing={1} sx={{ color: "text.secondary" }}>
<CircularProgress size={14} />
<Typography variant="caption">{t("models:grid.loadingMore")}</Typography>
</Stack>
)}
</Stack>
)}
</Stack>
) : (
<Stack alignItems="center" spacing={1} sx={{ py: 8, color: "text.secondary" }}>
<Typography variant="body2">{t("models:grid.empty")}</Typography>
</Stack>
)}
<Stack spacing={2}>
<Box sx={{ display: "flex", justifyContent: "flex-end" }}>
<SortTabs value={sortMode} onChange={setSortMode} />
</Box>
<Stack direction="row" spacing={2} alignItems="flex-start">
<CategoriesPanel
folders={folders}
loading={foldersLoading}
selectedId={folderId}
onSelect={onSelectFolder}
onCreate={createCategory}
onRename={renameCategory}
onDelete={deleteCategory}
onReorder={reorderCategories}
onUpdateMeta={updateCategoryMeta}
/>
<Box sx={{ flex: 1, minWidth: 0 }}>
{selectedFolder?.meta_title && (
<Box sx={{ mb: 2 }}>
<CategoryBanner folder={selectedFolder} />
</Box>
)}
{loading ? (
<Stack alignItems="center" sx={{ py: 8 }}>
<CircularProgress size={22} />
</Stack>
) : items.length ? (
<Stack spacing={2}>
<Box sx={{ display: "grid", gridTemplateColumns: "repeat(5, 1fr)", columnGap: "20px", rowGap: "20px" }}>
{items.map(item => (
<ModelCard
key={item.id}
item={item}
theme={theme}
previewMode={previewMode}
onDeleted={deletedId => setItems(prev => prev.filter(i => i.id !== deletedId))}
onFavoriteChange={updated => setItems(prev => prev.map(i => (i.id === updated.id ? updated : i)))}
onUnauthorized={onUnauthorized}
/>
))}
</Box>
{hasMore && (
<Stack ref={loadMoreSentinelRef} direction="row" justifyContent="center" sx={{ py: 1 }}>
{loadingMore && (
<Stack direction="row" alignItems="center" spacing={1} sx={{ color: "text.secondary" }}>
<CircularProgress size={14} />
<Typography variant="caption">{t("models:grid.loadingMore")}</Typography>
</Stack>
)}
</Stack>
)}
</Stack>
) : (
<Stack alignItems="center" spacing={1} sx={{ py: 8, color: "text.secondary" }}>
<Typography variant="body2">{t("models:grid.empty")}</Typography>
</Stack>
)}
</Box>
</Stack>
</Stack>
);
}