fix(clients): stop the Edit Client modal showing a stray light scrollbar

The client form body is capped at the viewport and scrolls internally
(49ef1449). Every tab ends with a Form.Item that keeps antd's 24px bottom
margin, so when the fields themselves fit, that empty margin alone pushed
the body past the cap: 752px of content in 740px at a 900px window. The
last item of each tab now drops the margin, so the body scrolls only when
real content overflows.

When it does scroll, the bar was painted light inside the dark modal: the
dark themes set body.dark and data-theme but never color-scheme, which is
what native scrollbars read. applyDom (panel, login and subscription
bundles) and the Storybook decorator now set it on the root element.
This commit is contained in:
Sanaei
2026-09-15 22:04:00 +02:00
parent 14b92fbcff
commit e8bab17c2f
5 changed files with 40 additions and 2 deletions
+1
View File
@@ -22,6 +22,7 @@ export const withTheme: Decorator = (Story, context) => {
useLayoutEffect(() => {
document.body.classList.remove('dark', 'light');
document.body.classList.add(dark ? 'dark' : 'light');
document.documentElement.style.colorScheme = dark ? 'dark' : 'light';
document.documentElement.removeAttribute('data-theme');
}, [dark]);
return (
+2
View File
@@ -15,6 +15,8 @@ function readBool(key: string, fallback: boolean): boolean {
function applyDom(isDark: boolean, isUltra: boolean) {
document.body.classList.remove('dark', 'light');
document.body.classList.add(isDark ? 'dark' : 'light');
// Native scrollbars read color-scheme, not the body class.
document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';
if (isUltra) {
document.documentElement.setAttribute('data-theme', 'ultra-dark');
} else {
@@ -0,0 +1,5 @@
/* The body is capped at the viewport and scrolls; a trailing item margin
alone must not push it past the cap and summon a scrollbar. */
.client-form-modal .ant-tabs-content > .ant-form-item:last-child {
margin-bottom: 0;
}
@@ -49,6 +49,7 @@ import type {
} from '@/hooks/useClients';
import { useFail2banStatusQuery, getLimitIpNotice } from '@/api/queries/useFail2banStatusQuery';
import { ClientFormSchema, ClientCreateFormSchema, type ClientFormValues } from '@/schemas/client';
import './ClientFormModal.css';
const FLOW_OPTIONS = Object.values(TLS_FLOW_CONTROL);
const VMESS_SECURITY_OPTIONS = ['auto', 'aes-128-gcm', 'chacha20-poly1305'] as const;
@@ -803,6 +804,7 @@ export default function ClientFormModal({
open={open}
title={isEdit ? t('pages.clients.editClient') : t('pages.clients.addClient')}
destroyOnHidden
className="client-form-modal"
width={720}
zIndex={CLIENT_FORM_MODAL_Z_INDEX}
style={{ top: 20 }}
+30 -2
View File
@@ -1,8 +1,8 @@
import { render } from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';
import { afterEach, expect, test } from 'vitest';
import { withTheme } from '../../.storybook/preview';
import { ThemeProvider } from '@/hooks/useTheme';
import { ThemeProvider, useTheme } from '@/hooks/useTheme';
function Story() {
return <div>Story</div>;
@@ -14,9 +14,37 @@ function StorybookTheme({ theme }: { theme: 'light' | 'dark' }) {
> as Parameters<typeof withTheme>[1]);
}
function ThemeToggle() {
const { toggleTheme } = useTheme();
return <button onClick={toggleTheme}>toggle</button>;
}
afterEach(() => {
document.body.className = '';
document.documentElement.removeAttribute('data-theme');
document.documentElement.style.colorScheme = '';
});
// Without color-scheme the browser paints native scrollbars light inside dark
// modals, e.g. the Edit Client body.
test('native scrollbars follow the panel theme', () => {
const { getByRole } = render(
<ThemeProvider>
<ThemeToggle />
</ThemeProvider>,
);
expect(document.documentElement.style.colorScheme).toBe('dark');
fireEvent.click(getByRole('button'));
expect(document.documentElement.style.colorScheme).toBe('light');
});
test('native scrollbars follow the Storybook theme', () => {
const { rerender } = render(<StorybookTheme theme="light" />);
expect(document.documentElement.style.colorScheme).toBe('light');
rerender(<StorybookTheme theme="dark" />);
expect(document.documentElement.style.colorScheme).toBe('dark');
});
test('preserves unrelated body classes when applying the Storybook theme', () => {