commit 6e63afc3fc082c96ecf194ee6cb1697b14ca1ec4
parent 9db445abec1cf5367c9704d60ab6a91c88d2db32
Author: triesap <triesap@radroots.dev>
Date: Fri, 21 Nov 2025 03:36:22 +0000
apps-lib-pwa: migrate and refactor settings view from `@radroots/apps-lib`, add trellis component system, glyph button variants, label swap control, layout wrapper, and update types
Diffstat:
19 files changed, 1151 insertions(+), 1 deletion(-)
diff --git a/apps-lib-pwa/src/lib/components/button/button-glyph-circle.svelte b/apps-lib-pwa/src/lib/components/button/button-glyph-circle.svelte
@@ -0,0 +1,25 @@
+<script lang="ts">
+ import type { IButtonGlyphCircle } from "$lib/types/components/lib";
+ import { fmt_cl, glyph_style_map } from "@radroots/apps-lib";
+ import GlyphButton from "./button-glyph.svelte";
+
+ let { basis }: { basis: IButtonGlyphCircle } = $props();
+
+ const styles = $derived(
+ basis?.glyph?.dim
+ ? glyph_style_map.get(basis?.glyph?.dim)
+ : glyph_style_map.get(`sm`),
+ );
+</script>
+
+{#if styles?.dim_1}
+ <div
+ class={`${fmt_cl(basis?.classes_wrap)} flex flex-col h-[${
+ styles?.dim_1
+ }px] w-[${
+ styles?.dim_1
+ }px] justify-center items-center rounded-full el-re`}
+ >
+ <GlyphButton basis={basis?.glyph} />
+ </div>
+{/if}
diff --git a/apps-lib-pwa/src/lib/components/button/button-glyph.svelte b/apps-lib-pwa/src/lib/components/button/button-glyph.svelte
@@ -0,0 +1,22 @@
+<script lang="ts">
+ import { type IGlyph, fmt_cl, glyph_style_map } from "@radroots/apps-lib";
+
+ let { basis }: { basis: IGlyph } = $props();
+ const styles = $derived(
+ basis?.dim ? glyph_style_map.get(basis.dim) : glyph_style_map.get(`sm`),
+ );
+</script>
+
+<!-- svelte-ignore a11y_consider_explicit_label -->
+<button
+ class={`${fmt_cl(
+ basis.classes,
+ )} flex flex-col justify-center items-center text-[${
+ styles?.gl_1
+ }px] el-re`}
+ onclick={async () => {
+ if (basis.callback) await basis.callback();
+ }}
+>
+ <i class={`ph-bold ph-${basis.key}`}></i>
+</button>
diff --git a/apps-lib-pwa/src/lib/components/layout/layout-trellis.svelte b/apps-lib-pwa/src/lib/components/layout/layout-trellis.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { type IBasisOpt, type IClOpt, fmt_cl } from "@radroots/apps-lib";
+ import type { Snippet } from "svelte";
+
+ let {
+ basis = undefined,
+ children,
+ }: {
+ basis?: IBasisOpt<IClOpt>;
+ children: Snippet;
+ } = $props();
+</script>
+
+<div
+ class={`${fmt_cl(
+ basis?.classes,
+ )} flex flex-col pb-12 gap-6 justify-center items-center scroll-hide`}
+>
+ {@render children()}
+</div>
diff --git a/apps-lib-pwa/src/lib/components/lib/label-swap.svelte b/apps-lib-pwa/src/lib/components/lib/label-swap.svelte
@@ -0,0 +1,47 @@
+<script lang="ts">
+ import {
+ fmt_cl,
+ type ILabelSwap,
+ type ILyOpt,
+ parse_layer,
+ } from "@radroots/apps-lib";
+
+ let {
+ basis,
+ el = $bindable(null),
+ }: {
+ basis: ILabelSwap & ILyOpt;
+ el?: HTMLLabelElement | null;
+ } = $props();
+
+ const layer = $derived(parse_layer(basis?.layer ? basis.layer : 1));
+</script>
+
+<div class={`flex flex-row justify-start items-center`}>
+ <!-- svelte-ignore a11y_label_has_associated_control -->
+ <label
+ bind:this={el}
+ class={`swap${basis.swap.toggle ? ` swap-active` : ``}`}
+ >
+ <div class="swap-on">
+ <p
+ class={`${fmt_cl(
+ basis.swap.on.classes ||
+ `text-nav_prev text-ly${layer}-gl-hl group-active:opacity-60`,
+ )} font-sans -translate-y-[1px] el-re`}
+ >
+ {basis.swap.on.value}
+ </p>
+ </div>
+ <div class="swap-off">
+ <p
+ class={`${fmt_cl(
+ basis.swap.off.classes ||
+ `text-nav_prev text-ly${layer}-gl-hl group-active:opacity-60`,
+ )} font-sans -translate-y-[1px] el-re`}
+ >
+ {basis.swap.off.value}
+ </p>
+ </div>
+ </label>
+</div>
diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-default-label.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-default-label.svelte
@@ -0,0 +1,37 @@
+<script lang="ts">
+ import type { ITrellisDefaultLabel } from "$lib/types/components/trellis";
+ import { fmt_cl } from "@radroots/apps-lib";
+ import type { ThemeLayer } from "@radroots/themes";
+
+ let {
+ layer,
+ labels,
+ classes = ``,
+ }: {
+ layer: ThemeLayer;
+ labels: ITrellisDefaultLabel[];
+ classes?: string;
+ } = $props();
+</script>
+
+<div class={`${fmt_cl(classes)} flex flex-row`}>
+ <p class={`font-sans text-trellis_ti text-ly${layer}-gl-shade`}>
+ {#each labels as label}
+ <span class={`${fmt_cl(label.classes)} font-sans text-trellis_ti`}>
+ {#if `callback` in label}
+ <button
+ class={``}
+ onclick={async () => {
+ if (`callback` in label && label.callback)
+ await label.callback();
+ }}
+ >
+ {label.label}
+ </button>
+ {:else}
+ {label.label}
+ {/if}
+ </span>
+ {/each}
+ </p>
+</div>
diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-end.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-end.svelte
@@ -0,0 +1,38 @@
+<script lang="ts">
+ import type { ITrellisBasisTouchEnd } from "$lib/types/components/trellis";
+ import { Glyph } from "@radroots/apps-lib";
+ import type { ThemeLayer } from "@radroots/themes";
+
+ let {
+ basis,
+ layer,
+ hide_active,
+ }: {
+ basis: ITrellisBasisTouchEnd;
+ layer: ThemeLayer;
+ hide_active: boolean;
+ } = $props();
+</script>
+
+<div
+ class={`absolute top-0 right-0 h-full w-max flex flex-row justify-center items-center`}
+>
+ <button
+ class={`flex pr-3`}
+ onclick={async (ev) => {
+ if (basis.callback) await basis.callback(ev);
+ }}
+ >
+ {#if basis.glyph}
+ <Glyph
+ basis={{
+ classes: `text-ly${layer}-gl-shade ${
+ hide_active ? `` : `group-active:text-ly${layer}-gl_a`
+ } translate-y-[1px] opacity-70`,
+ dim: `xs+`,
+ ...basis.glyph,
+ }}
+ />
+ {/if}
+ </button>
+</div>
diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-input.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-input.svelte
@@ -0,0 +1,91 @@
+<script lang="ts">
+ import type { ITrellisBasisInput } from "$lib/types/components/trellis";
+ import { fmt_trellis } from "$lib/utils/app";
+ import { fmt_cl, Glyph } from "@radroots/apps-lib";
+ import type { ThemeLayer } from "@radroots/themes";
+ import InputPwa from "../lib/input-pwa.svelte";
+ import LoadSymbol from "../lib/load-symbol.svelte";
+
+ let {
+ basis,
+ layer,
+ hide_border_b,
+ hide_border_t,
+ }: {
+ basis: ITrellisBasisInput;
+ layer: ThemeLayer;
+ hide_border_b: boolean;
+ hide_border_t: boolean;
+ } = $props();
+</script>
+
+<div class={`flex flex-row flex-grow h-full w-full`}>
+ <div
+ class={`${fmt_trellis(
+ hide_border_b,
+ hide_border_t,
+ )} flex flex-row h-line w-full justify-start items-center border-t-line border-ly${layer}-edge overflow-hidden`}
+ >
+ {#if basis.line_label && basis.line_label.value}
+ <div
+ class={`${fmt_cl(
+ basis.line_label.classes,
+ )} flex flex-row h-full justify-start items-center overflow-x-hidden`}
+ >
+ <p class={`font-sans text-ly${layer}-gl_b`}>
+ {basis.line_label.value}
+ </p>
+ </div>
+ {/if}
+ <div
+ class={`relative flex flex-row flex-grow h-full pr-12 justify-start items-center`}
+ >
+ <InputPwa
+ basis={{
+ ...basis.basis,
+ layer: layer,
+ }}
+ />
+ {#if basis.action}
+ {#if basis.action.visible}
+ <div
+ class={`absolute top-0 right-0 flex flex-row h-full w-12 pr-4 justify-end items-center fade-in`}
+ >
+ {#if basis.action.loading}
+ <div class={`flex flex-row fade-in`}>
+ <LoadSymbol
+ basis={{
+ dim: `glyph-send-button`,
+ blades: 8,
+ classes: `text-ly${layer}-gl el-re`,
+ }}
+ />
+ </div>
+ {:else}
+ <button
+ class={`group fade-in-long`}
+ onclick={async () => {
+ if (basis.action?.callback)
+ await basis.action.callback();
+ }}
+ >
+ <Glyph
+ basis={basis.action.glyph
+ ? {
+ dim: `md-`,
+ ...basis.action.glyph,
+ }
+ : {
+ key: `plus`,
+ classes: `text-ly${layer}-gl`,
+ dim: `md-`,
+ }}
+ />
+ </button>
+ {/if}
+ </div>
+ {/if}
+ {/if}
+ </div>
+ </div>
+</div>
diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-line.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-line.svelte
@@ -0,0 +1,60 @@
+<script lang="ts">
+ import { fmt_trellis } from "$lib/utils/app";
+ import type { ThemeLayer } from "@radroots/themes";
+ import type { CallbackPromiseGeneric } from "@radroots/utils";
+ import type { Snippet } from "svelte";
+ import LoadSymbol from "../lib/load-symbol.svelte";
+
+ let {
+ loading = false,
+ layer,
+ callback,
+ hide_border_b,
+ hide_border_t,
+ children,
+ el_end,
+ }: {
+ loading?: boolean;
+ layer: ThemeLayer;
+ callback?: CallbackPromiseGeneric<MouseEvent>;
+ hide_border_b: boolean;
+ hide_border_t: boolean;
+ children: Snippet;
+ el_end?: Snippet;
+ } = $props();
+</script>
+
+<button
+ class={`flex flex-row flex-grow overflow-hidden`}
+ onclick={async (ev) => {
+ if (callback) await callback(ev);
+ }}
+>
+ <div
+ class={`${fmt_trellis(
+ hide_border_b,
+ hide_border_t,
+ )} flex flex-row h-full w-full justify-center items-center border-t-line border-ly${layer}-edge el-re`}
+ >
+ {#if loading}
+ <div
+ class={`flex flex-row h-full w-full justify-center items-center`}
+ >
+ <LoadSymbol basis={{ dim: `sm` }} />
+ </div>
+ {:else}
+ <div
+ class={`relative group flex flex-row h-line w-full pr-[2px] justify-between items-center el-re`}
+ >
+ <div
+ class={`flex flex-row h-full w-trellis_display justify-between items-center`}
+ >
+ {@render children()}
+ </div>
+ {#if el_end}
+ {@render el_end()}
+ {/if}
+ </div>
+ {/if}
+ </div>
+</button>
diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-offset.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-offset.svelte
@@ -0,0 +1,72 @@
+<script lang="ts">
+ import type {
+ ITrellisBasisOffset,
+ ITrellisBasisOffsetMod,
+ } from "$lib/types/components/trellis";
+ import { Flex, fmt_cl, Glyph } from "@radroots/apps-lib";
+ import type { ThemeLayer } from "@radroots/themes";
+ import ButtonGlyphCircle from "../button/button-glyph-circle.svelte";
+ import LoadSymbol from "../lib/load-symbol.svelte";
+
+ let {
+ basis = undefined,
+ layer,
+ }: {
+ basis?: ITrellisBasisOffset;
+ layer: ThemeLayer;
+ } = $props();
+
+ const mod: ITrellisBasisOffsetMod = $derived(basis?.mod ? basis.mod : `sm`);
+</script>
+
+<div class={`flex flex-row h-full`}>
+ {#if mod === `sm`}
+ <div class={`${fmt_cl(``)} flex flex-row h-full w-[22px]`}>
+ <Flex />
+ </div>
+ {:else if mod === `glyph`}
+ <div class={`flex flex-row pr-[2px]`}>
+ <div class={`${fmt_cl(``)} flex flex-row h-full w-trellisOffset`}>
+ <Flex />
+ </div>
+ </div>
+ {:else if typeof mod === `object`}
+ <div
+ class={`flex flex-row h-full min-w-[20px] w-trellisOffset justify-center items-center pr-3`}
+ >
+ <button
+ class={`fade-in pl-2 translate-x-[3px] translate-y-[1px]`}
+ onclick={async (ev) => {
+ if (mod.loading) return;
+ else if (typeof basis !== `boolean` && basis?.callback)
+ await basis.callback(ev);
+ }}
+ >
+ {#if mod.loading}
+ <LoadSymbol basis={{ blades: 8, dim: `xs` }} />
+ {:else if `glyph` in mod}
+ <Glyph
+ basis={{
+ classes: mod.glyph.classes
+ ? mod.glyph.classes
+ : `text-ly${layer}-gl`,
+ ...mod.glyph,
+ }}
+ />
+ {:else if `glyph_circle` in mod}
+ <ButtonGlyphCircle
+ basis={{
+ classes_wrap: mod.glyph_circle?.classes_wrap,
+ glyph: {
+ classes: mod.glyph_circle?.glyph?.classes
+ ? mod.glyph_circle?.glyph?.classes
+ : `text-ly${layer}-gl`,
+ ...mod.glyph_circle?.glyph,
+ },
+ }}
+ />
+ {/if}
+ </button>
+ </div>
+ {/if}
+</div>
diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-row-display-value.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-row-display-value.svelte
@@ -0,0 +1,46 @@
+<script lang="ts">
+ import type { ITrellisKindDisplayValue } from "$lib/types/components/trellis";
+ import { get_label_classes_kind } from "$lib/utils/app";
+ import { Glyph, fmt_cl } from "@radroots/apps-lib";
+ import type { ThemeLayer } from "@radroots/themes";
+
+ let {
+ basis,
+ layer,
+ hide_active,
+ }: {
+ basis: ITrellisKindDisplayValue;
+ layer: ThemeLayer;
+ hide_active: boolean;
+ } = $props();
+</script>
+
+<button
+ class={`z-10 flex flex-grow justify-end`}
+ onclick={async (ev) => {
+ ev.stopPropagation();
+ if (basis.callback) await basis.callback(ev);
+ }}
+>
+ {#if `icon` in basis}
+ <Glyph
+ basis={{
+ classes:
+ basis.icon.classes ||
+ `${get_label_classes_kind(layer, `shade`, hide_active)}`,
+ key: basis.icon.key,
+ dim: `sm`,
+ }}
+ />
+ {:else if basis.label}
+ {#if `value` in basis.label}
+ <p
+ class={`${fmt_cl(
+ basis.label.classes,
+ )} font-sans text-line_d_e line-clamp-1 text-ly0-gl-label el-re`}
+ >
+ {basis.label.value}
+ </p>
+ {/if}
+ {/if}
+</button>
diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-row-label.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-row-label.svelte
@@ -0,0 +1,81 @@
+<script lang="ts">
+ import { get_label_classes_kind } from "$lib/utils/app";
+ import { type ILabelTupFields, fmt_cl } from "@radroots/apps-lib";
+ import type { ThemeLayer } from "@radroots/themes";
+ import ButtonGlyph from "../button/button-glyph.svelte";
+
+ let {
+ basis,
+ layer,
+ hide_active,
+ }: {
+ basis: ILabelTupFields;
+ layer: ThemeLayer;
+ hide_active: boolean;
+ } = $props();
+</script>
+
+<div class={`flex flex-row h-full items-center justify-between`}>
+ {#if basis.left && basis.left.length}
+ <div class={`flex flex-row h-full items-center truncate`}>
+ {#each basis.left as title_l}
+ <div
+ class={`${fmt_cl(
+ title_l.classes_wrap,
+ )} flex flex-row h-full items-center ${get_label_classes_kind(
+ layer,
+ undefined,
+ hide_active,
+ )} ${title_l.hide_truncate ? `` : `truncate`}`}
+ >
+ {#if `glyph` in title_l}
+ <div
+ class={`flex flex-row justify-start items-center pr-2`}
+ >
+ <ButtonGlyph basis={{ ...title_l.glyph }} />
+ </div>
+ {:else if `value` in title_l}
+ <p
+ class={`${fmt_cl(
+ title_l.classes,
+ )} font-sans text-line_d ${
+ title_l.hide_truncate ? `` : `truncate`
+ } el-re`}
+ >
+ {title_l.value || ``}
+ </p>
+ {/if}
+ </div>
+ {/each}
+ </div>
+ {/if}
+ {#if basis.right && basis.right.length}
+ <div
+ class={`flex flex-row h-full w-content items-center justify-end pr-4`}
+ >
+ {#each basis.right.reverse() as title_r}
+ <div
+ class={`${fmt_cl(
+ title_r.classes_wrap,
+ )} flex flex-row h-full gap-1 items-center ${
+ title_r.hide_truncate ? `` : `truncate`
+ }`}
+ >
+ {#if `glyph` in title_r}
+ <ButtonGlyph basis={{ ...title_r.glyph }} />
+ {:else if `value` in title_r}
+ <p
+ class={`${fmt_cl(
+ title_r.classes,
+ )} font-sans text-line_d text-ly${layer}-gl_d ${
+ title_r.hide_truncate ? `` : `truncate`
+ } el-re`}
+ >
+ {title_r.value || ``}
+ </p>
+ {/if}
+ </div>
+ {/each}
+ </div>
+ {/if}
+</div>
diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-select.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-select.svelte
@@ -0,0 +1,65 @@
+<script lang="ts">
+ import type { ITrellisBasisSelect } from "$lib/types/components/trellis";
+ import type { ThemeLayer } from "@radroots/themes";
+ import LoadSymbol from "../lib/load-symbol.svelte";
+ import SelectMenu from "../lib/select-menu.svelte";
+ import TrellisEnd from "./trellis-end.svelte";
+ import TrellisLine from "./trellis-line.svelte";
+ import TrellisRowDisplayValue from "./trellis-row-display-value.svelte";
+ import TrellisRowLabel from "./trellis-row-label.svelte";
+
+ let {
+ basis,
+ layer,
+ hide_active,
+ hide_border_b,
+ hide_border_t,
+ }: {
+ basis: ITrellisBasisSelect;
+ layer: ThemeLayer;
+ hide_active: boolean;
+ hide_border_b: boolean;
+ hide_border_t: boolean;
+ } = $props();
+
+ const loading = $derived(
+ typeof basis?.loading === `boolean` ? basis.loading : false,
+ );
+
+ const value = $derived(basis.el.value);
+</script>
+
+<TrellisLine
+ {layer}
+ {loading}
+ {hide_border_b}
+ {hide_border_t}
+ callback={basis.callback}
+>
+ <TrellisRowLabel basis={basis.label} {layer} {hide_active} />
+ {#if basis.display}
+ <div class={`flex flex-row pr-3 justify-center items-end`}>
+ <SelectMenu {value} basis={basis.el}>
+ {#if basis.display.loading}
+ <div
+ class={`flex flex-row h-full w-full justify-end items-center`}
+ >
+ <LoadSymbol basis={{ dim: `sm` }} />
+ </div>
+ {:else}
+ <TrellisRowDisplayValue
+ basis={{ ...basis.display }}
+ {layer}
+ {hide_active}
+ />
+ {/if}
+ </SelectMenu>
+ </div>
+ {/if}
+
+ {#snippet el_end()}
+ {#if basis.end}
+ <TrellisEnd basis={basis.end} {layer} {hide_active} />
+ {/if}
+ {/snippet}
+</TrellisLine>
diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-title.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-title.svelte
@@ -0,0 +1,79 @@
+<script lang="ts">
+ import type { ITrellisTitle } from "$lib/types/components/trellis";
+ import { Flex, fmt_cl, Glyph } from "@radroots/apps-lib";
+ import type { ThemeLayer } from "@radroots/themes";
+ import LabelSwap from "../lib/label-swap.svelte";
+
+ let {
+ basis,
+ layer = 0,
+ }: {
+ basis: ITrellisTitle;
+ layer: ThemeLayer;
+ } = $props();
+
+ const mod = $derived(basis?.mod ? basis.mod : `sm`);
+</script>
+
+<div
+ class={`${fmt_cl(
+ basis.classes,
+ )} flex flex-row h-[24px] w-full pl-[2px] gap-1 items-center`}
+>
+ <button
+ class={`flex flex-row h-full w-max items-center gap-1 ${
+ mod === `glyph` ? `pl-[36px]` : mod === `sm` ? `pl-[16px]` : ``
+ }`}
+ onclick={async () => {
+ if (basis && basis.callback) await basis.callback();
+ }}
+ >
+ {#if basis.value === true}
+ <Flex />
+ {:else}
+ <p
+ class={`font-sans text-trellis_ti text-ly${layer}-gl-label uppercase`}
+ >
+ {basis.value || ``}
+ </p>
+ {/if}
+ </button>
+ {#if basis.link}
+ <button
+ class={`${fmt_cl(
+ basis.link.classes,
+ )} group flex flex-row h-full w-max items-center`}
+ onclick={async () => {
+ if (basis.link && basis.link.callback)
+ await basis.link.callback();
+ }}
+ >
+ {#if basis.link.label}
+ {#if `swap` in basis.link.label}
+ <LabelSwap basis={basis.link.label} />
+ {:else if `value` in basis.link.label}
+ <p
+ class={`${fmt_cl(
+ basis.link.label.classes,
+ )} font-sans text-trellis_ti uppercase fade-in`}
+ >
+ {basis.link.label.value || ``}
+ </p>
+ {/if}
+ {/if}
+ {#if basis.link.glyph}
+ <div class={`flex flex-row w-max`}>
+ <Glyph
+ basis={{
+ ...basis.link.glyph,
+ dim: `xs-`,
+ classes: `${fmt_cl(
+ basis.link.glyph.classes,
+ )} fade-in`,
+ }}
+ />
+ </div>
+ {/if}
+ </button>
+ {/if}
+</div>
diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis-touch.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis-touch.svelte
@@ -0,0 +1,40 @@
+<script lang="ts">
+ import type { ITrellisBasisTouch } from "$lib/types/components/trellis";
+ import type { ThemeLayer } from "@radroots/themes";
+ import TrellisEnd from "./trellis-end.svelte";
+ import TrellisLine from "./trellis-line.svelte";
+ import TrellisRowDisplayValue from "./trellis-row-display-value.svelte";
+ import TrellisRowLabel from "./trellis-row-label.svelte";
+
+ let {
+ basis,
+ layer,
+ hide_active,
+ hide_border_b,
+ hide_border_t,
+ }: {
+ basis: ITrellisBasisTouch;
+ layer: ThemeLayer;
+ hide_active: boolean;
+ hide_border_b: boolean;
+ hide_border_t: boolean;
+ } = $props();
+</script>
+
+<TrellisLine {layer} {hide_border_b} {hide_border_t} callback={basis.callback}>
+ <TrellisRowLabel basis={basis.label} {layer} {hide_active} />
+ {#if basis.display}
+ <TrellisRowDisplayValue
+ basis={{
+ ...basis.display,
+ }}
+ {layer}
+ {hide_active}
+ />
+ {/if}
+ {#snippet el_end()}
+ {#if basis.end}
+ <TrellisEnd basis={basis.end} {layer} {hide_active} />
+ {/if}
+ {/snippet}
+</TrellisLine>
diff --git a/apps-lib-pwa/src/lib/components/trellis/trellis.svelte b/apps-lib-pwa/src/lib/components/trellis/trellis.svelte
@@ -0,0 +1,167 @@
+<script lang="ts">
+ import { app_lo } from "$lib/stores/app";
+ import type { ITrellis } from "$lib/types/components/trellis";
+ import { fmt_cl, get_context, parse_layer } from "@radroots/apps-lib";
+ import type { Snippet } from "svelte";
+ import TrellisDefaultLabel from "./trellis-default-label.svelte";
+ import TrellisInput from "./trellis-input.svelte";
+ import TrellisOffset from "./trellis-offset.svelte";
+ import TrellisSelect from "./trellis-select.svelte";
+ import TrellisTitle from "./trellis-title.svelte";
+ import TrellisTouch from "./trellis-touch.svelte";
+
+ const { ls } = get_context(`lib`);
+
+ let {
+ basis,
+ el_default,
+ el_offset,
+ el_append,
+ }: {
+ basis: ITrellis;
+ el_default?: Snippet;
+ el_offset?: Snippet;
+ el_append?: Snippet;
+ } = $props();
+
+ const hide_border_t = $derived(
+ typeof basis.hide_border_top === `boolean`
+ ? basis.hide_border_top
+ : true,
+ );
+
+ const hide_border_b = $derived(
+ typeof basis.hide_border_bottom === `boolean`
+ ? basis.hide_border_bottom
+ : true,
+ );
+
+ const hide_rounded = $derived(
+ typeof basis.hide_rounded === `boolean` ? basis.hide_rounded : false,
+ );
+
+ const set_title_background = $derived(
+ typeof basis.set_title_background === `boolean`
+ ? basis.set_title_background
+ : false,
+ );
+
+ const set_default_background = $derived(
+ typeof basis.set_default_background === `boolean`
+ ? basis.set_default_background
+ : false,
+ );
+</script>
+
+<div
+ id={basis.id || ``}
+ class={`${fmt_cl(basis.classes)} flex flex-col`}
+ data-view={basis.view || ``}
+>
+ <div
+ class={`relative flex flex-col h-auto w-lo_${$app_lo} gap-[3px] ${
+ set_title_background ? `bg-ly${basis.layer}` : ``
+ }`}
+ >
+ {#if basis.title && (!basis.default_el || (basis.default_el && basis.default_el.show_title))}
+ <TrellisTitle
+ basis={basis.title}
+ layer={parse_layer(basis.layer - 1)}
+ />
+ {/if}
+ {#if basis.default_el}
+ <div
+ class={`flex flex-col h-auto w-full justify-center items-center`}
+ >
+ {#if el_default}
+ {@render el_default()}
+ {:else if basis.default_el}
+ <TrellisDefaultLabel
+ layer={parse_layer(basis.layer - 1)}
+ labels={basis.default_el.labels
+ ? basis.default_el.labels
+ : [
+ {
+ label: `${$ls(
+ `common.no_items_to_display`,
+ )}.`,
+ },
+ ]}
+ />
+ {/if}
+ </div>
+ {:else if basis.list}
+ <div class={`flex flex-col w-full justify-center items-center`}>
+ {#each basis.list as li}
+ {#if li}
+ <div
+ class={`${
+ li.hide_field ? `hidden` : ``
+ } group flex flex-row h-full w-full justify-end items-center bg-ly${
+ basis.layer
+ } ${li.full_rounded ? `rounded-touch` : ``} ${
+ hide_rounded
+ ? ``
+ : `first:rounded-t-2xl last:rounded-b-2xl`
+ } ${
+ !li.hide_active
+ ? `active:bg-ly${basis.layer}_a`
+ : ``
+ } el-re`}
+ >
+ <div
+ class={`flex flex-row h-full w-full gap-1 items-center overflow-y-hidden`}
+ >
+ {#if !basis.hide_offset}
+ <TrellisOffset
+ basis={li.offset}
+ layer={basis.layer}
+ />
+ {/if}
+ {#if el_offset}
+ {@render el_offset()}
+ {/if}
+ {#if `touch` in li && li.touch}
+ <TrellisTouch
+ basis={li.touch}
+ layer={basis.layer}
+ {hide_border_b}
+ {hide_border_t}
+ hide_active={!!li.hide_active}
+ />
+ {:else if `input` in li && li.input}
+ <TrellisInput
+ basis={li.input}
+ layer={basis.layer}
+ {hide_border_b}
+ {hide_border_t}
+ />
+ {:else if `select` in li && li.select}
+ <TrellisSelect
+ basis={li.select}
+ layer={basis.layer}
+ {hide_border_b}
+ {hide_border_t}
+ hide_active={!!li.hide_active}
+ />
+ {/if}
+ </div>
+ </div>
+ {/if}
+ {/each}
+ </div>
+ {/if}
+ </div>
+ {#if el_append}
+ <div
+ class={`flex flex-col w-full ${
+ set_default_background ? `bg-ly${basis.layer}` : ``
+ }`}
+ >
+ {@render el_append()}
+ </div>
+ {/if}
+</div>
+<div
+ class={`hidden group-first:border-t-0 group-first:border-t-line group-first:border-b-0 group-first:border-b-line`}
+></div>
diff --git a/apps-lib-pwa/src/lib/index.ts b/apps-lib-pwa/src/lib/index.ts
@@ -1,4 +1,6 @@
+export { default as ButtonGlyphCircle } from "./components/button/button-glyph-circle.svelte";
export { default as ButtonGlyphSimple } from "./components/button/button-glyph-simple.svelte";
+export { default as ButtonGlyph } from "./components/button/button-glyph.svelte";
export { default as ButtonLabelDashed } from "./components/button/button-label-dashed.svelte";
export { default as ButtonLayoutBottom } from "./components/button/button-layout-bottom.svelte";
export { default as ButtonLayoutPair } from "./components/button/button-layout-pair.svelte";
@@ -14,6 +16,7 @@ export { default as FormLineLedgerLabelSelectLabel } from "./components/form/for
export { default as FormLineLedgerSelect } from "./components/form/form-line-ledger-select.svelte";
export { default as FormLineLedger } from "./components/form/form-line-ledger.svelte";
export { default as LayoutPage } from "./components/layout/layout-page.svelte";
+export { default as LayoutTrellis } from "./components/layout/layout-trellis.svelte";
export { default as LayoutView } from "./components/layout/layout-view.svelte";
export { default as LayoutWindow } from "./components/layout/layout-window.svelte";
export { default as CarouselContainer } from "./components/lib/carousel-container.svelte";
@@ -22,6 +25,7 @@ export { default as Css } from "./components/lib/css.svelte";
export { default as FloatPage } from "./components/lib/float-page.svelte";
export { default as InputPwa } from "./components/lib/input-pwa.svelte";
export { default as InputValue } from "./components/lib/input-value.svelte";
+export { default as LabelSwap } from "./components/lib/label-swap.svelte";
export { default as LoadCircle } from "./components/lib/load-circle.svelte";
export { default as LoadSymbol } from "./components/lib/load-symbol.svelte";
export { default as LogoCircleSm } from "./components/lib/logo-circle-sm.svelte";
@@ -37,8 +41,20 @@ export { default as ImageUploadPhotoAdd } from "./components/media/image-upload-
export { default as NavigationTabs } from "./components/navigation/navigation-tabs.svelte";
export { default as PageHeader } from "./components/navigation/page-header.svelte";
export { default as PageToolbar } from "./components/navigation/page-toolbar.svelte";
+export { default as TrellisDefaultLabel } from "./components/trellis/trellis-default-label.svelte";
+export { default as TrellisEnd } from "./components/trellis/trellis-end.svelte";
+export { default as TrellisInput } from "./components/trellis/trellis-input.svelte";
+export { default as TrellisLine } from "./components/trellis/trellis-line.svelte";
+export { default as TrellisOffset } from "./components/trellis/trellis-offset.svelte";
+export { default as TrellisRowDisplayValue } from "./components/trellis/trellis-row-display-value.svelte";
+export { default as TrellisRowLabel } from "./components/trellis/trellis-row-label.svelte";
+export { default as TrellisSelect } from "./components/trellis/trellis-select.svelte";
+export { default as TrellisTitle } from "./components/trellis/trellis-title.svelte";
+export { default as TrellisTouch } from "./components/trellis/trellis-touch.svelte";
+export { default as Trellis } from "./components/trellis/trellis.svelte";
export { default as FarmsAdd } from "./views/farms/farms-add.svelte";
export { default as Farms } from "./views/farms/farms.svelte";
export { default as ProfileEdit } from "./views/profile/profile-edit.svelte";
export { default as Profile } from "./views/profile/profile.svelte";
export { default as Home } from "./views/root/home.svelte";
+export { default as Settings } from "./views/root/settings.svelte";
diff --git a/apps-lib-pwa/src/lib/types/components/lib.ts b/apps-lib-pwa/src/lib/types/components/lib.ts
@@ -21,7 +21,7 @@ export type IMapMarkerArea = {
no_drag?: boolean;
}
-export type IGlyphCircle = {
+export type IButtonGlyphCircle = {
classes_wrap: string;
glyph: IGlyph
};
diff --git a/apps-lib-pwa/src/lib/types/components/trellis.ts b/apps-lib-pwa/src/lib/types/components/trellis.ts
@@ -0,0 +1,127 @@
+import type { GlyphKey, ICbGOpt, ICbOpt, IClOpt, IGl, IGlOpt, IGlyph, IInput, ILabel, ILabelOpt, ILabelTup, ILoadingOpt, ILy, ISelect } from "@radroots/apps-lib";
+import type { CallbackPromise } from "@radroots/utils";
+import type { IButtonGlyphCircle } from "./lib";
+
+export type ITrellisExtList = {
+ list: (ITrellisKind | undefined)[];
+}
+
+export type ITrellis = ILy &
+ IClOpt &
+ ITrellisStyles & {
+ id?: string;
+ view?: string;
+ title?: ITrellisTitle;
+ description?: ITrellisDescription;
+ default_el?: ITrellisDefault;
+ list?: (ITrellisKind | undefined)[];
+ hide_offset?: true;
+ };
+
+export type ITrellisStyles = {
+ hide_rounded?: boolean;
+ hide_border_top?: boolean;
+ hide_border_bottom?: boolean;
+ set_title_background?: boolean;
+ set_default_background?: boolean;
+};
+
+export type ITrellisTitle = ICbOpt &
+ IClOpt & {
+ mod?: ITrellisBasisOffsetMod,
+ value: string | true;
+ link?: ICbOpt &
+ IClOpt &
+ IGlOpt & ILabelOpt;
+ };
+
+export type ITrellisDescription = string | true;
+
+export type ITrellisBasisOffsetModKey = 'sm' | 'glyph';
+export type ITrellisBasisOffsetMod = ITrellisBasisOffsetModKey | (({ glyph: IGlyph } | { glyph_circle: IButtonGlyphCircle }) & {
+ loading?: boolean;
+});
+
+export type ITrellisDefault = {
+ labels?: ITrellisDefaultLabel[];
+ show_title?: boolean;
+};
+
+export type ITrellisDefaultLabel = ICbOpt & {
+ label: string;
+ classes?: string;
+};
+
+export type ITrellisKind = (
+ | ITrellisKindTouch
+ | ITrellisKindInput
+ | ITrellisKindSelect
+);
+
+export type ITrellisBasis = {
+ loading?: boolean;
+ hide_active?: boolean;
+ hide_field?: boolean;
+ offset?: ITrellisBasisOffset;
+ full_rounded?: boolean;
+};
+
+export type ITrellisBasisOffset = ICbGOpt<MouseEvent> &
+ IClOpt & {
+ mod?: ITrellisBasisOffsetMod;
+ classes?: string;
+ hide_space?: boolean;
+ hide_offset?: boolean;
+ };
+
+export type ITrellisKindDisplay = {
+ display?: ITrellisKindDisplayValue;
+}
+export type ITrellisKindDisplayValue = ICbGOpt<MouseEvent> & ILoadingOpt &
+ (ITrellisKindDisplayValueIcon | ILabel);
+
+
+export type ITrellisKindDisplayValueIcon = {
+ icon: {
+ classes?: string;
+ key: GlyphKey;
+ };
+};
+export type ITrellisKindTouch = ITrellisBasis & {
+ touch: ITrellisBasisTouch;
+};
+
+export type ITrellisBasisTouch = ICbGOpt<MouseEvent> &
+ ILabelTup & ITrellisKindDisplay & {
+ end?: ITrellisBasisTouchEnd;
+ };
+
+export type ITrellisKindInput = ITrellisBasis & {
+ input: ITrellisBasisInput;
+};
+
+export type ITrellisBasisInput = {
+ basis: IInput<string>;
+ line_label?: {
+ classes?: string;
+ value: string;
+ };
+ action?: {
+ visible: boolean;
+ loading?: boolean;
+ callback?: CallbackPromise;
+ glyph?: IGlyph
+ };
+};
+
+export type ITrellisKindSelect = ITrellisBasis & {
+ select: ITrellisBasisSelect;
+};
+
+export type ITrellisBasisSelect = ICbGOpt<MouseEvent> &
+ ILabelTup & ITrellisKindDisplay & ILoadingOpt & {
+ end?: ITrellisBasisTouchEnd;
+ el: ISelect & { value: string; };
+ };
+
+export type ITrellisBasisTouchEnd = ICbGOpt<MouseEvent> & IGl;
+\ No newline at end of file
diff --git a/apps-lib-pwa/src/lib/views/root/settings.svelte b/apps-lib-pwa/src/lib/views/root/settings.svelte
@@ -0,0 +1,116 @@
+<script lang="ts">
+ import { LayoutView, PageToolbar } from "$lib";
+ import LayoutTrellis from "$lib/components/layout/layout-trellis.svelte";
+ import Trellis from "$lib/components/trellis/trellis.svelte";
+ import type { ITrellisExtList } from "$lib/types/components/trellis";
+ import type { IViewBasis } from "$lib/types/views";
+ import {
+ get_context,
+ idb_kv_init_page,
+ symbols,
+ theme_mode,
+ } from "@radroots/apps-lib";
+ import { handle_err } from "@radroots/utils";
+ import { onMount } from "svelte";
+
+ const { ls, lc_color_mode } = get_context(`lib`);
+
+ let {
+ basis,
+ }: {
+ basis: IViewBasis<{
+ trellis_ext?: ITrellisExtList[];
+ }>;
+ } = $props();
+
+ onMount(async () => {
+ try {
+ if (!basis.kv_init_prevent) await idb_kv_init_page();
+ } catch (e) {
+ handle_err(e, `on_mount`);
+ }
+ });
+</script>
+
+<LayoutView>
+ <PageToolbar
+ basis={{
+ header: {
+ label: `${$ls(`common.settings`)}`,
+ },
+ }}
+ />
+ <LayoutTrellis>
+ <Trellis
+ basis={{
+ layer: 1,
+ title: {
+ value: `Appearance`,
+ },
+ list: [
+ {
+ hide_active: true,
+ select: {
+ label: {
+ left: [
+ {
+ value: `${$ls(`common.color_mode`)}`,
+ classes: `capitalize`,
+ },
+ ],
+ },
+ display: {
+ label: {
+ value: `${$theme_mode}`,
+ classes: `capitalize`,
+ },
+ },
+ el: {
+ value: $theme_mode,
+ options: [
+ {
+ entries: [
+ {
+ value: symbols.bullet,
+ label: `${$ls(`icu.choose_*`, {
+ value: `${$ls(
+ `common.color_mode`,
+ )}`.toLowerCase(),
+ })}`,
+ disabled: true,
+ },
+ {
+ value: `light`,
+ label: `${$ls(`common.light`)}`,
+ },
+ {
+ value: `dark`,
+ label: `${$ls(`common.dark`)}`,
+ },
+ ],
+ },
+ ],
+ callback: lc_color_mode,
+ },
+ end: {
+ glyph: {
+ key: `caret-right`,
+ },
+ },
+ },
+ },
+ ],
+ }}
+ />
+ {#if basis.trellis_ext?.length}
+ {#each basis.trellis_ext as trellis_ext}
+ <Trellis
+ basis={{
+ layer: 1,
+ list: trellis_ext.list,
+ }}
+ />
+ {/each}
+ {/if}
+ </LayoutTrellis>
+</LayoutView>