fix(ui): preserve card radius in MUI sx

This commit is contained in:
2026-07-23 01:17:22 +08:00
parent a0fd55eb37
commit 4edc660606
9 changed files with 82 additions and 35 deletions

View File

@@ -0,0 +1,49 @@
# Material You fork maintenance
This branch keeps Suwayomi behavior upstream-compatible and confines the visual fork to theme services, navigation components, manga presentation, and reader chrome.
## Update from upstream
```sh
git fetch origin
git switch codex/material-you
git rebase origin/master
pnpm test:material-you
pnpm format:check
pnpm lint
pnpm tsc
pnpm build
```
Resolve upstream conflicts by preserving server requests, GraphQL types, routing, reader state, and settings behavior. Material You additions should remain presentation-only.
## Build
Use Node 24 or newer and pnpm 11.1.2 or newer:
```sh
pnpm setup
pnpm build
```
The deployable static application is written to `build/`.
## NAS deployment
Suwayomi uses the custom WebUI directory:
```text
/vol4/1000/docker-software/manga-stack/suwayomi/webUI
```
Before each release, copy `server.conf` and the complete live `webUI/` directory into a timestamped directory below:
```text
/vol4/1000/docker-software/.integration-backups/
```
Upload `build/` to a sibling staging directory, compare file counts and hashes, then rename the staging directory to `webUI` in one operation. Keep `webUI.flavor=CUSTOM` and the automatic WebUI update interval at `0`. Recreate only the `suwayomi` service and force-refresh browser/PWA caches.
## Rollback
Stop or recreate only Suwayomi, move the failed live `webUI/` aside, restore the backed-up `webUI/` and `server.conf`, then recreate Suwayomi. Verify HTTP 200, the GraphQL `{ __typename }` query, hashed assets, and container logs before closing the rollback.

View File

@@ -79,7 +79,7 @@ export const MangaGridCard = memo(
flexDirection: 'column',
m: 0.25,
outline: selected ? '4px solid' : undefined,
borderRadius: MATERIAL_YOU_VISUALS.cardRadius,
borderRadius: MATERIAL_YOU_VISUALS.cardRadiusCss,
outlineColor: (theme) => theme.palette.primary.main,
backgroundColor: (theme) => (selected ? theme.palette.primary.main : undefined),
transition: `transform ${MATERIAL_YOU_VISUALS.standardTransition}, filter ${MATERIAL_YOU_VISUALS.standardTransition}`,
@@ -106,7 +106,7 @@ export const MangaGridCard = memo(
// force standard aspect ratio of manga covers
aspectRatio: MANGA_COVER_ASPECT_RATIO,
display: 'flex',
borderRadius: MATERIAL_YOU_VISUALS.cardRadius,
borderRadius: MATERIAL_YOU_VISUALS.cardRadiusCss,
}}
>
<CardActionArea

View File

@@ -87,27 +87,29 @@ export const Thumbnail = ({
[0, 0, 0, 255, 75],
],
}),
]).then(([palette, averageColor]) => {
if (!isDynamicColorResultCurrent(url, latestUrlRef.current, aborted)) {
return;
}
])
.then(([palette, averageColor]) => {
if (!isDynamicColorResultCurrent(url, latestUrlRef.current, aborted)) {
return;
}
if (
!palette.Vibrant ||
!palette.DarkVibrant ||
!palette.LightVibrant ||
!palette.LightMuted ||
!palette.Muted ||
!palette.DarkMuted
) {
return;
}
if (
!palette.Vibrant ||
!palette.DarkVibrant ||
!palette.LightVibrant ||
!palette.LightMuted ||
!palette.Muted ||
!palette.DarkMuted
) {
return;
}
setDynamicColor({
...palette,
average: averageColor,
} as TAppThemeContext['dynamicColor']);
}).catch(noOp);
setDynamicColor({
...palette,
average: averageColor,
} as TAppThemeContext['dynamicColor']);
})
.catch(noOp);
};
};

View File

@@ -64,9 +64,7 @@ export const DesktopSideBar = ({ navBarItems }: { navBarItems: NavbarItem[] }) =
sx={{
pt: 'env(safe-area-inset-top)',
pl: 'env(safe-area-inset-left)',
width: isCollapsed
? MATERIAL_YOU_NAVIGATION.railWidth
: MATERIAL_YOU_NAVIGATION.expandedWidth,
width: isCollapsed ? MATERIAL_YOU_NAVIGATION.railWidth : MATERIAL_YOU_NAVIGATION.expandedWidth,
}}
>
<DrawerHeader sx={{ justifyContent: isCollapsed ? 'center' : 'flex-end' }}>

View File

@@ -8,7 +8,7 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { createMaterialYouScheme, MATERIAL_YOU_STABLE_SEED } from './MaterialYouPalette.ts';
import { createMaterialYouScheme, MATERIAL_YOU_STABLE_SEED } from '@/features/theme/services/MaterialYouPalette.ts';
describe('createMaterialYouScheme', () => {
it('creates a deterministic stable blue light scheme', () => {

View File

@@ -50,7 +50,9 @@ const tone = (seed: string, lightness: number, saturationDelta: number = 0): str
const shiftedTone = (seed: string, hueDelta: number, lightness: number): string => {
const parsed = parseToHsl(seed);
return toUpperHex(hsl((parsed.hue + hueDelta + 360) % 360, coerceIn(parsed.saturation * 0.58, 0.28, 0.62), lightness));
return toUpperHex(
hsl((parsed.hue + hueDelta + 360) % 360, coerceIn(parsed.saturation * 0.58, 0.28, 0.62), lightness),
);
};
export const createMaterialYouScheme = (

View File

@@ -16,6 +16,7 @@ import {
describe('Material You visual tokens', () => {
it('defines lifted cards and low-distraction reader chrome', () => {
assert.equal(MATERIAL_YOU_VISUALS.cardRadius, 20);
assert.equal(MATERIAL_YOU_VISUALS.cardRadiusCss, '20px');
assert.equal(MATERIAL_YOU_VISUALS.cardHoverLift, -4);
assert.equal(MATERIAL_YOU_VISUALS.detailRadius, 24);
assert.equal(MATERIAL_YOU_VISUALS.readerChromeOpacity, 0.92);

View File

@@ -8,14 +8,12 @@
export const MATERIAL_YOU_VISUALS = {
cardRadius: 20,
cardRadiusCss: '20px',
cardHoverLift: -4,
detailRadius: 24,
readerChromeOpacity: 0.92,
standardTransition: '180ms cubic-bezier(0.2, 0, 0, 1)',
} as const;
export const isDynamicColorResultCurrent = (
requestedUrl: string,
currentUrl: string,
aborted: boolean,
): boolean => !aborted && requestedUrl === currentUrl;
export const isDynamicColorResultCurrent = (requestedUrl: string, currentUrl: string, aborted: boolean): boolean =>
!aborted && requestedUrl === currentUrl;

View File

@@ -34,10 +34,7 @@ const getVibrantColorForTheme = (
return mode === ThemeMode.LIGHT ? palette.DarkVibrant.hex : palette.LightVibrant.hex;
};
const getThemeSeed = (
appTheme: AppTheme['muiTheme'],
mode: Exclude<ThemeMode, ThemeMode.SYSTEM>,
): string => {
const getThemeSeed = (appTheme: AppTheme['muiTheme'], mode: Exclude<ThemeMode, ThemeMode.SYSTEM>): string => {
const colorScheme = appTheme.colorSchemes?.[mode];
if (typeof colorScheme !== 'object') {
return MATERIAL_YOU_STABLE_SEED;