feat: add pagination size selection and compact view toggle in series grid
This commit is contained in:
12
docs/api.md
12
docs/api.md
@@ -50,6 +50,18 @@
|
|||||||
- **Description** : Liste des bibliothèques
|
- **Description** : Liste des bibliothèques
|
||||||
- **Réponse** : `Library[]`
|
- **Réponse** : `Library[]`
|
||||||
|
|
||||||
|
### GET /libraries/[libraryId]
|
||||||
|
|
||||||
|
- **Description** : Page d'une bibliothèque
|
||||||
|
- **Paramètres** : `libraryId` dans l'URL
|
||||||
|
- **Query Parameters** :
|
||||||
|
- `page` : Numéro de page (défaut: 1)
|
||||||
|
- `size` : Nombre d'éléments par page (défaut: 20, valeurs possibles: 20, 50, 100)
|
||||||
|
- `unread` : Filtrer les séries non lues (défaut: false)
|
||||||
|
- `search` : Rechercher une série par titre
|
||||||
|
- `compact` : Mode d'affichage compact (défaut: false)
|
||||||
|
- **Réponse** : Page HTML avec la liste des séries
|
||||||
|
|
||||||
## 📖 Séries
|
## 📖 Séries
|
||||||
|
|
||||||
### GET /api/komga/series/[seriesId]
|
### GET /api/komga/series/[seriesId]
|
||||||
|
|||||||
@@ -35,9 +35,14 @@ Service de gestion des bibliothèques
|
|||||||
- Récupère une bibliothèque spécifique
|
- Récupère une bibliothèque spécifique
|
||||||
- Lance une erreur si non trouvée
|
- Lance une erreur si non trouvée
|
||||||
|
|
||||||
- `getLibrarySeries(libraryId: string, page: number = 0, size: number = 20, unreadOnly: boolean = false): Promise<LibraryResponse<Series>>`
|
- `getLibrarySeries(libraryId: string, page: number = 0, size: number = 20, unreadOnly: boolean = false, search?: string): Promise<LibraryResponse<Series>>`
|
||||||
- Récupère les séries d'une bibliothèque
|
- Récupère les séries d'une bibliothèque
|
||||||
- Supporte la pagination et le filtrage
|
- Supporte la pagination et le filtrage
|
||||||
|
- Paramètres :
|
||||||
|
- `page` : Numéro de page (défaut: 0)
|
||||||
|
- `size` : Nombre d'éléments par page (défaut: 20, valeurs possibles: 20, 50, 100)
|
||||||
|
- `unreadOnly` : Filtrer les séries non lues (défaut: false)
|
||||||
|
- `search` : Rechercher une série par titre (optionnel)
|
||||||
|
|
||||||
## 📖 SeriesService
|
## 📖 SeriesService
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"@radix-ui/react-dialog": "1.0.5",
|
"@radix-ui/react-dialog": "1.0.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||||
"@radix-ui/react-progress": "^1.1.2",
|
"@radix-ui/react-progress": "^1.1.2",
|
||||||
|
"@radix-ui/react-select": "^2.1.6",
|
||||||
"@radix-ui/react-slot": "1.0.2",
|
"@radix-ui/react-slot": "1.0.2",
|
||||||
"@radix-ui/react-toast": "1.1.5",
|
"@radix-ui/react-toast": "1.1.5",
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import { AppError } from "@/utils/errors";
|
|||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
params: { libraryId: string };
|
params: { libraryId: string };
|
||||||
searchParams: { page?: string; unread?: string; search?: string };
|
searchParams: { page?: string; unread?: string; search?: string; size?: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
const PAGE_SIZE = 20;
|
const DEFAULT_PAGE_SIZE = 20;
|
||||||
|
|
||||||
async function refreshLibrary(libraryId: string) {
|
async function refreshLibrary(libraryId: string) {
|
||||||
"use server";
|
"use server";
|
||||||
@@ -36,7 +36,8 @@ async function getLibrarySeries(
|
|||||||
libraryId: string,
|
libraryId: string,
|
||||||
page: number = 1,
|
page: number = 1,
|
||||||
unreadOnly: boolean = false,
|
unreadOnly: boolean = false,
|
||||||
search?: string
|
search?: string,
|
||||||
|
size: number = DEFAULT_PAGE_SIZE
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const pageIndex = page - 1;
|
const pageIndex = page - 1;
|
||||||
@@ -44,7 +45,7 @@ async function getLibrarySeries(
|
|||||||
const series: LibraryResponse<KomgaSeries> = await LibraryService.getLibrarySeries(
|
const series: LibraryResponse<KomgaSeries> = await LibraryService.getLibrarySeries(
|
||||||
libraryId,
|
libraryId,
|
||||||
pageIndex,
|
pageIndex,
|
||||||
PAGE_SIZE,
|
size,
|
||||||
unreadOnly,
|
unreadOnly,
|
||||||
search
|
search
|
||||||
);
|
);
|
||||||
@@ -61,16 +62,18 @@ async function LibraryPage({ params, searchParams }: PageProps) {
|
|||||||
const unread = (await searchParams).unread;
|
const unread = (await searchParams).unread;
|
||||||
const search = (await searchParams).search;
|
const search = (await searchParams).search;
|
||||||
const page = (await searchParams).page;
|
const page = (await searchParams).page;
|
||||||
|
const size = (await searchParams).size;
|
||||||
|
|
||||||
const currentPage = page ? parseInt(page) : 1;
|
const currentPage = page ? parseInt(page) : 1;
|
||||||
|
const pageSize = size ? parseInt(size) : DEFAULT_PAGE_SIZE;
|
||||||
const preferences: UserPreferences = await PreferencesService.getPreferences();
|
const preferences: UserPreferences = await PreferencesService.getPreferences();
|
||||||
|
|
||||||
// Utiliser le paramètre d'URL s'il existe, sinon utiliser la préférence utilisateur
|
// Utiliser le paramètre d'URL s'il existe, sinon utiliser la préférence utilisateur
|
||||||
const unreadOnly = unread !== undefined ? unread === "true" : preferences.showOnlyUnread;
|
const unreadOnly = unread !== undefined ? unread === "true" : preferences.showOnlyUnread;
|
||||||
|
console.log(unreadOnly);
|
||||||
try {
|
try {
|
||||||
const { data: series, library }: { data: LibraryResponse<KomgaSeries>; library: KomgaLibrary } =
|
const { data: series, library }: { data: LibraryResponse<KomgaSeries>; library: KomgaLibrary } =
|
||||||
await getLibrarySeries(libraryId, currentPage, unreadOnly, search);
|
await getLibrarySeries(libraryId, currentPage, unreadOnly, search, pageSize);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container py-8 space-y-8">
|
<div className="container py-8 space-y-8">
|
||||||
@@ -90,7 +93,7 @@ async function LibraryPage({ params, searchParams }: PageProps) {
|
|||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
totalPages={series.totalPages}
|
totalPages={series.totalPages}
|
||||||
totalElements={series.totalElements}
|
totalElements={series.totalElements}
|
||||||
pageSize={PAGE_SIZE}
|
pageSize={pageSize}
|
||||||
defaultShowOnlyUnread={preferences.showOnlyUnread}
|
defaultShowOnlyUnread={preferences.showOnlyUnread}
|
||||||
showOnlyUnread={unreadOnly}
|
showOnlyUnread={unreadOnly}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,11 +4,18 @@ import { SeriesGrid } from "./SeriesGrid";
|
|||||||
import { Pagination } from "@/components/ui/Pagination";
|
import { Pagination } from "@/components/ui/Pagination";
|
||||||
import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Loader2, Filter } from "lucide-react";
|
import { Loader2, Filter, LayoutGrid, LayoutList, LayoutTemplate } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { KomgaSeries } from "@/types/komga";
|
import type { KomgaSeries } from "@/types/komga";
|
||||||
import { SearchInput } from "./SearchInput";
|
import { SearchInput } from "./SearchInput";
|
||||||
import { useTranslate } from "@/hooks/useTranslate";
|
import { useTranslate } from "@/hooks/useTranslate";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
|
||||||
interface PaginatedSeriesGridProps {
|
interface PaginatedSeriesGridProps {
|
||||||
series: KomgaSeries[];
|
series: KomgaSeries[];
|
||||||
@@ -34,8 +41,25 @@ export function PaginatedSeriesGrid({
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const [isChangingPage, setIsChangingPage] = useState(false);
|
const [isChangingPage, setIsChangingPage] = useState(false);
|
||||||
const [showOnlyUnread, setShowOnlyUnread] = useState(initialShowOnlyUnread);
|
const [showOnlyUnread, setShowOnlyUnread] = useState(initialShowOnlyUnread);
|
||||||
|
const [isCompact, setIsCompact] = useState(searchParams.get("compact") === "true");
|
||||||
const { t } = useTranslate();
|
const { t } = useTranslate();
|
||||||
|
|
||||||
|
const updateUrlParams = async (updates: Record<string, string | null>) => {
|
||||||
|
setIsChangingPage(true);
|
||||||
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
|
||||||
|
// Mettre à jour les paramètres
|
||||||
|
Object.entries(updates).forEach(([key, value]) => {
|
||||||
|
if (value === null) {
|
||||||
|
params.delete(key);
|
||||||
|
} else {
|
||||||
|
params.set(key, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await router.push(`${pathname}?${params.toString()}`);
|
||||||
|
};
|
||||||
|
|
||||||
// Reset loading state when series change
|
// Reset loading state when series change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsChangingPage(false);
|
setIsChangingPage(false);
|
||||||
@@ -49,31 +73,39 @@ export function PaginatedSeriesGrid({
|
|||||||
// Apply default filter on initial load
|
// Apply default filter on initial load
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (defaultShowOnlyUnread && !searchParams.has("unread")) {
|
if (defaultShowOnlyUnread && !searchParams.has("unread")) {
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
updateUrlParams({ page: "1", unread: "true" });
|
||||||
params.set("page", "1");
|
|
||||||
params.set("unread", "true");
|
|
||||||
router.push(`${pathname}?${params.toString()}`);
|
|
||||||
}
|
}
|
||||||
}, [defaultShowOnlyUnread, pathname, router, searchParams]);
|
}, [defaultShowOnlyUnread, pathname, router, searchParams]);
|
||||||
|
|
||||||
const handlePageChange = async (page: number) => {
|
const handlePageChange = async (page: number) => {
|
||||||
setIsChangingPage(true);
|
await updateUrlParams({ page: page.toString(), compact: isCompact.toString() });
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
|
||||||
params.set("page", page.toString());
|
|
||||||
params.set("unread", showOnlyUnread.toString());
|
|
||||||
await router.push(`${pathname}?${params.toString()}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUnreadFilter = async () => {
|
const handleUnreadFilter = async () => {
|
||||||
setIsChangingPage(true);
|
|
||||||
const params = new URLSearchParams(searchParams.toString());
|
|
||||||
params.set("page", "1");
|
|
||||||
|
|
||||||
const newUnreadState = !showOnlyUnread;
|
const newUnreadState = !showOnlyUnread;
|
||||||
setShowOnlyUnread(newUnreadState);
|
setShowOnlyUnread(newUnreadState);
|
||||||
params.set("unread", newUnreadState.toString());
|
await updateUrlParams({
|
||||||
|
page: "1",
|
||||||
|
unread: newUnreadState ? "true" : "false",
|
||||||
|
compact: isCompact.toString(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
await router.push(`${pathname}?${params.toString()}`);
|
const handleCompactToggle = async () => {
|
||||||
|
const newCompactState = !isCompact;
|
||||||
|
setIsCompact(newCompactState);
|
||||||
|
await updateUrlParams({
|
||||||
|
page: "1",
|
||||||
|
compact: newCompactState.toString(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageSizeChange = async (value: string) => {
|
||||||
|
await updateUrlParams({
|
||||||
|
page: "1",
|
||||||
|
size: value,
|
||||||
|
compact: isCompact.toString(),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate start and end indices for display
|
// Calculate start and end indices for display
|
||||||
@@ -92,15 +124,42 @@ export function PaginatedSeriesGrid({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<div className="flex items-center justify-between flex-wrap gap-4">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
<div className="flex-1">
|
<div className="w-full sm:w-auto sm:flex-1">
|
||||||
<SearchInput placeholder={t("series.filters.search")} />
|
<SearchInput placeholder={t("series.filters.search")} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-2">
|
||||||
<p className="text-sm text-muted-foreground">{getShowingText()}</p>
|
<p className="text-sm text-muted-foreground">{getShowingText()}</p>
|
||||||
|
<Select value={pageSize.toString()} onValueChange={handlePageSizeChange}>
|
||||||
|
<SelectTrigger className="w-[80px]">
|
||||||
|
<LayoutList className="h-4 w-4 mr-2" />
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="20">20</SelectItem>
|
||||||
|
<SelectItem value="50">50</SelectItem>
|
||||||
|
<SelectItem value="100">100</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<button
|
||||||
|
onClick={handleCompactToggle}
|
||||||
|
className="inline-flex items-center gap-2 px-2 py-1.5 text-sm font-medium rounded-lg hover:bg-accent hover:text-accent-foreground whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{isCompact ? (
|
||||||
|
<>
|
||||||
|
<LayoutTemplate className="h-4 w-4" />
|
||||||
|
{t("series.filters.normal")}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<LayoutGrid className="h-4 w-4" />
|
||||||
|
{t("series.filters.compact")}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleUnreadFilter}
|
onClick={handleUnreadFilter}
|
||||||
className="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-lg hover:bg-accent hover:text-accent-foreground whitespace-nowrap"
|
className="inline-flex items-center gap-2 px-2 py-1.5 text-sm font-medium rounded-lg hover:bg-accent hover:text-accent-foreground whitespace-nowrap"
|
||||||
>
|
>
|
||||||
<Filter className="h-4 w-4" />
|
<Filter className="h-4 w-4" />
|
||||||
{showOnlyUnread ? t("series.filters.showAll") : t("series.filters.unread")}
|
{showOnlyUnread ? t("series.filters.showAll") : t("series.filters.unread")}
|
||||||
@@ -126,7 +185,7 @@ export function PaginatedSeriesGrid({
|
|||||||
isChangingPage ? "opacity-25" : "opacity-100"
|
isChangingPage ? "opacity-25" : "opacity-100"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<SeriesGrid series={series} />
|
<SeriesGrid series={series} isCompact={isCompact} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useTranslate } from "@/hooks/useTranslate";
|
|||||||
|
|
||||||
interface SeriesGridProps {
|
interface SeriesGridProps {
|
||||||
series: KomgaSeries[];
|
series: KomgaSeries[];
|
||||||
|
isCompact?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility function to get reading status info
|
// Utility function to get reading status info
|
||||||
@@ -42,7 +43,7 @@ const getReadingStatusInfo = (series: KomgaSeries, t: (key: string, options?: an
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SeriesGrid({ series }: SeriesGridProps) {
|
export function SeriesGrid({ series, isCompact = false }: SeriesGridProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslate();
|
const { t } = useTranslate();
|
||||||
|
|
||||||
@@ -55,14 +56,22 @@ export function SeriesGrid({ series }: SeriesGridProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-5">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"grid gap-4",
|
||||||
|
isCompact
|
||||||
|
? "grid-cols-3 sm:grid-cols-4 lg:grid-cols-6"
|
||||||
|
: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-5"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{series.map((series) => (
|
{series.map((series) => (
|
||||||
<button
|
<button
|
||||||
key={series.id}
|
key={series.id}
|
||||||
onClick={() => router.push(`/series/${series.id}`)}
|
onClick={() => router.push(`/series/${series.id}`)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group relative aspect-[2/3] overflow-hidden rounded-lg bg-muted",
|
"group relative aspect-[2/3] overflow-hidden rounded-lg bg-muted",
|
||||||
series.booksCount === series.booksReadCount && "opacity-50"
|
series.booksCount === series.booksReadCount && "opacity-50",
|
||||||
|
isCompact && "aspect-[3/4]"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<SeriesCover
|
<SeriesCover
|
||||||
|
|||||||
121
src/components/ui/select.tsx
Normal file
121
src/components/ui/select.tsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||||
|
import { Check, ChevronDown } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const Select = SelectPrimitive.Root;
|
||||||
|
|
||||||
|
const SelectGroup = SelectPrimitive.Group;
|
||||||
|
|
||||||
|
const SelectValue = SelectPrimitive.Value;
|
||||||
|
|
||||||
|
const SelectTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SelectPrimitive.Icon asChild>
|
||||||
|
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||||
|
</SelectPrimitive.Icon>
|
||||||
|
</SelectPrimitive.Trigger>
|
||||||
|
));
|
||||||
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
|
const SelectContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||||
|
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Portal>
|
||||||
|
<SelectPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
position === "popper" &&
|
||||||
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
position={position}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SelectPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
"p-1",
|
||||||
|
position === "popper" &&
|
||||||
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
));
|
||||||
|
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
const SelectLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
||||||
|
|
||||||
|
const SelectItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<SelectPrimitive.ItemIndicator>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
));
|
||||||
|
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||||
|
|
||||||
|
const SelectSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Select,
|
||||||
|
SelectGroup,
|
||||||
|
SelectValue,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectContent,
|
||||||
|
SelectLabel,
|
||||||
|
SelectItem,
|
||||||
|
SelectSeparator,
|
||||||
|
};
|
||||||
@@ -173,10 +173,12 @@
|
|||||||
"title": "Filters",
|
"title": "Filters",
|
||||||
"showAll": "Show all",
|
"showAll": "Show all",
|
||||||
"unread": "Unread",
|
"unread": "Unread",
|
||||||
"search": "Search series..."
|
"search": "Search series...",
|
||||||
|
"normal": "Normal",
|
||||||
|
"compact": "Compact"
|
||||||
},
|
},
|
||||||
"display": {
|
"display": {
|
||||||
"showing": "Showing series {start} to {end} of {total}",
|
"showing": "{start}-{end} of {total}",
|
||||||
"page": "Page {current} of {total}"
|
"page": "Page {current} of {total}"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
|
|||||||
@@ -173,10 +173,12 @@
|
|||||||
"title": "Filtres",
|
"title": "Filtres",
|
||||||
"showAll": "Afficher tout",
|
"showAll": "Afficher tout",
|
||||||
"unread": "À lire",
|
"unread": "À lire",
|
||||||
"search": "Rechercher une série..."
|
"search": "Rechercher une série...",
|
||||||
|
"normal": "Affichage normal",
|
||||||
|
"compact": "Affichage compact"
|
||||||
},
|
},
|
||||||
"display": {
|
"display": {
|
||||||
"showing": "Affichage des séries {start} à {end} sur {total}",
|
"showing": "{start}-{end} sur {total}",
|
||||||
"page": "Page {current} sur {total}"
|
"page": "Page {current} sur {total}"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
|
|||||||
44
yarn.lock
44
yarn.lock
@@ -486,6 +486,11 @@
|
|||||||
resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
|
resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
|
||||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||||
|
|
||||||
|
"@radix-ui/number@1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.0.tgz#1e95610461a09cdf8bb05c152e76ca1278d5da46"
|
||||||
|
integrity sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==
|
||||||
|
|
||||||
"@radix-ui/primitive@1.0.1":
|
"@radix-ui/primitive@1.0.1":
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz"
|
||||||
@@ -769,6 +774,33 @@
|
|||||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-select@^2.1.6":
|
||||||
|
version "2.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-2.1.6.tgz#79c07cac4de0188e6f7afb2720a87a0405d88849"
|
||||||
|
integrity sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/number" "1.1.0"
|
||||||
|
"@radix-ui/primitive" "1.1.1"
|
||||||
|
"@radix-ui/react-collection" "1.1.2"
|
||||||
|
"@radix-ui/react-compose-refs" "1.1.1"
|
||||||
|
"@radix-ui/react-context" "1.1.1"
|
||||||
|
"@radix-ui/react-direction" "1.1.0"
|
||||||
|
"@radix-ui/react-dismissable-layer" "1.1.5"
|
||||||
|
"@radix-ui/react-focus-guards" "1.1.1"
|
||||||
|
"@radix-ui/react-focus-scope" "1.1.2"
|
||||||
|
"@radix-ui/react-id" "1.1.0"
|
||||||
|
"@radix-ui/react-popper" "1.2.2"
|
||||||
|
"@radix-ui/react-portal" "1.1.4"
|
||||||
|
"@radix-ui/react-primitive" "2.0.2"
|
||||||
|
"@radix-ui/react-slot" "1.1.2"
|
||||||
|
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||||
|
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||||
|
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||||
|
"@radix-ui/react-use-previous" "1.1.0"
|
||||||
|
"@radix-ui/react-visually-hidden" "1.1.2"
|
||||||
|
aria-hidden "^1.2.4"
|
||||||
|
react-remove-scroll "^2.6.3"
|
||||||
|
|
||||||
"@radix-ui/react-slot@1.0.2":
|
"@radix-ui/react-slot@1.0.2":
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz"
|
resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz"
|
||||||
@@ -857,6 +889,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27"
|
||||||
integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==
|
integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==
|
||||||
|
|
||||||
|
"@radix-ui/react-use-previous@1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz#d4dd37b05520f1d996a384eb469320c2ada8377c"
|
||||||
|
integrity sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==
|
||||||
|
|
||||||
"@radix-ui/react-use-rect@1.1.0":
|
"@radix-ui/react-use-rect@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz#13b25b913bd3e3987cc9b073a1a164bb1cf47b88"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz#13b25b913bd3e3987cc9b073a1a164bb1cf47b88"
|
||||||
@@ -879,6 +916,13 @@
|
|||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-primitive" "1.0.3"
|
"@radix-ui/react-primitive" "1.0.3"
|
||||||
|
|
||||||
|
"@radix-ui/react-visually-hidden@1.1.2":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz#8f6025507eb5d8b4b3215ebfd2c71a6632323a62"
|
||||||
|
integrity sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-primitive" "2.0.2"
|
||||||
|
|
||||||
"@radix-ui/rect@1.1.0":
|
"@radix-ui/rect@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438"
|
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438"
|
||||||
|
|||||||
Reference in New Issue
Block a user