Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,10 @@ In order to prepare a production-ready version of the app, run:
```sh
pnpm build # or: pnpm run build
```

## Note on Shadcn CLI

The shadcn CLI which is used to install components using commands like `pnpm dlx shadcn@latest add card` uses a different file name casing style.
While the CLI uses `kebab-case.tsx` as the file name for components, e.g. `alert-dialog.tsx`, we would use `PascalCase.tsx`, e.g. `AlertDialog.tsx`.

If you are adding new Shadcn components to the project using the CLI, please make sure to change the name to the correct casing (PascalCase).
4 changes: 2 additions & 2 deletions app/components/BookCard/BookCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ function BookCard({ book }: BookCardProps) {
const [searchParams] = useSearchParams();

return (
<article className="relative flex flex-col h-full box-border rounded-base border border-border bg-surface">
<article className="relative flex flex-col h-full box-border bg-card rounded-md border-2 border-transparent hover:border-border">
<FavoriteButton book={book} />

<Link
to={{
pathname: `/book/${book.id}`,
search: searchParams.toString(),
}}
className="flex flex-1 flex-col h-full w-full cursor-pointer box-border rounded-base p-6 hover:border hover:border-theme"
className="flex flex-1 flex-col h-full w-full cursor-pointer box-border rounded-md p-6"

aria-label={`View details for ${book.title}`}
>
Expand Down
2 changes: 1 addition & 1 deletion app/components/BookCover/BookCover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function BookCover({ book }: BookCoverProps) {
className="w-full object-contain px-6 py-3"
/>
) : (
<p className="flex items-center justify-center mx-6 my-3 aspect-2/3 rounded-base bg-background text-center text-sm text-theme">
<p className="flex items-center justify-center mx-6 my-3 aspect-2/3 rounded-md bg-background text-center text-sm">
No cover available
</p>
);
Expand Down
2 changes: 1 addition & 1 deletion app/components/FavoriteButton/FavoriteButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function FavoriteButton({ book }: FavoriteButtonProps) {
return (
<button
type="button"
className={`absolute right-2 top-2 flex items-center justify-center h-8 w-8 cursor-pointer bg-surface text-2xl rounded-base border ${favorites.includes(book.id) ? "border-success text-success" : "border-border text-theme hover:border-theme"}`}
className={`absolute right-2 top-2 flex items-center justify-center h-8 w-8 cursor-pointer text-2xl rounded-md border ${favorites.includes(book.id) ? "border-primary text-primary" : ""}`}
onClick={saveFavorite}
aria-label={
// Toggle the accessibility label based on the current state for screen readers
Expand Down
6 changes: 3 additions & 3 deletions app/components/FilterBooksSection/FilterBooksSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function FilterBooksSection() {

return (
<section className="flex flex-col items-center mb-8">
<h2 className="my-4">Filter Books</h2>
<h2 className="my-4 text-2xl font-bold">Filter Books</h2>
<ul className="grid grid-cols-2 gap-4 max-[900px]:grid-cols-1">
{FILTER_INPUTS.map((input) => (
<li key={input.name} className="flex flex-col gap-2">
Expand All @@ -80,7 +80,7 @@ function FilterBooksSection() {
</label>
<input
type="text"
className="rounded-base border border-border bg-surface p-2 text-base hover:border-theme"
className="bg-white rounded-md border p-2 text-base h-full hover:border-primary"
name={input.name}
aria-label={input.ariaLabel}
placeholder={input.placeholder}
Expand All @@ -104,7 +104,7 @@ function FilterBooksSection() {
</span>
</label>
<select
className="rounded-base border border-border bg-surface p-2 text-base hover:border-theme"
className="bg-white rounded-md border p-2 text-base h-full hover:border-primary"
name="language"
aria-label="Search for language"
value={filters.language ?? ""}
Expand Down
2 changes: 1 addition & 1 deletion app/components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
function Footer() {
return (
<footer className="flex items-center justify-between bg-footer px-10 py-8 text-sm text-surface max-[800px]:flex-col max-[800px]:justify-center max-[800px]:gap-1">
<footer className="flex items-center justify-between bg-zinc-800 text-white px-10 py-8 text-sm max-[800px]:flex-col max-[800px]:justify-center max-[800px]:gap-1">
<p>IT2810 Webutvikling - Gruppe 37 - Prosjekt 1</p>
<p>2026</p>
</footer>
Expand Down
2 changes: 1 addition & 1 deletion app/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
function Header() {
return (
<header className="flex items-center justify-between border-b-2 border-theme bg-surface px-10 py-3.75">
<header className="flex items-center justify-between border-b-2 border-zinc-800 bg-white px-10 py-3.75">
<h1 className="m-0 text-2xl">Readr</h1>
</header>
);
Expand Down
4 changes: 2 additions & 2 deletions app/components/Pagination/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function Pagination({ totalPages }: { totalPages: number }) {
<section className="flex items-center justify-center gap-4 py-3 mb-10">
<button
type="button"
className="flex items-center justify-center w-24 cursor-pointer rounded-base border border-border bg-surface px-5 py-2 text-theme transition duration-150 ease-in-out hover:border-control-hover-border hover:bg-control-hover disabled:cursor-not-allowed disabled:border-border disabled:bg-muted-background disabled:text-muted"
className="flex items-center justify-center w-24 cursor-pointer rounded-md border px-5 py-2 transition duration-150 ease-in-out hover:not-disabled:bg-accent disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground"
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
>
Expand All @@ -28,7 +28,7 @@ function Pagination({ totalPages }: { totalPages: number }) {
</span>
<button
type="button"
className="flex items-center justify-center w-24 cursor-pointer rounded-base border border-border bg-surface px-5 py-2 text-theme transition duration-150 ease-in-out hover:border-control-hover-border hover:bg-control-hover disabled:cursor-not-allowed disabled:border-border disabled:bg-muted-background disabled:text-muted"
className="flex items-center justify-center w-24 cursor-pointer rounded-md border px-5 py-2 transition duration-150 ease-in-out hover:not-disabled:bg-accent disabled:cursor-not-allowed disabled:bg-muted disabled:text-muted-foreground"
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
Expand Down
6 changes: 3 additions & 3 deletions app/components/SearchBar/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function SearchBar() {
type="search"
name="search"
aria-label="Search for books"
className="h-12 w-125 px-4 rounded-base border border-border bg-surface text-base font-[inherit] hover:border-theme"
className="bg-white h-12 w-125 px-4 rounded-md border text-base hover:border-primary"
placeholder="Search books..."
value={inputField}
onChange={(e) => {
Expand All @@ -63,13 +63,13 @@ function SearchBar() {
<button
type="button"
onClick={() => setFiltersOpen(!filtersOpen)}
className="relative h-12 px-4 cursor-pointer rounded-base border border-border bg-surface text-base hover:border-theme"
className="bg-white relative h-12 px-4 cursor-pointer rounded-md border text-base hover:border-primary"
aria-label="Filter books"
>
<FilterIcon />
{filtersCount > 0 && (
<span
className="absolute bottom-0 right-0 flex items-center justify-center min-h-8 min-w-8 translate-x-1/2 translate-y-1/2 rounded-full bg-theme text-surface"
className="absolute bottom-0 right-0 flex items-center justify-center min-h-8 min-w-8 translate-x-1/2 translate-y-1/2 rounded-full bg-primary text-primary-foreground"
aria-label={filtersDescription}
>
{filtersCount}
Expand Down
185 changes: 185 additions & 0 deletions app/components/ui/AlertDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import * as React from "react";
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
import { cn } from "cn";

import { Button } from "~/components/ui/Button";

function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
}

function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
return (
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
);
}

function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
return (
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
);
}

function AlertDialogOverlay({
className,
...props
}: AlertDialogPrimitive.Backdrop.Props) {
return (
<AlertDialogPrimitive.Backdrop
data-slot="alert-dialog-overlay"
className={cn(
"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
className,
)}
{...props}
/>
);
}

function AlertDialogContent({
className,
size = "default",
...props
}: AlertDialogPrimitive.Popup.Props & {
size?: "default" | "sm";
}) {
return (
<AlertDialogPortal>
<AlertDialogOverlay />
<AlertDialogPrimitive.Popup
data-slot="alert-dialog-content"
data-size={size}
className={cn(
"group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
className,
)}
{...props}
/>
</AlertDialogPortal>
);
}

function AlertDialogHeader({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-header"
className={cn(
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
className,
)}
{...props}
/>
);
}

function AlertDialogFooter({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-footer"
className={cn(
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
className,
)}
{...props}
/>
);
}

function AlertDialogMedia({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="alert-dialog-media"
className={cn(
"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
className,
)}
{...props}
/>
);
}

function AlertDialogTitle({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
return (
<AlertDialogPrimitive.Title
data-slot="alert-dialog-title"
className={cn(
"font-heading text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
className,
)}
{...props}
/>
);
}

function AlertDialogDescription({
className,
...props
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
return (
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
className={cn(
"text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
className,
)}
{...props}
/>
);
}

function AlertDialogAction({
className,
...props
}: React.ComponentProps<typeof Button>) {
return (
<Button
data-slot="alert-dialog-action"
className={cn(className)}
{...props}
/>
);
}

function AlertDialogCancel({
className,
variant = "outline",
size = "default",
...props
}: AlertDialogPrimitive.Close.Props &
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
return (
<AlertDialogPrimitive.Close
data-slot="alert-dialog-cancel"
className={cn(className)}
render={<Button variant={variant} size={size} />}
{...props}
/>
);
}

export {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogOverlay,
AlertDialogPortal,
AlertDialogTitle,
AlertDialogTrigger,
};
Loading