web_lib

Common web application libraries
git clone https://radroots.dev/git/web_lib.git
Log | Files | Refs | LICENSE

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:
Aapps-lib-pwa/src/lib/components/button/button-glyph-circle.svelte | 25+++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/button/button-glyph.svelte | 22++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/layout/layout-trellis.svelte | 20++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/lib/label-swap.svelte | 47+++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/trellis/trellis-default-label.svelte | 37+++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/trellis/trellis-end.svelte | 38++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/trellis/trellis-input.svelte | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/trellis/trellis-line.svelte | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/trellis/trellis-offset.svelte | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/trellis/trellis-row-display-value.svelte | 46++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/trellis/trellis-row-label.svelte | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/trellis/trellis-select.svelte | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/trellis/trellis-title.svelte | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/trellis/trellis-touch.svelte | 40++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/components/trellis/trellis.svelte | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapps-lib-pwa/src/lib/index.ts | 16++++++++++++++++
Mapps-lib-pwa/src/lib/types/components/lib.ts | 2+-
Aapps-lib-pwa/src/lib/types/components/trellis.ts | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapps-lib-pwa/src/lib/views/root/settings.svelte | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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>