mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-27 12:49:03 +03:00
590 lines
23 KiB
TypeScript
590 lines
23 KiB
TypeScript
import Fade from "@mui/material/Fade";
|
|
import { alpha, createTheme, type Theme } from "@mui/material/styles";
|
|
|
|
// Two layered palettes, one per mode. Surface and elevated tones differ
|
|
// by ~5 luminance steps so the eye can pick out cards/dialogs from the
|
|
// page; the rest of the UI references this set so flipping `mode` flips
|
|
// every component override at once.
|
|
export type ThemeMode = "dark" | "light";
|
|
|
|
const DARK = {
|
|
surface: "#141414", // page / cards / paper / tables
|
|
elevated: "#191919", // app bar replacement, table heads, side bar
|
|
elevated2: "#1f1f1f", // hover states for table rows
|
|
// Borders bumped a touch (0.06 → 0.10 / 0.14 → 0.20) so outlined
|
|
// cards and table cell borders are actually visible against the
|
|
// very dark surface.
|
|
border: "rgba(255,255,255,0.10)",
|
|
borderStrong: "rgba(255,255,255,0.20)",
|
|
textPrimary: "#f5f5f5",
|
|
// Secondary / caption text was washing out at 0.66 — bumped to 0.80
|
|
// so subtitles, helper text and uppercase labels stay legible.
|
|
textSecondary: "rgba(255,255,255,0.80)",
|
|
scrollbar: "rgba(255,255,255,0.10)",
|
|
scrollbarHover: "rgba(255,255,255,0.18)",
|
|
iconButtonHover: "rgba(255,255,255,0.07)",
|
|
inputBg: "rgba(255,255,255,0.02)",
|
|
chipBg: "rgba(255,255,255,0.06)",
|
|
listItemHover: "rgba(255,255,255,0.04)",
|
|
tableHeadColor: "rgba(255,255,255,0.78)",
|
|
tooltipBg: "#222",
|
|
tooltipText: "#fff",
|
|
dialogShadow: "0 24px 48px rgba(0,0,0,0.55)",
|
|
} as const;
|
|
|
|
const LIGHT = {
|
|
surface: "#ffffff",
|
|
elevated: "#f5f7fa",
|
|
elevated2: "#e9edf3",
|
|
// Borders deep enough that outlined cards / table rows read clearly
|
|
// on pure-white surfaces.
|
|
border: "rgba(15,23,42,0.18)",
|
|
borderStrong: "rgba(15,23,42,0.32)",
|
|
textPrimary: "#0f172a",
|
|
// Secondary / caption text — pushed up to 0.82 so helper text,
|
|
// subtitles and chips stop looking washed out on white.
|
|
textSecondary: "rgba(15,23,42,0.82)",
|
|
scrollbar: "rgba(15,23,42,0.26)",
|
|
scrollbarHover: "rgba(15,23,42,0.40)",
|
|
iconButtonHover: "rgba(15,23,42,0.07)",
|
|
inputBg: "#ffffff",
|
|
chipBg: "rgba(15,23,42,0.10)",
|
|
listItemHover: "rgba(15,23,42,0.06)",
|
|
tableHeadColor: "rgba(15,23,42,0.82)",
|
|
tooltipBg: "#1e293b",
|
|
tooltipText: "#fff",
|
|
dialogShadow: "0 24px 48px rgba(15,23,42,0.18)",
|
|
} as const;
|
|
|
|
function colors(mode: ThemeMode) {
|
|
return mode === "light" ? LIGHT : DARK;
|
|
}
|
|
|
|
// Default accent. The user can override via the in-app color picker.
|
|
export const DEFAULT_ACCENT = "#3b82f6";
|
|
export const DEFAULT_MODE: ThemeMode = "dark";
|
|
|
|
// isHexColor reports whether the string looks like a 3- or 6-digit hex CSS
|
|
// color, e.g. "#fff" / "#7c83ff". Used to validate user-supplied accent
|
|
// values from localStorage / the picker before feeding them to MUI.
|
|
export function isHexColor(s: string): boolean {
|
|
return /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(s);
|
|
}
|
|
|
|
// pickContrastingTextColor returns "#0c0c0c" or "#ffffff" depending on which
|
|
// has better contrast against the given background. Used so primary buttons
|
|
// stay readable regardless of the chosen accent.
|
|
export function pickContrastingTextColor(hex: string): string {
|
|
const norm =
|
|
hex.length === 4
|
|
? "#" +
|
|
hex
|
|
.slice(1)
|
|
.split("")
|
|
.map((c) => c + c)
|
|
.join("")
|
|
: hex;
|
|
const r = parseInt(norm.slice(1, 3), 16);
|
|
const g = parseInt(norm.slice(3, 5), 16);
|
|
const b = parseInt(norm.slice(5, 7), 16);
|
|
// Relative luminance per WCAG, rough approximation.
|
|
const lum = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
|
|
return lum > 0.55 ? "#0c0c0c" : "#ffffff";
|
|
}
|
|
|
|
// ThemePalette exposes only the tokens callers actually read directly.
|
|
// MUI's own theme covers the rest (accent goes through `var(--sb-accent)`,
|
|
// borderStrong / elevated2 / accentSoft are only consumed inside the
|
|
// MUI theme overrides below, never via this struct).
|
|
export interface ThemePalette {
|
|
mode: ThemeMode;
|
|
surface: string;
|
|
elevated: string;
|
|
border: string;
|
|
}
|
|
|
|
// buildTheme produces a fresh MUI theme using the given accent color and
|
|
// theme mode. All component overrides reference the dynamic accent + mode
|
|
// palette so the entire UI updates the moment either changes.
|
|
export function buildTheme(
|
|
accentInput: string = DEFAULT_ACCENT,
|
|
modeInput: ThemeMode = DEFAULT_MODE,
|
|
): Theme {
|
|
const accent = isHexColor(accentInput) ? accentInput : DEFAULT_ACCENT;
|
|
const mode: ThemeMode = modeInput === "light" ? "light" : "dark";
|
|
const M = colors(mode);
|
|
const accentContrast = pickContrastingTextColor(accent);
|
|
|
|
return createTheme({
|
|
palette: {
|
|
mode,
|
|
background: { default: M.surface, paper: M.surface },
|
|
primary: { main: accent, contrastText: accentContrast },
|
|
secondary: { main: mode === "light" ? "#475569" : "#9aa0a6" },
|
|
divider: M.border,
|
|
text: {
|
|
primary: M.textPrimary,
|
|
secondary: M.textSecondary,
|
|
},
|
|
success: { main: "#22c55e" },
|
|
warning: { main: "#f59e0b" },
|
|
error: { main: "#ef4444" },
|
|
},
|
|
shape: { borderRadius: 10 },
|
|
typography: {
|
|
fontFamily:
|
|
'Inter, "SF Pro Display", Roboto, system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif',
|
|
h4: { fontWeight: 600, letterSpacing: -0.4 },
|
|
h5: { fontWeight: 600, letterSpacing: -0.3 },
|
|
h6: { fontWeight: 600, letterSpacing: -0.2 },
|
|
button: { fontWeight: 500 },
|
|
},
|
|
components: {
|
|
MuiCssBaseline: {
|
|
styleOverrides: {
|
|
":root": {
|
|
// Default accent CSS variable. AppThemeProvider overrides this
|
|
// on <html> at runtime; declaring a fallback here means the
|
|
// very first paint never renders with `var(--sb-accent)`
|
|
// pointing at nothing.
|
|
"--sb-accent": DEFAULT_ACCENT,
|
|
// Companion variable holding the readable foreground colour
|
|
// for content sitting on top of `--sb-accent` (e.g. the "+"
|
|
// create IconButton on the CrudPage toolbar). Computed via
|
|
// `pickContrastingTextColor`, kept in lockstep with
|
|
// `--sb-accent` by AppThemeProvider on every accent change.
|
|
"--sb-accent-contrast": accentContrast,
|
|
// Mode-aware surface tones, exposed so component sx blocks can
|
|
// pin sticky / overlapping elements (e.g. CrudPage's pinned
|
|
// Actions column) to exactly the same colour the surrounding
|
|
// Table / TableHead / hovered TableRow renders. Keeping them
|
|
// here means flipping the mode re-emits the trio in lockstep
|
|
// with every other component override.
|
|
"--sb-surface": M.surface,
|
|
"--sb-elevated": M.elevated,
|
|
"--sb-elevated2": M.elevated2,
|
|
},
|
|
html: {
|
|
// Reserve room for the vertical scrollbar even when it isn't
|
|
// present. Without this, anything that briefly hides the
|
|
// scrollbar (autofill, modals, even some browser quirks) shifts
|
|
// the entire viewport horizontally by ~17px and you see a
|
|
// "flick" in the sidebar / topbar text.
|
|
scrollbarGutter: "stable",
|
|
},
|
|
body: {
|
|
backgroundColor: M.surface,
|
|
color: M.textPrimary,
|
|
colorScheme: mode,
|
|
},
|
|
"*::-webkit-scrollbar": { width: 10, height: 10 },
|
|
"*::-webkit-scrollbar-track": { background: "transparent" },
|
|
"*::-webkit-scrollbar-thumb": {
|
|
background: M.scrollbar,
|
|
borderRadius: 8,
|
|
border: "2px solid transparent",
|
|
backgroundClip: "padding-box",
|
|
},
|
|
"*::-webkit-scrollbar-thumb:hover": {
|
|
background: M.scrollbarHover,
|
|
backgroundClip: "padding-box",
|
|
},
|
|
// Theme switching: View Transitions API circular reveal from the
|
|
// toggle's click point. Falls back to instant change in browsers
|
|
// without `document.startViewTransition`.
|
|
//
|
|
// Both pseudo-elements are static images (DOM snapshots), so we
|
|
// override the default cross-fade animation with our own clip-path
|
|
// reveal on the new state. The old snapshot stays at full opacity
|
|
// beneath the new one and is wiped away by the expanding circle.
|
|
// We deliberately do NOT disable transitions on the live DOM
|
|
// during the view-transition — that caused a "bounce" at the end
|
|
// when in-flight ripples / row-hover transitions snapped back.
|
|
"::view-transition-old(root), ::view-transition-new(root)": {
|
|
animation: "none",
|
|
mixBlendMode: "normal",
|
|
},
|
|
"::view-transition-old(root)": {
|
|
zIndex: 1,
|
|
},
|
|
"::view-transition-new(root)": {
|
|
zIndex: 2,
|
|
animation: "theme-reveal 520ms cubic-bezier(0.4, 0, 0.2, 1) forwards",
|
|
},
|
|
"@keyframes theme-reveal": {
|
|
from: {
|
|
clipPath:
|
|
"circle(0px at var(--theme-cx, 50%) var(--theme-cy, 50%))",
|
|
},
|
|
to: {
|
|
clipPath:
|
|
"circle(var(--theme-r, 150vmax) at var(--theme-cx, 50%) var(--theme-cy, 50%))",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
MuiPaper: {
|
|
defaultProps: { elevation: 0 },
|
|
styleOverrides: {
|
|
root: { backgroundImage: "none", backgroundColor: M.surface },
|
|
outlined: {
|
|
borderColor: M.border,
|
|
borderRadius: 12,
|
|
},
|
|
},
|
|
},
|
|
MuiCard: {
|
|
styleOverrides: {
|
|
root: {
|
|
backgroundColor: M.surface,
|
|
backgroundImage: "none",
|
|
border: `1px solid ${M.border}`,
|
|
borderRadius: 12,
|
|
transition:
|
|
"transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease",
|
|
},
|
|
},
|
|
},
|
|
MuiAppBar: {
|
|
styleOverrides: {
|
|
root: {
|
|
backgroundImage: "none",
|
|
backgroundColor: M.elevated,
|
|
boxShadow: "none",
|
|
borderBottom: `1px solid ${M.border}`,
|
|
},
|
|
},
|
|
},
|
|
MuiDrawer: {
|
|
styleOverrides: {
|
|
paper: {
|
|
backgroundImage: "none",
|
|
backgroundColor: M.elevated,
|
|
borderRight: `1px solid ${M.border}`,
|
|
},
|
|
},
|
|
},
|
|
MuiDialog: {
|
|
defaultProps: {
|
|
// Don't lock body scroll — MUI's default behaviour adds
|
|
// `overflow:hidden` + a `padding-right` to <body> equal to the
|
|
// scrollbar width while the modal is open, which shifts every
|
|
// `position:fixed` element (sidebar, topbar) horizontally by
|
|
// ~17px. Keeping the scrollbar avoids that flick.
|
|
disableScrollLock: true,
|
|
},
|
|
styleOverrides: {
|
|
paper: {
|
|
backgroundColor: M.surface,
|
|
backgroundImage: "none",
|
|
border: `1px solid ${M.border}`,
|
|
boxShadow: M.dialogShadow,
|
|
},
|
|
},
|
|
},
|
|
// Same scroll-lock fix for every other Modal-based opener so popping
|
|
// a colour picker, filter dropdown or autocomplete list doesn't yank
|
|
// the layout sideways.
|
|
//
|
|
// We also override the default `Grow` transition with `Fade` for
|
|
// every overlay element. `Grow` scales the element with
|
|
// `scale(0.75, 0.5625) → scale(1, 1)` — an *asymmetric* stretch in
|
|
// which the Y-axis grows from 56% to 100%. With most placements that
|
|
// means the top edge of the popup rises noticeably as the animation
|
|
// ends, which reads as the inner text "snapping upward" at the end
|
|
// of the open animation. A pure opacity fade has no transform at
|
|
// all, so the contents simply appear in their final position.
|
|
MuiPopover: {
|
|
defaultProps: {
|
|
disableScrollLock: true,
|
|
slots: { transition: Fade },
|
|
slotProps: { transition: { timeout: 160 } },
|
|
},
|
|
},
|
|
MuiMenu: {
|
|
defaultProps: {
|
|
disableScrollLock: true,
|
|
slots: { transition: Fade },
|
|
slotProps: { transition: { timeout: 160 } },
|
|
},
|
|
},
|
|
MuiModal: {
|
|
defaultProps: { disableScrollLock: true },
|
|
},
|
|
MuiTableContainer: {
|
|
styleOverrides: { root: { backgroundColor: M.surface } },
|
|
},
|
|
MuiTable: {
|
|
styleOverrides: {
|
|
root: {
|
|
backgroundColor: M.surface,
|
|
width: "100%",
|
|
},
|
|
},
|
|
},
|
|
MuiTableHead: {
|
|
styleOverrides: { root: { backgroundColor: M.elevated } },
|
|
},
|
|
MuiTableRow: {
|
|
styleOverrides: {
|
|
root: {
|
|
transition: "background-color 0.14s ease",
|
|
"&:hover": { backgroundColor: `${M.elevated2} !important` },
|
|
// Tone down the very last row's bottom border so the table edge
|
|
// doesn't read as a hard double line against the Paper outline.
|
|
"&:last-of-type td": { borderBottom: 0 },
|
|
},
|
|
},
|
|
},
|
|
MuiTableCell: {
|
|
styleOverrides: {
|
|
root: {
|
|
color: M.textPrimary,
|
|
borderBottomColor: M.border,
|
|
backgroundColor: "transparent",
|
|
fontSize: 13.5,
|
|
paddingTop: 16,
|
|
paddingBottom: 16,
|
|
},
|
|
|
|
sizeSmall: {
|
|
paddingTop: 16,
|
|
paddingBottom: 16,
|
|
},
|
|
head: {
|
|
fontWeight: 600,
|
|
fontSize: 11.5,
|
|
letterSpacing: 0.6,
|
|
textTransform: "uppercase",
|
|
color: M.tableHeadColor,
|
|
backgroundColor: M.elevated,
|
|
borderBottomColor: M.borderStrong,
|
|
// Slimmer header strip — the column-name bar shouldn't take
|
|
// as much vertical space as a data row, just enough to read
|
|
// the label comfortably.
|
|
paddingTop: 8,
|
|
paddingBottom: 8,
|
|
},
|
|
},
|
|
},
|
|
MuiButton: {
|
|
defaultProps: { disableElevation: true },
|
|
styleOverrides: {
|
|
root: {
|
|
textTransform: "none",
|
|
borderRadius: 8,
|
|
paddingInline: 14,
|
|
fontWeight: 500,
|
|
transition:
|
|
"background-color 0.16s ease, border-color 0.16s ease, transform 0.08s ease",
|
|
"&:active": { transform: "translateY(0.5px)" },
|
|
},
|
|
// Accent-coloured rules below reference `var(--sb-accent)` so the
|
|
// emotion class hash stays stable when the user picks a new
|
|
// accent. With a stable class, the declared `transition` actually
|
|
// animates the colour change instead of being voided by a class
|
|
// swap.
|
|
containedPrimary: {
|
|
backgroundColor: "var(--sb-accent)",
|
|
color: accentContrast,
|
|
transition:
|
|
"background-color 0.32s cubic-bezier(0.4,0,0.2,1), color 0.32s cubic-bezier(0.4,0,0.2,1)",
|
|
"&:hover": {
|
|
backgroundColor:
|
|
"color-mix(in srgb, var(--sb-accent) 92%, transparent)",
|
|
},
|
|
},
|
|
outlinedInherit: { borderColor: M.borderStrong },
|
|
},
|
|
},
|
|
MuiIconButton: {
|
|
styleOverrides: {
|
|
root: {
|
|
borderRadius: 8,
|
|
transition: "background-color 0.14s ease, color 0.14s ease",
|
|
// Desktop hover tint — gated on a real hover-capable
|
|
// pointer AND a desktop-width viewport so it never
|
|
// fires on touch devices or in Chrome DevTools' mobile
|
|
// preset (which keeps `(hover: hover)` true because
|
|
// the host machine still has a mouse, but does shrink
|
|
// the viewport, so `(min-width: 600px)` catches it).
|
|
// No companion `:active` / `:focus` rule on the mobile
|
|
// breakpoint by design: per the user's request the
|
|
// button gets zero post-tap visuals on a phone.
|
|
"@media (hover: hover) and (min-width: 600px)": {
|
|
"&:hover": { backgroundColor: M.iconButtonHover },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// Hide MUI's TouchRipple visual on the mobile / touch
|
|
// breakpoint for every ButtonBase-derived component
|
|
// (IconButton, Button, MenuItem, ListItemButton, …). The
|
|
// ripple is the growing circle that, on phones, can outlive
|
|
// the touch when the touchend event isn't delivered cleanly
|
|
// (drag-off, scroll, sluggish hardware) — that's the halo
|
|
// the user kept seeing after every tap. Desktop pointers
|
|
// fire mouseup / mouseleave reliably, so the ripple animation
|
|
// always completes its exit phase there and we leave it
|
|
// alone. The same `(hover: none), (max-width: 599.95px)` pair
|
|
// is used everywhere else in the theme so the rule fires
|
|
// both on real phones and inside Chrome DevTools'
|
|
// mobile-emulation preset.
|
|
MuiTouchRipple: {
|
|
styleOverrides: {
|
|
root: {
|
|
"@media (hover: none), (max-width: 599.95px)": {
|
|
display: "none",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
MuiOutlinedInput: {
|
|
styleOverrides: {
|
|
root: {
|
|
backgroundColor: M.inputBg,
|
|
"& .MuiOutlinedInput-notchedOutline": {
|
|
borderColor: M.border,
|
|
transition: "border-color 0.32s cubic-bezier(0.4,0,0.2,1)",
|
|
},
|
|
"&:hover .MuiOutlinedInput-notchedOutline": { borderColor: M.borderStrong },
|
|
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
|
|
borderColor: "var(--sb-accent)",
|
|
borderWidth: 1,
|
|
},
|
|
// Suppress the browser's autofill highlight (Firefox paints
|
|
// the field with a translucent blue band, Chrome/Safari with
|
|
// yellow). The cell's own background already reads as
|
|
// "active", so the autofill colour is purely noise.
|
|
//
|
|
// WebKit (Chrome/Safari) doesn't honour normal
|
|
// `background-color` overrides on autofilled inputs — the
|
|
// canonical workaround is a giant inset `box-shadow` that
|
|
// visually replaces the background, plus `text-fill-color`
|
|
// for the foreground colour the engine forces. Firefox uses
|
|
// the standard `:autofill` pseudo and respects ordinary
|
|
// `background-color` / `filter` overrides since v86.
|
|
"& input:-webkit-autofill, & input:-webkit-autofill:hover, & input:-webkit-autofill:focus, & input:-webkit-autofill:active":
|
|
{
|
|
WebkitBoxShadow: `0 0 0 1000px ${M.surface} inset !important`,
|
|
WebkitTextFillColor: `${M.textPrimary} !important`,
|
|
caretColor: `${M.textPrimary} !important`,
|
|
// The 5000s transition delay keeps the engine from
|
|
// re-running the autofill style transition on every
|
|
// focus / blur, which would re-paint the highlight.
|
|
transition: "background-color 5000s ease-in-out 0s",
|
|
},
|
|
"& input:autofill": {
|
|
backgroundColor: "transparent !important",
|
|
filter: "none !important",
|
|
color: `${M.textPrimary} !important`,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
MuiChip: {
|
|
styleOverrides: {
|
|
root: {
|
|
backgroundColor: M.chipBg,
|
|
color: M.textPrimary,
|
|
fontWeight: 500,
|
|
borderRadius: 6,
|
|
},
|
|
// "Backlit" badge with accent glow — used for selected items in
|
|
// multi-select boxes so they stand out without being garish.
|
|
colorPrimary: {
|
|
backgroundColor:
|
|
"color-mix(in srgb, var(--sb-accent) 14%, transparent)",
|
|
color: "var(--sb-accent)",
|
|
border:
|
|
"1px solid color-mix(in srgb, var(--sb-accent) 35%, transparent)",
|
|
transition:
|
|
"background-color 0.32s cubic-bezier(0.4,0,0.2,1), color 0.32s cubic-bezier(0.4,0,0.2,1), border-color 0.32s cubic-bezier(0.4,0,0.2,1)",
|
|
},
|
|
colorSuccess: { backgroundColor: alpha("#22c55e", 0.18), color: mode === "light" ? "#15803d" : "#86efac" },
|
|
colorWarning: { backgroundColor: alpha("#f59e0b", 0.18), color: mode === "light" ? "#a16207" : "#fcd34d" },
|
|
colorError: { backgroundColor: alpha("#ef4444", 0.18), color: mode === "light" ? "#b91c1c" : "#fca5a5" },
|
|
},
|
|
},
|
|
MuiListItemButton: {
|
|
styleOverrides: {
|
|
root: {
|
|
borderRadius: 8,
|
|
marginInline: 8,
|
|
paddingBlock: 6,
|
|
transition: "background-color 0.32s cubic-bezier(0.4,0,0.2,1)",
|
|
"&.Mui-selected": {
|
|
// Mode-aware accent tint: 16% on dark, 12% on light so the
|
|
// selected pill reads cleanly on both surfaces.
|
|
backgroundColor: `color-mix(in srgb, var(--sb-accent) ${
|
|
mode === "light" ? 12 : 16
|
|
}%, transparent)`,
|
|
"&:hover": {
|
|
backgroundColor:
|
|
"color-mix(in srgb, var(--sb-accent) 22%, transparent)",
|
|
},
|
|
"& .MuiListItemIcon-root": {
|
|
color: "var(--sb-accent)",
|
|
transition: "color 0.32s cubic-bezier(0.4,0,0.2,1)",
|
|
},
|
|
"& .MuiListItemText-primary": {
|
|
color: mode === "light" ? "#0f172a" : "#fff",
|
|
// Visually heavier on the active item without bumping
|
|
// `fontWeight`. Different font weights have different glyph
|
|
// advance widths, so toggling between 400 and 600 caused a
|
|
// ~1px horizontal jump every time the selection changed (or
|
|
// re-rendered on hover); painting an extra "stroke" via
|
|
// text-shadow gives the same heaviness with zero layout
|
|
// impact.
|
|
textShadow: "0 0 0.6px currentColor",
|
|
},
|
|
},
|
|
"&:hover": { backgroundColor: M.listItemHover },
|
|
},
|
|
},
|
|
},
|
|
MuiListItemIcon: {
|
|
styleOverrides: {
|
|
root: { minWidth: 36, color: M.textSecondary },
|
|
},
|
|
},
|
|
MuiTooltip: {
|
|
defaultProps: {
|
|
// See the MuiPopover comment above — Grow's asymmetric scale
|
|
// makes tooltip text appear to "snap upward" at the end of the
|
|
// open animation. A pure opacity fade has no transform so the
|
|
// contents simply appear in their final position.
|
|
slots: { transition: Fade },
|
|
slotProps: { transition: { timeout: 140 } },
|
|
},
|
|
styleOverrides: {
|
|
tooltip: {
|
|
backgroundColor: M.tooltipBg,
|
|
color: M.tooltipText,
|
|
fontSize: 12,
|
|
border: `1px solid ${M.border}`,
|
|
},
|
|
},
|
|
},
|
|
MuiAlert: {
|
|
styleOverrides: {
|
|
root: { borderRadius: 10 },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
export function buildPalette(
|
|
modeInput: ThemeMode = DEFAULT_MODE,
|
|
): ThemePalette {
|
|
const mode: ThemeMode = modeInput === "light" ? "light" : "dark";
|
|
const M = colors(mode);
|
|
return {
|
|
mode,
|
|
surface: M.surface,
|
|
elevated: M.elevated,
|
|
border: M.border,
|
|
};
|
|
}
|