mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Updating shadcn files (Uses bits-ui), moving text search to just search page.
This commit is contained in:
parent
79fde8beb8
commit
229d84df40
27 changed files with 353 additions and 214 deletions
3
src/app.d.ts
vendored
3
src/app.d.ts
vendored
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
};
|
||||||
|
|
|
||||||
28
src/lib/components/ui/radio-group/radio-group-item.svelte
Normal file
28
src/lib/components/ui/radio-group/radio-group-item.svelte
Normal 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>
|
||||||
18
src/lib/components/ui/radio-group/radio-group.svelte
Normal file
18
src/lib/components/ui/radio-group/radio-group.svelte
Normal 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>
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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%;
|
||||||
|
|
|
||||||
|
|
@ -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(''),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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>
|
</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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue