Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/theming/css/default.css
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,9 @@
--color-background-plain-text: #ffffff;
--image-background: url('/apps/theming/img/background/jo-myoung-hee-fluid.webp');
}
@media (prefers-reduced-motion: reduce) {
:root {
--animation-quick: 1ms;
--animation-slow: 1ms;
}
}
12 changes: 10 additions & 2 deletions apps/theming/lib/Controller/ThemingController.php
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,17 @@ public function getThemeStylesheet(string $themeId, bool $plain = false, bool $w
$variables .= "$variable:$value; ";
};

// Collapse the animation timing variables for users who requested reduced
// motion. Emitted right after the variables on the same selector so it
// overrides them in the cascade. We use 1ms rather than 0 so values read
// via `parseInt(...) || fallback` in JavaScript stay truthy and transition
// events still fire.
$reducedMotion = static fn (string $selector): string
=> "@media (prefers-reduced-motion: reduce) { $selector { --animation-quick: 1ms; --animation-slow: 1ms; } } ";

// If plain is set, the browser decides of the css priority
if ($plain) {
$css = ":root { $variables } " . $customCss;
$css = ":root { $variables } " . $reducedMotion(':root') . $customCss;
} else {
// If not set, we'll rely on the body class
// We need to separate @-rules from normal selectors, as they can't be nested
Expand All @@ -430,7 +438,7 @@ public function getThemeStylesheet(string $themeId, bool $plain = false, bool $w
$atRulesCss = implode('', $atRules[0]);
$scopedCss = preg_replace('/(@[^{]+{(?:[^{}]*|(?R))*})/', '', $customCssWithoutComments);

$css = "$atRulesCss [data-theme-$themeId] { $variables $scopedCss }";
$css = "$atRulesCss [data-theme-$themeId] { $variables $scopedCss } " . $reducedMotion("[data-theme-$themeId]");
}

try {
Expand Down
4 changes: 3 additions & 1 deletion apps/theming/lib/Themes/DefaultTheme.php
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,9 @@ public function getCSSVariables(): array {
// 1.5 * font-size for accessibility
'--default-line-height' => '1.5',

// TODO: support "(prefers-reduced-motion)"
// A "(prefers-reduced-motion)" media query collapses these to 1ms;
// see ThemingController::getThemeStylesheet() and the static fallback
// in apps/theming/css/default.css.
'--animation-quick' => '100ms',
'--animation-slow' => '300ms',

Expand Down
6 changes: 5 additions & 1 deletion apps/theming/tests/Themes/DefaultThemeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,13 @@ public function testThemindDisabledFallbackCss(): void {
$fallbackCss = file_get_contents(__DIR__ . '/../../css/default.css');
// Remove comments
$fallbackCss = preg_replace('/\s*\/\*[\s\S]*?\*\//m', '', $fallbackCss);
// The fallback also carries a prefers-reduced-motion override that zeroes
// the animation variables; it is not part of the theme variables, so drop
// any @media at-rules before comparing.
$fallbackCss = preg_replace('/@media[^{]*\{(?:[^{}]|\{[^{}]*\})*\}/', '', $fallbackCss);
// Remove blank lines
$fallbackCss = preg_replace('/\s*\n\n/', "\n", $fallbackCss);

$this->assertEquals($css, $fallbackCss);
$this->assertEquals(rtrim($css), rtrim($fallbackCss));
}
}
Loading