Updated Theme Switcher
This commit is contained in:
@@ -61,7 +61,7 @@ const item =
|
|||||||
aria-label={p.label}
|
aria-label={p.label}
|
||||||
>
|
>
|
||||||
<!-- Scope just this swatch to the palette so it inherits --primary -->
|
<!-- Scope just this swatch to the palette so it inherits --primary -->
|
||||||
<span data-swatch-scope data-color={p.key}>
|
<span data-swatch-scope data-color={p.key} class="wr:flex wr:items-center wr:justify-center">
|
||||||
<span
|
<span
|
||||||
class={["wr:inline-block wr:rounded-full wr:ring-2 wr:ring-offset-2 wr:ring-offset-background wr:transition-all wr:group-hover:ring-primary/50", dot].join(" ")}
|
class={["wr:inline-block wr:rounded-full wr:ring-2 wr:ring-offset-2 wr:ring-offset-background wr:transition-all wr:group-hover:ring-primary/50", dot].join(" ")}
|
||||||
data-swatch
|
data-swatch
|
||||||
@@ -85,7 +85,7 @@ const item =
|
|||||||
>
|
>
|
||||||
<span class="wr:inline-flex wr:items-center wr:gap-3">
|
<span class="wr:inline-flex wr:items-center wr:gap-3">
|
||||||
<!-- Trigger dot gets its own scoped palette -->
|
<!-- Trigger dot gets its own scoped palette -->
|
||||||
<span data-current-scope data-color={palettes[0].key}>
|
<span data-current-scope data-color={palettes[0].key} class="wr:flex wr:items-center wr:justify-center">
|
||||||
<span class={["wr:inline-block wr:rounded-full wr:ring-2 wr:ring-offset-2 wr:ring-offset-background", dot].join(" ")} data-swatch />
|
<span class={["wr:inline-block wr:rounded-full wr:ring-2 wr:ring-offset-2 wr:ring-offset-background", dot].join(" ")} data-swatch />
|
||||||
</span>
|
</span>
|
||||||
<span data-current-label class="wr:text-sm wr:font-medium">{palettes[0].label}</span>
|
<span data-current-label class="wr:text-sm wr:font-medium">{palettes[0].label}</span>
|
||||||
@@ -101,7 +101,7 @@ const item =
|
|||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
title="Brand color"
|
title="Brand color"
|
||||||
>
|
>
|
||||||
<span data-current-scope data-color={palettes[0].key}>
|
<span data-current-scope data-color={palettes[0].key} class="wr:flex wr:items-center wr:justify-center">
|
||||||
<span class={["wr:inline-block wr:rounded-full wr:ring-2 wr:ring-offset-2 wr:ring-offset-background", dot].join(" ")} data-swatch />
|
<span class={["wr:inline-block wr:rounded-full wr:ring-2 wr:ring-offset-2 wr:ring-offset-background", dot].join(" ")} data-swatch />
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -116,7 +116,7 @@ const item =
|
|||||||
>
|
>
|
||||||
{palettes.map(p => (
|
{palettes.map(p => (
|
||||||
<button type="button" role="menuitemradio" aria-checked="false" class={item} data-palette={p.key}>
|
<button type="button" role="menuitemradio" aria-checked="false" class={item} data-palette={p.key}>
|
||||||
<span data-swatch-scope data-color={p.key}>
|
<span data-swatch-scope data-color={p.key} class="wr:flex wr:items-center wr:justify-center">
|
||||||
<span class={["wr:inline-block wr:rounded-full wr:ring-2 wr:ring-offset-2 wr:ring-offset-background", dot].join(" ")} data-swatch />
|
<span class={["wr:inline-block wr:rounded-full wr:ring-2 wr:ring-offset-2 wr:ring-offset-background", dot].join(" ")} data-swatch />
|
||||||
</span>
|
</span>
|
||||||
<span class="wr:flex-1">{p.label}</span>
|
<span class="wr:flex-1">{p.label}</span>
|
||||||
|
|||||||
@@ -3,127 +3,65 @@ import type { HTMLAttributes } from "astro/types";
|
|||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
|
|
||||||
interface Props extends HTMLAttributes<"div"> {
|
interface Props extends HTMLAttributes<"div"> {
|
||||||
ui?: "icons" | "menu";
|
|
||||||
storageKey?: string;
|
storageKey?: string;
|
||||||
rootSelector?: string;
|
rootSelector?: string; // where to toggle theme, e.g. "html"
|
||||||
label?: string;
|
compact?: boolean; // smaller trigger button
|
||||||
compact?: boolean;
|
darkBgClass?: string[]; // non–pure-black dark background class
|
||||||
darkBgClass?: string[];
|
lightBgClass?: string[]; // light background class
|
||||||
lightBgClass?: string[];
|
rootExtraClass?: string; // always-on root class (optional)
|
||||||
rootExtraClass?: string;
|
showLabel?: boolean; // show text next to icon in trigger
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ui = "menu",
|
|
||||||
storageKey = "wr-theme",
|
storageKey = "wr-theme",
|
||||||
rootSelector = "html",
|
rootSelector = "html",
|
||||||
label = "",
|
|
||||||
compact = false,
|
compact = false,
|
||||||
darkBgClass = ["wr-theme-dark-soft"],
|
darkBgClass = ["wr-theme-dark-soft"],
|
||||||
lightBgClass = ["wr-theme-light"],
|
lightBgClass = ["wr-theme-light"],
|
||||||
rootExtraClass = "",
|
rootExtraClass = "",
|
||||||
|
showLabel = false,
|
||||||
class: className,
|
class: className,
|
||||||
...rest
|
...rest
|
||||||
}: Props = Astro.props;
|
}: Props = Astro.props;
|
||||||
|
|
||||||
const btn =
|
|
||||||
"wr:inline-flex wr:items-center wr:gap-2 wr:rounded-xl wr:border wr:border-border wr:bg-background/80 wr:backdrop-blur-sm " +
|
|
||||||
"wr:text-foreground wr:h-10 wr:px-3 wr:text-sm wr:shadow-sm wr:transition wr:duration-200 wr:ease-out " +
|
|
||||||
"wr:hover:bg-muted/70 wr:focus-visible:outline-none wr:focus-visible:ring-2 wr:focus-visible:ring-primary/40 wr:focus-visible:ring-offset-2";
|
|
||||||
|
|
||||||
const iconBtn =
|
const iconBtn =
|
||||||
"wr:inline-flex wr:items-center wr:justify-center wr:h-9 wr:w-9 wr:rounded-xl wr:border wr:border-border wr:bg-background/80 wr:backdrop-blur-sm " +
|
"wr:inline-flex wr:items-center wr:justify-center wr:h-9 wr:w-9 wr:rounded-xl wr:border wr:border-border wr:bg-background/80 wr:backdrop-blur-sm " +
|
||||||
"wr:text-foreground wr:shadow-sm wr:transition wr:duration-200 wr:ease-out wr:hover:bg-muted/70 " +
|
"wr:text-foreground wr:shadow-sm wr:transition wr:duration-200 wr:ease-out wr:hover:bg-muted/70 " +
|
||||||
"wr:focus-visible:outline-none wr:focus-visible:ring-2 wr:focus-visible:ring-primary/40 wr:focus-visible:ring-offset-2";
|
"wr:focus-visible:outline-none wr:focus-visible:ring-2 wr:focus-visible:ring-primary/40 wr:focus-visible:ring-offset-2 wr:ring-offset-background";
|
||||||
|
|
||||||
|
const menuBtn =
|
||||||
|
"wr:inline-flex wr:items-center wr:gap-2 wr:rounded-xl wr:border wr:border-border wr:bg-background/80 wr:backdrop-blur-sm " +
|
||||||
|
"wr:text-foreground wr:h-10 wr:px-3 wr:text-sm wr:shadow-sm wr:transition wr:duration-200 wr:ease-out " +
|
||||||
|
"wr:hover:bg-muted/70 wr:focus-visible:outline-none wr:focus-visible:ring-2 wr:focus-visible:ring-primary/40 wr:focus-visible:ring-offset-2 wr:ring-offset-background";
|
||||||
|
|
||||||
const item =
|
const item =
|
||||||
"wr:flex wr:items-center wr:gap-3 wr:w-full wr:px-3 wr:py-2.5 wr:rounded-lg wr:text-sm wr:hover:bg-muted/70 wr:transition wr:duration-150";
|
"wr:flex wr:items-center wr:gap-3 wr:w-full wr:px-3 wr:py-2.5 wr:rounded-lg wr:text-sm wr:hover:bg-muted/70 wr:transition wr:duration-150";
|
||||||
|
|
||||||
const icon = "wr:size-4";
|
const icon = "wr:size-4";
|
||||||
const checkBase = "wr:size-4 wr:opacity-0 wr:scale-90 wr:transition";
|
const checkBase = "wr:size-4 wr:opacity-0 wr:scale-90 wr:transition";
|
||||||
|
const containerId = `ts-${Math.random().toString(36).slice(2)}`;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div
|
<div id={containerId} class={["wr:relative", className].join(" ")} {...rest}>
|
||||||
class={["wr:flex wr:items-center wr:gap-3", className].join(" ")}
|
<!-- Trigger: icon-only by default; set showLabel=true to include text -->
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{label && <span class="wr:text-sm wr:text-muted-foreground">{label}</span>}
|
|
||||||
|
|
||||||
{
|
|
||||||
ui === "icons" && (
|
|
||||||
<div class="wr:flex wr:items-center wr:gap-2" data-theme-switcher>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={[iconBtn, compact && "wr:h-8 wr:w-8"]
|
class={showLabel ? menuBtn : [iconBtn, compact && "wr:h-8 wr:w-8"].filter(Boolean).join(" ")}
|
||||||
.filter(Boolean)
|
|
||||||
.join(" ")}
|
|
||||||
data-set-theme="light"
|
|
||||||
title="Light"
|
|
||||||
aria-label="Light"
|
|
||||||
>
|
|
||||||
<Icon name="mdi:weather-sunny" class={icon} />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class={[iconBtn, compact && "wr:h-8 wr:w-8"]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(" ")}
|
|
||||||
data-set-theme="dark"
|
|
||||||
title="Dark"
|
|
||||||
aria-label="Dark"
|
|
||||||
>
|
|
||||||
<Icon name="mdi:weather-night" class={icon} />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class={[iconBtn, compact && "wr:h-8 wr:w-8"]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(" ")}
|
|
||||||
data-set-theme="system"
|
|
||||||
title="System"
|
|
||||||
aria-label="System"
|
|
||||||
>
|
|
||||||
<Icon name="mdi:laptop" class={icon} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
ui === "menu" && (
|
|
||||||
<div class="wr:relative" data-theme-switcher data-theme-menu>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class={[btn, "wr:min-w-[10.5rem] wr:justify-between"].join(
|
|
||||||
" ",
|
|
||||||
)}
|
|
||||||
data-menu-trigger
|
data-menu-trigger
|
||||||
aria-haspopup="menu"
|
aria-haspopup="menu"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
|
title="Theme"
|
||||||
>
|
>
|
||||||
<span class="wr:inline-flex wr:items-center wr:gap-2">
|
<span class="wr:inline-flex wr:items-center wr:gap-2">
|
||||||
<span data-current-icon="light" class="wr:inline">
|
<span data-current-icon="light" class="wr:inline"><Icon name="mdi:weather-sunny" class={icon} /></span>
|
||||||
<Icon name="mdi:weather-sunny" class={icon} />
|
<span data-current-icon="dark" class="wr:hidden"><Icon name="mdi:weather-night" class={icon} /></span>
|
||||||
|
<span data-current-icon="system" class="wr:hidden"><Icon name="mdi:laptop" class={icon} /></span>
|
||||||
|
{showLabel && <span data-current-label class="wr:text-sm wr:font-medium">System</span>}
|
||||||
</span>
|
</span>
|
||||||
<span data-current-icon="dark" class="wr:hidden">
|
{showLabel && <Icon name="mdi:chevron-down" class="wr:size-4 wr:opacity-70" />}
|
||||||
<Icon name="mdi:weather-night" class={icon} />
|
|
||||||
</span>
|
|
||||||
<span data-current-icon="system" class="wr:hidden">
|
|
||||||
<Icon name="mdi:laptop" class={icon} />
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
data-current-label
|
|
||||||
class="wr:text-sm wr:font-medium"
|
|
||||||
>
|
|
||||||
System
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<Icon
|
|
||||||
name="mdi:chevron-down"
|
|
||||||
class="wr:size-4 wr:opacity-70"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Dropdown -->
|
||||||
<div
|
<div
|
||||||
class="wr:absolute wr:right-0 wr:z-50 wr:mt-2 wr:w-56 wr:rounded-2xl wr:border wr:border-border wr:bg-popover/90 wr:backdrop-blur-md wr:text-popover-foreground wr:shadow-lg wr:p-1.5 wr:hidden
|
class="wr:absolute wr:right-0 wr:z-50 wr:mt-2 wr:w-56 wr:rounded-2xl wr:border wr:border-border wr:bg-popover/90 wr:backdrop-blur-md wr:text-popover-foreground wr:shadow-lg wr:p-1.5 wr:hidden
|
||||||
wr:origin-top-right wr:scale-95 wr:opacity-0 wr:transition wr:duration-150 wr:ease-out"
|
wr:origin-top-right wr:scale-95 wr:opacity-0 wr:transition wr:duration-150 wr:ease-out"
|
||||||
@@ -131,120 +69,69 @@ const checkBase = "wr:size-4 wr:opacity-0 wr:scale-90 wr:transition";
|
|||||||
aria-label="Theme"
|
aria-label="Theme"
|
||||||
data-menu-panel
|
data-menu-panel
|
||||||
>
|
>
|
||||||
<button
|
<button type="button" role="menuitemradio" aria-checked="false" class={item} data-set-theme="light">
|
||||||
type="button"
|
|
||||||
role="menuitemradio"
|
|
||||||
aria-checked="false"
|
|
||||||
class={item}
|
|
||||||
data-set-theme="light"
|
|
||||||
>
|
|
||||||
<Icon name="mdi:weather-sunny" class={icon} />
|
<Icon name="mdi:weather-sunny" class={icon} />
|
||||||
<div class="wr:flex-1 wr:font-medium">Light</div>
|
<div class="wr:flex-1 wr:font-medium">Light</div>
|
||||||
<Icon name="mdi:check" class={checkBase} data-check />
|
<Icon name="mdi:check" class={checkBase} data-check />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button type="button" role="menuitemradio" aria-checked="false" class={item} data-set-theme="dark">
|
||||||
type="button"
|
|
||||||
role="menuitemradio"
|
|
||||||
aria-checked="false"
|
|
||||||
class={item}
|
|
||||||
data-set-theme="dark"
|
|
||||||
>
|
|
||||||
<Icon name="mdi:weather-night" class={icon} />
|
<Icon name="mdi:weather-night" class={icon} />
|
||||||
<div class="wr:flex-1 wr:font-medium">Dark</div>
|
<div class="wr:flex-1 wr:font-medium">Dark</div>
|
||||||
<Icon name="mdi:check" class={checkBase} data-check />
|
<Icon name="mdi:check" class={checkBase} data-check />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button type="button" role="menuitemradio" aria-checked="true" class={item} data-set-theme="system">
|
||||||
type="button"
|
|
||||||
role="menuitemradio"
|
|
||||||
aria-checked="true"
|
|
||||||
class={item}
|
|
||||||
data-set-theme="system"
|
|
||||||
>
|
|
||||||
<Icon name="mdi:laptop" class={icon} />
|
<Icon name="mdi:laptop" class={icon} />
|
||||||
<div class="wr:flex-1 wr:font-medium">System</div>
|
<div class="wr:flex-1 wr:font-medium">System</div>
|
||||||
<Icon name="mdi:check" class={checkBase} data-check />
|
<Icon name="mdi:check" class={checkBase} data-check />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script
|
<script is:inline define:vars={{ containerId, storageKey, rootSelector, darkBgClass, lightBgClass, rootExtraClass }}>
|
||||||
is:inline
|
|
||||||
define:vars={{
|
|
||||||
storageKey,
|
|
||||||
rootSelector,
|
|
||||||
darkBgClass,
|
|
||||||
lightBgClass,
|
|
||||||
rootExtraClass,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
(() => {
|
(() => {
|
||||||
const root =
|
const host = document.getElementById(containerId);
|
||||||
rootSelector === "html"
|
if (!host) return;
|
||||||
|
|
||||||
|
const root = rootSelector === "html"
|
||||||
? document.documentElement
|
? document.documentElement
|
||||||
: document.querySelector(rootSelector) ||
|
: document.querySelector(rootSelector) || document.documentElement;
|
||||||
document.documentElement;
|
|
||||||
|
|
||||||
const mql = window.matchMedia("(prefers-color-scheme: dark)");
|
const mql = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
let lastThemeClasses = [];
|
let lastThemeClasses = [];
|
||||||
|
|
||||||
function setRootClasses(list) {
|
function setRootClasses(list) {
|
||||||
lastThemeClasses.forEach((c) => c && root.classList.remove(c));
|
lastThemeClasses.forEach(c => c && root.classList.remove(c));
|
||||||
list.filter(Boolean).forEach((c) => root.classList.add(c));
|
list.filter(Boolean).forEach(c => root.classList.add(c));
|
||||||
lastThemeClasses = list.filter(Boolean);
|
lastThemeClasses = list.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reflectUI(mode) {
|
function reflectUI(mode) {
|
||||||
const wrap = document.querySelector("[data-theme-switcher]");
|
// Trigger icon + optional label
|
||||||
if (!wrap) return;
|
const trig = host.querySelector("[data-menu-trigger]");
|
||||||
|
|
||||||
// icon buttons
|
|
||||||
wrap.querySelectorAll("[data-set-theme]").forEach((btn) => {
|
|
||||||
const active = btn.getAttribute("data-set-theme") === mode;
|
|
||||||
btn.classList.toggle("wr:bg-muted/70", active);
|
|
||||||
});
|
|
||||||
|
|
||||||
// menu radios + checks
|
|
||||||
const panel = wrap.querySelector("[data-menu-panel]");
|
|
||||||
if (panel) {
|
|
||||||
panel
|
|
||||||
.querySelectorAll("[role='menuitemradio']")
|
|
||||||
.forEach((item) => {
|
|
||||||
const active =
|
|
||||||
item.getAttribute("data-set-theme") === mode;
|
|
||||||
item.setAttribute(
|
|
||||||
"aria-checked",
|
|
||||||
active ? "true" : "false",
|
|
||||||
);
|
|
||||||
item.classList.toggle("wr:bg-muted/60", active);
|
|
||||||
// check icon visibility handled by CSS via [aria-checked="true"] [data-check]
|
|
||||||
});
|
|
||||||
|
|
||||||
const trig = wrap.querySelector("[data-menu-trigger]");
|
|
||||||
if (trig) {
|
if (trig) {
|
||||||
const labelSpan = trig.querySelector(
|
const labelSpan = trig.querySelector("[data-current-label]");
|
||||||
"[data-current-label]",
|
if (labelSpan) labelSpan.textContent = mode[0].toUpperCase() + mode.slice(1);
|
||||||
);
|
trig.querySelectorAll("[data-current-icon]").forEach(el => {
|
||||||
if (labelSpan)
|
const is = el.getAttribute("data-current-icon") === mode;
|
||||||
labelSpan.textContent =
|
|
||||||
mode[0].toUpperCase() + mode.slice(1);
|
|
||||||
trig.querySelectorAll("[data-current-icon]").forEach(
|
|
||||||
(el) => {
|
|
||||||
const is =
|
|
||||||
el.getAttribute("data-current-icon") === mode;
|
|
||||||
el.classList.toggle("wr:hidden", !is);
|
el.classList.toggle("wr:hidden", !is);
|
||||||
el.classList.toggle("wr:inline", is);
|
el.classList.toggle("wr:inline", is);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Menu radios & checks
|
||||||
|
const panel = host.querySelector("[data-menu-panel]");
|
||||||
|
if (panel) {
|
||||||
|
panel.querySelectorAll("[role='menuitemradio']").forEach(item => {
|
||||||
|
const active = item.getAttribute("data-set-theme") === mode;
|
||||||
|
item.setAttribute("aria-checked", active ? "true" : "false");
|
||||||
|
item.classList.toggle("wr:bg-muted/60", active);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openMenu(wrap, open) {
|
function openMenu(open) {
|
||||||
const trigger = wrap.querySelector("[data-menu-trigger]");
|
const trigger = host.querySelector("[data-menu-trigger]");
|
||||||
const panel = wrap.querySelector("[data-menu-panel]");
|
const panel = host.querySelector("[data-menu-panel]");
|
||||||
trigger?.setAttribute("aria-expanded", open ? "true" : "false");
|
trigger?.setAttribute("aria-expanded", open ? "true" : "false");
|
||||||
if (!panel) return;
|
if (!panel) return;
|
||||||
if (open) {
|
if (open) {
|
||||||
@@ -260,62 +147,56 @@ const checkBase = "wr:size-4 wr:opacity-0 wr:scale-90 wr:transition";
|
|||||||
}
|
}
|
||||||
|
|
||||||
function apply(mode) {
|
function apply(mode) {
|
||||||
const resolved =
|
const resolved = mode === "system" ? (mql.matches ? "dark" : "light") : mode;
|
||||||
mode === "system" ? (mql.matches ? "dark" : "light") : mode;
|
|
||||||
root.classList.toggle("dark", resolved === "dark");
|
root.classList.toggle("dark", resolved === "dark");
|
||||||
root.setAttribute("data-theme", resolved);
|
root.setAttribute("data-theme", resolved);
|
||||||
document.documentElement.style.colorScheme = resolved;
|
document.documentElement.style.colorScheme = resolved;
|
||||||
setRootClasses([
|
setRootClasses([rootExtraClass, ...(resolved === "dark" ? darkBgClass : lightBgClass)]);
|
||||||
rootExtraClass,
|
|
||||||
...(resolved === "dark" ? darkBgClass : lightBgClass),
|
|
||||||
]);
|
|
||||||
localStorage.setItem(storageKey, mode);
|
localStorage.setItem(storageKey, mode);
|
||||||
reflectUI(mode);
|
reflectUI(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
const saved = localStorage.getItem(storageKey) || "system";
|
// Init
|
||||||
|
const saved = (localStorage.getItem(storageKey) ) || "system";
|
||||||
apply(saved);
|
apply(saved);
|
||||||
|
|
||||||
|
// react to system changes when mode=system
|
||||||
mql.addEventListener?.("change", () => {
|
mql.addEventListener?.("change", () => {
|
||||||
if ((localStorage.getItem(storageKey) || "system") === "system")
|
if ((localStorage.getItem(storageKey) ) === "system") apply("system");
|
||||||
apply("system");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("click", (e) => {
|
// Events (scoped to this component)
|
||||||
const menuWrap = e.target.closest?.("[data-theme-menu]");
|
host.addEventListener("click", (e) => {
|
||||||
const trigger = e.target.closest?.("[data-menu-trigger]");
|
const trigger = (e.target )?.closest?.("[data-menu-trigger]");
|
||||||
const choose = e.target.closest?.("[data-set-theme]");
|
if (trigger && host.contains(trigger)) {
|
||||||
|
const expanded = trigger.getAttribute("aria-expanded") === "true";
|
||||||
|
openMenu(!expanded);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (choose) {
|
const choose = (e.target )?.closest?.("[data-set-theme]");
|
||||||
|
if (choose && host.contains(choose)) {
|
||||||
apply(choose.getAttribute("data-set-theme") );
|
apply(choose.getAttribute("data-set-theme") );
|
||||||
const wrap = choose.closest("[data-theme-menu]");
|
openMenu(false);
|
||||||
if (wrap) openMenu(wrap, false);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (trigger && menuWrap) {
|
|
||||||
const expanded =
|
|
||||||
trigger.getAttribute("aria-expanded") === "true";
|
|
||||||
openMenu(menuWrap, !expanded);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
document
|
|
||||||
.querySelectorAll("[data-theme-menu]")
|
|
||||||
.forEach((w) => openMenu(w, false));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Close on outside click
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
if (!host.contains(e.target )) openMenu(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Esc to close
|
||||||
document.addEventListener("keydown", (e) => {
|
document.addEventListener("keydown", (e) => {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") openMenu(false);
|
||||||
document
|
|
||||||
.querySelectorAll("[data-theme-menu]")
|
|
||||||
.forEach((w) => openMenu(w, false));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style is:inline>
|
<style is:inline>
|
||||||
/* Show the check icon ONLY on the active item in the menu */
|
/* Check icon ONLY on active item */
|
||||||
[data-theme-menu] [role="menuitemradio"][aria-checked="true"] [data-check] {
|
#{containerId} [role="menuitemradio"][aria-checked="true"] [data-check] {
|
||||||
opacity: 1 !important;
|
opacity: 1 !important;
|
||||||
transform: scale(1) !important;
|
transform: scale(1) !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,12 @@ import Base from "../layouts/Base.astro";
|
|||||||
<div
|
<div
|
||||||
class="wrnx wr:flex wr:items-center wr:flex-col wr:gap-4 wr:w-full wr:h-screen"
|
class="wrnx wr:flex wr:items-center wr:flex-col wr:gap-4 wr:w-full wr:h-screen"
|
||||||
>
|
>
|
||||||
|
<!-- Icon-only trigger (default) -->
|
||||||
<ThemeSwitcher
|
<ThemeSwitcher
|
||||||
ui="select"
|
darkBgClass={["wr:dark:bg-neutral-500", "wr:text-white"]}
|
||||||
darkBgClass={["wr:dark:bg-neutral-700", "wr:text-white"]}
|
|
||||||
lightBgClass={["wr:bg-white", "wr:text-black"]}
|
lightBgClass={["wr:bg-white", "wr:text-black"]}
|
||||||
ui="menu"
|
|
||||||
rootExtraClass=""
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ColorSwitcher ui="menu" trigger="dot" />
|
<ColorSwitcher ui="menu" trigger="dot" />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user