feat: update navigation labels and add new components for item listing
Deploy to Cloudflare Pages / deploy (push) Failing after 41s
Deploy to Cloudflare Pages / deploy (push) Failing after 41s
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn } from "$lib/utils";
|
||||||
|
import FileExclamationPointIcon from "@lucide/svelte/icons/file-exclamation-point";
|
||||||
|
import type { HTMLImgAttributes } from "svelte/elements";
|
||||||
|
|
||||||
|
const props: HTMLImgAttributes = $props();
|
||||||
|
let loaded = $state(false);
|
||||||
|
let error = $state(false);
|
||||||
|
$effect(() => {
|
||||||
|
void props.src;
|
||||||
|
loaded = false;
|
||||||
|
error = false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if error}
|
||||||
|
<div class="flex items-center justify-center opacity-50">
|
||||||
|
<FileExclamationPointIcon />
|
||||||
|
</div>{:else}
|
||||||
|
<img
|
||||||
|
{...props}
|
||||||
|
onload={() => (loaded = true)}
|
||||||
|
onerror={() => (error = true)}
|
||||||
|
class={cn("opacity-0", loaded && "opacity-100")}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<script lang="ts" module>
|
||||||
|
import { type VariantProps, tv } from "tailwind-variants";
|
||||||
|
|
||||||
|
export const badgeVariants = tv({
|
||||||
|
base: "h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap transition-colors focus-visible:ring-[3px] [&>svg]:pointer-events-none",
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
||||||
|
secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
||||||
|
destructive: "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
|
||||||
|
outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
||||||
|
ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type BadgeVariant = VariantProps<typeof badgeVariants>["variant"];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { HTMLAnchorAttributes } from "svelte/elements";
|
||||||
|
import { cn, type WithElementRef } from "$lib/utils.js";
|
||||||
|
|
||||||
|
let {
|
||||||
|
ref = $bindable(null),
|
||||||
|
href,
|
||||||
|
class: className,
|
||||||
|
variant = "default",
|
||||||
|
children,
|
||||||
|
...restProps
|
||||||
|
}: WithElementRef<HTMLAnchorAttributes> & {
|
||||||
|
variant?: BadgeVariant;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:element
|
||||||
|
this={href ? "a" : "span"}
|
||||||
|
bind:this={ref}
|
||||||
|
data-slot="badge"
|
||||||
|
{href}
|
||||||
|
class={cn(badgeVariants({ variant }), className)}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</svelte:element>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { default as Badge } from "./badge.svelte";
|
||||||
|
export { badgeVariants, type BadgeVariant } from "./badge.svelte";
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"nav": {
|
"nav": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"search": "Search",
|
"list": "Fridge",
|
||||||
"scan": "Scan",
|
"scan": "Scan",
|
||||||
"guide": "Guide",
|
"guide": "Guide",
|
||||||
"settings": "Settings"
|
"settings": "Settings"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"nav": {
|
"nav": {
|
||||||
"home": "หน้าหลัก",
|
"home": "หน้าหลัก",
|
||||||
"search": "ค้นหา",
|
"list": "รายการ",
|
||||||
"scan": "สแกน",
|
"scan": "สแกน",
|
||||||
"guide": "ไกด์",
|
"guide": "ไกด์",
|
||||||
"settings": "การตั้งค่า"
|
"settings": "การตั้งค่า"
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<script>
|
||||||
|
import Item from "./item.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2 p-2">
|
||||||
|
{#each Array(20)}
|
||||||
|
<Item
|
||||||
|
name="ข้าวมันไก่"
|
||||||
|
expiry={new Date()}
|
||||||
|
icon="abc"
|
||||||
|
category="อาหารจารเดียว"
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Image from "$lib/components/image.svelte";
|
||||||
|
import Badge from "$lib/components/ui/badge/badge.svelte";
|
||||||
|
|
||||||
|
const {
|
||||||
|
icon,
|
||||||
|
name,
|
||||||
|
expiry,
|
||||||
|
category,
|
||||||
|
}: { icon: string; name: string; expiry: Date; category: string } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title={`Item ${name}`}
|
||||||
|
class="flex cursor-pointer items-start gap-3 rounded-md border p-3 text-left transition-colors hover:bg-accent"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex size-16 shrink-0 items-center justify-center rounded bg-muted"
|
||||||
|
>
|
||||||
|
<!-- {r.paid ? (
|
||||||
|
<Check class="size-5 text-green-500" />
|
||||||
|
) : r.expires && new Date(r.expires) <= new Date() ? (
|
||||||
|
<Circle class="size-5 text-muted-foreground" />
|
||||||
|
) : (
|
||||||
|
<Clock class="size-5 text-yellow-500" />
|
||||||
|
)} -->
|
||||||
|
<Image src={icon} />
|
||||||
|
</div>
|
||||||
|
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="truncate font-medium">{name}</span>
|
||||||
|
</div>
|
||||||
|
<span class="truncate text-xs text-muted-foreground">{expiry}</span>
|
||||||
|
<div class="flex items-center gap-2 text-xs">
|
||||||
|
<Badge variant="outline">{category}</Badge>
|
||||||
|
<span class="font-medium">123 ฿</span>
|
||||||
|
<!-- {r.paid ? (
|
||||||
|
<span class="text-green-500">ชำระแล้ว</span>
|
||||||
|
) : r.expires && new Date(r.expires) <= new Date() ? (
|
||||||
|
<span class="text-muted-foreground">หมดเวลา</span>
|
||||||
|
) : (
|
||||||
|
<span class="text-yellow-500">รอชำระ</span>
|
||||||
|
)} -->
|
||||||
|
<span class="text-green-500">ชำระแล้ว</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import NavButton from "$lib/components/nav/nav-button.svelte";
|
import NavButton from "$lib/components/nav/nav-button.svelte";
|
||||||
import NavCam from "$lib/components/nav/nav-cam.svelte";
|
import NavCam from "$lib/components/nav/nav-cam.svelte";
|
||||||
import HomeIcon from "@lucide/svelte/icons/house";
|
import HomeIcon from "@lucide/svelte/icons/house";
|
||||||
import SearchIcon from "@lucide/svelte/icons/search";
|
import ListIcon from "@lucide/svelte/icons/list";
|
||||||
import GuideIcon from "@lucide/svelte/icons/book-image";
|
import GuideIcon from "@lucide/svelte/icons/book-image";
|
||||||
import SettingsIcon from "@lucide/svelte/icons/settings";
|
import SettingsIcon from "@lucide/svelte/icons/settings";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
@@ -10,13 +10,13 @@
|
|||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div class="wrap flex h-dvh flex-col overflow-hidden">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar flex items-center justify-evenly bg-sidebar">
|
<div class="navbar flex items-center justify-evenly bg-sidebar">
|
||||||
<NavButton label={$_("nav.home")} icon={HomeIcon} href="/app/home" />
|
<NavButton label={$_("nav.home")} icon={HomeIcon} href="/app/home" />
|
||||||
<NavButton label={$_("nav.search")} icon={SearchIcon} href="/app/search" />
|
<NavButton label={$_("nav.list")} icon={ListIcon} href="/app/list" />
|
||||||
<NavCam />
|
<NavCam />
|
||||||
<NavButton label={$_("nav.guide")} icon={GuideIcon} href="/app/guide" />
|
<NavButton label={$_("nav.guide")} icon={GuideIcon} href="/app/guide" />
|
||||||
<NavButton
|
<NavButton
|
||||||
@@ -29,14 +29,21 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.navbar {
|
.navbar {
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 64px;
|
height: 64px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-bottom: 64px;
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
search here
|
|
||||||
Reference in New Issue
Block a user