mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Apply global dark mode styling to UI (#43033)
This commit is contained in:
parent
ea9a3352df
commit
8ed339f012
30 changed files with 657 additions and 100 deletions
|
|
@ -252,6 +252,7 @@ const ActionsDropdown = ({
|
|||
}),
|
||||
menu: (provided) => ({
|
||||
...provided,
|
||||
backgroundColor: COLORS["core-fleet-white"],
|
||||
boxShadow: "0 2px 6px rgba(0, 0, 0, 0.1)",
|
||||
borderRadius: "4px",
|
||||
zIndex: 6,
|
||||
|
|
|
|||
|
|
@ -65,6 +65,10 @@
|
|||
// color styles
|
||||
&__white {
|
||||
background-color: $core-fleet-white;
|
||||
|
||||
body.dark-mode & {
|
||||
background-color: $ui-fleet-black-5;
|
||||
}
|
||||
}
|
||||
|
||||
&__grey {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
.target-chip-selector {
|
||||
padding: $pad-small;
|
||||
background-color: $core-fleet-white;
|
||||
color: $ui-fleet-black-75;
|
||||
border: none;
|
||||
box-shadow: inset 0 0 0 1px $ui-fleet-black-25;
|
||||
border-radius: $border-radius-medium;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
.modal {
|
||||
&__background {
|
||||
@include position(fixed, 0 0 0 0);
|
||||
background-color: rgba($core-fleet-black, 0.4);
|
||||
background-color: $core-fleet-black-overlay-40;
|
||||
z-index: 101;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -182,3 +182,64 @@
|
|||
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bdu3f/BwAlfgctduB85QAAAABJRU5ErkJggg==)
|
||||
right repeat-y;
|
||||
}
|
||||
|
||||
/* Dark mode overrides */
|
||||
body.dark-mode .ace_editor.ace-fleet {
|
||||
background-color: #161819;
|
||||
color: #b3b6c1;
|
||||
border-color: #2e3035;
|
||||
}
|
||||
|
||||
body.dark-mode .ace_editor.ace-fleet:hover {
|
||||
border-color: #636777;
|
||||
}
|
||||
|
||||
body.dark-mode .ace_editor.ace-fleet.ace_focus {
|
||||
border-color: #7b79ff;
|
||||
}
|
||||
|
||||
body.dark-mode .ace_editor.ace-fleet.ace_focus .ace_scroller {
|
||||
box-shadow: 1px 1px 1px 1px #7b79ff;
|
||||
}
|
||||
|
||||
body.dark-mode .ace-fleet .ace_content {
|
||||
background: #111214;
|
||||
}
|
||||
|
||||
body.dark-mode .ace-fleet .ace_gutter {
|
||||
background: #111214;
|
||||
color: #636777;
|
||||
border-right-color: #2e3035;
|
||||
}
|
||||
|
||||
body.dark-mode .ace-fleet .ace_gutter-active-line {
|
||||
background-color: #1e2024;
|
||||
}
|
||||
|
||||
body.dark-mode .ace-fleet .ace_cursor {
|
||||
color: #b3b6c1;
|
||||
}
|
||||
|
||||
body.dark-mode .ace-fleet .ace_keyword {
|
||||
color: #b3b6c1;
|
||||
}
|
||||
|
||||
body.dark-mode .ace-fleet .ace_marker-layer .ace_selection {
|
||||
background: rgba(123, 121, 255, 0.2);
|
||||
}
|
||||
|
||||
body.dark-mode .ace-fleet .ace_marker-layer .ace_bracket {
|
||||
border-color: #3e4248;
|
||||
}
|
||||
|
||||
body.dark-mode .ace-fleet .ace_marker-layer .ace_selected-word {
|
||||
border-color: #3e4248;
|
||||
}
|
||||
|
||||
body.dark-mode .ace-fleet .ace_invisible {
|
||||
color: #3e4248;
|
||||
}
|
||||
|
||||
body.dark-mode .ace-fleet .ace_print-margin {
|
||||
background: #1e2024;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
background-color: $core-fleet-white;
|
||||
box-sizing: border-box;
|
||||
border-left: 1px solid $ui-gray;
|
||||
|
||||
body.dark-mode & {
|
||||
background-color: $ui-fleet-black-5;
|
||||
}
|
||||
min-width: 340px;
|
||||
width: 340px;
|
||||
padding: $pad-xxlarge;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ $main-max-width-including-padding: 1280px + 32px + 32px; // Cannot use variables
|
|||
top: 0;
|
||||
right: 0;
|
||||
width: $side-panel-width;
|
||||
height: auto;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
&--selected {
|
||||
font-weight: $bold;
|
||||
background-color: $ui-fleet-black-5;
|
||||
color: $core-fleet-black;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
|
|
|
|||
|
|
@ -18,17 +18,17 @@ $shadow-transition-width: 10px;
|
|||
background-image:
|
||||
/* Shadows */ linear-gradient(
|
||||
to right,
|
||||
white,
|
||||
$core-fleet-white,
|
||||
$transparent
|
||||
),
|
||||
linear-gradient(to left, white, $transparent),
|
||||
linear-gradient(to left, $core-fleet-white, $transparent),
|
||||
/* Shadow covers */
|
||||
linear-gradient(to right, $ui-shadow, white $shadow-transition-width),
|
||||
linear-gradient(to left, $ui-shadow, white $shadow-transition-width);
|
||||
linear-gradient(to right, $ui-shadow, $core-fleet-white $shadow-transition-width),
|
||||
linear-gradient(to left, $ui-shadow, $core-fleet-white $shadow-transition-width);
|
||||
|
||||
background-position: left center, right center, left center, right center;
|
||||
background-repeat: no-repeat;
|
||||
background-color: white;
|
||||
background-color: $core-fleet-white;
|
||||
background-size: $shadow-width 100%, $shadow-width 100%, 50% 100%,
|
||||
50% 100%;
|
||||
|
||||
|
|
@ -431,7 +431,7 @@ $shadow-transition-width: 10px;
|
|||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
background-color: $loading-overlay;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ const TeamsDropdown = ({
|
|||
singleValue: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
...variableSingleValueStyles,
|
||||
color: COLORS["core-fleet-black"],
|
||||
lineHeight: "normal",
|
||||
paddingLeft: 0,
|
||||
paddingRight: "8px",
|
||||
|
|
@ -220,7 +221,8 @@ const TeamsDropdown = ({
|
|||
}),
|
||||
menu: (baseStyles) => ({
|
||||
...baseStyles,
|
||||
boxShadow: "0 2px 6px rgba(0, 0, 0, 0.1)",
|
||||
backgroundColor: COLORS["core-fleet-white"],
|
||||
boxShadow: `0 2px 6px rgba(0, 0, 0, 0.1), 0 0 0 1px ${COLORS["ui-fleet-black-10"]}`,
|
||||
borderRadius: "4px",
|
||||
zIndex: 6,
|
||||
overflow: "hidden",
|
||||
|
|
|
|||
|
|
@ -396,7 +396,7 @@ $base-class: "button";
|
|||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: rgba($core-fleet-black, 0.05);
|
||||
background-color: $core-fleet-black-overlay-05;
|
||||
color: $core-fleet-green-over;
|
||||
|
||||
svg {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ const meta: Meta<typeof DropdownButton> = {
|
|||
values: [
|
||||
{
|
||||
name: "header",
|
||||
value: "linear-gradient(270deg, #201e43 0%, #353d62 100%)",
|
||||
value: "linear-gradient(270deg, #202226 0%, #353840 100%)",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -212,7 +212,8 @@
|
|||
}
|
||||
|
||||
.Select-menu-outer {
|
||||
box-shadow: 0 4px 10px rgba(52, 59, 96, 0.15);
|
||||
background-color: $core-fleet-white;
|
||||
box-shadow: 0 0 0 1px $ui-fleet-black-10;
|
||||
z-index: 6;
|
||||
overflow: hidden;
|
||||
max-height: 198px; // Fits 4 options without scrolling
|
||||
|
|
@ -233,6 +234,7 @@
|
|||
|
||||
.Select-option {
|
||||
color: $core-fleet-black;
|
||||
background-color: transparent;
|
||||
font-size: $x-small;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
|
|
|
|||
|
|
@ -337,6 +337,7 @@ export const generateCustomDropdownStyles = (
|
|||
},
|
||||
singleValue: (provided) => ({
|
||||
...provided,
|
||||
color: COLORS["core-fleet-black"],
|
||||
fontSize: "13px",
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
|
|
@ -352,7 +353,8 @@ export const generateCustomDropdownStyles = (
|
|||
}),
|
||||
menu: (provided) => ({
|
||||
...provided,
|
||||
boxShadow: "0 2px 6px rgba(0, 0, 0, 0.1)",
|
||||
backgroundColor: COLORS["core-fleet-white"],
|
||||
boxShadow: `0 2px 6px rgba(0, 0, 0, 0.1), 0 0 0 1px ${COLORS["ui-fleet-black-10"]}`,
|
||||
borderRadius: "4px",
|
||||
zIndex: 6,
|
||||
overflow: "hidden",
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@ class OrgLogoIcon extends Component {
|
|||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
src: PropTypes.string.isRequired,
|
||||
invertDark: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
src: fleetAvatar,
|
||||
invertDark: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -68,14 +70,16 @@ class OrgLogoIcon extends Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
const { className, invertDark } = this.props;
|
||||
const { imageSrc } = this.state;
|
||||
const { onError } = this;
|
||||
|
||||
const classNames =
|
||||
imageSrc === fleetAvatar
|
||||
? classnames(baseClass, className, "default-fleet-logo")
|
||||
: classnames(baseClass, className);
|
||||
: classnames(baseClass, className, {
|
||||
[`${baseClass}--invert-dark`]: invertDark,
|
||||
});
|
||||
|
||||
return (
|
||||
<img
|
||||
|
|
|
|||
|
|
@ -9,3 +9,8 @@
|
|||
.default-fleet-logo {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
|
||||
body.dark-mode .default-fleet-logo,
|
||||
body.dark-mode .org-logo-icon--invert-dark {
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import React, { useContext } from "react";
|
||||
import React, { useContext, useState, useEffect } from "react";
|
||||
import { Link } from "react-router";
|
||||
import classnames from "classnames";
|
||||
|
||||
import { getPathWithQueryParams, QueryParams } from "utilities/url";
|
||||
import { LEARN_MORE_ABOUT_BASE_LINK } from "utilities/constants";
|
||||
import { isDarkMode } from "utilities/theme";
|
||||
|
||||
import { AppContext } from "context/app";
|
||||
|
||||
|
|
@ -123,6 +124,17 @@ const SiteTopNav = ({
|
|||
isNoAccess,
|
||||
} = useContext(AppContext);
|
||||
|
||||
const [darkMode, setDarkMode] = useState(() => isDarkMode());
|
||||
|
||||
useEffect(() => {
|
||||
const onThemeChange = (e: Event) => {
|
||||
setDarkMode((e as CustomEvent).detail.dark);
|
||||
};
|
||||
window.addEventListener("fleet-theme-change", onThemeChange);
|
||||
return () =>
|
||||
window.removeEventListener("fleet-theme-change", onThemeChange);
|
||||
}, []);
|
||||
|
||||
const isActiveDetailPage = isDetailPage(currentPath);
|
||||
const isActiveGlobalPage = isGlobalPage(currentPath);
|
||||
|
||||
|
|
@ -139,7 +151,10 @@ const SiteTopNav = ({
|
|||
|
||||
const renderNavItem = (navItem: INavItem) => {
|
||||
const { name, iconName, withParams } = navItem;
|
||||
const orgLogoURL = config.org_info.org_logo_url_light_background;
|
||||
const darkLogoURL = config.org_info.org_logo_url;
|
||||
const lightLogoURL = config.org_info.org_logo_url_light_background;
|
||||
const hasDarkLogo = darkLogoURL && darkLogoURL !== lightLogoURL;
|
||||
const orgLogoURL = darkMode && hasDarkLogo ? darkLogoURL : lightLogoURL;
|
||||
const active = navItem.location.regex.test(currentPath);
|
||||
|
||||
const navItemBaseClass = "site-nav-item";
|
||||
|
|
@ -156,7 +171,11 @@ const SiteTopNav = ({
|
|||
to={navItem.location.pathname}
|
||||
>
|
||||
<div className={`${navItemBaseClass}__logo`}>
|
||||
<OrgLogoIcon className="logo" src={orgLogoURL} />
|
||||
<OrgLogoIcon
|
||||
className="logo"
|
||||
src={orgLogoURL}
|
||||
invertDark={!hasDarkLogo}
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -224,7 +224,8 @@ const UserMenu = ({
|
|||
}),
|
||||
menu: (provided) => ({
|
||||
...provided,
|
||||
boxShadow: "0 2px 6px rgba(0, 0, 0, 0.1)",
|
||||
backgroundColor: COLORS["core-fleet-white"],
|
||||
boxShadow: `0 2px 6px rgba(0, 0, 0, 0.1), 0 0 0 1px ${COLORS["ui-fleet-black-10"]}`,
|
||||
borderRadius: "4px",
|
||||
zIndex: 6,
|
||||
marginTop: "7px",
|
||||
|
|
@ -250,7 +251,7 @@ const UserMenu = ({
|
|||
padding: "10px 8px",
|
||||
fontSize: "15px",
|
||||
backgroundColor: getOptionBackgroundColor(state),
|
||||
color: COLORS["tooltip-bg"],
|
||||
color: COLORS["core-fleet-black"],
|
||||
whiteSpace: "nowrap",
|
||||
"&:hover": {
|
||||
backgroundColor: COLORS["ui-fleet-black-5"],
|
||||
|
|
|
|||
1
frontend/components/top_nav/UserMenu/_styles.scss
Normal file
1
frontend/components/top_nav/UserMenu/_styles.scss
Normal file
|
|
@ -0,0 +1 @@
|
|||
// Styles for user-menu are defined inline via react-select's `styles` prop.
|
||||
|
|
@ -7,8 +7,10 @@ import "regenerator-runtime/runtime";
|
|||
import "./public-path";
|
||||
import routes from "./router";
|
||||
import "./index.scss";
|
||||
import { initTheme } from "./utilities/theme";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
initTheme();
|
||||
const { document } = global;
|
||||
const app = document.getElementById("app");
|
||||
const root = createRoot(app);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
greyCell,
|
||||
readableDate,
|
||||
} from "utilities/helpers";
|
||||
import { isDarkMode, toggleDarkMode } from "utilities/theme";
|
||||
|
||||
interface IAccountSidePanelProps {
|
||||
currentUser: IUser;
|
||||
|
|
@ -35,6 +36,7 @@ const AccountSidePanel = ({
|
|||
}: IAccountSidePanelProps): JSX.Element => {
|
||||
const { isPremiumTier, config } = useContext(AppContext);
|
||||
const [versionData, setVersionData] = useState<IVersionData>();
|
||||
const [darkMode, setDarkMode] = useState(() => isDarkMode());
|
||||
|
||||
useEffect(() => {
|
||||
const getVersionData = async () => {
|
||||
|
|
@ -74,6 +76,59 @@ const AccountSidePanel = ({
|
|||
newTab
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__theme-toggle`}>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
className={`${baseClass}__theme-icon`}
|
||||
>
|
||||
<circle
|
||||
cx="8"
|
||||
cy="8"
|
||||
r="3.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
<path
|
||||
d="M8 1v2M8 13v2M1 8h2M13 8h2M3.05 3.05l1.41 1.41M11.54 11.54l1.41 1.41M3.05 12.95l1.41-1.41M11.54 4.46l1.41-1.41"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={darkMode}
|
||||
aria-label="Toggle dark mode"
|
||||
className={`button button--unstyled ${baseClass}__toggle ${
|
||||
darkMode ? `${baseClass}__toggle--active` : ""
|
||||
}`}
|
||||
onClick={() => setDarkMode(toggleDarkMode())}
|
||||
>
|
||||
<div
|
||||
className={`${baseClass}__toggle-dot ${
|
||||
darkMode ? `${baseClass}__toggle-dot--active` : ""
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
className={`${baseClass}__theme-icon`}
|
||||
>
|
||||
<path
|
||||
d="M14.3 10.7A7 7 0 0 1 5.3 1.7 7 7 0 1 0 14.3 10.7Z"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{isPremiumTier && (
|
||||
<DataSet
|
||||
title="Fleets"
|
||||
|
|
|
|||
|
|
@ -46,6 +46,50 @@
|
|||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__theme-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: $pad-small;
|
||||
}
|
||||
|
||||
&__theme-icon {
|
||||
color: $ui-fleet-black-50;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__toggle {
|
||||
transition: background-color 150ms ease-in-out;
|
||||
background-color: $ui-fleet-black-50;
|
||||
border-radius: 12px;
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
min-width: 35px;
|
||||
width: 35px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&--active {
|
||||
background-color: $core-fleet-green;
|
||||
}
|
||||
}
|
||||
|
||||
&__toggle-dot {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
transition: left 150ms ease-in-out;
|
||||
border-radius: 50%;
|
||||
background-color: $core-fleet-white;
|
||||
|
||||
&--active {
|
||||
left: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
&__version {
|
||||
font-size: $xx-small;
|
||||
text-align: center;
|
||||
|
|
|
|||
|
|
@ -203,7 +203,8 @@ const ActivityTypeDropdown = ({
|
|||
...generateCustomDropdownStyles(),
|
||||
menu: (provided) => ({
|
||||
...provided,
|
||||
boxShadow: "0 2px 6px rgba(0, 0, 0, 0.1)",
|
||||
backgroundColor: COLORS["core-fleet-white"],
|
||||
boxShadow: `0 0 0 1px ${COLORS["ui-fleet-black-10"]}`,
|
||||
borderRadius: "4px",
|
||||
zIndex: 6,
|
||||
overflow: "hidden",
|
||||
|
|
|
|||
|
|
@ -37,6 +37,12 @@
|
|||
gap: $pad-xsmall;
|
||||
}
|
||||
|
||||
&__card-icon {
|
||||
body.dark-mode & {
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
&__count {
|
||||
font-size: $large;
|
||||
white-space: nowrap;
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@
|
|||
margin-top: 0;
|
||||
z-index: 2;
|
||||
animation: fade-in 150ms ease-out;
|
||||
background-color: $core-fleet-white;
|
||||
}
|
||||
|
||||
.label-filter-select__menu-list {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ html {
|
|||
|
||||
body {
|
||||
color: $ui-fleet-black-75;
|
||||
background-color: $core-fleet-white;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Inter", sans-serif;
|
||||
|
|
@ -16,6 +17,26 @@ body {
|
|||
line-height: 1.5;
|
||||
}
|
||||
|
||||
// Applied briefly by theme.ts during toggle to fade all colors smoothly.
|
||||
// Removed after 300ms so it doesn't interfere with normal interactions.
|
||||
// Only applies when the user hasn't requested reduced motion.
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
body.theme-transition,
|
||||
body.theme-transition *,
|
||||
body.theme-transition *::before,
|
||||
body.theme-transition *::after {
|
||||
transition: background-color 300ms ease, color 300ms ease,
|
||||
border-color 300ms ease, box-shadow 300ms ease, fill 300ms ease,
|
||||
stroke 300ms ease !important;
|
||||
}
|
||||
|
||||
// Hide the gradient image during theme switch so background-color
|
||||
// can transition smoothly without the gradient snapping.
|
||||
body.theme-transition .core-wrapper {
|
||||
background-image: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
|
|
@ -218,30 +239,35 @@ input {
|
|||
}
|
||||
|
||||
// syntax highlighting for pretty-printed JSON
|
||||
// Pinned to dark palette — code blocks stay dark in both modes.
|
||||
pre {
|
||||
padding: $pad-large;
|
||||
background-color: $core-fleet-black;
|
||||
color: $core-fleet-white;
|
||||
background-color: $static-black;
|
||||
color: $static-white;
|
||||
border-radius: 4px;
|
||||
white-space: pre-wrap;
|
||||
|
||||
.string {
|
||||
color: $rainbow-green;
|
||||
color: #63c740;
|
||||
}
|
||||
.number {
|
||||
color: $rainbow-orange;
|
||||
color: #faa669;
|
||||
}
|
||||
.boolean {
|
||||
color: $rainbow-blue;
|
||||
color: #5cabdf;
|
||||
}
|
||||
.null {
|
||||
color: magenta;
|
||||
}
|
||||
.key {
|
||||
color: $core-fleet-white;
|
||||
color: $static-white;
|
||||
}
|
||||
}
|
||||
|
||||
body.dark-mode pre {
|
||||
background-color: #111214;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-top: $pad-xlarge;
|
||||
margin-bottom: $pad-xlarge;
|
||||
|
|
@ -258,3 +284,64 @@ dl {
|
|||
dd {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// Dark mode: remap illustration SVG fill/stroke colors via attribute
|
||||
// selectors so light-palette graphics adapt to dark backgrounds.
|
||||
// Grouped by luminance band — light backgrounds darken, accents stay.
|
||||
// Uses [attr] on any element (path, rect, circle, etc.).
|
||||
body.dark-mode .empty-table__image-wrapper {
|
||||
// Very light fills (near-white backgrounds) → dark surface
|
||||
[fill="#F7F8FC" i],
|
||||
[fill="#F0F0FF" i],
|
||||
[fill="#F1F0FF" i],
|
||||
[fill="#F8F8FF" i],
|
||||
[fill="#F9FAFC" i],
|
||||
[fill="#EDEEF2" i],
|
||||
[fill="#fff" i],
|
||||
[fill="#ffffff" i],
|
||||
[fill="white"] {
|
||||
fill: #252830;
|
||||
}
|
||||
|
||||
// Light decorative fills → dark decorative
|
||||
[fill="#E1E1FF" i],
|
||||
[fill="#E0E7F9" i],
|
||||
[fill="#E9E9FF" i],
|
||||
[fill="#F1F1FD" i],
|
||||
[fill="#E3E3FE" i],
|
||||
[fill="#E0E3E3" i],
|
||||
[fill="#D3E8F3" i] {
|
||||
fill: #282b50;
|
||||
}
|
||||
|
||||
// Medium fills → muted dark
|
||||
[fill="#C9D5F3" i],
|
||||
[fill="#D2D2FF" i],
|
||||
[fill="#D9D9FE" i],
|
||||
[fill="#E2E4EA" i],
|
||||
[fill="#C5C7D1" i],
|
||||
[fill="#CFCFF3" i],
|
||||
[fill="#C3C3EE" i],
|
||||
[fill="#E6E6F7" i],
|
||||
[fill="#D7DAF5" i],
|
||||
[fill="#C0CCCF" i],
|
||||
[fill="#B0B2E7" i] {
|
||||
fill: #363a62;
|
||||
}
|
||||
|
||||
// Medium strokes → subtle dark borders
|
||||
[stroke="#D3DAE4" i],
|
||||
[stroke="#CDCDF5" i],
|
||||
[stroke="#C8D1F2" i],
|
||||
[stroke="#CBCFF2" i],
|
||||
[stroke="#CECEF5" i],
|
||||
[stroke="#E2E2FE" i],
|
||||
[stroke="#C5C7D1" i] {
|
||||
stroke: #3e4268;
|
||||
}
|
||||
|
||||
// Dark text fills → lighten for contrast on dark bg
|
||||
[fill="#8B8FA2" i] {
|
||||
fill: #b3b6c1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,81 +1,279 @@
|
|||
// =============================================================================
|
||||
// CSS Custom Properties — the single source of truth for all themed colors.
|
||||
// Light mode values live on :root; dark mode overrides live on body.dark-mode.
|
||||
// SCSS variables below are thin aliases so every existing stylesheet just works.
|
||||
// =============================================================================
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Light mode (default)
|
||||
// ---------------------------------------------------------------------------
|
||||
:root {
|
||||
// 2025 branding
|
||||
--core-fleet-black: #192147;
|
||||
--core-fleet-green: #009a7d;
|
||||
--core-fleet-white: #ffffff;
|
||||
--ui-fleet-black-75: #515774;
|
||||
--ui-fleet-black-50: #8b8fa2;
|
||||
--ui-fleet-black-33: #b3b6c1;
|
||||
--ui-fleet-black-25: #c5c7d1;
|
||||
--ui-fleet-black-10: #e2e4ea;
|
||||
--ui-fleet-black-5: #f4f4f6;
|
||||
|
||||
// 2025 secondary / interaction colors
|
||||
--ui-fleet-black-75-over: #454c66;
|
||||
--ui-fleet-black-75-down: #3a3e59;
|
||||
--core-fleet-green-over: #00886c;
|
||||
--core-fleet-green-down: #00775f;
|
||||
--ui-fleet-black-5-down: #f0f1f4;
|
||||
|
||||
// Core accent
|
||||
--core-fleet-blue: #3e4771;
|
||||
--core-vibrant-blue: #6a67fe;
|
||||
--core-vibrant-red: #ff5c83;
|
||||
--core-fleet-purple: #ae6ddf;
|
||||
--core-dark-blue-grey: #506e92;
|
||||
--site-nav-on-hover: #0e1533;
|
||||
|
||||
// UI
|
||||
--ui-fleet-blue-10: #f9fafc;
|
||||
--ui-dark-blue-gray: #afbec1;
|
||||
--ui-blue-gray: #dbe3e5;
|
||||
--ui-gray: #e3e3e3;
|
||||
--ui-light-grey: #fafafa;
|
||||
--ui-off-white: #f9fafc;
|
||||
--ui-shadow: #e9e9e9;
|
||||
--ui-vibrant-blue-50: rgba(106, 103, 254, 0.5);
|
||||
--ui-vibrant-blue-25: #d9d9fe;
|
||||
--ui-vibrant-blue-10: #f1f0ff;
|
||||
--tooltip-bg: #3e4771;
|
||||
|
||||
// Notifications & status
|
||||
--ui-offline: #8b8fa2;
|
||||
--ui-success: #3db67b;
|
||||
--ui-warning: #ebbc43;
|
||||
--ui-error: #d66c7b;
|
||||
--ui-info: #6a67fe;
|
||||
--ui-yellow-banner: #fef7e0;
|
||||
--ui-yellow-banner-outline: #ece0bb;
|
||||
|
||||
// TS COLORS aliases (match keys used in colors.ts for JS inline styles)
|
||||
--core-fleet-red: #ff5c83;
|
||||
--ui-blue-hover: #5d5ae7;
|
||||
--ui-blue-pressed: #4b4ab4;
|
||||
--ui-blue-50: rgba(106, 103, 254, 0.5);
|
||||
--ui-blue-25: #d9d9fe;
|
||||
--ui-blue-10: #f1f0ff;
|
||||
--status-success: #3db67b;
|
||||
--status-warning: #f8cd6b;
|
||||
--status-error: #ed6e85;
|
||||
|
||||
// Rainbow
|
||||
--rainbow-orange: #faa669;
|
||||
--rainbow-green: #63c740;
|
||||
--rainbow-blue: #5cabdf;
|
||||
|
||||
// Gradients
|
||||
--gradient-background: #eff6fa;
|
||||
|
||||
// Button hover / active
|
||||
--core-vibrant-red-over: #e93661;
|
||||
--core-vibrant-red-down: #cb3559;
|
||||
--core-vibrant-blue-over: #5d5ae7;
|
||||
--core-vibrant-blue-down: #4b4ab4;
|
||||
--ui-success-over: #36a26d;
|
||||
--ui-success-down: #2b7f56;
|
||||
--core-fleet-blue-over: #303860;
|
||||
--core-fleet-blue-down: #192147;
|
||||
|
||||
// Overlay / semi-transparent (used in Modal & Button)
|
||||
--core-fleet-black-overlay-40: rgba(25, 33, 71, 0.4);
|
||||
--core-fleet-black-overlay-05: rgba(25, 33, 71, 0.05);
|
||||
--loading-overlay: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dark mode
|
||||
// ---------------------------------------------------------------------------
|
||||
body.dark-mode {
|
||||
// Branding — text lightens, backgrounds darken
|
||||
// Surface hierarchy (darkest → lightest):
|
||||
// Base #181a1f → Surface-0 #1e2128 → Surface-1 #252830
|
||||
// → Surface-2 #32363e → Surface-3 #42464f
|
||||
--core-fleet-black: #e2e4ea;
|
||||
--core-fleet-green: #009a7d;
|
||||
--core-fleet-white: #181a1f;
|
||||
--ui-fleet-black-75: #b3b6c1;
|
||||
--ui-fleet-black-50: #8b8fa2;
|
||||
--ui-fleet-black-33: #636777;
|
||||
--ui-fleet-black-25: #42464f;
|
||||
--ui-fleet-black-10: #32363e;
|
||||
--ui-fleet-black-5: #252830;
|
||||
|
||||
// Secondary / interaction
|
||||
--ui-fleet-black-75-over: #c5c7d1;
|
||||
--ui-fleet-black-75-down: #d5d7de;
|
||||
--core-fleet-green-over: #01a889;
|
||||
--core-fleet-green-down: #02be9c;
|
||||
--ui-fleet-black-5-down: #2c2f37;
|
||||
|
||||
// Core accent — slightly brighter for dark-bg contrast
|
||||
--core-fleet-blue: #6a6f8a;
|
||||
--core-vibrant-blue: #7b79ff;
|
||||
--core-vibrant-red: #ff7a9a;
|
||||
--core-fleet-purple: #c08ae8;
|
||||
--core-dark-blue-grey: #6e8eaf;
|
||||
--site-nav-on-hover: #252830;
|
||||
|
||||
// UI
|
||||
--ui-fleet-blue-10: #1e2128;
|
||||
--ui-dark-blue-gray: #4a5560;
|
||||
--ui-blue-gray: #2d313a;
|
||||
--ui-gray: #32363e;
|
||||
--ui-light-grey: #1e2128;
|
||||
--ui-off-white: #1e2128;
|
||||
--ui-shadow: #0e1014;
|
||||
--ui-vibrant-blue-50: rgba(123, 121, 255, 0.3);
|
||||
--ui-vibrant-blue-25: #282736;
|
||||
--ui-vibrant-blue-10: #1e1f2a;
|
||||
--tooltip-bg: #353940;
|
||||
|
||||
// Notifications & status — slightly brighter
|
||||
--ui-offline: #8b8fa2;
|
||||
--ui-success: #4dc98b;
|
||||
--ui-warning: #f0ca5e;
|
||||
--ui-error: #e07888;
|
||||
--ui-info: #7b79ff;
|
||||
--ui-yellow-banner: #2e291d;
|
||||
--ui-yellow-banner-outline: #4e4125;
|
||||
|
||||
// TS COLORS aliases
|
||||
--core-fleet-red: #ff7a9a;
|
||||
--ui-blue-hover: #8c8aff;
|
||||
--ui-blue-pressed: #6b69d9;
|
||||
--ui-blue-50: rgba(123, 121, 255, 0.3);
|
||||
--ui-blue-25: #282736;
|
||||
--ui-blue-10: #1e1f2a;
|
||||
--status-success: #4dc98b;
|
||||
--status-warning: #f0ca5e;
|
||||
--status-error: #e07888;
|
||||
|
||||
// Rainbow
|
||||
--rainbow-orange: #fbb580;
|
||||
--rainbow-green: #75d455;
|
||||
--rainbow-blue: #70bbea;
|
||||
|
||||
// Gradients — slightly lighter top for subtle depth
|
||||
--gradient-background: #1c1f25;
|
||||
|
||||
// Button hover / active
|
||||
--core-vibrant-red-over: #ff8da5;
|
||||
--core-vibrant-red-down: #e07090;
|
||||
--core-vibrant-blue-over: #8c8aff;
|
||||
--core-vibrant-blue-down: #6b69d9;
|
||||
--ui-success-over: #5dd99b;
|
||||
--ui-success-down: #4dc98b;
|
||||
--core-fleet-blue-over: #7a7f96;
|
||||
--core-fleet-blue-down: #e2e4ea;
|
||||
|
||||
// Overlays
|
||||
--core-fleet-black-overlay-40: rgba(0, 0, 0, 0.6);
|
||||
--core-fleet-black-overlay-05: rgba(226, 228, 234, 0.06);
|
||||
--loading-overlay: rgba(24, 26, 31, 0.8);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SCSS variable aliases — every existing component stylesheet keeps working.
|
||||
// =============================================================================
|
||||
|
||||
// 2025 branding
|
||||
$core-fleet-black: #192147;
|
||||
$core-fleet-green: #009a7d;
|
||||
$core-fleet-white: #ffffff;
|
||||
$ui-fleet-black-75: #515774;
|
||||
$ui-fleet-black-50: #8b8fa2;
|
||||
$ui-fleet-black-33: #b3b6c1;
|
||||
$ui-fleet-black-25: #c5c7d1;
|
||||
$ui-fleet-black-10: #e2e4ea;
|
||||
$ui-fleet-black-5: #f4f4f6;
|
||||
$core-focused-outline: $core-fleet-black;
|
||||
$core-fleet-black: var(--core-fleet-black);
|
||||
$core-fleet-green: var(--core-fleet-green);
|
||||
$core-fleet-white: var(--core-fleet-white);
|
||||
$ui-fleet-black-75: var(--ui-fleet-black-75);
|
||||
$ui-fleet-black-50: var(--ui-fleet-black-50);
|
||||
$ui-fleet-black-33: var(--ui-fleet-black-33);
|
||||
$ui-fleet-black-25: var(--ui-fleet-black-25);
|
||||
$ui-fleet-black-10: var(--ui-fleet-black-10);
|
||||
$ui-fleet-black-5: var(--ui-fleet-black-5);
|
||||
$core-focused-outline: var(--core-fleet-black);
|
||||
|
||||
// 2025 secondary colors
|
||||
$ui-fleet-black-75-over: darken(#515774, 5%);
|
||||
$ui-fleet-black-75-down: darken(#515774, 10%);
|
||||
$core-fleet-green-over: darken(#009a7d, 5%);
|
||||
$core-fleet-green-down: darken(#009a7d, 10%);
|
||||
$ui-fleet-black-5-down: #f0f1f4; // As specified in design system
|
||||
$ui-fleet-black-75-over: var(--ui-fleet-black-75-over);
|
||||
$ui-fleet-black-75-down: var(--ui-fleet-black-75-down);
|
||||
$core-fleet-green-over: var(--core-fleet-green-over);
|
||||
$core-fleet-green-down: var(--core-fleet-green-down);
|
||||
$ui-fleet-black-5-down: var(--ui-fleet-black-5-down);
|
||||
|
||||
// Core
|
||||
$core-fleet-blue: #3e4771;
|
||||
$core-vibrant-blue: #6a67fe;
|
||||
$core-vibrant-red: #ff5c83;
|
||||
$core-fleet-purple: #ae6ddf;
|
||||
$core-dark-blue-grey: #506e92;
|
||||
$site-nav-on-hover: #0e1533;
|
||||
$core-fleet-blue: var(--core-fleet-blue);
|
||||
$core-vibrant-blue: var(--core-vibrant-blue);
|
||||
$core-vibrant-red: var(--core-vibrant-red);
|
||||
$core-fleet-purple: var(--core-fleet-purple);
|
||||
$core-dark-blue-grey: var(--core-dark-blue-grey);
|
||||
$site-nav-on-hover: var(--site-nav-on-hover);
|
||||
|
||||
// UI
|
||||
$ui-fleet-blue-10: #f9fafc;
|
||||
$ui-dark-blue-gray: #afbec1;
|
||||
$ui-blue-gray: #dbe3e5;
|
||||
$ui-gray: #e3e3e3;
|
||||
$ui-light-grey: #fafafa;
|
||||
$ui-off-white: #f9fafc; // rgba(249, 250, 252, 1)
|
||||
$ui-shadow: #e9e9e9;
|
||||
$ui-vibrant-blue-50: rgba(106, 103, 254, 0.5);
|
||||
$ui-vibrant-blue-25: #d9d9fe;
|
||||
$ui-vibrant-blue-10: #f1f0ff; // rgba(241, 240, 255, 1)
|
||||
$tooltip-bg: #3e4771;
|
||||
$ui-fleet-blue-10: var(--ui-fleet-blue-10);
|
||||
$ui-dark-blue-gray: var(--ui-dark-blue-gray);
|
||||
$ui-blue-gray: var(--ui-blue-gray);
|
||||
$ui-gray: var(--ui-gray);
|
||||
$ui-light-grey: var(--ui-light-grey);
|
||||
$ui-off-white: var(--ui-off-white);
|
||||
$ui-shadow: var(--ui-shadow);
|
||||
$ui-vibrant-blue-50: var(--ui-vibrant-blue-50);
|
||||
$ui-vibrant-blue-25: var(--ui-vibrant-blue-25);
|
||||
$ui-vibrant-blue-10: var(--ui-vibrant-blue-10);
|
||||
$tooltip-bg: var(--tooltip-bg);
|
||||
|
||||
// Notifications & status & specific messages
|
||||
$ui-offline: #8b8fa2;
|
||||
$ui-success: #3db67b;
|
||||
$ui-warning: #ebbc43;
|
||||
$ui-error: #d66c7b;
|
||||
$ui-info: #6a67fe;
|
||||
$ui-yellow-banner: #fef7e0;
|
||||
$ui-yellow-banner-outline: #ece0bb;
|
||||
$ui-offline: var(--ui-offline);
|
||||
$ui-success: var(--ui-success);
|
||||
$ui-warning: var(--ui-warning);
|
||||
$ui-error: var(--ui-error);
|
||||
$ui-info: var(--ui-info);
|
||||
$ui-yellow-banner: var(--ui-yellow-banner);
|
||||
$ui-yellow-banner-outline: var(--ui-yellow-banner-outline);
|
||||
|
||||
// Rainbow
|
||||
$rainbow-orange: #faa669;
|
||||
$rainbow-green: #63c740;
|
||||
$rainbow-blue: #5cabdf;
|
||||
$rainbow-orange: var(--rainbow-orange);
|
||||
$rainbow-green: var(--rainbow-green);
|
||||
$rainbow-blue: var(--rainbow-blue);
|
||||
|
||||
// Gradients
|
||||
$gradient-background: #eff6fa; // 2025 gradient start color
|
||||
$gradients-dark-gradient: linear-gradient(270deg, #201e43 0%, #353d62 100%);
|
||||
$gradient-background: var(--gradient-background);
|
||||
// These gradients are already dark; no theme swap needed.
|
||||
$gradients-dark-gradient: linear-gradient(270deg, #202226 0%, #353840 100%);
|
||||
$gradients-dark-gradient-vertical: linear-gradient(
|
||||
360deg,
|
||||
#201e43 0%,
|
||||
#353d62 100%
|
||||
#202226 0%,
|
||||
#353840 100%
|
||||
);
|
||||
$gradients-bright-gradient: linear-gradient(180deg, #ae6ddf 0%, #6a67fe 100%);
|
||||
|
||||
// Colors for over (hover) and down (active) buttons styles
|
||||
$core-vibrant-red-over: #e93661;
|
||||
$core-vibrant-red-down: #cb3559;
|
||||
$core-vibrant-blue-over: #5d5ae7;
|
||||
$core-vibrant-blue-down: #4b4ab4;
|
||||
$ui-success-over: #36a26d;
|
||||
$ui-success-down: #2b7f56;
|
||||
$core-fleet-blue-over: #303860;
|
||||
$core-fleet-blue-down: $core-fleet-black;
|
||||
// Colors for over (hover) and down (active) button styles
|
||||
$core-vibrant-red-over: var(--core-vibrant-red-over);
|
||||
$core-vibrant-red-down: var(--core-vibrant-red-down);
|
||||
$core-vibrant-blue-over: var(--core-vibrant-blue-over);
|
||||
$core-vibrant-blue-down: var(--core-vibrant-blue-down);
|
||||
$ui-success-over: var(--ui-success-over);
|
||||
$ui-success-down: var(--ui-success-down);
|
||||
$core-fleet-blue-over: var(--core-fleet-blue-over);
|
||||
$core-fleet-blue-down: var(--core-fleet-blue-down);
|
||||
|
||||
$transparent: rgba(255, 255, 255, 0);
|
||||
|
||||
// Opaque colors for table shadows
|
||||
// l = lowest color a = (255-l)/255
|
||||
// Overlay aliases (replace inline rgba($core-fleet-black, …) calls)
|
||||
$core-fleet-black-overlay-40: var(--core-fleet-black-overlay-40);
|
||||
$core-fleet-black-overlay-05: var(--core-fleet-black-overlay-05);
|
||||
$loading-overlay: var(--loading-overlay);
|
||||
|
||||
// Static colors — not themed, same in both light and dark mode.
|
||||
// Use for elements that are always dark surfaces with light text (tooltips, code blocks).
|
||||
$static-white: #e8eaf0;
|
||||
$static-black: #192147;
|
||||
|
||||
// Opaque colors for table shadows — compile-time SCSS math, not themed.
|
||||
// These are subtle edge effects; dark-mode polish can refine them later.
|
||||
$ui-vibrant-blue-10-alpha: (255-240)/255; // rgba(241, 240, 255)
|
||||
$ui-vibrant-blue-10-opaque: rgba(
|
||||
(241-240) / $ui-vibrant-blue-10-alpha,
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
export type Colors = keyof typeof COLORS;
|
||||
|
||||
export const COLORS = {
|
||||
// Static fallback values (used during SSR or if CSS custom property is missing)
|
||||
const STATIC_COLORS = {
|
||||
// 2025 branding
|
||||
"core-fleet-black": "#192147", // Headers, thead, Field :focus outline, keyboard :focus-visible outline
|
||||
"core-fleet-black": "#192147",
|
||||
"core-fleet-green": "#009A7D",
|
||||
"core-fleet-white": "#FFFFFF",
|
||||
"ui-fleet-black-75": "#515774",
|
||||
"ui-fleet-black-50": "#8B8FA2", // Field :hover borders
|
||||
"ui-fleet-black-50": "#8B8FA2",
|
||||
"ui-fleet-black-33": "#B3B6C1",
|
||||
"ui-fleet-black-25": "#C5C7D1",
|
||||
"ui-fleet-black-10": "#E2E4EA", // Field borders, card borders
|
||||
"ui-fleet-black-10": "#E2E4EA",
|
||||
"ui-fleet-black-5": "#F4F4F6",
|
||||
|
||||
// 2025 secondary colors
|
||||
// Sass functions only work in SCSS, not in runtime TypeScript or JavaScript files
|
||||
"ui-fleet-black-75-over": "#454C66", // darken(#515774, 5%) or color.adjust(#515774, $lightness: -5%)
|
||||
"ui-fleet-black-75-down": "#3A3E59", // darken(#515774, 10%) or color.adjust(#515774, $lightness: -10%)
|
||||
"core-fleet-green-over": "#00886C", // "darken(#009A7D, 5%)" or color.adjust(#009A7D, $lightness: -5%)
|
||||
"core-fleet-green-down": "#00775F", // "darken(#009A7D, 10%)" or color.adjust(#009A7D, $lightness: -10%)
|
||||
"ui-fleet-black-75-over": "#454C66",
|
||||
"ui-fleet-black-75-down": "#3A3E59",
|
||||
"core-fleet-green-over": "#00886C",
|
||||
"core-fleet-green-down": "#00775F",
|
||||
|
||||
// core colors
|
||||
"core-fleet-blue": "#6A67FE", // TODO: lots of work to correctly match scss core-fleet-blue and not ui-vibrant-blue
|
||||
"core-fleet-blue": "#3E4771",
|
||||
"core-fleet-red": "#FF5C83",
|
||||
"core-fleet-purple": "#AE6DDF",
|
||||
|
||||
// ui colors
|
||||
"core-vibrant-blue": "#6A67FE",
|
||||
"core-vibrant-red": "#FF5C83",
|
||||
"ui-off-white": "#F9FAFC",
|
||||
"ui-blue-hover": "#5D5AE7",
|
||||
"ui-blue-pressed": "#4B4AB4",
|
||||
|
|
@ -35,6 +35,7 @@ export const COLORS = {
|
|||
"ui-light-grey": "#FAFAFA",
|
||||
"ui-error": "#d66c7b",
|
||||
"ui-warning": "#ebbc43",
|
||||
"ui-fleet-black-5-down": "#F0F1F4",
|
||||
|
||||
// Notifications & status
|
||||
"status-success": "#3DB67B",
|
||||
|
|
@ -45,4 +46,26 @@ export const COLORS = {
|
|||
"core-vibrant-blue-down": "#4b4ab4",
|
||||
"ui-vibrant-blue-25": "#d9d9fe",
|
||||
"ui-vibrant-blue-10": "#f1f0ff",
|
||||
};
|
||||
} as const;
|
||||
|
||||
export type Colors = keyof typeof STATIC_COLORS;
|
||||
|
||||
// Proxy returns CSS var() references so inline JS styles automatically adapt
|
||||
// to light/dark mode without calling getComputedStyle. Falls back to the
|
||||
// static value when running outside a browser (e.g. SSR or tests in jsdom).
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const COLORS: Record<Colors, string> = new Proxy(
|
||||
STATIC_COLORS as Record<Colors, string>,
|
||||
{
|
||||
get(target, key, receiver) {
|
||||
if (typeof key === "symbol") {
|
||||
return Reflect.get(target, key, receiver);
|
||||
}
|
||||
const fallback = target[key as Colors] ?? "";
|
||||
if (typeof document !== "undefined") {
|
||||
return fallback ? `var(--${key}, ${fallback})` : `var(--${key})`;
|
||||
}
|
||||
return fallback;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ $max-width: 2560px;
|
|||
width: max-content;
|
||||
max-width: 360px;
|
||||
padding: 6px;
|
||||
color: $core-fleet-white;
|
||||
color: $static-white;
|
||||
background-color: $tooltip-bg;
|
||||
font-weight: $regular;
|
||||
font-size: $xx-small;
|
||||
|
|
@ -541,8 +541,8 @@ $max-width: 2560px;
|
|||
}
|
||||
|
||||
@mixin gradient-background {
|
||||
background: linear-gradient(180deg, #eff6fa 0%, #fff 100%);
|
||||
background-image: linear-gradient(180deg, $gradient-background 0%, $core-fleet-white 100%);
|
||||
background-size: 100% 150px; // Must do this or gradient can be different heights on different pages
|
||||
background-repeat: no-repeat;
|
||||
background-color: #fff; // area below gradient will just be white
|
||||
background-color: $core-fleet-white; // area below gradient — transitionable
|
||||
}
|
||||
|
|
|
|||
32
frontend/utilities/theme.ts
Normal file
32
frontend/utilities/theme.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
const THEME_KEY = "fleet-dark-mode";
|
||||
const TRANSITION_MS = 300;
|
||||
|
||||
export const isDarkMode = (): boolean => {
|
||||
return localStorage.getItem(THEME_KEY) === "true";
|
||||
};
|
||||
|
||||
export const toggleDarkMode = (): boolean => {
|
||||
const dark = !isDarkMode();
|
||||
localStorage.setItem(THEME_KEY, String(dark));
|
||||
|
||||
// Add a temporary class that applies a blanket transition to all elements
|
||||
// so the entire UI fades smoothly instead of individual pieces snapping.
|
||||
document.body.classList.add("theme-transition");
|
||||
document.body.classList.toggle("dark-mode", dark);
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.classList.remove("theme-transition");
|
||||
}, TRANSITION_MS);
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("fleet-theme-change", { detail: { dark } })
|
||||
);
|
||||
|
||||
return dark;
|
||||
};
|
||||
|
||||
export const initTheme = (): void => {
|
||||
if (isDarkMode()) {
|
||||
document.body.classList.add("dark-mode");
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue