mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
Implement Glassy Dark theme (#1388)
* implement theme * refactor theme stylesheets to load inline to simplify vite bundling * add missing css module scope name for web build --------- Co-authored-by: jeffvli <jeffvictorli@gmail.com>
This commit is contained in:
@@ -22,6 +22,7 @@ export const THEME_DATA = [
|
|||||||
{ label: 'Solarized Light', type: 'light', value: AppTheme.SOLARIZED_LIGHT },
|
{ label: 'Solarized Light', type: 'light', value: AppTheme.SOLARIZED_LIGHT },
|
||||||
{ label: 'GitHub Dark', type: 'dark', value: AppTheme.GITHUB_DARK },
|
{ label: 'GitHub Dark', type: 'dark', value: AppTheme.GITHUB_DARK },
|
||||||
{ label: 'GitHub Light', type: 'light', value: AppTheme.GITHUB_LIGHT },
|
{ label: 'GitHub Light', type: 'light', value: AppTheme.GITHUB_LIGHT },
|
||||||
|
{ label: 'Glassy Dark', type: 'dark', value: AppTheme.GLASSY_DARK },
|
||||||
{ label: 'Monokai', type: 'dark', value: AppTheme.MONOKAI },
|
{ label: 'Monokai', type: 'dark', value: AppTheme.MONOKAI },
|
||||||
{ label: 'High Contrast Dark', type: 'dark', value: AppTheme.HIGH_CONTRAST_DARK },
|
{ label: 'High Contrast Dark', type: 'dark', value: AppTheme.HIGH_CONTRAST_DARK },
|
||||||
{ label: 'High Contrast Light', type: 'light', value: AppTheme.HIGH_CONTRAST_LIGHT },
|
{ label: 'High Contrast Light', type: 'light', value: AppTheme.HIGH_CONTRAST_LIGHT },
|
||||||
@@ -48,7 +49,7 @@ export const useAppTheme = (overrideTheme?: AppTheme) => {
|
|||||||
const nativeImageAspect = useNativeAspectRatio();
|
const nativeImageAspect = useNativeAspectRatio();
|
||||||
const { builtIn, custom, system, type } = useFontSettings();
|
const { builtIn, custom, system, type } = useFontSettings();
|
||||||
const textStyleRef = useRef<HTMLStyleElement | null>(null);
|
const textStyleRef = useRef<HTMLStyleElement | null>(null);
|
||||||
const loadedStylesheetsRef = useRef<Set<string>>(new Set());
|
const themeInlineStylesRef = useRef<HTMLStyleElement | null>(null);
|
||||||
const getCurrentTheme = () => window.matchMedia('(prefers-color-scheme: dark)').matches;
|
const getCurrentTheme = () => window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
const [isDarkTheme, setIsDarkTheme] = useState(getCurrentTheme());
|
const [isDarkTheme, setIsDarkTheme] = useState(getCurrentTheme());
|
||||||
const { followSystemTheme, theme, themeDark, themeLight, useThemeAccentColor } =
|
const { followSystemTheme, theme, themeDark, themeLight, useThemeAccentColor } =
|
||||||
@@ -58,54 +59,17 @@ export const useAppTheme = (overrideTheme?: AppTheme) => {
|
|||||||
setIsDarkTheme(e.matches);
|
setIsDarkTheme(e.matches);
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadStylesheet = (href: string): Promise<void> => {
|
const applyInlineStylesheets = useCallback((inlineCssStrings: string[] = []) => {
|
||||||
return new Promise((resolve, reject) => {
|
const cssText = inlineCssStrings.filter(Boolean).join('\n');
|
||||||
if (loadedStylesheetsRef.current.has(href)) {
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const link = document.createElement('link');
|
if (!themeInlineStylesRef.current) {
|
||||||
link.rel = 'stylesheet';
|
const styleEl = document.createElement('style');
|
||||||
link.href = href;
|
styleEl.id = 'theme-inline-styles';
|
||||||
link.onload = () => {
|
document.head.appendChild(styleEl);
|
||||||
loadedStylesheetsRef.current.add(href);
|
themeInlineStylesRef.current = styleEl;
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
link.onerror = () => {
|
|
||||||
console.warn(`Failed to load stylesheet: ${href}`);
|
|
||||||
reject(new Error(`Failed to load stylesheet: ${href}`));
|
|
||||||
};
|
|
||||||
|
|
||||||
document.head.appendChild(link);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const unloadStylesheet = (href: string) => {
|
|
||||||
const existingLink = document.querySelector(`link[href="${href}"]`);
|
|
||||||
if (existingLink) {
|
|
||||||
existingLink.remove();
|
|
||||||
loadedStylesheetsRef.current.delete(href);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadThemeStylesheets = useCallback(async (stylesheets: string[] = []) => {
|
|
||||||
if (loadedStylesheetsRef.current.size > 0) {
|
|
||||||
loadedStylesheetsRef.current.forEach((href) => unloadStylesheet(href));
|
|
||||||
loadedStylesheetsRef.current.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stylesheets.length === 0) {
|
themeInlineStylesRef.current.textContent = cssText;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadPromises = stylesheets.map((href) =>
|
|
||||||
loadStylesheet(href).catch((error) => {
|
|
||||||
console.warn(`Error loading stylesheet ${href}:`, error);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(loadPromises);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getSelectedTheme = () => {
|
const getSelectedTheme = () => {
|
||||||
@@ -204,10 +168,8 @@ export const useAppTheme = (overrideTheme?: AppTheme) => {
|
|||||||
}, [nativeImageAspect]);
|
}, [nativeImageAspect]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appTheme?.stylesheets) {
|
applyInlineStylesheets(appTheme?.stylesheets ?? []);
|
||||||
loadThemeStylesheets(appTheme.stylesheets);
|
}, [selectedTheme, appTheme?.stylesheets, applyInlineStylesheets]);
|
||||||
}
|
|
||||||
}, [selectedTheme, appTheme?.stylesheets, loadThemeStylesheets]);
|
|
||||||
|
|
||||||
const themeVars = useMemo(() => {
|
const themeVars = useMemo(() => {
|
||||||
return Object.entries(appTheme?.app ?? {})
|
return Object.entries(appTheme?.app ?? {})
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export enum AppTheme {
|
|||||||
DRACULA = 'dracula',
|
DRACULA = 'dracula',
|
||||||
GITHUB_DARK = 'githubDark',
|
GITHUB_DARK = 'githubDark',
|
||||||
GITHUB_LIGHT = 'githubLight',
|
GITHUB_LIGHT = 'githubLight',
|
||||||
|
GLASSY_DARK = 'glassyDark',
|
||||||
GRUVBOX_DARK = 'gruvboxDark',
|
GRUVBOX_DARK = 'gruvboxDark',
|
||||||
GRUVBOX_LIGHT = 'gruvboxLight',
|
GRUVBOX_LIGHT = 'gruvboxLight',
|
||||||
HIGH_CONTRAST_DARK = 'highContrastDark',
|
HIGH_CONTRAST_DARK = 'highContrastDark',
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { defaultLight } from '/@/shared/themes/default-light/default-light';
|
|||||||
import { dracula } from '/@/shared/themes/dracula/dracula';
|
import { dracula } from '/@/shared/themes/dracula/dracula';
|
||||||
import { githubDark } from '/@/shared/themes/github-dark/github-dark';
|
import { githubDark } from '/@/shared/themes/github-dark/github-dark';
|
||||||
import { githubLight } from '/@/shared/themes/github-light/github-light';
|
import { githubLight } from '/@/shared/themes/github-light/github-light';
|
||||||
|
import { glassyDark } from '/@/shared/themes/glassy-dark/glassy-dark';
|
||||||
import { gruvboxDark } from '/@/shared/themes/gruvbox-dark/gruvbox-dark';
|
import { gruvboxDark } from '/@/shared/themes/gruvbox-dark/gruvbox-dark';
|
||||||
import { gruvboxLight } from '/@/shared/themes/gruvbox-light/gruvbox-light';
|
import { gruvboxLight } from '/@/shared/themes/gruvbox-light/gruvbox-light';
|
||||||
import { highContrastDark } from '/@/shared/themes/high-contrast-dark/high-contrast-dark';
|
import { highContrastDark } from '/@/shared/themes/high-contrast-dark/high-contrast-dark';
|
||||||
@@ -43,6 +44,7 @@ export const appTheme: Record<AppTheme, AppThemeConfiguration> = {
|
|||||||
[AppTheme.DRACULA]: dracula,
|
[AppTheme.DRACULA]: dracula,
|
||||||
[AppTheme.GITHUB_DARK]: githubDark,
|
[AppTheme.GITHUB_DARK]: githubDark,
|
||||||
[AppTheme.GITHUB_LIGHT]: githubLight,
|
[AppTheme.GITHUB_LIGHT]: githubLight,
|
||||||
|
[AppTheme.GLASSY_DARK]: glassyDark,
|
||||||
[AppTheme.GRUVBOX_DARK]: gruvboxDark,
|
[AppTheme.GRUVBOX_DARK]: gruvboxDark,
|
||||||
[AppTheme.GRUVBOX_LIGHT]: gruvboxLight,
|
[AppTheme.GRUVBOX_LIGHT]: gruvboxLight,
|
||||||
[AppTheme.HIGH_CONTRAST_DARK]: highContrastDark,
|
[AppTheme.HIGH_CONTRAST_DARK]: highContrastDark,
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import glassyOverridesCss from './glassy_overrides.css?inline';
|
||||||
|
|
||||||
|
import { AppThemeConfiguration } from '/@/shared/themes/app-theme-types';
|
||||||
|
|
||||||
|
export const glassyDark: AppThemeConfiguration = {
|
||||||
|
app: {
|
||||||
|
'overlay-header':
|
||||||
|
'linear-gradient(transparent 0%, rgb(13 17 23 / 85%) 100%), var(--theme-background-noise)',
|
||||||
|
'overlay-subheader':
|
||||||
|
'linear-gradient(180deg, rgb(13 17 23 / 5%) 0%, var(--theme-colors-background) 100%), var(--theme-background-noise)',
|
||||||
|
'scrollbar-handle-background': 'rgba(88, 166, 255, 20%)',
|
||||||
|
'scrollbar-handle-hover-background': 'rgba(88, 166, 255, 40%)',
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
background: 'rgb(2, 2, 6)',
|
||||||
|
'background-alternate': 'rgb(0, 0, 0)',
|
||||||
|
black: 'rgb(0, 0, 0)',
|
||||||
|
foreground: 'rgb(225, 225, 225)',
|
||||||
|
'foreground-muted': 'rgb(150, 150, 150)',
|
||||||
|
'state-error': 'rgb(204, 50, 50)',
|
||||||
|
'state-info': 'rgb(53, 116, 252)',
|
||||||
|
'state-success': 'rgb(50, 204, 50)',
|
||||||
|
'state-warning': 'rgb(255, 120, 120)',
|
||||||
|
surface: 'rgb(4, 4, 9)',
|
||||||
|
'surface-foreground': 'rgb(215, 215, 215)',
|
||||||
|
white: 'rgb(255, 255, 255)',
|
||||||
|
},
|
||||||
|
mode: 'dark',
|
||||||
|
stylesheets: [glassyOverridesCss],
|
||||||
|
};
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
.fs-player-bar-module-container {
|
||||||
|
background: rgb(0 0 0 / 40%) !important;
|
||||||
|
backdrop-filter: blur(2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-poster-card-module-image {
|
||||||
|
border-radius: 18px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-grid-card-controls-module-grid-card-controls-container {
|
||||||
|
border-radius: 18px;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fsplayer-text {
|
||||||
|
font-size: 45px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-full-screen-player-image-module-metadata-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-full-screen-player-queue-module-grid-container::before {
|
||||||
|
border-radius: 18px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
.mantine-Modal-overlay {
|
||||||
|
backdrop-filter: blur(3px);
|
||||||
|
}
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
|
||||||
|
.fs-modal-module-content,
|
||||||
|
.fs-select-module-dropdown,
|
||||||
|
.fs-popover-module-dropdown,
|
||||||
|
.fs-dialog-module-root,
|
||||||
|
.fs-context-menu-module-content,
|
||||||
|
.fs-dropdown-menu-module-menu-dropdown,
|
||||||
|
.fs-accordion-module-panel {
|
||||||
|
background: rgb(4 4 9 / 50%) !important;
|
||||||
|
backdrop-filter: blur(2rem);
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
.fs-multi-select-module-input,
|
||||||
|
a {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-context-menu-module-content,
|
||||||
|
.fs-dropdown-menu-module-menu-dropdown {
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-accordion-module-panel {
|
||||||
|
border-radius: 18px;
|
||||||
|
backdrop-filter: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-accordion-module-control {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-modal-module-header {
|
||||||
|
background: rgb(4 4 9 / 80%) !important;
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-select-module-dropdown,
|
||||||
|
.fs-popover-module-dropdown {
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
.mantine-Center-root img {
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
|
||||||
|
.ag-header {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-left-controls-module-playerbar-image {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
.favorite_icon .mantine-ActionIcon-icon {
|
||||||
|
justify-content: left;
|
||||||
|
}
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
|
||||||
|
.fork-header svg {
|
||||||
|
padding-left: 2px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-button-module-root[data-variant='outline'] {
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-poster-card-module-image-container {
|
||||||
|
border-radius: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
.mantine-Table-th {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
|
||||||
|
table {
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-sidebar-module-accordion-content a {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-main-content-module-main-content-container {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
.mantine-Tabs-root {
|
||||||
|
input {
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:not(.mantine-focus-never) {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
.mantine-Slider-track::before {
|
||||||
|
background-color: var(--theme-colors-surface);
|
||||||
|
}
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
|
||||||
|
/* stylelint-disable selector-not-notation */
|
||||||
|
.fs-image-module-image:not(.ag-cell *)
|
||||||
|
:not(.fs-left-controls-module-image *)
|
||||||
|
:not(.fs-sidebar-playlist-list-module-row-group *) {
|
||||||
|
border-radius: 18px !important;
|
||||||
|
}
|
||||||
|
/* stylelint-enable selector-not-notation */
|
||||||
|
|
||||||
|
.fs-left-controls-module-image {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fork-server-selector {
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
.mantine-SegmentedControl-indicator,
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
.fs-segmented-control-module-root,
|
||||||
|
input,
|
||||||
|
button {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-text-input-module-input,
|
||||||
|
[cmdk-item][data-selected] {
|
||||||
|
background: rgb(4 4 9 / 50%) !important;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-modal-module-content [cmdk-separator] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button fixes */
|
||||||
|
.fs-button-module-root[data-variant='filled'] {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
.mantine-Accordion-label {
|
||||||
|
button,
|
||||||
|
a {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
.mantine-Grid-col button {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
|
||||||
|
/* share dialog */
|
||||||
|
.fs-modal-module-body {
|
||||||
|
.fs-textarea-module-input {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-accordion-module-panel {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-feature-carousel-module-image-column {
|
||||||
|
align-items: normal !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
.mantine-Badge-root {
|
||||||
|
background: rgb(1 1 5 / 45%);
|
||||||
|
}
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
|
||||||
|
.fs-sidebar-module-image-container img {
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-expanded-list-item-module-container {
|
||||||
|
position: relative;
|
||||||
|
bottom: 90px;
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-sidebar-play-queue-module-lyrics-section {
|
||||||
|
bottom: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-page-header-module-container {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-tabs-module-tab {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
.fs-full-screen-player-module-container .mantine-Group-root button {
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
|
||||||
|
.fs-full-screen-player-image-module-image {
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-segmented-control-module-root {
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-segmented-control-module-label[data-active='true'],
|
||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
.mantine-SegmentedControl-control {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
/* stylelint-enable selector-class-pattern */
|
||||||
|
|
||||||
|
.fs-table-config-module-group {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-server-selector-module-button-group {
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
Vendored
+10
@@ -2,3 +2,13 @@ declare module '*.module.css' {
|
|||||||
const classes: { [key: string]: string };
|
const classes: { [key: string]: string };
|
||||||
export default classes;
|
export default classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '*.css?raw' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.css?inline' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
css: {
|
css: {
|
||||||
modules: {
|
modules: {
|
||||||
|
generateScopedName: 'fs-[name]-[local]',
|
||||||
localsConvention: 'camelCase',
|
localsConvention: 'camelCase',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user