Added new Theme Switcher and Color Switcher
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
---
|
||||
import type { HTMLAttributes } from "astro/types";
|
||||
|
||||
interface Palette { key: string; label: string; }
|
||||
|
||||
interface Props extends HTMLAttributes<"div"> {
|
||||
storageKey?: string;
|
||||
rootSelector?: string; // where to set data-color (e.g., "html" or ".wrnx")
|
||||
ui?: "swatches" | "menu"; // grid or dropdown
|
||||
showLabels?: boolean; // for swatch grid
|
||||
compact?: boolean; // smaller dots
|
||||
trigger?: "default" | "dot"; // dot = only a colored circle in trigger
|
||||
palettes?: Palette[]; // keys/labels to display
|
||||
}
|
||||
|
||||
const {
|
||||
storageKey = "wr-color",
|
||||
rootSelector = "html",
|
||||
ui = "menu",
|
||||
showLabels = false,
|
||||
compact = false,
|
||||
trigger = "default",
|
||||
class: className,
|
||||
palettes = [
|
||||
{ key: "ocean", label: "Ocean" },
|
||||
{ key: "forest", label: "Forest" },
|
||||
{ key: "sunset", label: "Sunset" },
|
||||
{ key: "grape", label: "Grape" },
|
||||
{ key: "slate", label: "Slate" },
|
||||
{ key: "mint", label: "Mint" },
|
||||
{ key: "amber", label: "Amber" },
|
||||
{ key: "crimson", label: "Crimson" },
|
||||
{ key: "indigo", label: "Indigo" },
|
||||
],
|
||||
...rest
|
||||
}: Props = Astro.props;
|
||||
|
||||
const dot = compact ? "wr:h-3.5 wr:w-3.5" : "wr:h-4 wr:w-4";
|
||||
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 =
|
||||
"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:focus-visible:outline-none wr:focus-visible:ring-2 wr:focus-visible:ring-primary/40 wr:focus-visible:ring-offset-2";
|
||||
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";
|
||||
---
|
||||
|
||||
{ui === "swatches" ? (
|
||||
<div class={["wr:flex wr:flex-wrap wr:items-center wr:gap-3", className].join(" ")} {...rest} data-color-switcher>
|
||||
{palettes.map(p => (
|
||||
<button
|
||||
type="button"
|
||||
class="wr:relative wr:flex wr:flex-col wr:items-center wr:gap-1 wr:group wr:opacity-60 wr:transition wr:duration-150"
|
||||
data-palette={p.key}
|
||||
aria-checked="false"
|
||||
role="radio"
|
||||
title={p.label}
|
||||
aria-label={p.label}
|
||||
>
|
||||
<!-- Scope just this swatch to the palette so it inherits --primary -->
|
||||
<span data-swatch-scope data-color={p.key}>
|
||||
<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(" ")}
|
||||
data-swatch
|
||||
/>
|
||||
</span>
|
||||
<!-- check icon (hidden until active) -->
|
||||
<svg data-check viewBox="0 0 24 24" class="wr:absolute wr:inset-0 wr:m-auto wr:size-4 wr:text-background wr:opacity-0 wr:scale-90 wr:transition" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
|
||||
{showLabels && <span class="wr:text-xs wr:text-muted-foreground">{p.label}</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div class={["wr:relative", className].join(" ")} {...rest} data-color-switcher data-color-menu>
|
||||
{trigger === "default" ? (
|
||||
<button
|
||||
type="button"
|
||||
class={[btn, "wr:min-w-[10.5rem] wr:justify-between"].join(" ")}
|
||||
data-menu-trigger
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="wr:inline-flex wr:items-center wr:gap-3">
|
||||
<!-- Trigger dot gets its own scoped palette -->
|
||||
<span data-current-scope data-color={palettes[0].key}>
|
||||
<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 data-current-label class="wr:text-sm wr:font-medium">{palettes[0].label}</span>
|
||||
</span>
|
||||
<svg class="wr:size-4 wr:opacity-70" viewBox="0 0 24 24" fill="none"><path d="M7 10l5 5 5-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
class={iconBtn}
|
||||
data-menu-trigger
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
title="Brand color"
|
||||
>
|
||||
<span data-current-scope data-color={palettes[0].key}>
|
||||
<span class={["wr:inline-block wr:rounded-full wr:ring-2 wr:ring-offset-2 wr:ring-offset-background", dot].join(" ")} data-swatch />
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<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
|
||||
wr:origin-top-right wr:scale-95 wr:opacity-0 wr:transition wr:duration-150 wr:ease-out"
|
||||
role="menu"
|
||||
aria-label="Brand color"
|
||||
data-menu-panel
|
||||
>
|
||||
{palettes.map(p => (
|
||||
<button type="button" role="menuitemradio" aria-checked="false" class={item} data-palette={p.key}>
|
||||
<span data-swatch-scope data-color={p.key}>
|
||||
<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 class="wr:flex-1">{p.label}</span>
|
||||
<svg data-check class="wr:size-4 wr:opacity-0 wr:scale-90 wr:transition" viewBox="0 0 24 24" fill="none"><path d="M20 6L9 17l-5-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<script is:inline define:vars={{ storageKey, rootSelector, palettes }}>
|
||||
(() => {
|
||||
const root = rootSelector === "html" ? document.documentElement : document.querySelector(rootSelector) || document.documentElement;
|
||||
|
||||
function setColor(key) {
|
||||
root.setAttribute("data-color", key);
|
||||
localStorage.setItem(storageKey, key);
|
||||
reflectUI(key);
|
||||
}
|
||||
|
||||
function reflectUI(key) {
|
||||
// mark active & show check
|
||||
document.querySelectorAll("[data-color-switcher] [data-palette]").forEach((btn) => {
|
||||
const active = btn.getAttribute("data-palette") === key;
|
||||
btn.setAttribute("aria-checked", active ? "true" : "false");
|
||||
btn.classList.toggle("wr:opacity-100", active);
|
||||
btn.classList.toggle("wr:opacity-60", !active);
|
||||
const chk = btn.querySelector("[data-check]");
|
||||
if (chk) {
|
||||
chk.classList.toggle("wr:opacity-100", active);
|
||||
chk.classList.toggle("wr:opacity-0", !active);
|
||||
chk.classList.toggle("wr:scale-100", active);
|
||||
chk.classList.toggle("wr:scale-90", !active);
|
||||
}
|
||||
});
|
||||
|
||||
// update trigger label and its scoped color
|
||||
document.querySelectorAll("[data-color-menu]").forEach((wrap) => {
|
||||
const label = wrap.querySelector("[data-current-label]");
|
||||
const scope = wrap.querySelector("[data-current-scope]");
|
||||
if (label) label.textContent = (palettes.find(p => p.key === key)?.label) || key;
|
||||
if (scope) scope.setAttribute("data-color", key);
|
||||
wrap.querySelectorAll("[role='menuitemradio']").forEach((it) => {
|
||||
it.setAttribute("aria-checked", it.getAttribute("data-palette") === key ? "true" : "false");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function openMenu(wrap, open) {
|
||||
const trigger = wrap.querySelector("[data-menu-trigger]");
|
||||
const panel = wrap.querySelector("[data-menu-panel]");
|
||||
trigger?.setAttribute("aria-expanded", open ? "true" : "false");
|
||||
if (!panel) return;
|
||||
if (open) {
|
||||
panel.classList.remove("wr:hidden");
|
||||
requestAnimationFrame(() => {
|
||||
panel.classList.remove("wr:scale-95","wr:opacity-0");
|
||||
panel.classList.add("wr:scale-100","wr:opacity-100");
|
||||
});
|
||||
} else {
|
||||
panel.classList.add("wr:scale-95","wr:opacity-0");
|
||||
setTimeout(() => panel.classList.add("wr:hidden"), 150);
|
||||
}
|
||||
}
|
||||
|
||||
// initial
|
||||
const saved = localStorage.getItem(storageKey);
|
||||
const initial = saved && palettes.some(p => p.key === saved) ? saved : palettes[0].key;
|
||||
setColor(initial);
|
||||
|
||||
// wire
|
||||
document.addEventListener("click", (e) => {
|
||||
const choose = e.target.closest?.("[data-color-switcher] [data-palette]");
|
||||
if (choose) {
|
||||
setColor(choose.getAttribute("data-palette"));
|
||||
const menu = e.target.closest?.("[data-color-menu]");
|
||||
if (menu) openMenu(menu, false);
|
||||
return;
|
||||
}
|
||||
const trigger = e.target.closest?.("[data-color-menu] [data-menu-trigger]");
|
||||
const menuWrap = e.target.closest?.("[data-color-menu]");
|
||||
if (trigger && menuWrap) {
|
||||
const expanded = trigger.getAttribute("aria-expanded") === "true";
|
||||
openMenu(menuWrap, !expanded);
|
||||
return;
|
||||
}
|
||||
document.querySelectorAll("[data-color-menu]").forEach(w => openMenu(w, false));
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") {
|
||||
document.querySelectorAll("[data-color-menu]").forEach(w => openMenu(w, false));
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style is:inline>
|
||||
/* Swatches use your role token directly */
|
||||
[data-swatch] {
|
||||
background: hsl(var(--primary));
|
||||
}
|
||||
|
||||
/* Active check only on the selected item */
|
||||
[data-color-switcher] [data-palette][aria-checked="true"] [data-check] {
|
||||
opacity: 1 !important;
|
||||
transform: scale(1) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,322 @@
|
||||
---
|
||||
import type { HTMLAttributes } from "astro/types";
|
||||
import { Icon } from "astro-icon/components";
|
||||
|
||||
interface Props extends HTMLAttributes<"div"> {
|
||||
ui?: "icons" | "menu";
|
||||
storageKey?: string;
|
||||
rootSelector?: string;
|
||||
label?: string;
|
||||
compact?: boolean;
|
||||
darkBgClass?: string[];
|
||||
lightBgClass?: string[];
|
||||
rootExtraClass?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
ui = "menu",
|
||||
storageKey = "wr-theme",
|
||||
rootSelector = "html",
|
||||
label = "",
|
||||
compact = false,
|
||||
darkBgClass = ["wr-theme-dark-soft"],
|
||||
lightBgClass = ["wr-theme-light"],
|
||||
rootExtraClass = "",
|
||||
class: className,
|
||||
...rest
|
||||
}: 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 =
|
||||
"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:focus-visible:outline-none wr:focus-visible:ring-2 wr:focus-visible:ring-primary/40 wr:focus-visible:ring-offset-2";
|
||||
|
||||
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";
|
||||
|
||||
const icon = "wr:size-4";
|
||||
const checkBase = "wr:size-4 wr:opacity-0 wr:scale-90 wr:transition";
|
||||
---
|
||||
|
||||
<div
|
||||
class={["wr:flex wr:items-center wr:gap-3", className].join(" ")}
|
||||
{...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
|
||||
type="button"
|
||||
class={[iconBtn, compact && "wr:h-8 wr:w-8"]
|
||||
.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
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="wr:inline-flex wr:items-center wr:gap-2">
|
||||
<span data-current-icon="light" class="wr:inline">
|
||||
<Icon name="mdi:weather-sunny" class={icon} />
|
||||
</span>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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
|
||||
wr:origin-top-right wr:scale-95 wr:opacity-0 wr:transition wr:duration-150 wr:ease-out"
|
||||
role="menu"
|
||||
aria-label="Theme"
|
||||
data-menu-panel
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class={item}
|
||||
data-set-theme="light"
|
||||
>
|
||||
<Icon name="mdi:weather-sunny" class={icon} />
|
||||
<div class="wr:flex-1 wr:font-medium">Light</div>
|
||||
<Icon name="mdi:check" class={checkBase} data-check />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class={item}
|
||||
data-set-theme="dark"
|
||||
>
|
||||
<Icon name="mdi:weather-night" class={icon} />
|
||||
<div class="wr:flex-1 wr:font-medium">Dark</div>
|
||||
<Icon name="mdi:check" class={checkBase} data-check />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="menuitemradio"
|
||||
aria-checked="true"
|
||||
class={item}
|
||||
data-set-theme="system"
|
||||
>
|
||||
<Icon name="mdi:laptop" class={icon} />
|
||||
<div class="wr:flex-1 wr:font-medium">System</div>
|
||||
<Icon name="mdi:check" class={checkBase} data-check />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<script
|
||||
is:inline
|
||||
define:vars={{
|
||||
storageKey,
|
||||
rootSelector,
|
||||
darkBgClass,
|
||||
lightBgClass,
|
||||
rootExtraClass,
|
||||
}}
|
||||
>
|
||||
(() => {
|
||||
const root =
|
||||
rootSelector === "html"
|
||||
? document.documentElement
|
||||
: document.querySelector(rootSelector) ||
|
||||
document.documentElement;
|
||||
|
||||
const mql = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
let lastThemeClasses = [];
|
||||
|
||||
function setRootClasses(list) {
|
||||
lastThemeClasses.forEach((c) => c && root.classList.remove(c));
|
||||
list.filter(Boolean).forEach((c) => root.classList.add(c));
|
||||
lastThemeClasses = list.filter(Boolean);
|
||||
}
|
||||
|
||||
function reflectUI(mode) {
|
||||
const wrap = document.querySelector("[data-theme-switcher]");
|
||||
if (!wrap) return;
|
||||
|
||||
// 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) {
|
||||
const labelSpan = trig.querySelector(
|
||||
"[data-current-label]",
|
||||
);
|
||||
if (labelSpan)
|
||||
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:inline", is);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openMenu(wrap, open) {
|
||||
const trigger = wrap.querySelector("[data-menu-trigger]");
|
||||
const panel = wrap.querySelector("[data-menu-panel]");
|
||||
trigger?.setAttribute("aria-expanded", open ? "true" : "false");
|
||||
if (!panel) return;
|
||||
if (open) {
|
||||
panel.classList.remove("wr:hidden");
|
||||
requestAnimationFrame(() => {
|
||||
panel.classList.remove("wr:scale-95", "wr:opacity-0");
|
||||
panel.classList.add("wr:scale-100", "wr:opacity-100");
|
||||
});
|
||||
} else {
|
||||
panel.classList.add("wr:scale-95", "wr:opacity-0");
|
||||
setTimeout(() => panel.classList.add("wr:hidden"), 150);
|
||||
}
|
||||
}
|
||||
|
||||
function apply(mode) {
|
||||
const resolved =
|
||||
mode === "system" ? (mql.matches ? "dark" : "light") : mode;
|
||||
root.classList.toggle("dark", resolved === "dark");
|
||||
root.setAttribute("data-theme", resolved);
|
||||
document.documentElement.style.colorScheme = resolved;
|
||||
setRootClasses([
|
||||
rootExtraClass,
|
||||
...(resolved === "dark" ? darkBgClass : lightBgClass),
|
||||
]);
|
||||
localStorage.setItem(storageKey, mode);
|
||||
reflectUI(mode);
|
||||
}
|
||||
|
||||
const saved = localStorage.getItem(storageKey) || "system";
|
||||
apply(saved);
|
||||
|
||||
mql.addEventListener?.("change", () => {
|
||||
if ((localStorage.getItem(storageKey) || "system") === "system")
|
||||
apply("system");
|
||||
});
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
const menuWrap = e.target.closest?.("[data-theme-menu]");
|
||||
const trigger = e.target.closest?.("[data-menu-trigger]");
|
||||
const choose = e.target.closest?.("[data-set-theme]");
|
||||
|
||||
if (choose) {
|
||||
apply(choose.getAttribute("data-set-theme"));
|
||||
const wrap = choose.closest("[data-theme-menu]");
|
||||
if (wrap) openMenu(wrap, false);
|
||||
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));
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") {
|
||||
document
|
||||
.querySelectorAll("[data-theme-menu]")
|
||||
.forEach((w) => openMenu(w, false));
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style is:inline>
|
||||
/* Show the check icon ONLY on the active item in the menu */
|
||||
[data-theme-menu] [role="menuitemradio"][aria-checked="true"] [data-check] {
|
||||
opacity: 1 !important;
|
||||
transform: scale(1) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -5,7 +5,7 @@ import { cn } from "../utils/cn";
|
||||
const attributes = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="en" data-theme="" data-color="sunset">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
|
||||
+11
-1
@@ -1,14 +1,24 @@
|
||||
---
|
||||
import Base from "../layouts/Base.astro";
|
||||
import Button from "../components/Button.astro";
|
||||
import ThemeSwitcher from "../components/ThemeSwitcher.astro";
|
||||
import ColorSwitcher from "../components/ColorSwitcher.astro";
|
||||
---
|
||||
|
||||
<Base>
|
||||
<ThemeSwitcher
|
||||
ui="select"
|
||||
darkBgClass={["wr:dark:bg-neutral-700", "wr:text-white"]}
|
||||
lightBgClass={["wr:bg-white", "wr:text-black"]}
|
||||
rootExtraClass=""
|
||||
/>
|
||||
<ColorSwitcher />
|
||||
|
||||
<div class="wr:p-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
icon="lets-icons:check-ring-duotone-line"
|
||||
color="danger"
|
||||
color="primary"
|
||||
size="md"
|
||||
>
|
||||
Submit
|
||||
|
||||
@@ -14,5 +14,8 @@ import Base from "../layouts/Base.astro";
|
||||
<li class="list-item">
|
||||
<a href="/forms">Forms</a>
|
||||
</li>
|
||||
<li class="list-item">
|
||||
<a href="/theme">Theme</a>
|
||||
</li>
|
||||
</ul>
|
||||
</Base>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
import ThemeSwitcher from "../components/ThemeSwitcher.astro";
|
||||
import ColorSwitcher from "../components/ColorSwitcher.astro";
|
||||
import Base from "../layouts/Base.astro";
|
||||
---
|
||||
|
||||
<Base>
|
||||
<div
|
||||
class="wrnx wr:flex wr:items-center wr:flex-col wr:gap-4 wr:w-full wr:h-screen"
|
||||
>
|
||||
<ThemeSwitcher
|
||||
ui="select"
|
||||
darkBgClass={["wr:dark:bg-neutral-700", "wr:text-white"]}
|
||||
lightBgClass={["wr:bg-white", "wr:text-black"]}
|
||||
ui="menu"
|
||||
rootExtraClass=""
|
||||
/>
|
||||
<ColorSwitcher ui="menu" trigger="dot" />
|
||||
|
||||
<div>
|
||||
<h1 class="wr:text-primary">Test Heading</h1>
|
||||
</div>
|
||||
</div>
|
||||
</Base>
|
||||
Reference in New Issue
Block a user