Theme menu: the same palettes as INBUXA webmail
Under the user menu, Theme offers ihasmail's palettes, in the same order and under the same names. ihasmail is the default look, the colors the admin already had, until someone picks another. The choice is remembered and applied before the first paint, and dark mode still toggles on its own. The other palettes' colors are ihasmail's, already contrast-checked, mapped onto the admin's tokens by scripts/import-palettes.py; re-running it re-syncs them. NOTICE carries the palettes' MIT notices and the fonts' OFL notices.
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
# Third-party notices
|
||||
|
||||
INBUXA Admin is licensed under the AGPL-3.0; see LICENSES/. This file records
|
||||
work by other people that ships inside it and the terms it comes under.
|
||||
|
||||
## Color palettes
|
||||
|
||||
Ten of the palettes offered under the user menu › Theme are the work of their own
|
||||
projects and are used under the MIT license. Only the published color values
|
||||
are used — no code, and nothing from anyone else's reimplementation of them.
|
||||
The values as fetched from each project are recorded in ihasmail's repository,
|
||||
in `.palette-sources/palettes-upstream.md`, and the shades between them are
|
||||
derived by ihasmail's `scripts/build-palettes.py`, which also lifts any tier that
|
||||
would not meet the contrast ihasmail claims. INBUXA Admin takes ihasmail's
|
||||
derived colors as they are (`scripts/import-palettes.py`).
|
||||
|
||||
### Dracula and Alucard
|
||||
|
||||
Copyright (c) 2016 Dracula Theme — https://github.com/dracula/dracula-theme
|
||||
Licensed under the MIT license. "Dracula" is the dark variant and "Alucard" the
|
||||
light one; both are published in that repository's own "Color Palette (OSS)"
|
||||
section.
|
||||
|
||||
### Gruvbox
|
||||
|
||||
Copyright (c) 2018 Pavel Pertsev — https://github.com/morhetz/gruvbox
|
||||
Licensed under the MIT license.
|
||||
|
||||
### Rosé Pine
|
||||
|
||||
Copyright (c) 2021 Rosé Pine — https://github.com/rose-pine/rose-pine-theme
|
||||
Licensed under the MIT license. The light variant is "Dawn".
|
||||
|
||||
### Tokyo Night
|
||||
|
||||
Copyright (c) 2019 enkia — https://github.com/enkia/tokyo-night-vscode-theme
|
||||
Licensed under the MIT license. The light variant is "Day".
|
||||
|
||||
### Catppuccin
|
||||
|
||||
Copyright (c) 2021 Catppuccin — https://github.com/catppuccin/palette
|
||||
Licensed under the MIT license. "Mocha" is the dark variant and "Latte" the
|
||||
light one; both are published in that repository's palette.json.
|
||||
|
||||
### Solarized
|
||||
|
||||
Copyright (c) 2011 Ethan Schoonover — https://github.com/altercation/solarized
|
||||
Licensed under the MIT license. Light and dark are both original to it, and
|
||||
share one set of accent values by design.
|
||||
|
||||
### Ayu
|
||||
|
||||
Copyright (c) Konstantin Pschera — https://github.com/ayu-theme/ayu-colors
|
||||
Licensed under the MIT license. The two signature accent colors come from the
|
||||
same author's ayu-theme/vscode-ayu, also MIT.
|
||||
|
||||
### Kanagawa
|
||||
|
||||
Copyright (c) 2021 Tommaso Laurenzi — https://github.com/rebelot/kanagawa.nvim
|
||||
Licensed under the MIT license. "Wave" is the dark variant and "Lotus" the
|
||||
light one. The theme takes its name from Hokusai's print.
|
||||
|
||||
### Everforest
|
||||
|
||||
Copyright (c) 2019 Sainnhe Park — https://github.com/sainnhe/everforest
|
||||
Licensed under the MIT license. The medium-contrast variant of each mode is
|
||||
the one used here.
|
||||
|
||||
### Primer
|
||||
|
||||
Copyright (c) GitHub, Inc. — https://github.com/primer/primitives
|
||||
Licensed under the MIT license, which covers the color values. "GitHub" and
|
||||
the Invertocat logo are trademarks of GitHub, Inc.; this palette is named
|
||||
"Primer" after the design system and is neither affiliated with nor endorsed
|
||||
by GitHub.
|
||||
---
|
||||
|
||||
The MIT license, under which all ten are used:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## Fonts
|
||||
|
||||
The interface's typefaces are bundled with it, from the Fontsource packages,
|
||||
under the SIL Open Font License 1.1. The full license text ships with each
|
||||
package (`LICENSE` in `@fontsource-variable/inter` and
|
||||
`@fontsource-variable/space-grotesk`), and it applies to the font files in the
|
||||
built app.
|
||||
|
||||
- **Inter**: Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter).
|
||||
- **Space Grotesk**: Copyright (c) 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk).
|
||||
@@ -8,7 +8,7 @@
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import * as LucideIcons from 'lucide-react';
|
||||
const { Sun, Moon, User, LogOut, Check, Menu, Search, FileCode } = LucideIcons;
|
||||
const { Sun, Moon, User, LogOut, Check, Menu, Search, FileCode, Palette } = LucideIcons;
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { CommandPalette } from '@/components/common/CommandPalette';
|
||||
import {
|
||||
@@ -17,9 +17,15 @@ import {
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { isPaletteId, PALETTES } from '@/lib/palettes';
|
||||
import Logo from '@/components/common/Logo';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell';
|
||||
@@ -49,6 +55,8 @@ export function TopBar() {
|
||||
const navigate = useNavigate();
|
||||
const theme = useUIStore((s) => s.theme);
|
||||
const toggleTheme = useUIStore((s) => s.toggleTheme);
|
||||
const palette = useUIStore((s) => s.palette);
|
||||
const setPalette = useUIStore((s) => s.setPalette);
|
||||
const toggleSidebar = useUIStore((s) => s.toggleSidebar);
|
||||
const setActiveSection = useUIStore((s) => s.setActiveSection);
|
||||
const activeSection = useUIStore((s) => s.activeSection);
|
||||
@@ -216,6 +224,31 @@ export function TopBar() {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* INBUXA: the same palettes as INBUXA webmail. */}
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<Palette className="mr-2 h-4 w-4" />
|
||||
{t('theme.menu', 'Theme')}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="w-52">
|
||||
<DropdownMenuRadioGroup value={palette} onValueChange={(v) => isPaletteId(v) && setPalette(v)}>
|
||||
{PALETTES.map((p) => (
|
||||
<DropdownMenuRadioItem key={p.id} value={p.id} className="gap-2">
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="h-3.5 w-3.5 shrink-0 rounded-full ring-1 ring-black/10 dark:ring-white/15"
|
||||
style={{ background: theme === 'dark' ? p.swatch[1] : p.swatch[0] }}
|
||||
/>
|
||||
<span className="notranslate" translate="no">
|
||||
{p.id === 'default' ? t('theme.classic', 'Classic') : p.name}
|
||||
</span>
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem asChild>
|
||||
<a href={sourceDownloadUrl()} target="_blank" rel="noopener noreferrer">
|
||||
<FileCode className="mr-2 h-4 w-4" />
|
||||
|
||||
@@ -391,5 +391,9 @@
|
||||
"evening": "Good evening",
|
||||
"subtitle": "Here's how your mail server is doing.",
|
||||
"signedInAs": "Signed in as {{username}}"
|
||||
},
|
||||
"theme": {
|
||||
"menu": "Theme",
|
||||
"classic": "Classic"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
@import 'tailwindcss';
|
||||
@import './palettes.css';
|
||||
|
||||
:root {
|
||||
/* INBUXA, light: warm paper, the mark's navy for text, its teal to act. */
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Coffey Labs
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
* The themes the admin offers: the same palettes as INBUXA webmail, in the
|
||||
* same order and under the same names. "ihasmail" is the default, the look in
|
||||
* index.css. The others' colors are in palettes.css, generated from ihasmail's
|
||||
* by scripts/import-palettes.py.
|
||||
*/
|
||||
export type PaletteId =
|
||||
| 'default'
|
||||
| 'ihasmail'
|
||||
| 'dracula'
|
||||
| 'gruvbox'
|
||||
| 'rose-pine'
|
||||
| 'tokyo-night'
|
||||
| 'catppuccin'
|
||||
| 'solarized'
|
||||
| 'ayu'
|
||||
| 'kanagawa'
|
||||
| 'everforest'
|
||||
| 'primer';
|
||||
|
||||
export const DEFAULT_PALETTE: PaletteId = 'ihasmail';
|
||||
|
||||
/** `swatch` is the palette's accent, light and dark, for the menu's color dot. */
|
||||
export const PALETTES: { id: PaletteId; name: string; swatch: [string, string] }[] = [
|
||||
{ id: 'default', name: 'Classic', swatch: ['#0f766e', '#2dd4bf'] },
|
||||
{ id: 'ihasmail', name: 'ihasmail', swatch: ['#0d8a82', '#46cac3'] },
|
||||
{ id: 'dracula', name: 'Dracula', swatch: ['#644ac9', '#bd93f9'] },
|
||||
{ id: 'gruvbox', name: 'Gruvbox', swatch: ['#076678', '#83a598'] },
|
||||
{ id: 'rose-pine', name: 'Rosé Pine', swatch: ['#907aa9', '#c4a7e7'] },
|
||||
{ id: 'tokyo-night', name: 'Tokyo Night', swatch: ['#2959aa', '#7aa2f7'] },
|
||||
{ id: 'catppuccin', name: 'Catppuccin', swatch: ['#8839ef', '#cba6f7'] },
|
||||
{ id: 'solarized', name: 'Solarized', swatch: ['#268bd2', '#268bd2'] },
|
||||
{ id: 'ayu', name: 'Ayu', swatch: ['#cb7f14', '#e6b450'] },
|
||||
{ id: 'kanagawa', name: 'Kanagawa', swatch: ['#624c83', '#7e9cd8'] },
|
||||
{ id: 'everforest', name: 'Everforest', swatch: ['#7d8f01', '#a7c080'] },
|
||||
{ id: 'primer', name: 'Primer', swatch: ['#0969da', '#58a6ff'] },
|
||||
];
|
||||
|
||||
export function isPaletteId(v: unknown): v is PaletteId {
|
||||
return PALETTES.some((p) => p.id === v);
|
||||
}
|
||||
|
||||
export function applyPalette(id: PaletteId) {
|
||||
if (id === DEFAULT_PALETTE) delete document.documentElement.dataset.palette;
|
||||
else document.documentElement.dataset.palette = id;
|
||||
}
|
||||
@@ -27,6 +27,11 @@ import { loadLogoOnce } from './lib/logoCache';
|
||||
if (persisted) {
|
||||
const parsed = JSON.parse(persisted);
|
||||
const theme = parsed?.state?.theme;
|
||||
// INBUXA: the palette too, before the first paint, so it doesn't flash.
|
||||
const palette = parsed?.state?.palette;
|
||||
if (typeof palette === 'string' && palette !== 'ihasmail' && /^[a-z-]+$/.test(palette)) {
|
||||
document.documentElement.dataset.palette = palette;
|
||||
}
|
||||
if (theme === 'dark' || theme === 'light') {
|
||||
document.documentElement.classList.toggle('dark', theme === 'dark');
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,672 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Coffey Labs
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/*
|
||||
* Generated by scripts/import-palettes.py from ihasmail's palettes. Don't
|
||||
* edit by hand. The palettes' own credits are in NOTICE.
|
||||
*
|
||||
* No data-palette attribute is the default, "ihasmail": the tokens in index.css.
|
||||
*/
|
||||
|
||||
:root[data-palette="default"] {
|
||||
--background: #f6f8fa;
|
||||
--foreground: #111827;
|
||||
--card: #ffffff;
|
||||
--card-foreground: #111827;
|
||||
--popover: #ffffff;
|
||||
--popover-foreground: #111827;
|
||||
--content-background: #eef1f4;
|
||||
--primary: #0f766e;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #eef1f4;
|
||||
--secondary-foreground: #111827;
|
||||
--muted: #eef1f4;
|
||||
--muted-foreground: #5b6472;
|
||||
--accent: #d9f1ee;
|
||||
--accent-foreground: #0b5750;
|
||||
--highlight: #b45309;
|
||||
--highlight-soft: #fef3c7;
|
||||
--destructive: #dc2626;
|
||||
--border: #e3e7ec;
|
||||
--input: #cbd2da;
|
||||
--ring: #0f766e;
|
||||
--chart-1: #0f766e;
|
||||
--chart-2: #b45309;
|
||||
--chart-3: #2563eb;
|
||||
--chart-4: #16a34a;
|
||||
--chart-5: #9333ea;
|
||||
--destructive-foreground: #ffffff;
|
||||
}
|
||||
|
||||
.dark[data-palette="default"] {
|
||||
--background: #0b1220;
|
||||
--foreground: #e5e9f0;
|
||||
--card: #111a2b;
|
||||
--card-foreground: #e5e9f0;
|
||||
--popover: #111a2b;
|
||||
--popover-foreground: #e5e9f0;
|
||||
--content-background: #0d1524;
|
||||
--primary: #2dd4bf;
|
||||
--primary-foreground: #052e2b;
|
||||
--secondary: #0d1524;
|
||||
--secondary-foreground: #e5e9f0;
|
||||
--muted: #0d1524;
|
||||
--muted-foreground: #9aa5b8;
|
||||
--accent: rgba(45, 212, 191, 0.16);
|
||||
--accent-foreground: #99f6e4;
|
||||
--highlight: #fbbf24;
|
||||
--highlight-soft: rgba(251, 191, 36, 0.15);
|
||||
--destructive: #f87171;
|
||||
--border: #1f2a3d;
|
||||
--input: #31405a;
|
||||
--ring: #2dd4bf;
|
||||
--chart-1: #2dd4bf;
|
||||
--chart-2: #fbbf24;
|
||||
--chart-3: #60a5fa;
|
||||
--chart-4: #4ade80;
|
||||
--chart-5: #c084fc;
|
||||
--destructive-foreground: #0b1720;
|
||||
}
|
||||
|
||||
:root[data-palette="dracula"] {
|
||||
--background: #fffbeb;
|
||||
--foreground: #1f1f1f;
|
||||
--card: #ffffff;
|
||||
--card-foreground: #1f1f1f;
|
||||
--popover: #ffffff;
|
||||
--popover-foreground: #1f1f1f;
|
||||
--content-background: #f6f1de;
|
||||
--primary: #644ac9;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #f6f1de;
|
||||
--secondary-foreground: #1f1f1f;
|
||||
--muted: #f6f1de;
|
||||
--muted-foreground: #6c664b;
|
||||
--accent: #e9e2e6;
|
||||
--accent-foreground: #644ac9;
|
||||
--highlight: #a34d14;
|
||||
--highlight-soft: rgba(163, 77, 20, 0.15);
|
||||
--destructive: #cb3a2a;
|
||||
--border: #cfcfde;
|
||||
--input: #91919a;
|
||||
--ring: #644ac9;
|
||||
--chart-1: #644ac9;
|
||||
--chart-2: #a34d14;
|
||||
--chart-3: #036a96;
|
||||
--chart-4: #14710a;
|
||||
--chart-5: #a3144d;
|
||||
--destructive-foreground: #ffffff;
|
||||
}
|
||||
|
||||
.dark[data-palette="dracula"] {
|
||||
--background: #282a36;
|
||||
--foreground: #f8f8f2;
|
||||
--card: #2f3140;
|
||||
--card-foreground: #f8f8f2;
|
||||
--popover: #2f3140;
|
||||
--popover-foreground: #f8f8f2;
|
||||
--content-background: #21222c;
|
||||
--primary: #bd93f9;
|
||||
--primary-foreground: #282a36;
|
||||
--secondary: #21222c;
|
||||
--secondary-foreground: #f8f8f2;
|
||||
--muted: #21222c;
|
||||
--muted-foreground: #8390b7;
|
||||
--accent: rgba(189, 147, 249, 0.16);
|
||||
--accent-foreground: #bf96f9;
|
||||
--highlight: #ffb86c;
|
||||
--highlight-soft: rgba(255, 184, 108, 0.15);
|
||||
--destructive: #ff5555;
|
||||
--border: #44475a;
|
||||
--input: #717381;
|
||||
--ring: #bd93f9;
|
||||
--chart-1: #bd93f9;
|
||||
--chart-2: #ffb86c;
|
||||
--chart-3: #8be9fd;
|
||||
--chart-4: #50fa7b;
|
||||
--chart-5: #ff79c6;
|
||||
--destructive-foreground: #0b1720;
|
||||
}
|
||||
|
||||
:root[data-palette="gruvbox"] {
|
||||
--background: #fbf1c7;
|
||||
--foreground: #3c3836;
|
||||
--card: #f9f5d7;
|
||||
--card-foreground: #3c3836;
|
||||
--popover: #f9f5d7;
|
||||
--popover-foreground: #3c3836;
|
||||
--content-background: #f2e5bc;
|
||||
--primary: #076678;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #f2e5bc;
|
||||
--secondary-foreground: #3c3836;
|
||||
--muted: #f2e5bc;
|
||||
--muted-foreground: #776b60;
|
||||
--accent: #d9debc;
|
||||
--accent-foreground: #076678;
|
||||
--highlight: #af3a03;
|
||||
--highlight-soft: rgba(175, 58, 3, 0.15);
|
||||
--destructive: #9d0006;
|
||||
--border: #d5c4a1;
|
||||
--input: #948871;
|
||||
--ring: #076678;
|
||||
--chart-1: #076678;
|
||||
--chart-2: #af3a03;
|
||||
--chart-3: #076678;
|
||||
--chart-4: #746f0d;
|
||||
--chart-5: #8f3f71;
|
||||
--destructive-foreground: #ffffff;
|
||||
}
|
||||
|
||||
.dark[data-palette="gruvbox"] {
|
||||
--background: #282828;
|
||||
--foreground: #ebdbb2;
|
||||
--card: #32302f;
|
||||
--card-foreground: #ebdbb2;
|
||||
--popover: #32302f;
|
||||
--popover-foreground: #ebdbb2;
|
||||
--content-background: #1d2021;
|
||||
--primary: #83a598;
|
||||
--primary-foreground: #282828;
|
||||
--secondary: #1d2021;
|
||||
--secondary-foreground: #ebdbb2;
|
||||
--muted: #1d2021;
|
||||
--muted-foreground: #a89984;
|
||||
--accent: rgba(131, 165, 152, 0.16);
|
||||
--accent-foreground: #8cab9f;
|
||||
--highlight: #fe8019;
|
||||
--highlight-soft: rgba(254, 128, 25, 0.15);
|
||||
--destructive: #fb5440;
|
||||
--border: #504945;
|
||||
--input: #787168;
|
||||
--ring: #83a598;
|
||||
--chart-1: #83a598;
|
||||
--chart-2: #fe8019;
|
||||
--chart-3: #83a598;
|
||||
--chart-4: #b8bb26;
|
||||
--chart-5: #d3869b;
|
||||
--destructive-foreground: #0b1720;
|
||||
}
|
||||
|
||||
:root[data-palette="rose-pine"] {
|
||||
--background: #faf4ed;
|
||||
--foreground: #464261;
|
||||
--card: #fffaf3;
|
||||
--card-foreground: #464261;
|
||||
--popover: #fffaf3;
|
||||
--popover-foreground: #464261;
|
||||
--content-background: #f2e9e1;
|
||||
--primary: #907aa9;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #f2e9e1;
|
||||
--secondary-foreground: #464261;
|
||||
--muted: #f2e9e1;
|
||||
--muted-foreground: #716d89;
|
||||
--accent: #ebe3e3;
|
||||
--accent-foreground: #705f84;
|
||||
--highlight: #986622;
|
||||
--highlight-soft: rgba(152, 102, 34, 0.15);
|
||||
--destructive: #a45a6f;
|
||||
--border: #dfd9d2;
|
||||
--input: #908c8b;
|
||||
--ring: #907aa9;
|
||||
--chart-1: #907aa9;
|
||||
--chart-2: #986622;
|
||||
--chart-3: #286983;
|
||||
--chart-4: #467881;
|
||||
--chart-5: #7a6890;
|
||||
--destructive-foreground: #ffffff;
|
||||
}
|
||||
|
||||
.dark[data-palette="rose-pine"] {
|
||||
--background: #191724;
|
||||
--foreground: #e0def4;
|
||||
--card: #1f1d2e;
|
||||
--card-foreground: #e0def4;
|
||||
--popover: #1f1d2e;
|
||||
--popover-foreground: #e0def4;
|
||||
--content-background: #14121f;
|
||||
--primary: #c4a7e7;
|
||||
--primary-foreground: #191724;
|
||||
--secondary: #14121f;
|
||||
--secondary-foreground: #e0def4;
|
||||
--muted: #14121f;
|
||||
--muted-foreground: #908caa;
|
||||
--accent: rgba(196, 167, 231, 0.16);
|
||||
--accent-foreground: #c4a7e7;
|
||||
--highlight: #f6c177;
|
||||
--highlight-soft: rgba(246, 193, 119, 0.15);
|
||||
--destructive: #eb6f92;
|
||||
--border: #26233a;
|
||||
--input: #666376;
|
||||
--ring: #c4a7e7;
|
||||
--chart-1: #c4a7e7;
|
||||
--chart-2: #f6c177;
|
||||
--chart-3: #9ccfd8;
|
||||
--chart-4: #5089a0;
|
||||
--chart-5: #c4a7e7;
|
||||
--destructive-foreground: #0b1720;
|
||||
}
|
||||
|
||||
:root[data-palette="tokyo-night"] {
|
||||
--background: #e6e7ed;
|
||||
--foreground: #343b59;
|
||||
--card: #f2f3f7;
|
||||
--card-foreground: #343b59;
|
||||
--popover: #f2f3f7;
|
||||
--popover-foreground: #343b59;
|
||||
--content-background: #d6d8df;
|
||||
--primary: #2959aa;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #d6d8df;
|
||||
--secondary-foreground: #343b59;
|
||||
--muted: #d6d8df;
|
||||
--muted-foreground: #484c61;
|
||||
--accent: #ccd3e4;
|
||||
--accent-foreground: #2959aa;
|
||||
--highlight: #8f5e15;
|
||||
--highlight-soft: rgba(143, 94, 21, 0.15);
|
||||
--destructive: #8c4351;
|
||||
--border: #c1c2c7;
|
||||
--input: #83848a;
|
||||
--ring: #2959aa;
|
||||
--chart-1: #2959aa;
|
||||
--chart-2: #8f5e15;
|
||||
--chart-3: #006c86;
|
||||
--chart-4: #385f0d;
|
||||
--chart-5: #65359d;
|
||||
--destructive-foreground: #ffffff;
|
||||
}
|
||||
|
||||
.dark[data-palette="tokyo-night"] {
|
||||
--background: #1a1b26;
|
||||
--foreground: #c0caf5;
|
||||
--card: #1f2130;
|
||||
--card-foreground: #c0caf5;
|
||||
--popover: #1f2130;
|
||||
--popover-foreground: #c0caf5;
|
||||
--content-background: #16161e;
|
||||
--primary: #7aa2f7;
|
||||
--primary-foreground: #1a1b26;
|
||||
--secondary: #16161e;
|
||||
--secondary-foreground: #c0caf5;
|
||||
--muted: #16161e;
|
||||
--muted-foreground: #a9b1d6;
|
||||
--accent: rgba(122, 162, 247, 0.16);
|
||||
--accent-foreground: #7aa2f7;
|
||||
--highlight: #e0af68;
|
||||
--highlight-soft: rgba(224, 175, 104, 0.15);
|
||||
--destructive: #f7768e;
|
||||
--border: #363b54;
|
||||
--input: #62677f;
|
||||
--ring: #7aa2f7;
|
||||
--chart-1: #7aa2f7;
|
||||
--chart-2: #e0af68;
|
||||
--chart-3: #7dcfff;
|
||||
--chart-4: #9ece6a;
|
||||
--chart-5: #bb9af7;
|
||||
--destructive-foreground: #0b1720;
|
||||
}
|
||||
|
||||
:root[data-palette="catppuccin"] {
|
||||
--background: #e6e9ef;
|
||||
--foreground: #484b64;
|
||||
--card: #eff1f5;
|
||||
--card-foreground: #484b64;
|
||||
--popover: #eff1f5;
|
||||
--popover-foreground: #484b64;
|
||||
--content-background: #dce0e8;
|
||||
--primary: #8839ef;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #dce0e8;
|
||||
--secondary-foreground: #484b64;
|
||||
--muted: #dce0e8;
|
||||
--muted-foreground: #64677c;
|
||||
--accent: #d9d0ef;
|
||||
--accent-foreground: #7632d0;
|
||||
--highlight: #b24608;
|
||||
--highlight-soft: rgba(178, 70, 8, 0.15);
|
||||
--destructive: #d00f38;
|
||||
--border: #ccd0da;
|
||||
--input: #83858e;
|
||||
--ring: #8839ef;
|
||||
--chart-1: #8839ef;
|
||||
--chart-2: #b24608;
|
||||
--chart-3: #1c5fe4;
|
||||
--chart-4: #307820;
|
||||
--chart-5: #9a4e86;
|
||||
--destructive-foreground: #ffffff;
|
||||
}
|
||||
|
||||
.dark[data-palette="catppuccin"] {
|
||||
--background: #1e1e2e;
|
||||
--foreground: #cdd6f4;
|
||||
--card: #313244;
|
||||
--card-foreground: #cdd6f4;
|
||||
--popover: #313244;
|
||||
--popover-foreground: #cdd6f4;
|
||||
--content-background: #181825;
|
||||
--primary: #cba6f7;
|
||||
--primary-foreground: #1e1e2e;
|
||||
--secondary: #181825;
|
||||
--secondary-foreground: #cdd6f4;
|
||||
--muted: #181825;
|
||||
--muted-foreground: #a6adc8;
|
||||
--accent: rgba(203, 166, 247, 0.16);
|
||||
--accent-foreground: #cba6f7;
|
||||
--highlight: #fab387;
|
||||
--highlight-soft: rgba(250, 179, 135, 0.15);
|
||||
--destructive: #f38ba8;
|
||||
--border: #45475a;
|
||||
--input: #66697c;
|
||||
--ring: #cba6f7;
|
||||
--chart-1: #cba6f7;
|
||||
--chart-2: #fab387;
|
||||
--chart-3: #89b4fa;
|
||||
--chart-4: #a6e3a1;
|
||||
--chart-5: #f5c2e7;
|
||||
--destructive-foreground: #0b1720;
|
||||
}
|
||||
|
||||
:root[data-palette="solarized"] {
|
||||
--background: #fdf6e3;
|
||||
--foreground: #47565c;
|
||||
--card: #fffdf6;
|
||||
--card-foreground: #47565c;
|
||||
--popover: #fffdf6;
|
||||
--popover-foreground: #47565c;
|
||||
--content-background: #eee8d5;
|
||||
--primary: #268bd2;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #eee8d5;
|
||||
--secondary-foreground: #47565c;
|
||||
--muted: #eee8d5;
|
||||
--muted-foreground: #687272;
|
||||
--accent: #dfe7e1;
|
||||
--accent-foreground: #1d6ba2;
|
||||
--highlight: #c34815;
|
||||
--highlight-soft: rgba(195, 72, 21, 0.15);
|
||||
--destructive: #d5302e;
|
||||
--border: #e6dfc8;
|
||||
--input: #928f83;
|
||||
--ring: #268bd2;
|
||||
--chart-1: #268bd2;
|
||||
--chart-2: #c34815;
|
||||
--chart-3: #217e77;
|
||||
--chart-4: #687700;
|
||||
--chart-5: #666ab8;
|
||||
--destructive-foreground: #ffffff;
|
||||
}
|
||||
|
||||
.dark[data-palette="solarized"] {
|
||||
--background: #002b36;
|
||||
--foreground: #a8b4b6;
|
||||
--card: #073642;
|
||||
--card-foreground: #a8b4b6;
|
||||
--popover: #073642;
|
||||
--popover-foreground: #a8b4b6;
|
||||
--content-background: #001f28;
|
||||
--primary: #268bd2;
|
||||
--primary-foreground: #002b36;
|
||||
--secondary: #001f28;
|
||||
--secondary-foreground: #a8b4b6;
|
||||
--muted: #001f28;
|
||||
--muted-foreground: #809196;
|
||||
--accent: rgba(38, 139, 210, 0.16);
|
||||
--accent-foreground: #56a5dc;
|
||||
--highlight: #d67147;
|
||||
--highlight-soft: rgba(214, 113, 71, 0.15);
|
||||
--destructive: #e56563;
|
||||
--border: #0d4552;
|
||||
--input: #4e767f;
|
||||
--ring: #268bd2;
|
||||
--chart-1: #268bd2;
|
||||
--chart-2: #d67147;
|
||||
--chart-3: #2aa198;
|
||||
--chart-4: #859900;
|
||||
--chart-5: #8488cd;
|
||||
--destructive-foreground: #0b1720;
|
||||
}
|
||||
|
||||
:root[data-palette="ayu"] {
|
||||
--background: #f8f9fa;
|
||||
--foreground: #52565b;
|
||||
--card: #fcfcfc;
|
||||
--card-foreground: #52565b;
|
||||
--popover: #fcfcfc;
|
||||
--popover-foreground: #52565b;
|
||||
--content-background: #ebeef0;
|
||||
--primary: #cb7f14;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #ebeef0;
|
||||
--secondary-foreground: #52565b;
|
||||
--muted: #ebeef0;
|
||||
--muted-foreground: #697381;
|
||||
--accent: #f2e8da;
|
||||
--accent-foreground: #945d0f;
|
||||
--highlight: #af5d23;
|
||||
--highlight-soft: rgba(175, 93, 35, 0.15);
|
||||
--destructive: #b45555;
|
||||
--border: #dfe2e5;
|
||||
--input: #8d9092;
|
||||
--ring: #cb7f14;
|
||||
--chart-1: #cb7f14;
|
||||
--chart-2: #af5d23;
|
||||
--chart-3: #3a7a90;
|
||||
--chart-4: #5e7d00;
|
||||
--chart-5: #8664a7;
|
||||
--destructive-foreground: #ffffff;
|
||||
}
|
||||
|
||||
.dark[data-palette="ayu"] {
|
||||
--background: #0d1017;
|
||||
--foreground: #bfbdb6;
|
||||
--card: #10141c;
|
||||
--card-foreground: #bfbdb6;
|
||||
--popover: #10141c;
|
||||
--popover-foreground: #bfbdb6;
|
||||
--content-background: #070a0f;
|
||||
--primary: #e6b450;
|
||||
--primary-foreground: #0d1017;
|
||||
--secondary: #070a0f;
|
||||
--secondary-foreground: #bfbdb6;
|
||||
--muted: #070a0f;
|
||||
--muted-foreground: #747c8e;
|
||||
--accent: rgba(230, 180, 80, 0.16);
|
||||
--accent-foreground: #e6b450;
|
||||
--highlight: #ff8f40;
|
||||
--highlight-soft: rgba(255, 143, 64, 0.15);
|
||||
--destructive: #f07178;
|
||||
--border: #1b1f29;
|
||||
--input: #5f6167;
|
||||
--ring: #e6b450;
|
||||
--chart-1: #e6b450;
|
||||
--chart-2: #ff8f40;
|
||||
--chart-3: #39bae6;
|
||||
--chart-4: #aad94c;
|
||||
--chart-5: #d2a6ff;
|
||||
--destructive-foreground: #0b1720;
|
||||
}
|
||||
|
||||
:root[data-palette="kanagawa"] {
|
||||
--background: #e5ddb0;
|
||||
--foreground: #434350;
|
||||
--card: #f2ecbc;
|
||||
--card-foreground: #434350;
|
||||
--popover: #f2ecbc;
|
||||
--popover-foreground: #434350;
|
||||
--content-background: #dcd5ac;
|
||||
--primary: #624c83;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #dcd5ac;
|
||||
--secondary-foreground: #434350;
|
||||
--muted: #dcd5ac;
|
||||
--muted-foreground: #636155;
|
||||
--accent: #d3c9aa;
|
||||
--accent-foreground: #604a80;
|
||||
--highlight: #934e00;
|
||||
--highlight-soft: rgba(147, 78, 0, 0.15);
|
||||
--destructive: #ac3747;
|
||||
--border: #d5cea3;
|
||||
--input: #807c65;
|
||||
--ring: #624c83;
|
||||
--chart-1: #624c83;
|
||||
--chart-2: #934e00;
|
||||
--chart-3: #47618f;
|
||||
--chart-4: #53673b;
|
||||
--chart-5: #914a62;
|
||||
--destructive-foreground: #ffffff;
|
||||
}
|
||||
|
||||
.dark[data-palette="kanagawa"] {
|
||||
--background: #1f1f28;
|
||||
--foreground: #dcd7ba;
|
||||
--card: #2a2a37;
|
||||
--card-foreground: #dcd7ba;
|
||||
--popover: #2a2a37;
|
||||
--popover-foreground: #dcd7ba;
|
||||
--content-background: #16161d;
|
||||
--primary: #7e9cd8;
|
||||
--primary-foreground: #1f1f28;
|
||||
--secondary: #16161d;
|
||||
--secondary-foreground: #dcd7ba;
|
||||
--muted: #16161d;
|
||||
--muted-foreground: #898881;
|
||||
--accent: rgba(126, 156, 216, 0.16);
|
||||
--accent-foreground: #7e9cd8;
|
||||
--highlight: #ff9e3b;
|
||||
--highlight-soft: rgba(255, 158, 59, 0.15);
|
||||
--destructive: #ed5050;
|
||||
--border: #363646;
|
||||
--input: #696970;
|
||||
--ring: #7e9cd8;
|
||||
--chart-1: #7e9cd8;
|
||||
--chart-2: #ff9e3b;
|
||||
--chart-3: #7fb4ca;
|
||||
--chart-4: #98bb6c;
|
||||
--chart-5: #d27e99;
|
||||
--destructive-foreground: #0b1720;
|
||||
}
|
||||
|
||||
:root[data-palette="everforest"] {
|
||||
--background: #efebd4;
|
||||
--foreground: #444e54;
|
||||
--card: #fdf6e3;
|
||||
--card-foreground: #444e54;
|
||||
--popover: #fdf6e3;
|
||||
--popover-foreground: #444e54;
|
||||
--content-background: #e6e2cc;
|
||||
--primary: #7d8f01;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #e6e2cc;
|
||||
--secondary-foreground: #444e54;
|
||||
--muted: #e6e2cc;
|
||||
--muted-foreground: #646c63;
|
||||
--accent: #dfdeb6;
|
||||
--accent-foreground: #5a6701;
|
||||
--highlight: #a45419;
|
||||
--highlight-soft: rgba(164, 84, 25, 0.15);
|
||||
--destructive: #b83f3d;
|
||||
--border: #bdc3af;
|
||||
--input: #84887c;
|
||||
--ring: #7d8f01;
|
||||
--chart-1: #7d8f01;
|
||||
--chart-2: #a45419;
|
||||
--chart-3: #2c7096;
|
||||
--chart-4: #616f01;
|
||||
--chart-5: #9e4b84;
|
||||
--destructive-foreground: #ffffff;
|
||||
}
|
||||
|
||||
.dark[data-palette="everforest"] {
|
||||
--background: #2d353b;
|
||||
--foreground: #d3c6aa;
|
||||
--card: #343f44;
|
||||
--card-foreground: #d3c6aa;
|
||||
--popover: #343f44;
|
||||
--popover-foreground: #d3c6aa;
|
||||
--content-background: #232a2e;
|
||||
--primary: #a7c080;
|
||||
--primary-foreground: #2d353b;
|
||||
--secondary: #232a2e;
|
||||
--secondary-foreground: #d3c6aa;
|
||||
--muted: #232a2e;
|
||||
--muted-foreground: #949f97;
|
||||
--accent: rgba(167, 192, 128, 0.16);
|
||||
--accent-foreground: #a7c080;
|
||||
--highlight: #e69875;
|
||||
--highlight-soft: rgba(230, 152, 117, 0.15);
|
||||
--destructive: #e67e80;
|
||||
--border: #475258;
|
||||
--input: #787e7e;
|
||||
--ring: #a7c080;
|
||||
--chart-1: #a7c080;
|
||||
--chart-2: #e69875;
|
||||
--chart-3: #7fbbb3;
|
||||
--chart-4: #a7c080;
|
||||
--chart-5: #d699b6;
|
||||
--destructive-foreground: #0b1720;
|
||||
}
|
||||
|
||||
:root[data-palette="primer"] {
|
||||
--background: #f6f8fa;
|
||||
--foreground: #25292e;
|
||||
--card: #ffffff;
|
||||
--card-foreground: #25292e;
|
||||
--popover: #ffffff;
|
||||
--popover-foreground: #25292e;
|
||||
--content-background: #eff2f5;
|
||||
--primary: #0969da;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #eff2f5;
|
||||
--secondary-foreground: #25292e;
|
||||
--muted: #eff2f5;
|
||||
--muted-foreground: #59636e;
|
||||
--accent: #d5e4f6;
|
||||
--accent-foreground: #0861c9;
|
||||
--highlight: #9a6700;
|
||||
--highlight-soft: rgba(154, 103, 0, 0.15);
|
||||
--destructive: #cf222e;
|
||||
--border: #d1d9e0;
|
||||
--input: #898f94;
|
||||
--ring: #0969da;
|
||||
--chart-1: #0969da;
|
||||
--chart-2: #9a6700;
|
||||
--chart-3: #0550ae;
|
||||
--chart-4: #116329;
|
||||
--chart-5: #8250df;
|
||||
--destructive-foreground: #ffffff;
|
||||
}
|
||||
|
||||
.dark[data-palette="primer"] {
|
||||
--background: #0d1117;
|
||||
--foreground: #f0f6fc;
|
||||
--card: #151b23;
|
||||
--card-foreground: #f0f6fc;
|
||||
--popover: #151b23;
|
||||
--popover-foreground: #f0f6fc;
|
||||
--content-background: #010409;
|
||||
--primary: #58a6ff;
|
||||
--primary-foreground: #0d1117;
|
||||
--secondary: #010409;
|
||||
--secondary-foreground: #f0f6fc;
|
||||
--muted: #010409;
|
||||
--muted-foreground: #9198a1;
|
||||
--accent: rgba(88, 166, 255, 0.16);
|
||||
--accent-foreground: #58a6ff;
|
||||
--highlight: #e3b341;
|
||||
--highlight-soft: rgba(227, 179, 65, 0.15);
|
||||
--destructive: #ff7b72;
|
||||
--border: #3d444d;
|
||||
--input: #5a6169;
|
||||
--ring: #58a6ff;
|
||||
--chart-1: #58a6ff;
|
||||
--chart-2: #e3b341;
|
||||
--chart-3: #79c0ff;
|
||||
--chart-4: #56d364;
|
||||
--chart-5: #d2a8ff;
|
||||
--destructive-foreground: #0b1720;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
import { applyPalette, DEFAULT_PALETTE, isPaletteId, type PaletteId } from '@/lib/palettes';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
@@ -12,6 +13,8 @@ type Theme = 'light' | 'dark';
|
||||
|
||||
interface UIState {
|
||||
theme: Theme;
|
||||
/** INBUXA: the color palette, the same set INBUXA webmail offers. */
|
||||
palette: PaletteId;
|
||||
sidebarOpen: boolean;
|
||||
/** INBUXA: the sidebar folded to a rail of icon tiles, on wide screens. */
|
||||
sidebarCollapsed: boolean;
|
||||
@@ -19,6 +22,7 @@ interface UIState {
|
||||
|
||||
toggleTheme: () => void;
|
||||
setTheme: (theme: Theme) => void;
|
||||
setPalette: (palette: PaletteId) => void;
|
||||
toggleSidebar: () => void;
|
||||
setSidebarOpen: (open: boolean) => void;
|
||||
toggleSidebarCollapsed: () => void;
|
||||
@@ -40,6 +44,7 @@ export const useUIStore = create<UIState>()(
|
||||
typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light',
|
||||
sidebarOpen: typeof window !== 'undefined' ? (window.matchMedia?.('(min-width: 768px)').matches ?? true) : true,
|
||||
sidebarCollapsed: false,
|
||||
palette: DEFAULT_PALETTE,
|
||||
activeSection: '',
|
||||
|
||||
toggleTheme: () => {
|
||||
@@ -61,6 +66,11 @@ export const useUIStore = create<UIState>()(
|
||||
set({ sidebarOpen: open });
|
||||
},
|
||||
|
||||
setPalette: (palette) => {
|
||||
applyPalette(palette);
|
||||
set({ palette });
|
||||
},
|
||||
|
||||
toggleSidebarCollapsed: () => {
|
||||
set({ sidebarCollapsed: !get().sidebarCollapsed });
|
||||
},
|
||||
@@ -73,12 +83,14 @@ export const useUIStore = create<UIState>()(
|
||||
name: 'inbuxa-ui',
|
||||
partialize: (state) => ({
|
||||
theme: state.theme,
|
||||
palette: state.palette,
|
||||
sidebarCollapsed: state.sidebarCollapsed,
|
||||
}),
|
||||
onRehydrateStorage: () => {
|
||||
return (state) => {
|
||||
if (state) {
|
||||
applyThemeClass(state.theme);
|
||||
applyPalette(isPaletteId(state.palette) ? state.palette : DEFAULT_PALETTE);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user