Added more themes
This commit is contained in:
parent
cfd88b43c3
commit
5e23a984e1
436
app/globals.css
436
app/globals.css
|
|
@ -5,8 +5,9 @@
|
||||||
@import "@uiw/react-markdown-preview/markdown.css";
|
@import "@uiw/react-markdown-preview/markdown.css";
|
||||||
|
|
||||||
/* "dark:" must be true for every dark-surfaced theme, not just .dark --
|
/* "dark:" must be true for every dark-surfaced theme, not just .dark --
|
||||||
Ocean is a second dark look, so both classes turn on dark utilities. */
|
the dark looks (Dark, Ocean, Pine, Plum, Midnight, Ember, Rosewood) all
|
||||||
@custom-variant dark (&:is(.dark *, .ocean *));
|
turn on dark utilities. */
|
||||||
|
@custom-variant dark (&:is(.dark *, .ocean *, .theme-pine *, .theme-plum *, .theme-midnight *, .theme-ember *, .theme-rosewood *));
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--color-background: var(--background);
|
--color-background: var(--background);
|
||||||
|
|
@ -58,13 +59,25 @@
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
/* ---------------------------------------------------------------------------
|
||||||
Themes
|
Themes
|
||||||
The app ships four looks, chosen via the theme picker (next-themes):
|
The app ships fourteen looks, chosen via the theme picker:
|
||||||
default -- calm, cool neutral light theme (the safe, focused one)
|
Light:
|
||||||
sunset -- warm paper + persimmon accent, a cozier light theme
|
default -- calm, cool neutral light theme (the safe, focused one)
|
||||||
dark -- deep charcoal with an indigo accent
|
sunset -- warm paper + persimmon accent, a cozier light theme
|
||||||
ocean -- deep blue-green night theme with a seafoam accent
|
meadow -- fresh green paper with an emerald accent
|
||||||
|
honey -- buttercream paper with a honey-amber accent
|
||||||
|
rose -- soft pink paper with a raspberry accent ("Ros\xe9")
|
||||||
|
lavender -- lilac paper with a violet accent
|
||||||
|
slate -- cool grey-blue paper with a steel-blue accent
|
||||||
|
Dark:
|
||||||
|
dark -- deep charcoal with an indigo accent
|
||||||
|
ocean -- deep blue-green night theme with a seafoam accent
|
||||||
|
pine -- deep forest green with a mint accent
|
||||||
|
plum -- deep plum night with an orchid accent
|
||||||
|
midnight -- near-black navy with an ice-periwinkle accent
|
||||||
|
ember -- warm hearth charcoal with a glowing amber accent
|
||||||
|
rosewood -- deep wine with a rose accent
|
||||||
Each is a complete token set; components only ever use tokens, so adding
|
Each is a complete token set; components only ever use tokens, so adding
|
||||||
a fifth theme later is just another block like these.
|
another theme later is just another block like these.
|
||||||
--------------------------------------------------------------------------- */
|
--------------------------------------------------------------------------- */
|
||||||
|
|
||||||
:root,
|
:root,
|
||||||
|
|
@ -225,6 +238,413 @@
|
||||||
--sidebar-ring: oklch(0.72 0.11 192);
|
--sidebar-ring: oklch(0.72 0.11 192);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Meadow -- fresh green paper + emerald accent: a spring morning on the desk.
|
||||||
|
Light-surfaced; pairs with the neutral group palette as well as anything.
|
||||||
|
*/
|
||||||
|
.theme-meadow {
|
||||||
|
color-scheme: light;
|
||||||
|
--background: oklch(0.975 0.012 145);
|
||||||
|
--foreground: oklch(0.26 0.03 150);
|
||||||
|
--card: oklch(0.99 0.006 145);
|
||||||
|
--card-foreground: oklch(0.26 0.03 150);
|
||||||
|
--popover: oklch(0.992 0.005 145);
|
||||||
|
--popover-foreground: oklch(0.26 0.03 150);
|
||||||
|
/* Emerald -- leafy enough to feel grown, saturated enough to lead. */
|
||||||
|
--primary: oklch(0.55 0.16 155);
|
||||||
|
--primary-foreground: oklch(0.98 0.01 145);
|
||||||
|
--secondary: oklch(0.945 0.018 145);
|
||||||
|
--secondary-foreground: oklch(0.31 0.05 150);
|
||||||
|
--muted: oklch(0.938 0.016 145);
|
||||||
|
--muted-foreground: oklch(0.53 0.04 150);
|
||||||
|
--accent: oklch(0.925 0.03 150);
|
||||||
|
--accent-foreground: oklch(0.35 0.1 155);
|
||||||
|
--destructive: oklch(0.57 0.21 25);
|
||||||
|
--border: oklch(0.89 0.018 145);
|
||||||
|
--input: oklch(0.86 0.02 145);
|
||||||
|
--ring: oklch(0.57 0.14 155);
|
||||||
|
--lane: oklch(0.94 0.015 145);
|
||||||
|
--chart-1: oklch(0.8 0.08 145);
|
||||||
|
--chart-2: oklch(0.68 0.11 152);
|
||||||
|
--chart-3: oklch(0.55 0.12 158);
|
||||||
|
--chart-4: oklch(0.44 0.1 152);
|
||||||
|
--chart-5: oklch(0.33 0.08 148);
|
||||||
|
--radius: 0.8rem;
|
||||||
|
--sidebar: oklch(0.965 0.01 145);
|
||||||
|
--sidebar-foreground: oklch(0.26 0.03 150);
|
||||||
|
--sidebar-primary: oklch(0.55 0.16 155);
|
||||||
|
--sidebar-primary-foreground: oklch(0.98 0.01 145);
|
||||||
|
--sidebar-accent: oklch(0.925 0.03 150);
|
||||||
|
--sidebar-accent-foreground: oklch(0.35 0.1 155);
|
||||||
|
--sidebar-border: oklch(0.89 0.018 145);
|
||||||
|
--sidebar-ring: oklch(0.57 0.14 155);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Honey -- buttercream paper + honey-amber accent: golden hour, mellowed.
|
||||||
|
Light-surfaced; warmer than Sunset's terracotta, softer and yellower. */
|
||||||
|
.theme-honey {
|
||||||
|
color-scheme: light;
|
||||||
|
--background: oklch(0.972 0.024 95);
|
||||||
|
--foreground: oklch(0.28 0.04 80);
|
||||||
|
--card: oklch(0.988 0.015 95);
|
||||||
|
--card-foreground: oklch(0.28 0.04 80);
|
||||||
|
--popover: oklch(0.99 0.014 95);
|
||||||
|
--popover-foreground: oklch(0.28 0.04 80);
|
||||||
|
/* Honey-amber -- glowing but gentle, like light through glass. */
|
||||||
|
--primary: oklch(0.6 0.16 70);
|
||||||
|
--primary-foreground: oklch(0.98 0.015 90);
|
||||||
|
--secondary: oklch(0.948 0.03 92);
|
||||||
|
--secondary-foreground: oklch(0.33 0.06 78);
|
||||||
|
--muted: oklch(0.94 0.028 92);
|
||||||
|
--muted-foreground: oklch(0.55 0.05 82);
|
||||||
|
--accent: oklch(0.93 0.04 88);
|
||||||
|
--accent-foreground: oklch(0.37 0.11 75);
|
||||||
|
--destructive: oklch(0.57 0.21 25);
|
||||||
|
--border: oklch(0.885 0.03 90);
|
||||||
|
--input: oklch(0.855 0.032 90);
|
||||||
|
--ring: oklch(0.62 0.14 78);
|
||||||
|
--lane: oklch(0.942 0.026 92);
|
||||||
|
--chart-1: oklch(0.82 0.1 85);
|
||||||
|
--chart-2: oklch(0.7 0.13 78);
|
||||||
|
--chart-3: oklch(0.58 0.14 72);
|
||||||
|
--chart-4: oklch(0.47 0.12 75);
|
||||||
|
--chart-5: oklch(0.36 0.09 80);
|
||||||
|
/* A touch softer than default -- honey is meant to feel mellow. */
|
||||||
|
--radius: 0.85rem;
|
||||||
|
--sidebar: oklch(0.962 0.02 95);
|
||||||
|
--sidebar-foreground: oklch(0.28 0.04 80);
|
||||||
|
--sidebar-primary: oklch(0.6 0.16 70);
|
||||||
|
--sidebar-primary-foreground: oklch(0.98 0.015 90);
|
||||||
|
--sidebar-accent: oklch(0.93 0.04 88);
|
||||||
|
--sidebar-accent-foreground: oklch(0.37 0.11 75);
|
||||||
|
--sidebar-border: oklch(0.885 0.03 90);
|
||||||
|
--sidebar-ring: oklch(0.62 0.14 78);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ros\xe9 -- soft pink paper + raspberry accent: a blush, not a ballroom.
|
||||||
|
Light-surfaced. */
|
||||||
|
.theme-rose {
|
||||||
|
color-scheme: light;
|
||||||
|
--background: oklch(0.972 0.014 15);
|
||||||
|
--foreground: oklch(0.27 0.035 15);
|
||||||
|
--card: oklch(0.988 0.008 18);
|
||||||
|
--card-foreground: oklch(0.27 0.035 15);
|
||||||
|
--popover: oklch(0.99 0.008 18);
|
||||||
|
--popover-foreground: oklch(0.27 0.035 15);
|
||||||
|
/* Raspberry -- enough punch to be the accent, sweet enough to sit
|
||||||
|
comfortably inside the pink paper. */
|
||||||
|
--primary: oklch(0.55 0.19 12);
|
||||||
|
--primary-foreground: oklch(0.98 0.01 20);
|
||||||
|
--secondary: oklch(0.948 0.02 15);
|
||||||
|
--secondary-foreground: oklch(0.32 0.06 12);
|
||||||
|
--muted: oklch(0.94 0.018 15);
|
||||||
|
--muted-foreground: oklch(0.54 0.045 12);
|
||||||
|
--accent: oklch(0.93 0.032 14);
|
||||||
|
--accent-foreground: oklch(0.36 0.11 12);
|
||||||
|
/* A deeper red than the rest of the pink, so errors still read as errors. */
|
||||||
|
--destructive: oklch(0.55 0.22 20);
|
||||||
|
--border: oklch(0.888 0.022 15);
|
||||||
|
--input: oklch(0.86 0.024 15);
|
||||||
|
--ring: oklch(0.58 0.16 14);
|
||||||
|
--lane: oklch(0.942 0.02 15);
|
||||||
|
--chart-1: oklch(0.8 0.07 20);
|
||||||
|
--chart-2: oklch(0.68 0.11 15);
|
||||||
|
--chart-3: oklch(0.56 0.13 12);
|
||||||
|
--chart-4: oklch(0.45 0.12 10);
|
||||||
|
--chart-5: oklch(0.34 0.09 15);
|
||||||
|
/* Softer corners -- the most romantic look in the set. */
|
||||||
|
--radius: 0.9rem;
|
||||||
|
--sidebar: oklch(0.962 0.012 15);
|
||||||
|
--sidebar-foreground: oklch(0.27 0.035 15);
|
||||||
|
--sidebar-primary: oklch(0.55 0.19 12);
|
||||||
|
--sidebar-primary-foreground: oklch(0.98 0.01 20);
|
||||||
|
--sidebar-accent: oklch(0.93 0.032 14);
|
||||||
|
--sidebar-accent-foreground: oklch(0.36 0.11 12);
|
||||||
|
--sidebar-border: oklch(0.888 0.022 15);
|
||||||
|
--sidebar-ring: oklch(0.58 0.16 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lavender -- lilac paper + violet accent: dusk without the dark.
|
||||||
|
Light-surfaced. */
|
||||||
|
.theme-lavender {
|
||||||
|
color-scheme: light;
|
||||||
|
--background: oklch(0.972 0.012 300);
|
||||||
|
--foreground: oklch(0.27 0.035 305);
|
||||||
|
--card: oklch(0.988 0.008 300);
|
||||||
|
--card-foreground: oklch(0.27 0.035 305);
|
||||||
|
--popover: oklch(0.99 0.008 300);
|
||||||
|
--popover-foreground: oklch(0.27 0.035 305);
|
||||||
|
/* Violet -- a little more saturated than the paper's tint so it leads. */
|
||||||
|
--primary: oklch(0.53 0.19 300);
|
||||||
|
--primary-foreground: oklch(0.98 0.01 300);
|
||||||
|
--secondary: oklch(0.948 0.018 300);
|
||||||
|
--secondary-foreground: oklch(0.32 0.06 305);
|
||||||
|
--muted: oklch(0.94 0.016 300);
|
||||||
|
--muted-foreground: oklch(0.54 0.045 305);
|
||||||
|
--accent: oklch(0.93 0.03 302);
|
||||||
|
--accent-foreground: oklch(0.36 0.11 305);
|
||||||
|
--destructive: oklch(0.57 0.21 25);
|
||||||
|
--border: oklch(0.888 0.018 300);
|
||||||
|
--input: oklch(0.86 0.02 300);
|
||||||
|
--ring: oklch(0.56 0.16 300);
|
||||||
|
--lane: oklch(0.942 0.015 300);
|
||||||
|
--chart-1: oklch(0.8 0.07 300);
|
||||||
|
--chart-2: oklch(0.68 0.11 302);
|
||||||
|
--chart-3: oklch(0.56 0.13 305);
|
||||||
|
--chart-4: oklch(0.45 0.12 308);
|
||||||
|
--chart-5: oklch(0.34 0.09 305);
|
||||||
|
--radius: 0.85rem;
|
||||||
|
--sidebar: oklch(0.962 0.012 300);
|
||||||
|
--sidebar-foreground: oklch(0.27 0.035 305);
|
||||||
|
--sidebar-primary: oklch(0.53 0.19 300);
|
||||||
|
--sidebar-primary-foreground: oklch(0.98 0.01 300);
|
||||||
|
--sidebar-accent: oklch(0.93 0.03 302);
|
||||||
|
--sidebar-accent-foreground: oklch(0.36 0.11 305);
|
||||||
|
--sidebar-border: oklch(0.888 0.018 300);
|
||||||
|
--sidebar-ring: oklch(0.56 0.16 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slate -- cool grey-blue paper + steel-blue accent: the "work mode" look.
|
||||||
|
Light-surfaced; the crispest corners in the set. */
|
||||||
|
.theme-slate {
|
||||||
|
color-scheme: light;
|
||||||
|
--background: oklch(0.968 0.006 240);
|
||||||
|
--foreground: oklch(0.25 0.02 245);
|
||||||
|
--card: oklch(0.985 0.004 240);
|
||||||
|
--card-foreground: oklch(0.25 0.02 245);
|
||||||
|
--popover: oklch(0.988 0.004 240);
|
||||||
|
--popover-foreground: oklch(0.25 0.02 245);
|
||||||
|
/* Steel blue -- calmer than Default's indigo, quieter by design. */
|
||||||
|
--primary: oklch(0.52 0.11 240);
|
||||||
|
--primary-foreground: oklch(0.98 0.005 240);
|
||||||
|
--secondary: oklch(0.945 0.008 240);
|
||||||
|
--secondary-foreground: oklch(0.31 0.03 245);
|
||||||
|
--muted: oklch(0.938 0.008 240);
|
||||||
|
--muted-foreground: oklch(0.52 0.02 245);
|
||||||
|
--accent: oklch(0.925 0.014 242);
|
||||||
|
--accent-foreground: oklch(0.35 0.08 245);
|
||||||
|
--destructive: oklch(0.57 0.2 25);
|
||||||
|
--border: oklch(0.895 0.006 240);
|
||||||
|
--input: oklch(0.875 0.006 240);
|
||||||
|
--ring: oklch(0.55 0.09 240);
|
||||||
|
--lane: oklch(0.94 0.007 240);
|
||||||
|
--chart-1: oklch(0.82 0.04 240);
|
||||||
|
--chart-2: oklch(0.7 0.06 242);
|
||||||
|
--chart-3: oklch(0.58 0.08 244);
|
||||||
|
--chart-4: oklch(0.47 0.07 246);
|
||||||
|
--chart-5: oklch(0.36 0.05 248);
|
||||||
|
/* Crisper corners -- the most "engineered" look in the set. */
|
||||||
|
--radius: 0.7rem;
|
||||||
|
--sidebar: oklch(0.958 0.005 240);
|
||||||
|
--sidebar-foreground: oklch(0.25 0.02 245);
|
||||||
|
--sidebar-primary: oklch(0.52 0.11 240);
|
||||||
|
--sidebar-primary-foreground: oklch(0.98 0.005 240);
|
||||||
|
--sidebar-accent: oklch(0.925 0.014 242);
|
||||||
|
--sidebar-accent-foreground: oklch(0.35 0.08 245);
|
||||||
|
--sidebar-border: oklch(0.895 0.006 240);
|
||||||
|
--sidebar-ring: oklch(0.55 0.09 240);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pine -- deep forest green night + mint accent: the woods after rain.
|
||||||
|
Dark-surfaced (see the `dark` custom-variant above). */
|
||||||
|
.theme-pine {
|
||||||
|
color-scheme: dark;
|
||||||
|
--background: oklch(0.2 0.035 160);
|
||||||
|
--foreground: oklch(0.92 0.02 150);
|
||||||
|
--card: oklch(0.245 0.04 158);
|
||||||
|
--card-foreground: oklch(0.92 0.02 150);
|
||||||
|
--popover: oklch(0.255 0.042 156);
|
||||||
|
--popover-foreground: oklch(0.92 0.02 150);
|
||||||
|
/* Mint -- luminous against the deep green, without Ocean's cool cast. */
|
||||||
|
--primary: oklch(0.76 0.12 165);
|
||||||
|
--primary-foreground: oklch(0.2 0.05 160);
|
||||||
|
--secondary: oklch(0.295 0.042 158);
|
||||||
|
--secondary-foreground: oklch(0.92 0.02 150);
|
||||||
|
--muted: oklch(0.285 0.04 158);
|
||||||
|
--muted-foreground: oklch(0.7 0.03 150);
|
||||||
|
--accent: oklch(0.33 0.05 160);
|
||||||
|
--accent-foreground: oklch(0.9 0.03 165);
|
||||||
|
--destructive: oklch(0.7 0.17 25);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--input: oklch(1 0 0 / 14%);
|
||||||
|
--ring: oklch(0.72 0.1 165);
|
||||||
|
--lane: oklch(1 0 0 / 6%);
|
||||||
|
--chart-1: oklch(0.76 0.1 165);
|
||||||
|
--chart-2: oklch(0.63 0.11 160);
|
||||||
|
--chart-3: oklch(0.5 0.11 155);
|
||||||
|
--chart-4: oklch(0.4 0.09 150);
|
||||||
|
--chart-5: oklch(0.3 0.07 150);
|
||||||
|
--radius: 0.8rem;
|
||||||
|
--sidebar: oklch(0.225 0.04 158);
|
||||||
|
--sidebar-foreground: oklch(0.92 0.02 150);
|
||||||
|
--sidebar-primary: oklch(0.76 0.12 165);
|
||||||
|
--sidebar-primary-foreground: oklch(0.2 0.05 160);
|
||||||
|
--sidebar-accent: oklch(0.33 0.05 160);
|
||||||
|
--sidebar-accent-foreground: oklch(0.9 0.03 165);
|
||||||
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
|
--sidebar-ring: oklch(0.72 0.1 165);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plum -- deep plum night + orchid accent: the richest dark in the set.
|
||||||
|
Dark-surfaced. */
|
||||||
|
.theme-plum {
|
||||||
|
color-scheme: dark;
|
||||||
|
--background: oklch(0.2 0.03 315);
|
||||||
|
--foreground: oklch(0.92 0.02 320);
|
||||||
|
--card: oklch(0.245 0.035 315);
|
||||||
|
--card-foreground: oklch(0.92 0.02 320);
|
||||||
|
--popover: oklch(0.255 0.038 315);
|
||||||
|
--popover-foreground: oklch(0.92 0.02 320);
|
||||||
|
/* Orchid -- bright enough to lead the eye, deep enough to stay nocturnal. */
|
||||||
|
--primary: oklch(0.74 0.13 320);
|
||||||
|
--primary-foreground: oklch(0.2 0.05 315);
|
||||||
|
--secondary: oklch(0.295 0.04 315);
|
||||||
|
--secondary-foreground: oklch(0.92 0.02 320);
|
||||||
|
--muted: oklch(0.285 0.04 315);
|
||||||
|
--muted-foreground: oklch(0.7 0.03 320);
|
||||||
|
--accent: oklch(0.33 0.05 318);
|
||||||
|
--accent-foreground: oklch(0.9 0.03 322);
|
||||||
|
--destructive: oklch(0.7 0.17 25);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--input: oklch(1 0 0 / 14%);
|
||||||
|
--ring: oklch(0.7 0.12 320);
|
||||||
|
--lane: oklch(1 0 0 / 6%);
|
||||||
|
--chart-1: oklch(0.76 0.1 320);
|
||||||
|
--chart-2: oklch(0.63 0.11 318);
|
||||||
|
--chart-3: oklch(0.5 0.11 315);
|
||||||
|
--chart-4: oklch(0.4 0.1 312);
|
||||||
|
--chart-5: oklch(0.3 0.08 315);
|
||||||
|
--radius: 0.8rem;
|
||||||
|
--sidebar: oklch(0.225 0.035 315);
|
||||||
|
--sidebar-foreground: oklch(0.92 0.02 320);
|
||||||
|
--sidebar-primary: oklch(0.74 0.13 320);
|
||||||
|
--sidebar-primary-foreground: oklch(0.2 0.05 315);
|
||||||
|
--sidebar-accent: oklch(0.33 0.05 318);
|
||||||
|
--sidebar-accent-foreground: oklch(0.9 0.03 322);
|
||||||
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
|
--sidebar-ring: oklch(0.7 0.12 320);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Midnight -- the darkest surface in the set, near-black navy, with an
|
||||||
|
ice-periwinkle accent: a clear winter night.
|
||||||
|
Dark-surfaced. */
|
||||||
|
.theme-midnight {
|
||||||
|
color-scheme: dark;
|
||||||
|
--background: oklch(0.16 0.02 262);
|
||||||
|
--foreground: oklch(0.93 0.01 258);
|
||||||
|
--card: oklch(0.2 0.022 260);
|
||||||
|
--card-foreground: oklch(0.93 0.01 258);
|
||||||
|
--popover: oklch(0.21 0.024 260);
|
||||||
|
--popover-foreground: oklch(0.93 0.01 258);
|
||||||
|
/* Ice periwinkle -- pale and cool, a thin light against the black. */
|
||||||
|
--primary: oklch(0.76 0.09 255);
|
||||||
|
--primary-foreground: oklch(0.17 0.03 260);
|
||||||
|
--secondary: oklch(0.255 0.024 262);
|
||||||
|
--secondary-foreground: oklch(0.93 0.01 258);
|
||||||
|
--muted: oklch(0.245 0.024 262);
|
||||||
|
--muted-foreground: oklch(0.7 0.015 258);
|
||||||
|
--accent: oklch(0.29 0.035 260);
|
||||||
|
--accent-foreground: oklch(0.9 0.02 255);
|
||||||
|
--destructive: oklch(0.7 0.17 25);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--input: oklch(1 0 0 / 14%);
|
||||||
|
--ring: oklch(0.74 0.08 255);
|
||||||
|
--lane: oklch(1 0 0 / 6%);
|
||||||
|
--chart-1: oklch(0.78 0.07 255);
|
||||||
|
--chart-2: oklch(0.66 0.08 258);
|
||||||
|
--chart-3: oklch(0.54 0.09 260);
|
||||||
|
--chart-4: oklch(0.44 0.08 262);
|
||||||
|
--chart-5: oklch(0.34 0.06 265);
|
||||||
|
--radius: 0.75rem;
|
||||||
|
--sidebar: oklch(0.185 0.022 262);
|
||||||
|
--sidebar-foreground: oklch(0.93 0.01 258);
|
||||||
|
--sidebar-primary: oklch(0.76 0.09 255);
|
||||||
|
--sidebar-primary-foreground: oklch(0.17 0.03 260);
|
||||||
|
--sidebar-accent: oklch(0.29 0.035 260);
|
||||||
|
--sidebar-accent-foreground: oklch(0.9 0.02 255);
|
||||||
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
|
--sidebar-ring: oklch(0.74 0.08 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ember -- warm hearth charcoal + glowing amber accent: Sunset's dark twin.
|
||||||
|
Dark-surfaced. */
|
||||||
|
.theme-ember {
|
||||||
|
color-scheme: dark;
|
||||||
|
--background: oklch(0.2 0.025 45);
|
||||||
|
--foreground: oklch(0.92 0.015 60);
|
||||||
|
--card: oklch(0.245 0.028 48);
|
||||||
|
--card-foreground: oklch(0.92 0.015 60);
|
||||||
|
--popover: oklch(0.255 0.03 48);
|
||||||
|
--popover-foreground: oklch(0.92 0.015 60);
|
||||||
|
/* Ember amber -- warm glow, like coals catching the evening. */
|
||||||
|
--primary: oklch(0.72 0.14 62);
|
||||||
|
--primary-foreground: oklch(0.2 0.04 50);
|
||||||
|
--secondary: oklch(0.295 0.03 50);
|
||||||
|
--secondary-foreground: oklch(0.92 0.015 60);
|
||||||
|
--muted: oklch(0.285 0.03 50);
|
||||||
|
--muted-foreground: oklch(0.7 0.025 55);
|
||||||
|
--accent: oklch(0.33 0.04 52);
|
||||||
|
--accent-foreground: oklch(0.9 0.03 62);
|
||||||
|
--destructive: oklch(0.7 0.17 25);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--input: oklch(1 0 0 / 14%);
|
||||||
|
--ring: oklch(0.7 0.12 62);
|
||||||
|
--lane: oklch(1 0 0 / 6%);
|
||||||
|
--chart-1: oklch(0.78 0.1 58);
|
||||||
|
--chart-2: oklch(0.66 0.12 55);
|
||||||
|
--chart-3: oklch(0.54 0.12 52);
|
||||||
|
--chart-4: oklch(0.44 0.1 50);
|
||||||
|
--chart-5: oklch(0.34 0.08 52);
|
||||||
|
--radius: 0.85rem;
|
||||||
|
--sidebar: oklch(0.225 0.028 48);
|
||||||
|
--sidebar-foreground: oklch(0.92 0.015 60);
|
||||||
|
--sidebar-primary: oklch(0.72 0.14 62);
|
||||||
|
--sidebar-primary-foreground: oklch(0.2 0.04 50);
|
||||||
|
--sidebar-accent: oklch(0.33 0.04 52);
|
||||||
|
--sidebar-accent-foreground: oklch(0.9 0.03 62);
|
||||||
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
|
--sidebar-ring: oklch(0.7 0.12 62);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rosewood -- deep wine + rose accent: Ros\xe9's dark twin.
|
||||||
|
Dark-surfaced. */
|
||||||
|
.theme-rosewood {
|
||||||
|
color-scheme: dark;
|
||||||
|
--background: oklch(0.2 0.04 18);
|
||||||
|
--foreground: oklch(0.92 0.02 20);
|
||||||
|
--card: oklch(0.245 0.045 18);
|
||||||
|
--card-foreground: oklch(0.92 0.02 20);
|
||||||
|
--popover: oklch(0.255 0.048 18);
|
||||||
|
--popover-foreground: oklch(0.92 0.02 20);
|
||||||
|
/* Rose -- bright enough to lead, warm enough to stay in the wine. */
|
||||||
|
--primary: oklch(0.74 0.13 20);
|
||||||
|
--primary-foreground: oklch(0.2 0.05 15);
|
||||||
|
--secondary: oklch(0.295 0.045 18);
|
||||||
|
--secondary-foreground: oklch(0.92 0.02 20);
|
||||||
|
--muted: oklch(0.285 0.045 18);
|
||||||
|
--muted-foreground: oklch(0.7 0.03 20);
|
||||||
|
--accent: oklch(0.33 0.055 18);
|
||||||
|
--accent-foreground: oklch(0.9 0.03 22);
|
||||||
|
--destructive: oklch(0.7 0.17 25);
|
||||||
|
--border: oklch(1 0 0 / 10%);
|
||||||
|
--input: oklch(1 0 0 / 14%);
|
||||||
|
--ring: oklch(0.72 0.11 20);
|
||||||
|
--lane: oklch(1 0 0 / 6%);
|
||||||
|
--chart-1: oklch(0.78 0.09 20);
|
||||||
|
--chart-2: oklch(0.66 0.11 18);
|
||||||
|
--chart-3: oklch(0.54 0.12 16);
|
||||||
|
--chart-4: oklch(0.44 0.1 15);
|
||||||
|
--chart-5: oklch(0.34 0.08 18);
|
||||||
|
--radius: 0.85rem;
|
||||||
|
--sidebar: oklch(0.225 0.045 18);
|
||||||
|
--sidebar-foreground: oklch(0.92 0.02 20);
|
||||||
|
--sidebar-primary: oklch(0.74 0.13 20);
|
||||||
|
--sidebar-primary-foreground: oklch(0.2 0.05 15);
|
||||||
|
--sidebar-accent: oklch(0.33 0.055 18);
|
||||||
|
--sidebar-accent-foreground: oklch(0.9 0.03 22);
|
||||||
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
|
--sidebar-ring: oklch(0.72 0.11 20);
|
||||||
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
|
|
|
||||||
|
|
@ -57,14 +57,15 @@ export default function RootLayout({ children }: LayoutProps<"/">) {
|
||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
>
|
>
|
||||||
<body className="min-h-full flex flex-col">
|
<body className="min-h-full flex flex-col">
|
||||||
{/* Four looks: Default (calm light), Sunset (warm light), Dark, Ocean
|
{/* Fourteen looks: seven light (Default, Sunset, Meadow, Honey,
|
||||||
(deep blue-green) -- complete token sets in app/globals.css.
|
Rosé, Lavender, Slate) and seven dark (Dark, Ocean, Pine, Plum,
|
||||||
"system" resolves to Default/Dark by OS preference.
|
Midnight, Ember, Rosewood) -- complete token sets in
|
||||||
No-FOUC theme restore: runs before first paint, applies the
|
app/globals.css. "system" resolves to Default/Dark by OS
|
||||||
stored preference to <html> (class + color-scheme). */}
|
preference. No-FOUC theme restore: runs before first paint,
|
||||||
|
applies the stored preference to <html> (class + color-scheme). */}
|
||||||
<script
|
<script
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `!(function(){try{var r=document.documentElement,c=['theme-default','theme-sunset','dark','ocean','light'];for(var i=0;i<c.length;i++){r.classList.remove(c[i]);}var v=null;try{v=localStorage.getItem('theme');}catch(e){}var m={default:'theme-default',sunset:'theme-sunset',dark:'dark',ocean:'ocean'};if(v==='system'||!v){v=window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'default';}if(m[v]){r.classList.add(m[v]);r.style.colorScheme=(v==='dark'||v==='ocean')?'dark':'light';}}catch(e){}})();`,
|
__html: `!(function(){try{var r=document.documentElement,c=['theme-default','theme-sunset','theme-meadow','theme-honey','theme-rose','theme-lavender','theme-slate','dark','ocean','theme-pine','theme-plum','theme-midnight','theme-ember','theme-rosewood','light'];for(var i=0;i<c.length;i++){r.classList.remove(c[i]);}var v=null;try{v=localStorage.getItem('theme');}catch(e){}var m={default:'theme-default',sunset:'theme-sunset',meadow:'theme-meadow',honey:'theme-honey',rose:'theme-rose',lavender:'theme-lavender',slate:'theme-slate',dark:'dark',ocean:'ocean',pine:'theme-pine',plum:'theme-plum',midnight:'theme-midnight',ember:'theme-ember',rosewood:'theme-rosewood'};var d=['dark','ocean','pine','plum','midnight','ember','rosewood'];if(v==='system'||!v){v=window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'default';}if(m[v]){r.classList.add(m[v]);r.style.colorScheme=d.indexOf(v)>=0?'dark':'light';}}catch(e){}})();`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ThemeProvider defaultTheme="system">
|
<ThemeProvider defaultTheme="system">
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,29 @@ import type { ReactNode } from "react";
|
||||||
* it renders as plain server HTML and actually executes.
|
* it renders as plain server HTML and actually executes.
|
||||||
*
|
*
|
||||||
* Semantics (kept compatible for all existing consumers):
|
* Semantics (kept compatible for all existing consumers):
|
||||||
* - `theme` the stored preference: default | sunset | dark | ocean | system
|
* - `theme` the stored preference: one of THEMES, or "system"
|
||||||
* - `resolvedTheme` the theme actually applied (system resolved via
|
* - `resolvedTheme` the theme actually applied (system resolved via
|
||||||
* prefers-color-scheme), always one of the four
|
* prefers-color-scheme), always one of THEMES
|
||||||
* - html classes .theme-default | .theme-sunset | .dark | .ocean (see globals.css)
|
* - html classes .theme-default | .theme-sunset | ... (see globals.css)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const THEMES = ["default", "sunset", "dark", "ocean"] as const;
|
/* Order is the picker order: all the light looks, then the dark looks. */
|
||||||
|
export const THEMES = [
|
||||||
|
"default",
|
||||||
|
"sunset",
|
||||||
|
"meadow",
|
||||||
|
"honey",
|
||||||
|
"rose",
|
||||||
|
"lavender",
|
||||||
|
"slate",
|
||||||
|
"dark",
|
||||||
|
"ocean",
|
||||||
|
"pine",
|
||||||
|
"plum",
|
||||||
|
"midnight",
|
||||||
|
"ember",
|
||||||
|
"rosewood",
|
||||||
|
] as const;
|
||||||
export type ThemeName = (typeof THEMES)[number];
|
export type ThemeName = (typeof THEMES)[number];
|
||||||
export type RawTheme = ThemeName | "system";
|
export type RawTheme = ThemeName | "system";
|
||||||
|
|
||||||
|
|
@ -36,11 +52,32 @@ const STORAGE_KEY = "theme";
|
||||||
const THEME_CLASSES: Record<ThemeName, string> = {
|
const THEME_CLASSES: Record<ThemeName, string> = {
|
||||||
default: "theme-default",
|
default: "theme-default",
|
||||||
sunset: "theme-sunset",
|
sunset: "theme-sunset",
|
||||||
|
meadow: "theme-meadow",
|
||||||
|
honey: "theme-honey",
|
||||||
|
rose: "theme-rose",
|
||||||
|
lavender: "theme-lavender",
|
||||||
|
slate: "theme-slate",
|
||||||
dark: "dark",
|
dark: "dark",
|
||||||
ocean: "ocean",
|
ocean: "ocean",
|
||||||
|
pine: "theme-pine",
|
||||||
|
plum: "theme-plum",
|
||||||
|
midnight: "theme-midnight",
|
||||||
|
ember: "theme-ember",
|
||||||
|
rosewood: "theme-rosewood",
|
||||||
};
|
};
|
||||||
const ALL_CLASSES = Object.values(THEME_CLASSES);
|
const ALL_CLASSES = Object.values(THEME_CLASSES);
|
||||||
const DARK_SURFACES: ThemeName[] = ["dark", "ocean"];
|
/* Themes whose *surfaces* are dark (see app/globals.css). The single source
|
||||||
|
* of truth for every dark-surface check: isDarkTheme, the sonner toaster,
|
||||||
|
* color-scheme, and the `dark:` custom-variant in globals.css. */
|
||||||
|
export const DARK_SURFACES: ThemeName[] = [
|
||||||
|
"dark",
|
||||||
|
"ocean",
|
||||||
|
"pine",
|
||||||
|
"plum",
|
||||||
|
"midnight",
|
||||||
|
"ember",
|
||||||
|
"rosewood",
|
||||||
|
];
|
||||||
|
|
||||||
interface ThemeContextValue {
|
interface ThemeContextValue {
|
||||||
theme: RawTheme;
|
theme: RawTheme;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,27 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useTheme } from "@/components/theme/theme-provider";
|
import type { LucideIcon } from "lucide-react";
|
||||||
import { Moon, Sun, Monitor, Sunset, Waves, Check } from "lucide-react";
|
import {
|
||||||
|
Monitor,
|
||||||
|
Check,
|
||||||
|
Sun,
|
||||||
|
Moon,
|
||||||
|
Sunset,
|
||||||
|
Waves,
|
||||||
|
Leaf,
|
||||||
|
Hexagon,
|
||||||
|
Rose,
|
||||||
|
Flower2,
|
||||||
|
Mountain,
|
||||||
|
TreePine,
|
||||||
|
Grape,
|
||||||
|
Star,
|
||||||
|
Flame,
|
||||||
|
Wine,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
import { useTheme } from "@/components/theme/theme-provider";
|
||||||
|
import type { ThemeName } from "@/components/theme/theme-provider";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
|
@ -12,31 +31,72 @@ import {
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
// The four looks, shown as two "modes" with a shared System row. Each entry
|
type ThemeOption = {
|
||||||
// is one next-themes value (see app/layout.tsx for the class mapping); the
|
value: ThemeName;
|
||||||
// little dot is the theme's own accent so the picker previews the vibe.
|
label: string;
|
||||||
const OPTIONS = [
|
icon: LucideIcon;
|
||||||
|
swatch: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The looks, grouped by surface. Each value is one next-themes value (see
|
||||||
|
// app/layout.tsx for the class mapping); the little dot is the theme's own
|
||||||
|
// accent so the picker previews the vibe.
|
||||||
|
const LIGHT_OPTIONS: ThemeOption[] = [
|
||||||
{ value: "default", label: "Default", icon: Sun, swatch: "#4f56e0" },
|
{ value: "default", label: "Default", icon: Sun, swatch: "#4f56e0" },
|
||||||
{ value: "sunset", label: "Sunset", icon: Sunset, swatch: "#c2410c" },
|
{ value: "sunset", label: "Sunset", icon: Sunset, swatch: "#c2410c" },
|
||||||
|
{ value: "meadow", label: "Meadow", icon: Leaf, swatch: "#059669" },
|
||||||
|
{ value: "honey", label: "Honey", icon: Hexagon, swatch: "#d97706" },
|
||||||
|
{ value: "rose", label: "Rosé", icon: Rose, swatch: "#e11d48" },
|
||||||
|
{ value: "lavender", label: "Lavender", icon: Flower2, swatch: "#7c3aed" },
|
||||||
|
{ value: "slate", label: "Slate", icon: Mountain, swatch: "#4a6d9c" },
|
||||||
|
];
|
||||||
|
const DARK_OPTIONS: ThemeOption[] = [
|
||||||
{ value: "dark", label: "Dark", icon: Moon, swatch: "#7c83f2" },
|
{ value: "dark", label: "Dark", icon: Moon, swatch: "#7c83f2" },
|
||||||
{ value: "ocean", label: "Ocean", icon: Waves, swatch: "#5eead4" },
|
{ value: "ocean", label: "Ocean", icon: Waves, swatch: "#5eead4" },
|
||||||
] as const;
|
{ value: "pine", label: "Pine", icon: TreePine, swatch: "#34d399" },
|
||||||
|
{ value: "plum", label: "Plum", icon: Grape, swatch: "#c084fc" },
|
||||||
|
{ value: "midnight", label: "Midnight", icon: Star, swatch: "#93c5fd" },
|
||||||
|
{ value: "ember", label: "Ember", icon: Flame, swatch: "#fbbf24" },
|
||||||
|
{ value: "rosewood", label: "Rosewood", icon: Wine, swatch: "#fb7185" },
|
||||||
|
];
|
||||||
|
const ALL_OPTIONS = [...LIGHT_OPTIONS, ...DARK_OPTIONS];
|
||||||
|
|
||||||
const CURRENT_ICONS = {
|
function OptionRow({
|
||||||
default: Sun,
|
option,
|
||||||
sunset: Sunset,
|
active,
|
||||||
dark: Moon,
|
onSelect,
|
||||||
ocean: Waves,
|
}: {
|
||||||
system: Monitor,
|
option: ThemeOption;
|
||||||
} as const;
|
active: boolean;
|
||||||
|
onSelect: () => void;
|
||||||
|
}) {
|
||||||
|
const Icon = option.icon;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onSelect}
|
||||||
|
className="group relative flex w-full cursor-pointer items-center gap-2.5 rounded-md px-2 py-1.5 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50"
|
||||||
|
>
|
||||||
|
<Icon className="size-4 text-muted-foreground group-focus:text-current" />
|
||||||
|
<span>{option.label}</span>
|
||||||
|
<span
|
||||||
|
className="ml-auto size-3 rounded-full ring-1 ring-foreground/15"
|
||||||
|
style={{ backgroundColor: option.swatch }}
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
{active && <Check className="absolute right-2 -mr-4 size-4" />}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function ThemeToggle({ collapsed }: { collapsed?: boolean }) {
|
export function ThemeToggle({ collapsed }: { collapsed?: boolean }) {
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
// The picker always shows the four real themes; "system" is handled by a
|
// The picker shows all the real themes in Light/Dark sections; "system" is
|
||||||
// dedicated row since it resolves to one of them per device preference.
|
// handled by a dedicated row since it resolves to one of them per device
|
||||||
const known = OPTIONS.find((o) => o.value === theme);
|
// preference.
|
||||||
|
const known = ALL_OPTIONS.find((o) => o.value === theme);
|
||||||
const isSystem = theme === "system" || theme === undefined;
|
const isSystem = theme === "system" || theme === undefined;
|
||||||
const Icon = (isSystem ? CURRENT_ICONS.system : known?.icon) ?? CURRENT_ICONS.system;
|
const Icon = (isSystem ? Monitor : known?.icon) ?? Monitor;
|
||||||
const label = isSystem ? "System" : known?.label ?? "Default";
|
const label = isSystem ? "System" : known?.label ?? "Default";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -49,28 +109,29 @@ export function ThemeToggle({ collapsed }: { collapsed?: boolean }) {
|
||||||
className={collapsed ? undefined : "w-full justify-start gap-2"}
|
className={collapsed ? undefined : "w-full justify-start gap-2"}
|
||||||
>
|
>
|
||||||
<Icon className="size-4" />
|
<Icon className="size-4" />
|
||||||
{!collapsed && <span>Theme</span>}
|
{!collapsed && <span>{label}</span>}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<DropdownMenuContent align="start" side="right" className="w-52">
|
<DropdownMenuContent align="start" side="right" className="w-52">
|
||||||
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
|
<DropdownMenuLabel>Light</DropdownMenuLabel>
|
||||||
{OPTIONS.map((option) => (
|
{LIGHT_OPTIONS.map((option) => (
|
||||||
<button
|
<OptionRow
|
||||||
key={option.value}
|
key={option.value}
|
||||||
type="button"
|
option={option}
|
||||||
onClick={() => setTheme(option.value)}
|
active={theme === option.value}
|
||||||
className="group relative flex w-full cursor-pointer items-center gap-2.5 rounded-md px-2 py-1.5 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50"
|
onSelect={() => setTheme(option.value)}
|
||||||
>
|
/>
|
||||||
<option.icon className="size-4 text-muted-foreground group-focus:text-current" />
|
))}
|
||||||
<span>{option.label}</span>
|
<DropdownMenuSeparator />
|
||||||
<span
|
<DropdownMenuLabel>Dark</DropdownMenuLabel>
|
||||||
className="ml-auto size-3 rounded-full ring-1 ring-foreground/15"
|
{DARK_OPTIONS.map((option) => (
|
||||||
style={{ backgroundColor: option.swatch }}
|
<OptionRow
|
||||||
aria-hidden
|
key={option.value}
|
||||||
/>
|
option={option}
|
||||||
{theme === option.value && <Check className="absolute right-2 -mr-4 size-4" />}
|
active={theme === option.value}
|
||||||
</button>
|
onSelect={() => setTheme(option.value)}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { useTheme, resolveTheme } from "./theme-provider";
|
import { useTheme, resolveTheme, DARK_SURFACES } from "./theme-provider";
|
||||||
|
|
||||||
// The themes whose *surfaces* are dark (see app/globals.css): "dark" and
|
// The themes whose *surfaces* are dark (see app/globals.css) come from the
|
||||||
// "ocean". Anything that used to compare `resolvedTheme === "dark"` to pick
|
// provider's single source of truth -- currently Dark, Ocean, Pine, Plum,
|
||||||
// a color variant (group card fills/strokes, markdown widget color modes,
|
// Midnight, Ember, Rosewood. Anything that used to compare
|
||||||
// ...) uses this instead, so Ocean is treated exactly like Dark.
|
// `resolvedTheme === "dark"` to pick a color variant (group card
|
||||||
const DARK_SURFACES = new Set(["dark", "ocean"]);
|
// fills/strokes, markdown widget color modes, ...) uses this instead.
|
||||||
|
|
||||||
function useMounted(): boolean {
|
function useMounted(): boolean {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
@ -19,7 +19,8 @@ function useMounted(): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True when the active theme has dark surfaces (Dark or Ocean).
|
* True when the active theme has dark surfaces (Dark, Ocean, Pine, Plum,
|
||||||
|
* Midnight, Ember, Rosewood).
|
||||||
*
|
*
|
||||||
* Reactive to UI theme switches: the value is derived from the provider's
|
* Reactive to UI theme switches: the value is derived from the provider's
|
||||||
* theme state (the single source of truth), so the moment `setTheme` fires,
|
* theme state (the single source of truth), so the moment `setTheme` fires,
|
||||||
|
|
@ -34,13 +35,14 @@ export function isDarkTheme(): boolean {
|
||||||
const mounted = useMounted();
|
const mounted = useMounted();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
if (!mounted) return false;
|
if (!mounted) return false;
|
||||||
return DARK_SURFACES.has(resolveTheme(theme));
|
return DARK_SURFACES.includes(resolveTheme(theme));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `data-color-mode` value the markdown editor/preview widgets expect.
|
* The `data-color-mode` value the markdown editor/preview widgets expect.
|
||||||
* They only understand light/dark, so the light "sunset" look reports as
|
* They only understand light/dark, so the light looks (default, sunset,
|
||||||
* light and the dark "ocean" look reports as dark.
|
* meadow, honey, rose, lavender, slate) report as light and the dark looks
|
||||||
|
* report as dark.
|
||||||
*/
|
*/
|
||||||
export function colorModeFromTheme(): "light" | "dark" {
|
export function colorModeFromTheme(): "light" | "dark" {
|
||||||
return isDarkTheme() ? "dark" : "light";
|
return isDarkTheme() ? "dark" : "light";
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useTheme } from "@/components/theme/theme-provider"
|
import { useTheme, DARK_SURFACES } from "@/components/theme/theme-provider"
|
||||||
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
||||||
import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"
|
import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { resolvedTheme } = useTheme()
|
const { resolvedTheme } = useTheme()
|
||||||
// Sonner only knows light/dark -- map the app's looks (default/sunset are
|
// Sonner only knows light/dark -- map the app's looks onto what it
|
||||||
// light; dark/ocean are dark) onto what it understands.
|
// understands, using the provider's single dark-surface list.
|
||||||
const theme: ToasterProps["theme"] =
|
const theme: ToasterProps["theme"] = DARK_SURFACES.includes(resolvedTheme)
|
||||||
resolvedTheme === "dark" || resolvedTheme === "ocean" ? "dark" : "light"
|
? "dark"
|
||||||
|
: "light"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sonner
|
<Sonner
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue