A pinch of responsiveness
This commit is contained in:
14
packages/frontend/src/hooks/useBreakpoint.ts
Normal file
14
packages/frontend/src/hooks/useBreakpoint.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { useSyncExternalStore } from "react";
|
||||||
|
|
||||||
|
const query = window.matchMedia("@media (width >= 48rem)");
|
||||||
|
|
||||||
|
const subscribe = (callback: () => void) => {
|
||||||
|
query.addEventListener("change", callback);
|
||||||
|
return () => {
|
||||||
|
query.removeEventListener("change", callback);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSnapshot = () => query.matches;
|
||||||
|
|
||||||
|
export const useBreakpoint = () => useSyncExternalStore(subscribe, getSnapshot);
|
||||||
@@ -5,6 +5,7 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogT
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
|
import { useBreakpoint } from "@/hooks/useBreakpoint";
|
||||||
import { useCache } from "@/hooks/useCache";
|
import { useCache } from "@/hooks/useCache";
|
||||||
import { useLoading } from "@/hooks/useLoading";
|
import { useLoading } from "@/hooks/useLoading";
|
||||||
import { authors, created, DEBOUNCE, modified, SAVE_DELAY } from "@/snippets";
|
import { authors, created, DEBOUNCE, modified, SAVE_DELAY } from "@/snippets";
|
||||||
@@ -25,6 +26,8 @@ export function Pieces() {
|
|||||||
const [importDialogOpen, setImportDialogOpen] = useState(false);
|
const [importDialogOpen, setImportDialogOpen] = useState(false);
|
||||||
|
|
||||||
const debounce = useRef(Effect.void);
|
const debounce = useRef(Effect.void);
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
const columns = breakpoint ? 4 : 1;
|
||||||
|
|
||||||
const { isLoading, error, data: pieceIds, refresh } = useLoading(Effect.gen(function* () {
|
const { isLoading, error, data: pieceIds, refresh } = useLoading(Effect.gen(function* () {
|
||||||
yield* debounce.current;
|
yield* debounce.current;
|
||||||
@@ -39,7 +42,7 @@ export function Pieces() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 overflow-y-auto flex flex-col items-start gap-4">
|
<div className="p-4 overflow-y-auto flex flex-col items-start gap-4">
|
||||||
<div className="flex flex-row gap-4">
|
<div className="flex flex-row flex-wrap gap-4">
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline">
|
<Button variant="outline">
|
||||||
@@ -57,7 +60,7 @@ export function Pieces() {
|
|||||||
<ImportPiecesDialogContent setDialogOpen={setImportDialogOpen} refresh={refresh} />
|
<ImportPiecesDialogContent setDialogOpen={setImportDialogOpen} refresh={refresh} />
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Input
|
<Input
|
||||||
className="w-[32ch]"
|
className="w-full md:w-[32ch]"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Tytuł"
|
placeholder="Tytuł"
|
||||||
value={name}
|
value={name}
|
||||||
@@ -67,7 +70,7 @@ export function Pieces() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
className="w-[32ch]"
|
className="w-full md:w-[32ch]"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Twórcy"
|
placeholder="Twórcy"
|
||||||
value={author}
|
value={author}
|
||||||
@@ -78,6 +81,7 @@ export function Pieces() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Table>
|
<Table>
|
||||||
|
{breakpoint && (
|
||||||
<TableHeader className="bg-white sticky top-0">
|
<TableHeader className="bg-white sticky top-0">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Tytuł</TableHead>
|
<TableHead>Tytuł</TableHead>
|
||||||
@@ -86,10 +90,11 @@ export function Pieces() {
|
|||||||
<TableHead className="text-center">Zmodyfikowano</TableHead>
|
<TableHead className="text-center">Zmodyfikowano</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
|
)}
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={4} >
|
<TableCell colSpan={columns} >
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<Loader2 className="animate-spin" />
|
<Loader2 className="animate-spin" />
|
||||||
Ładowanie…
|
Ładowanie…
|
||||||
@@ -98,7 +103,7 @@ export function Pieces() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
) : error !== null ? (
|
) : error !== null ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={4} className="text-center">
|
<TableCell colSpan={columns} className="text-center">
|
||||||
{Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error)}`}
|
{Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error)}`}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -121,10 +126,13 @@ function PieceRow(props: PieceRow.Props) {
|
|||||||
|
|
||||||
const { isLoading, error, data: piece } = useCache(pieceCache, props.pieceId);
|
const { isLoading, error, data: piece } = useCache(pieceCache, props.pieceId);
|
||||||
|
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
const columns = breakpoint ? 4 : 1;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={4}>Ładowanie…</TableCell>
|
<TableCell colSpan={columns}>Ładowanie…</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -132,7 +140,7 @@ function PieceRow(props: PieceRow.Props) {
|
|||||||
if (error !== null) {
|
if (error !== null) {
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={4}>
|
<TableCell colSpan={columns}>
|
||||||
Wystąpił błąd: {Match.value(error).pipe(
|
Wystąpił błąd: {Match.value(error).pipe(
|
||||||
Match.tag("FetchError", () => "Nie można połączyć się z serwerem"),
|
Match.tag("FetchError", () => "Nie można połączyć się z serwerem"),
|
||||||
Match.tag("NotFound", () => "Utwór nie istnieje"),
|
Match.tag("NotFound", () => "Utwór nie istnieje"),
|
||||||
@@ -147,6 +155,7 @@ function PieceRow(props: PieceRow.Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
{breakpoint ? (<>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Link className="underline" to={piece.pieceId}>{piece.name}</Link>
|
<Link className="underline" to={piece.pieceId}>{piece.name}</Link>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -159,6 +168,12 @@ function PieceRow(props: PieceRow.Props) {
|
|||||||
<TableCell className="text-center text-xs">
|
<TableCell className="text-center text-xs">
|
||||||
{modified(piece)}
|
{modified(piece)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
</>) : (
|
||||||
|
<TableCell className="flex flex-col">
|
||||||
|
<Link className="underline" to={piece.pieceId}>{piece.name}</Link>
|
||||||
|
<div className="text-xs">{authors(piece)}</div>
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogT
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
|
import { useBreakpoint } from "@/hooks/useBreakpoint";
|
||||||
import { useCache } from "@/hooks/useCache";
|
import { useCache } from "@/hooks/useCache";
|
||||||
import { useLoading } from "@/hooks/useLoading";
|
import { useLoading } from "@/hooks/useLoading";
|
||||||
import { created, DEBOUNCE, modified } from "@/snippets";
|
import { created, DEBOUNCE, modified } from "@/snippets";
|
||||||
@@ -19,6 +20,8 @@ export function Repertoires() {
|
|||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
|
|
||||||
const debounce = useRef(Effect.void);
|
const debounce = useRef(Effect.void);
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
const columns = breakpoint ? 4 : 1;
|
||||||
|
|
||||||
const { isLoading, error, data: repertoireIds } = useLoading(Effect.gen(function* () {
|
const { isLoading, error, data: repertoireIds } = useLoading(Effect.gen(function* () {
|
||||||
yield* debounce.current;
|
yield* debounce.current;
|
||||||
@@ -32,7 +35,7 @@ export function Repertoires() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 overflow-y-auto flex flex-col items-start gap-4">
|
<div className="p-4 overflow-y-auto flex flex-col items-start gap-4">
|
||||||
<div className="flex flex-row gap-4">
|
<div className="flex flex-row flex-wrap gap-4">
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline">
|
<Button variant="outline">
|
||||||
@@ -42,7 +45,7 @@ export function Repertoires() {
|
|||||||
<AddRepertoireDialogContent />
|
<AddRepertoireDialogContent />
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Input
|
<Input
|
||||||
className="w-[32ch]"
|
className="w-full md:w-[32ch]"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Nazwa"
|
placeholder="Nazwa"
|
||||||
value={name}
|
value={name}
|
||||||
@@ -53,6 +56,7 @@ export function Repertoires() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Table>
|
<Table>
|
||||||
|
{breakpoint && (
|
||||||
<TableHeader className="bg-white sticky top-0">
|
<TableHeader className="bg-white sticky top-0">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Nazwa</TableHead>
|
<TableHead>Nazwa</TableHead>
|
||||||
@@ -61,10 +65,11 @@ export function Repertoires() {
|
|||||||
<TableHead className="text-center">Zmodyfikowano</TableHead>
|
<TableHead className="text-center">Zmodyfikowano</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
|
)}
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={4} >
|
<TableCell colSpan={columns} >
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<Loader2 className="animate-spin" />
|
<Loader2 className="animate-spin" />
|
||||||
Ładowanie…
|
Ładowanie…
|
||||||
@@ -73,7 +78,7 @@ export function Repertoires() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
) : error !== null ? (
|
) : error !== null ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={4} className="text-center">
|
<TableCell colSpan={columns} className="text-center">
|
||||||
{Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error)}`}
|
{Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${JSON.stringify(error)}`}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -96,10 +101,13 @@ function RepertoireRow(props: RepertoireRow.Props) {
|
|||||||
|
|
||||||
const { isLoading, error, data: repertoire } = useCache(repertoireCache, props.repertoireId);
|
const { isLoading, error, data: repertoire } = useCache(repertoireCache, props.repertoireId);
|
||||||
|
|
||||||
|
const breakpoint = useBreakpoint();
|
||||||
|
const columns = breakpoint ? 4 : 1;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={4}>Ładowanie…</TableCell>
|
<TableCell colSpan={columns}>Ładowanie…</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -107,7 +115,7 @@ function RepertoireRow(props: RepertoireRow.Props) {
|
|||||||
if (error !== null) {
|
if (error !== null) {
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={4}>
|
<TableCell colSpan={columns}>
|
||||||
Wystąpił błąd: {Match.value(error).pipe(
|
Wystąpił błąd: {Match.value(error).pipe(
|
||||||
Match.tag("FetchError", () => "Nie można połączyć się z serwerem"),
|
Match.tag("FetchError", () => "Nie można połączyć się z serwerem"),
|
||||||
Match.tag("NotFound", () => "Repertuar nie istnieje"),
|
Match.tag("NotFound", () => "Repertuar nie istnieje"),
|
||||||
@@ -136,6 +144,7 @@ function RepertoireRow(props: RepertoireRow.Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
{breakpoint ? (<>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Link className="underline" to={repertoire.repertoireId}>{repertoire.name}</Link>
|
<Link className="underline" to={repertoire.repertoireId}>{repertoire.name}</Link>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -148,6 +157,12 @@ function RepertoireRow(props: RepertoireRow.Props) {
|
|||||||
<TableCell className="text-center text-xs">
|
<TableCell className="text-center text-xs">
|
||||||
{modified(repertoire)}
|
{modified(repertoire)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
</>) : (
|
||||||
|
<TableCell className="flex flex-col">
|
||||||
|
<Link className="underline" to={repertoire.repertoireId}>{repertoire.name}</Link>
|
||||||
|
<div className="text-xs">{...piecesParts}</div>
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user