fix(ui): align theme toggle with dashboard style
This commit is contained in:
parent
c84de9aaa9
commit
9c948bd746
7 changed files with 99 additions and 17 deletions
|
|
@ -34,8 +34,11 @@ describe("React app dashboard state view", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(html).toContain('data-ui-theme-toggle="true"');
|
expect(html).toContain('data-ui-theme-toggle="true"');
|
||||||
|
expect(html).toContain('data-ui-theme-current="dark"');
|
||||||
expect(html).toContain('aria-label="Switch to light theme"');
|
expect(html).toContain('aria-label="Switch to light theme"');
|
||||||
expect(html).not.toContain("aria-pressed");
|
expect(html).not.toContain("aria-pressed");
|
||||||
|
expect(html).toContain("Theme");
|
||||||
expect(html).toContain("Dark");
|
expect(html).toContain("Dark");
|
||||||
|
expect(html).toContain("Light");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,22 @@ export function ThemeToggle({ theme, onThemeChange }: ThemeToggleProps) {
|
||||||
<button
|
<button
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
className="theme-toggle"
|
className="theme-toggle"
|
||||||
|
data-ui-theme-current={theme}
|
||||||
data-ui-theme-toggle="true"
|
data-ui-theme-toggle="true"
|
||||||
onClick={() => onThemeChange(nextTheme)}
|
onClick={() => onThemeChange(nextTheme)}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<IconGlyph
|
<span className="theme-toggle__label">Theme</span>
|
||||||
name={theme === "dark" ? "mdi:weather-night" : "mdi:white-balance-sunny"}
|
<span className="theme-toggle__switch" aria-hidden="true">
|
||||||
size="sm"
|
<span className="theme-toggle__cell" data-active={theme === "dark"}>
|
||||||
/>
|
<IconGlyph name="mdi:weather-night" size="sm" />
|
||||||
<span>{theme === "dark" ? "Dark" : "Light"}</span>
|
<span>Dark</span>
|
||||||
|
</span>
|
||||||
|
<span className="theme-toggle__cell" data-active={theme === "light"}>
|
||||||
|
<IconGlyph name="mdi:white-balance-sunny" size="sm" />
|
||||||
|
<span>Light</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { FooterCell } from "./FooterCell";
|
||||||
import { IconButton } from "./IconButton";
|
import { IconButton } from "./IconButton";
|
||||||
import { ServiceRow } from "./ServiceRow";
|
import { ServiceRow } from "./ServiceRow";
|
||||||
import { TelemetryCard } from "./TelemetryCard";
|
import { TelemetryCard } from "./TelemetryCard";
|
||||||
|
import { ThemeToggle } from "./ThemeToggle";
|
||||||
import { dashboardPreviewFixtures } from "../fixtures";
|
import { dashboardPreviewFixtures } from "../fixtures";
|
||||||
|
|
||||||
describe("dashboard UI components", () => {
|
describe("dashboard UI components", () => {
|
||||||
|
|
@ -144,4 +145,20 @@ describe("dashboard UI components", () => {
|
||||||
expect(iconButton).toContain("class=\"icon-button custom-icon-action ");
|
expect(iconButton).toContain("class=\"icon-button custom-icon-action ");
|
||||||
expect(iconButton).toContain("aria-expanded=\"false\"");
|
expect(iconButton).toContain("aria-expanded=\"false\"");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("renders the theme toggle as a segmented instrument control", () => {
|
||||||
|
const body = renderToString(
|
||||||
|
<ThemeToggle theme="light" onThemeChange={() => undefined} />,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(body).toContain('class="theme-toggle"');
|
||||||
|
expect(body).toContain('data-ui-theme-current="light"');
|
||||||
|
expect(body).toContain('aria-label="Switch to dark theme"');
|
||||||
|
expect(body).toContain('class="theme-toggle__label"');
|
||||||
|
expect(body).toContain('class="theme-toggle__switch"');
|
||||||
|
expect(body).toContain('data-active="true"');
|
||||||
|
expect(body).toContain("Theme");
|
||||||
|
expect(body).toContain("Dark");
|
||||||
|
expect(body).toContain("Light");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -565,35 +565,90 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle {
|
.theme-toggle {
|
||||||
display: inline-grid;
|
display: grid;
|
||||||
grid-auto-flow: column;
|
grid-template-columns: 2.72rem minmax(0, 1fr);
|
||||||
gap: var(--ui-space-2);
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
width: 9.75rem;
|
||||||
min-width: 6.5rem;
|
min-width: 0;
|
||||||
min-height: 2.15rem;
|
min-height: 2.18rem;
|
||||||
border: var(--ui-border);
|
border: var(--ui-border-strong);
|
||||||
background: var(--ui-color-surface-raised);
|
background: var(--ui-color-surface-module);
|
||||||
color: var(--ui-color-text);
|
color: var(--ui-color-text);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
font-size: 0.62rem;
|
font-size: 0.55rem;
|
||||||
font-weight: 850;
|
font-weight: 850;
|
||||||
letter-spacing: 0;
|
letter-spacing: 0;
|
||||||
padding: 0 var(--ui-space-3);
|
padding: 0.13rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
box-shadow: var(--ui-shadow-hard);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle:hover {
|
.theme-toggle:hover {
|
||||||
border-color: var(--ui-color-accent);
|
border-color: var(--ui-color-accent);
|
||||||
color: var(--ui-color-accent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle span {
|
.theme-toggle__label {
|
||||||
|
display: grid;
|
||||||
|
align-self: stretch;
|
||||||
|
place-items: center;
|
||||||
|
border-right: var(--ui-border);
|
||||||
|
color: var(--ui-color-muted);
|
||||||
|
font-size: 0.48rem;
|
||||||
|
font-weight: 800;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle__switch {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 0.1rem;
|
||||||
|
min-width: 0;
|
||||||
|
padding-left: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle__cell {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
|
gap: 0.2rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 1.68rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
color: var(--ui-color-muted);
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0 0.22rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.theme-toggle__cell[data-active="true"] {
|
||||||
|
border-color: var(--ui-color-accent);
|
||||||
|
background: var(--ui-color-accent);
|
||||||
|
color: var(--ui-color-accent-contrast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle__cell > span {
|
||||||
|
color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle:hover .theme-toggle__cell[data-active="false"] {
|
||||||
|
border-color: var(--ui-color-line);
|
||||||
|
color: var(--ui-color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle .icon-glyph {
|
||||||
|
width: 0.82rem;
|
||||||
|
height: 0.82rem;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle .icon-glyph svg {
|
||||||
|
width: 0.78rem;
|
||||||
|
height: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
.icon-glyph {
|
.icon-glyph {
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
width: 1.55rem;
|
width: 1.55rem;
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 254 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 275 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 276 KiB |
Loading…
Add table
Add a link
Reference in a new issue