Updating shadcn files (Uses bits-ui), moving text search to just search page.

This commit is contained in:
Bradley Shellnut 2023-11-06 16:36:51 -08:00
parent 79fde8beb8
commit 229d84df40
27 changed files with 353 additions and 214 deletions

3
src/app.d.ts vendored
View file

@ -2,7 +2,7 @@
// for information about these interfaces // for information about these interfaces
// and what to do when importing types // and what to do when importing types
import type { User } from '@prisma/client'; import type { PrismaClient, User } from '@prisma/client';
type User = Omit<User, 'created_at' | 'updated_at'>; type User = Omit<User, 'created_at' | 'updated_at'>;
@ -15,6 +15,7 @@ declare global {
interface Locals { interface Locals {
auth: import('lucia').AuthRequest; auth: import('lucia').AuthRequest;
user: Lucia.UserAttributes; user: Lucia.UserAttributes;
prisma: PrismaClient;
startTimer: number; startTimer: number;
error: string; error: string;
errorId: string; errorId: string;

View file

View file

@ -1,74 +1,18 @@
<script lang="ts"> <script lang="ts">
import { tick } from 'svelte';
import { fade, fly } from 'svelte/transition';
import { superForm } from 'sveltekit-superforms/client'; import { superForm } from 'sveltekit-superforms/client';
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte'; import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
import type { SuperValidated } from 'sveltekit-superforms/index'; import type { SuperValidated } from 'sveltekit-superforms';
import { boredState } from '$lib/stores/boredState';
import AdvancedSearch from '$lib/components/search/advancedSearch/index.svelte';
import { xl, md, sm } from '$lib/stores/mediaQueryStore';
import { gameStore } from '$lib/stores/gameSearchStore';
import Pagination from '$lib/components/pagination/index.svelte';
import Game from '$lib/components/game/index.svelte';
import { type GameType, type SavedGameType } from '$lib/types';
import type { SearchSchema } from '$lib/zodValidation'; import type { SearchSchema } from '$lib/zodValidation';
import { Label } from '$components/ui/label'; import { Label } from '$lib/components/ui/label';
import { Input } from '$components/ui/input'; import { Input } from '$lib/components/ui/input';
import { Button } from '$components/ui/button'; import { Button } from '$lib/components/ui/button';
export let data; export let data;
console.log("text search data", data); console.log("text search data", data);
export let showButton: boolean = false; export let showButton: boolean = false;
export let advancedSearch: boolean = false; export let advancedSearch: boolean = false;
const { games, totalCount } = data?.searchData; const { form, errors }: SuperValidated<SearchSchema> = superForm(data.form);
const { form, errors, enhance, constraints, message }: SuperValidated<SearchSchema> = superForm(data.form);
let gameToRemove: GameType | SavedGameType;
let numberOfGameSkeleton = 1;
let submitButton: HTMLElement;
let pageSize = +form?.limit || 10;
let totalItems = +form?.searchData?.totalCount || 0;
let offset = +form?.skip || 0;
let page = Math.floor(offset / pageSize) + 1 || 1;
let submitting = $boredState?.loading;
let name = form?.name || '';
let disclosureOpen = $errors.length > 0 || false;
$: showPagination = totalCount > pageSize;
if ($xl) {
numberOfGameSkeleton = 8;
} else if ($md) {
numberOfGameSkeleton = 3;
} else if ($sm) {
numberOfGameSkeleton = 2;
} else {
numberOfGameSkeleton = 1;
}
async function handleNextPageEvent(event: CustomEvent) {
if (+event?.detail?.page === page + 1) {
page += 1;
}
await tick();
submitButton.click();
}
async function handlePreviousPageEvent(event: CustomEvent) {
if (+event?.detail?.page === page - 1) {
page -= 1;
}
await tick();
submitButton.click();
}
async function handlePerPageEvent(event: CustomEvent) {
page = 1;
pageSize = event.detail.pageSize;
await tick();
submitButton.click();
}
const dev = process.env.NODE_ENV !== 'production'; const dev = process.env.NODE_ENV !== 'production';
@ -77,13 +21,13 @@
</script> </script>
{#if dev} {#if dev}
<SuperDebug data={$form} /> <SuperDebug collapsible data={$form} />
{/if} {/if}
<search> <search>
<form id="search-form" action="/search" method="GET"> <form id="search-form" action="/search" method="GET">
<div class="search"> <div class="search">
<fieldset class="text-search" aria-busy={submitting} disabled={submitting}> <fieldset class="text-search">
<Label for="label">Search</Label> <Label for="label">Search</Label>
<Input type="text" id="q" class={$errors.q && "outline outline-destructive"} name="q" placeholder="Search board games" data-invalid={$errors.q} bind:value={$form.q} /> <Input type="text" id="q" class={$errors.q && "outline outline-destructive"} name="q" placeholder="Search board games" data-invalid={$errors.q} bind:value={$form.q} />
{#if $errors.q} {#if $errors.q}
@ -92,34 +36,6 @@
<input id="skip" type="hidden" name="skip" bind:value={$form.skip} /> <input id="skip" type="hidden" name="skip" bind:value={$form.skip} />
<input id="limit" type="hidden" name="limit" bind:value={$form.limit} /> <input id="limit" type="hidden" name="limit" bind:value={$form.limit} />
</fieldset> </fieldset>
<!-- {#if advancedSearch} -->
<!-- <Disclosure> -->
<!-- <DisclosureButton
class="disclosure-button"
on:click={() => (disclosureOpen = !disclosureOpen)}
> -->
<!-- <span>Advanced Search?</span> -->
<!-- <ChevronRightIcon
class="icon disclosure-icon"
style={disclosureOpen
? 'transform: rotate(90deg); transition: transform 0.5s ease;'
: 'transform: rotate(0deg); transition: transform 0.5s ease;'}
/> -->
<!-- </DisclosureButton> -->
<!-- {#if disclosureOpen}
<div transition:fade|global> -->
<!-- Using `static`, `DisclosurePanel` is always rendered,
and ignores the `open` state -->
<!-- <DisclosurePanel static> -->
<!-- {#if disclosureOpen}
<AdvancedSearch {form} {errors} {constraints} />
{/if} -->
<!-- </DisclosurePanel> -->
<!-- </div> -->
<!-- {/if} -->
<!-- </Disclosure> -->
<!-- {/if} -->
</div> </div>
{#if showButton} {#if showButton}
<Button type="submit">Submit</Button> <Button type="submit">Submit</Button>
@ -127,32 +43,6 @@
</form> </form>
</search> </search>
<section class="games">
<h1>Games Found:</h1>
<div class="games-list">
{#if totalCount > 0}
{#each games as game (game.id)}
<Game {game} />
{/each}
{:else}
<h2>Sorry no games found!</h2>
{/if}
</div>
{#if showPagination && $gameStore?.length > 0}
<Pagination
{pageSize}
{page}
{totalItems}
forwardText="Next"
backwardText="Prev"
pageSizes={[10, 25, 50, 100]}
on:nextPageEvent={handleNextPageEvent}
on:previousPageEvent={handlePreviousPageEvent}
on:perPageEvent={handlePerPageEvent}
/>
{/if}
</section>
<style lang="postcss"> <style lang="postcss">
:global(.disclosure-button) { :global(.disclosure-button) {
display: flex; display: flex;
@ -160,6 +50,12 @@
place-items: center; place-items: center;
} }
#search-form {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
button { button {
padding: 1rem; padding: 1rem;
margin: 1.5rem 0; margin: 1.5rem 0;
@ -177,35 +73,4 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
} }
.games {
margin: 2rem 0rem;
& h1 {
margin-bottom: 2rem;
}
}
.games-list {
display: grid;
--listColumns: 4;
grid-template-columns: repeat(var(--listColumns), minmax(250px, 1fr));
gap: 2rem;
@media (width >= 1500px) {
--listColumns: 3;
}
@media (1000px < width <= 1500px) {
--listColumns: 3;
}
@media (600px < width <= 1000px) {
--listColumns: 2;
}
@media (width <= 600px) {
--listColumns: 1;
}
}
</style> </style>

View file

@ -1,13 +1,10 @@
<script lang="ts"> <script lang="ts">
import { Button as ButtonPrimitive } from "bits-ui"; import { Button as ButtonPrimitive } from "bits-ui";
import { cn } from "$lib/utils"; import { cn } from "$lib/utils";
import { buttonVariants, type Size, type Variant } from "."; import { buttonVariants, type Props, type Events } from ".";
type $$Props = ButtonPrimitive.Props & { type $$Props = Props;
variant?: Variant; type $$Events = Events;
size?: Size;
};
type $$Events = ButtonPrimitive.Events;
let className: $$Props["class"] = undefined; let className: $$Props["class"] = undefined;
export let variant: $$Props["variant"] = "default"; export let variant: $$Props["variant"] = "default";
@ -19,6 +16,7 @@
<ButtonPrimitive.Root <ButtonPrimitive.Root
{builders} {builders}
class={cn(buttonVariants({ variant, size, className }))} class={cn(buttonVariants({ variant, size, className }))}
type="button"
{...$$restProps} {...$$restProps}
on:click on:click
on:keydown on:keydown

View file

@ -1,35 +1,51 @@
import Root from './button.svelte'; import Root from "./button.svelte";
import { tv, type VariantProps } from 'tailwind-variants'; import { tv, type VariantProps } from "tailwind-variants";
import type { Button as ButtonPrimitive } from "bits-ui";
export const buttonVariants = tv({ const buttonVariants = tv({
base: 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', base: "inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variants: { variants: {
variant: { variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90', default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', destructive:
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', "bg-destructive text-destructive-foreground hover:bg-destructive/90",
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', outline:
ghost: 'hover:bg-accent hover:text-accent-foreground', "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
link: 'text-primary underline-offset-4 hover:underline' secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline"
}, },
size: { size: {
default: 'h-10 px-4 py-2', default: "h-10 px-4 py-2",
sm: 'h-9 rounded-md px-3', sm: "h-9 rounded-md px-3",
lg: 'h-11 rounded-md px-8', lg: "h-11 rounded-md px-8",
icon: 'h-10 w-10' icon: "h-10 w-10"
} }
}, },
defaultVariants: { defaultVariants: {
variant: 'default', variant: "default",
size: 'default' size: "default"
} }
}); });
export type Variant = VariantProps<typeof buttonVariants>['variant']; type Variant = VariantProps<typeof buttonVariants>["variant"];
export type Size = VariantProps<typeof buttonVariants>['size']; type Size = VariantProps<typeof buttonVariants>["size"];
type Props = ButtonPrimitive.Props & {
variant?: Variant;
size?: Size;
};
type Events = ButtonPrimitive.Events;
export { export {
Root, Root,
type Props,
type Events,
// //
Root as Button Root as Button,
type Props as ButtonProps,
type Events as ButtonEvents,
buttonVariants
}; };

View file

@ -12,11 +12,11 @@
</script> </script>
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
bind:checked
class={cn( class={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", "box-content peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
className className
)} )}
bind:checked
{...$$restProps} {...$$restProps}
on:click on:click
> >
@ -26,9 +26,9 @@
let:isIndeterminate let:isIndeterminate
> >
{#if isChecked} {#if isChecked}
<Check class="h-4 w-4" /> <Check class="h-3.5 w-3.5" />
{:else if isIndeterminate} {:else if isIndeterminate}
<Minus class="h-4 w-4" /> <Minus class="h-3.5 w-3.5" />
{/if} {/if}
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>

View file

@ -8,6 +8,7 @@
export let onCheckedChange: $$Props["onCheckedChange"] = undefined; export let onCheckedChange: $$Props["onCheckedChange"] = undefined;
const { name, setValue, attrStore, value } = getFormField(); const { name, setValue, attrStore, value } = getFormField();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { name: nameAttr, value: valueAttr, ...rest } = $attrStore; const { name: nameAttr, value: valueAttr, ...rest } = $attrStore;
</script> </script>

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Form as FormPrimitive } from "formsnap"; import { Form as FormPrimitive } from "formsnap";
import { cn } from "@/utils"; import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements"; import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLSpanElement>; type $$Props = HTMLAttributes<HTMLSpanElement>;

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { cn } from "@/utils"; import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements"; import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLDivElement>; type $$Props = HTMLAttributes<HTMLDivElement>;

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { Label as LabelPrimitive } from "bits-ui"; import type { Label as LabelPrimitive } from "bits-ui";
import { getFormField } from "formsnap"; import { getFormField } from "formsnap";
import { cn } from "@/utils"; import { cn } from "$lib/utils";
import { Label } from "$lib/components/ui/label"; import { Label } from "$lib/components/ui/label";
type $$Props = LabelPrimitive.Props; type $$Props = LabelPrimitive.Props;
@ -13,7 +13,7 @@
</script> </script>
<Label <Label
for={ids.input} for={$ids.input}
class={cn($errors && "text-destructive", className)} class={cn($errors && "text-destructive", className)}
{...$$restProps} {...$$restProps}
> >

View file

@ -5,7 +5,7 @@
type $$Props = SelectPrimitive.Props; type $$Props = SelectPrimitive.Props;
const { setValue, name, value } = getFormField(); const { setValue, name, value } = getFormField();
export let onSelectedChange: $$Props["onSelectedChange"]; export let onSelectedChange: $$Props["onSelectedChange"] = undefined;
</script> </script>
<Select.Root <Select.Root

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Form as FormPrimitive } from "formsnap"; import { Form as FormPrimitive } from "formsnap";
import { cn } from "@/utils"; import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements"; import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLParagraphElement>; type $$Props = HTMLAttributes<HTMLParagraphElement>;

View file

@ -18,6 +18,7 @@ import Button from "./form-button.svelte";
const Root = FormPrimitive.Root; const Root = FormPrimitive.Root;
const Field = FormPrimitive.Field; const Field = FormPrimitive.Field;
const Control = FormPrimitive.Control;
const RadioItem = RadioGroupComp.Item; const RadioItem = RadioGroupComp.Item;
const NativeRadio = FormPrimitive.Radio; const NativeRadio = FormPrimitive.Radio;
const SelectContent = SelectComp.Content; const SelectContent = SelectComp.Content;
@ -36,6 +37,7 @@ export type TextareaGetFormField = Omit<
export { export {
Root, Root,
Field, Field,
Control,
Item, Item,
Input, Input,
Label, Label,
@ -59,6 +61,7 @@ export {
// //
Root as Form, Root as Form,
Field as FormField, Field as FormField,
Control as FormControl,
Item as FormItem, Item as FormItem,
Input as FormInput, Input as FormInput,
Textarea as FormTextarea, Textarea as FormTextarea,

View file

@ -13,7 +13,7 @@
<input <input
class={cn( class={cn(
"flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-foreground file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
bind:value bind:value

View file

@ -1,2 +1,15 @@
export { default as RadioGroup } from "./RadioGroup.svelte"; import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
export { default as RadioGroupItem } from "./RadioGroupItem.svelte";
import Root from "./radio-group.svelte";
import Item from "./radio-group-item.svelte";
const Input = RadioGroupPrimitive.Input;
export {
Root,
Input,
Item,
//
Root as RadioGroup,
Input as RadioGroupInput,
Item as RadioGroupItem
};

View file

@ -0,0 +1,28 @@
<script lang="ts">
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
import { Circle } from "lucide-svelte";
import { cn } from "$lib/utils";
type $$Props = RadioGroupPrimitive.ItemProps;
type $$Events = RadioGroupPrimitive.ItemEvents;
let className: $$Props["class"] = undefined;
export let value: $$Props["value"];
export { className as class };
</script>
<RadioGroupPrimitive.Item
{value}
class={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...$$restProps}
on:click
>
<div class="flex items-center justify-center">
<RadioGroupPrimitive.ItemIndicator>
<Circle class="h-2.5 w-2.5 fill-current text-current" />
</RadioGroupPrimitive.ItemIndicator>
</div>
</RadioGroupPrimitive.Item>

View file

@ -0,0 +1,18 @@
<script lang="ts">
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
import { cn } from "$lib/utils";
type $$Props = RadioGroupPrimitive.Props;
let className: $$Props["class"] = undefined;
export let value: $$Props["value"] = undefined;
export { className as class };
</script>
<RadioGroupPrimitive.Root
bind:value
class={cn("grid gap-2", className)}
{...$$restProps}
>
<slot />
</RadioGroupPrimitive.Root>

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { cn } from "$lib/utils";
import { Select as SelectPrimitive } from "bits-ui"; import { Select as SelectPrimitive } from "bits-ui";
import { Check } from "lucide-svelte"; import { Check } from "lucide-svelte";
import { cn } from "$lib/utils";
type $$Props = SelectPrimitive.ItemProps; type $$Props = SelectPrimitive.ItemProps;
type $$Events = SelectPrimitive.ItemEvents; type $$Events = SelectPrimitive.ItemEvents;
@ -18,7 +18,7 @@
{disabled} {disabled}
{label} {label}
class={cn( class={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", "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
{...$$restProps} {...$$restProps}

View file

@ -4,6 +4,7 @@
@layer base { @layer base {
:root { :root {
--font-serif: 'Inter', sans-serif;
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 20 14.3% 4.1%; --foreground: 20 14.3% 4.1%;
--card: 0 0% 100%; --card: 0 0% 100%;

View file

@ -50,6 +50,14 @@ const Search = z.object({
skip: z.number().min(0).default(0) skip: z.number().min(0).default(0)
}); });
// minAge: z
// .string()
// .min(1)
// .transform((v) => +v)
// .refine((minAge) => !isNaN(minAge), { message: 'Must be a number' })
// .refine((minAge) => minAge >= 1 && minAge <= 120, { message: 'Must be between 1 and 120' })
// .optional(),
export const search_schema = z export const search_schema = z
.object({ .object({
q: z.string().trim().optional().default(''), q: z.string().trim().optional().default(''),

View file

@ -18,18 +18,6 @@ import {
} from '$lib/utils/dbUtils.js'; } from '$lib/utils/dbUtils.js';
// import { listGameSchema } from '$lib/config/zod-schemas.js'; // import { listGameSchema } from '$lib/config/zod-schemas.js';
/**
* Asynchronous function searchForGames to fetch games from a local and remote repository based on the given parameters.
* @async
* @function searchForGames
* @param {SearchQuery} urlQueryParams - An object that represents the search parameters. It includes properties like name, min_players,
* max_players, min_playtime, max_playtime, min_age, skip, limit which are used to define the search condition for games.
* @param {any} locals - An object that contains data related to the local server environment like user information.
* @param {Function} eventFetch - A function that fetches games from the local API.
* @returns {Object} returns an object with totalCount property which is the total number of games fetched and games property which is
* an array of all the games fetched. If any error occurred during the operation, it returns an object with totalCount as 0 and games as empty array.
* @throws will throw an error if the response received from fetching games operation is not OK (200).
*/
async function searchForGames( async function searchForGames(
locals: App.Locals, locals: App.Locals,
eventFetch: Function, eventFetch: Function,

View file

@ -1,10 +1,192 @@
<script lang="ts"> <script lang="ts">
import TextSearch from '$lib/components/search/textSearch/index.svelte'; import { dev } from '$app/environment';
import type { SuperValidated } from 'sveltekit-superforms';
import { superForm } from 'sveltekit-superforms/client';
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
import { createPagination, melt } from '@melt-ui/svelte';
import { ChevronLeft, ChevronRight } from 'lucide-svelte';
import type { SearchSchema } from '$lib/zodValidation';
import Game from '$lib/components/game/index.svelte';
import { Label } from '$lib/components/ui/label';
import { Input } from '$lib/components/ui/input';
import { Button } from '$lib/components/ui/button';
export let data; export let data;
console.log("search page data", data);
const { form, errors }: SuperValidated<SearchSchema> = superForm(data.form);
const { games, totalCount } = data?.searchData;
let submitButton: HTMLElement;
let pageSize = +form?.limit || 10;
$: showPagination = totalCount > pageSize;
const {
elements: { root, pageTrigger, prevButton, nextButton },
states: { pages, range }
} = createPagination({
count: totalCount,
perPage: pageSize,
defaultPage: 1,
siblingCount: 1
});
// async function handleNextPageEvent(event: CustomEvent) {
// if (+event?.detail?.page === page + 1) {
// page += 1;
// }
// await tick();
// submitButton.click();
// }
// async function handlePreviousPageEvent(event: CustomEvent) {
// if (+event?.detail?.page === page - 1) {
// page -= 1;
// }
// await tick();
// submitButton.click();
// }
// async function handlePerPageEvent(event: CustomEvent) {
// page = 1;
// pageSize = event.detail.pageSize;
// await tick();
// submitButton.click();
// }
</script> </script>
<div class="game-search"> <div class="game-search">
<TextSearch showButton advancedSearch {data} /> {#if dev}
<SuperDebug collapsible data={$form} />
{/if}
<search>
<form id="search-form" action="/search" method="GET">
<div class="search">
<fieldset class="text-search">
<Label for="label">Search</Label>
<Input type="text" id="q" class={$errors.q && "outline outline-destructive"} name="q" placeholder="Search board games" data-invalid={$errors.q} bind:value={$form.q} />
{#if $errors.q}
<p class="text-sm text-destructive">{$errors.q}</p>
{/if}
<input id="skip" type="hidden" name="skip" bind:value={$form.skip} />
<input id="limit" type="hidden" name="limit" bind:value={$form.limit} />
</fieldset>
</div>
<Button type="submit">Submit</Button>
</form>
</search>
<section class="games">
<h1>Games Found:</h1>
<div class="games-list">
{#if totalCount > 0}
{#each games as game (game.id)}
<Game {game} />
{/each}
{:else}
<h2>Sorry no games found!</h2>
{/if}
</div>
{#if showPagination}
<nav use:melt={$root}>
<p class="text-center">Showing items {$range.start} - {$range.end}</p>
<div class="buttons">
<button use:melt={$prevButton}><ChevronLeft /></button>
{#each $pages as page (page.key)}
{#if page.type === 'ellipsis'}
<span>...</span>
{:else}
<button use:melt={$pageTrigger(page)} on:m-click={() => console.log('test')}>{page.value}</button>
{/if}
{/each}
<button use:melt={$nextButton} on:m-click|preventDefault={() => console.log('test')} on:m-keydown|preventDefault={() => console.log('test')}><ChevronRight /></button>
</div>
</nav>
{/if}
</section>
</div> </div>
<style lang="postcss">
nav {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.text-center {
text-align: center;
}
#search-form {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
button {
display: grid;
place-items: center;
border-radius: 2px;
background-color: rgb(var(--color-white) / 1);
color: rgb(var(--color-magnum-700) / 1);
box-shadow: 0px 1px 2px 0px rgb(var(--color-black) / 0.05);
font-size: 14px;
padding-inline: 0.75rem;
height: 2rem;
}
button:hover {
opacity: 0.75;
}
button:disabled {
cursor: not-allowed;
opacity: 0.5;
}
nav :global(button[data-selected]) {
background-color: rgb(var(--color-magnum-900));
color: rgb(var(--color-white));
}
.buttons {
display: flex;
align-items: center;
gap: 0.5rem;
}
.games {
margin: 2rem 0rem;
& h1 {
margin-bottom: 2rem;
}
}
.games-list {
display: grid;
--listColumns: 4;
grid-template-columns: repeat(var(--listColumns), minmax(250px, 1fr));
gap: 2rem;
@media (width >= 1500px) {
--listColumns: 3;
}
@media (1000px < width <= 1500px) {
--listColumns: 3;
}
@media (600px < width <= 1000px) {
--listColumns: 2;
}
@media (width <= 600px) {
--listColumns: 1;
}
}
</style>

View file

@ -2,7 +2,7 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
</script> </script>
<div> <div class="error">
{#if $page.status === 404} {#if $page.status === 404}
<h1>The page you requested doesn't exist! 🤷‍♂️</h1> <h1>The page you requested doesn't exist! 🤷‍♂️</h1>
<h3 class="mt-6"><a href="/">Go Home</a></h3> <h3 class="mt-6"><a href="/">Go Home</a></h3>
@ -16,11 +16,23 @@
{/if} {/if}
</div> </div>
<style> <style style="postcss">
h1 { .error {
margin-top: var(--spacing-64); display: grid;
font-family: var(--font-sans); place-items: center;
font-size: var(--font-32);
text-align: center; text-align: center;
margin-top: 4rem;
}
h1 {
font-size: 1.125rem;
line-height: 1.75rem;
font-weight: 600;
}
h3 {
font-size: 1rem;
line-height: 1.5rem;
font-weight: 600;
} }
</style> </style>

View file

@ -7,9 +7,12 @@
import 'iconify-icon'; import 'iconify-icon';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { onNavigate } from "$app/navigation"; import { onNavigate } from "$app/navigation";
import { boredState } from '$lib/stores/boredState';
import Analytics from '$lib/components/analytics.svelte'; import Analytics from '$lib/components/analytics.svelte';
import { theme } from '$state/theme'; import { theme } from '$state/theme';
import PageLoadingIndicator from '$lib/page_loading_indicator.svelte'; import PageLoadingIndicator from '$lib/page_loading_indicator.svelte';
import Portal from "$lib/Portal.svelte";
import Loading from "$components/loading.svelte";
const dev = process.env.NODE_ENV !== 'production'; const dev = process.env.NODE_ENV !== 'production';
@ -49,7 +52,7 @@
// } // }
// $: isOpen = $boredState?.dialog?.isOpen; // $: isOpen = $boredState?.dialog?.isOpen;
// $: loading = $boredState?.loading; $: loading = $boredState?.loading;
onMount(() => { onMount(() => {
// set the theme to the user's active theme // set the theme to the user's active theme
@ -97,7 +100,7 @@
<slot /> <slot />
</div> </div>
<!-- {#if loading} {#if loading}
<Portal> <Portal>
<div class="loading"> <div class="loading">
<Loading /> <Loading />
@ -105,7 +108,7 @@
</div> </div>
<div class="background" /> <div class="background" />
</Portal> </Portal>
{/if} --> {/if}
<Toaster /> <Toaster />

View file

@ -44,7 +44,9 @@ export const GET = async ({ url, locals }) => {
name: true, name: true,
slug: true, slug: true,
thumb_url: true thumb_url: true
} },
take: limit,
skip
}); });
} }