Skip to content

Commit ba2d2fa

Browse files
authored
fix: align Switch with selection controls 2.0 (#4394)
* fix(next): use same dynamic color variable for hover as the other selection controls * feat(next): add SelectionControlsAlignment story for visual comparison * fix(next): align Switch spacing and margins with Checkbox and Radio - Use --eds-sizing-icon-lg for consistent icon sizing - Recalculate track (22x8px) and handle (12px) dimensions - Add gap compensation for label alignment - Use --eds-generic-gap-horizontal token for consistent spacing - Add density-specific margin adjustment for comfortable mode - Add flex-shrink: 0 to prevent control from shrinking * fix(next): remove unused --eds-field-width from Switch * fix(next): shortened comment about gap compensation * fix(next): margin comment shorter * fix(next): use fixed rem value for Switch gap compensation * fix(next): add min-block-size to Switch control and snapshot test - Add min-block-size to match icon height for proper spacing when label wraps - Add snapshot test for consistency with Checkbox and Radio - Remove alignment comparison story (no longer needed) * test(next): update snapshots for Field.Label font-size change to md
1 parent b5f886d commit ba2d2fa

8 files changed

Lines changed: 85 additions & 16 deletions

File tree

packages/eds-core-react/src/components/next/Checkbox/__snapshots__/Checkbox.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ exports[`Checkbox (next) matches snapshot 1`] = `
6868
class="eds-field__label"
6969
data-baseline="center"
7070
data-font-family="ui"
71-
data-font-size="lg"
71+
data-font-size="md"
7272
data-font-weight="normal"
7373
data-line-height="default"
7474
data-tracking="normal"

packages/eds-core-react/src/components/next/Field/Field.Label.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const FieldLabel = forwardRef<HTMLLabelElement, FieldLabelProps>(
99
ref={ref}
1010
as="label"
1111
family="ui"
12-
size="lg"
12+
size="md"
1313
baseline="center"
1414
lineHeight="default"
1515
weight="normal"

packages/eds-core-react/src/components/next/Radio/__snapshots__/Radio.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ exports[`Radio (next) matches snapshot 1`] = `
5353
class="eds-field__label"
5454
data-baseline="center"
5555
data-font-family="ui"
56-
data-font-size="lg"
56+
data-font-size="md"
5757
data-font-weight="normal"
5858
data-line-height="default"
5959
data-tracking="normal"

packages/eds-core-react/src/components/next/Switch/Switch.test.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ const ControlledSwitch = ({ onChange }: ControlledProps) => {
2424
}
2525

2626
describe('Switch (next)', () => {
27+
it('matches snapshot', () => {
28+
const { asFragment } = render(<Switch label="switch" />)
29+
expect(asFragment()).toMatchSnapshot()
30+
})
31+
2732
describe('Rendering', () => {
2833
it('renders with label', () => {
2934
render(<Switch label="Enable notifications" />)

packages/eds-core-react/src/components/next/Switch/Switch.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(function Switch(
4343
data-font-size="md"
4444
data-selectable-space="md"
4545
data-space-proportions="squished"
46+
data-color-appearance={disabled ? 'neutral' : 'accent'}
4647
>
4748
<span
4849
className="eds-switch__control"
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
2+
3+
exports[`Switch (next) matches snapshot 1`] = `
4+
<DocumentFragment>
5+
<div
6+
class="eds-field eds-switch"
7+
data-color-appearance="accent"
8+
data-font-size="md"
9+
data-position="start"
10+
data-selectable-space="md"
11+
data-space-proportions="squished"
12+
>
13+
<span
14+
class="eds-switch__control"
15+
data-color-appearance="neutral"
16+
>
17+
<input
18+
class="eds-switch__input"
19+
id="test-id"
20+
role="switch"
21+
type="checkbox"
22+
/>
23+
<span
24+
class="eds-switch__track"
25+
>
26+
<span
27+
class="eds-switch__handle"
28+
/>
29+
</span>
30+
</span>
31+
<label
32+
class="eds-field__label"
33+
data-baseline="center"
34+
data-font-family="ui"
35+
data-font-size="md"
36+
data-font-weight="normal"
37+
data-line-height="default"
38+
data-tracking="normal"
39+
for="test-id"
40+
>
41+
switch
42+
</label>
43+
</div>
44+
</DocumentFragment>
45+
`;

packages/eds-core-react/src/components/next/Switch/switch.css

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,35 @@
11
/* Switch component - uses Field for layout */
22
@layer eds-components {
33
.eds-switch {
4-
--eds-field-width: fit-content; /* Override Field's default 100% width */
5-
--_icon-size: var(--eds-typography-icon-size, var(--eds-sizing-icon-md));
6-
--_handle-size: calc(var(--_icon-size) * 0.6);
7-
--_track-width: calc(var(--_icon-size) * 1.1);
8-
--_track-height: calc(var(--_icon-size) * 0.4);
4+
--_switch-hover-color: var(--eds-color-bg-fill-muted-default);
5+
--_icon-size: var(--eds-sizing-icon-lg);
6+
/* Track: 22x8px, Handle: 12px (spacious density with 24px icon size) */
7+
--_handle-size: calc(var(--_icon-size) / 2);
8+
--_track-width: calc(var(--_icon-size) * 11 / 12);
9+
--_track-height: calc(var(--_icon-size) / 3);
910
/* TODO: Replace with semantic token when available */
1011
--_track-hover-bg: var(--eds-color-neutral-2);
12+
/*
13+
* Gap compensation for label alignment with Checkbox and Radio.
14+
* Checkbox/Radio icons have ~3px internal SVG padding, but the Switch
15+
* track is rendered edge-to-edge. This requires smaller gap adjustment
16+
* to achieve consistent label positioning across all selection controls.
17+
*/
18+
--_switch-gap-compensation: 0.1rem;
1119

1220
position: relative;
13-
gap: var(--eds-typography-gap-horizontal);
14-
padding: var(--eds-selectable-space-vertical)
15-
var(--eds-selectable-space-horizontal);
21+
gap: calc(
22+
var(--eds-generic-gap-horizontal) + var(--_switch-gap-compensation)
23+
);
24+
padding-inline: var(--eds-selectable-space-horizontal);
25+
padding-block: var(--eds-selectable-space-vertical);
26+
width: auto;
1627
border-radius: var(--eds-spacing-border-radius-rounded);
1728
cursor: pointer;
1829
transition: background-color 150ms;
30+
}
1931

32+
.eds-switch {
2033
&:has(.eds-switch__input:focus-visible) {
2134
outline: var(--eds-sizing-stroke-thin) solid var(--eds-color-border-focus);
2235
outline-offset: var(--eds-sizing-stroke-thick);
@@ -28,7 +41,7 @@
2841

2942
@media (hover: hover) and (pointer: fine) {
3043
&:hover:not([data-disabled='true']) {
31-
background-color: var(--eds-color-bg-accent-fill-muted-default);
44+
background-color: var(--_switch-hover-color);
3245
}
3346
}
3447

@@ -47,9 +60,14 @@
4760
align-items: center;
4861
justify-content: center;
4962
flex-shrink: 0;
50-
/* Same margins as icons for proper form alignment */
51-
margin-block: calc(var(--_icon-size) * -0.25);
52-
margin-inline: calc(var(--_icon-size) * -0.1);
63+
min-block-size: var(--_icon-size);
64+
/* Negative margin for alignment with Checkbox/Radio icons */
65+
margin-block: -6px;
66+
margin-inline: -1.9px;
67+
}
68+
69+
[data-density='comfortable'] .eds-switch__control {
70+
margin-inline: -2.1px;
5371
}
5472

5573
.eds-switch__input {

packages/eds-core-react/src/components/next/TextField/__snapshots__/TextField.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ exports[`TextField (Next EDS 2.0) Matches snapshot 1`] = `
1212
class="eds-field__label"
1313
data-baseline="center"
1414
data-font-family="ui"
15-
data-font-size="lg"
15+
data-font-size="md"
1616
data-font-weight="normal"
1717
data-line-height="default"
1818
data-tracking="normal"

0 commit comments

Comments
 (0)