Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Use inline SVGs for Radio icon #1460

Merged
merged 35 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
0cffadf
Use svg for radio buttons
alimpens Jul 26, 2024
3347689
Rename radio circle to icon
alimpens Jul 26, 2024
76e3ac4
Remove unnecessary rules
alimpens Jul 26, 2024
e47f043
Rename mixin
alimpens Jul 26, 2024
75f61b6
Temp
alimpens Jul 31, 2024
62d9238
Separate radio button from check mark
alimpens Aug 12, 2024
956fcfb
Merge branch 'develop' into feat/DES-649-use-svg-for-radio
alimpens Aug 12, 2024
675051e
Move calculation to tokens
alimpens Aug 14, 2024
540a204
Move radio button size to tokens
alimpens Aug 14, 2024
45a24fe
Use link appearance tokens for underline styling
alimpens Aug 14, 2024
2879582
Merge branch 'feat/DES-649-use-svg-for-radio' of https://github.com/A…
alimpens Aug 14, 2024
c713d91
Merge branch 'develop' into feat/DES-649-use-svg-for-radio
VincentSmedinga Sep 4, 2024
9da979b
Merge branch 'develop' of https://github.com/Amsterdam/design-system …
alimpens Sep 13, 2024
a874b33
Use inline SVG icon
alimpens Sep 13, 2024
4393fd8
Group forced colors overrides
alimpens Sep 13, 2024
9e61e89
Update tokens
alimpens Sep 13, 2024
3c2e305
Allow custom icon
alimpens Sep 16, 2024
fbc8fef
Remove icon control
alimpens Sep 16, 2024
7fd26ee
Add test
alimpens Sep 16, 2024
57b1869
Typo
alimpens Sep 16, 2024
1b2ab42
Remove unnecessary CSS
alimpens Sep 20, 2024
c048321
Use flex shorthand
alimpens Sep 20, 2024
76ad6d2
Merge branch 'feat/DES-649-use-svg-for-radio' of https://github.com/A…
alimpens Sep 20, 2024
208cdbb
Use correct size
alimpens Sep 20, 2024
68f6c37
Prevent SVGR from changing classes defined in SVG
alimpens Sep 20, 2024
1a6dd2e
Use classes instead of element selectors
alimpens Sep 20, 2024
86c33bc
Consistently use SVG for shapes, CSS for styling
alimpens Sep 20, 2024
dae2f11
Use consistent CSS selectors
alimpens Sep 20, 2024
5509fcd
Use custom icon
alimpens Sep 20, 2024
f281acc
Do not use relative values in SVG
alimpens Sep 20, 2024
f15a2b0
Merge branch 'develop' of https://github.com/Amsterdam/design-system …
alimpens Sep 20, 2024
09efefe
Update tests
alimpens Sep 20, 2024
2f9ad81
Fix cutoff issue on zoom out
alimpens Sep 20, 2024
2a12a6c
Rename tokens
alimpens Sep 20, 2024
672ad9b
Merge branch 'develop' into feat/DES-649-use-svg-for-radio
VincentSmedinga Sep 25, 2024
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
231 changes: 140 additions & 91 deletions packages/css/src/components/radio/radio.scss
VincentSmedinga marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,7 @@
@include input-label-focus;
}

.ams-radio__circle {
align-items: center;
block-size: calc(var(--ams-radio-font-size) * var(--ams-radio-line-height));
display: flex;
flex-shrink: 0;
inline-size: 1.5rem;

&::after {
background-position: center;
background-repeat: no-repeat;
background-size: 1rem;
block-size: 1.5rem;
border-color: var(--ams-radio-circle-border-color);
border-radius: 100%;
border-style: solid;
border-width: var(--ams-radio-circle-border-width);
box-sizing: border-box;
content: "";
inline-size: 100%;
}
}

// Default
.ams-radio__label {
color: var(--ams-radio-color);
cursor: pointer;
Expand All @@ -44,111 +23,181 @@
gap: var(--ams-radio-gap);
line-height: var(--ams-radio-line-height);
outline-offset: var(--ams-radio-outline-offset);
position: relative;
VincentSmedinga marked this conversation as resolved.
Show resolved Hide resolved
text-decoration-thickness: var(--ams-radio-text-decoration-thickness);
text-underline-offset: var(--ams-radio-text-underline-offset);
VincentSmedinga marked this conversation as resolved.
Show resolved Hide resolved

@include text-rendering;
}

&:hover {
color: var(--ams-radio-hover-color);
text-decoration-line: underline;
text-decoration-thickness: var(--ams-radio-hover-text-decoration-thickness);
text-underline-offset: 0.375rem;
.ams-radio__icon-container {
block-size: var(--ams-radio-icon-container-block-size);
display: flex;
flex-shrink: 0;
alimpens marked this conversation as resolved.
Show resolved Hide resolved
inline-size: var(--ams-radio-icon-container-inline-size);
}

.ams-radio__circle::after {
border-color: var(--ams-radio-circle-hover-border-color);
}
.ams-radio__icon {
circle:first-child {
stroke: var(--ams-radio-icon-outer-stroke);
stroke-width: 0.15rem;
VincentSmedinga marked this conversation as resolved.
Show resolved Hide resolved
}

@include text-rendering;
circle:nth-child(2) {
VincentSmedinga marked this conversation as resolved.
Show resolved Hide resolved
display: none;
fill: var(--ams-radio-icon-inner-fill);
}
}

// Default checked
.ams-radio__input:checked {
+ .ams-radio__label .ams-radio__circle::after {
background-image: var(--ams-radio-circle-checked-background-image);
// Default hover
.ams-radio__label:hover {
color: var(--ams-radio-hover-color);
text-decoration-line: var(--ams-radio-hover-text-decoration-line);

.ams-radio__icon circle:first-child {
stroke: var(--ams-radio-icon-hover-outer-stroke);
}
}

// Invalid unchecked
.ams-radio__input[aria-invalid="true"] {
+ .ams-radio__label .ams-radio__circle::after {
border-color: var(--ams-radio-circle-invalid-border-color);
.ams-radio__icon circle:nth-child(2) {
fill: var(--ams-radio-icon-hover-inner-fill);
}
}

// Disabled unchecked
.ams-radio__input:disabled {
+ .ams-radio__label {
color: var(--ams-radio-disabled-color);
cursor: not-allowed;
// Invalid
.ams-radio__input[aria-invalid="true"] + .ams-radio__label {
.ams-radio__icon circle:first-child {
stroke: var(--ams-radio-icon-invalid-outer-stroke);
}

.ams-radio__circle::after {
border-color: var(--ams-radio-circle-disabled-border-color);
border-width: var(--ams-radio-circle-disabled-border-width);
}
.ams-radio__icon circle:nth-child(2) {
fill: var(--ams-radio-icon-invalid-inner-fill);
}
}

// Invalid checked
.ams-radio__input[aria-invalid="true"]:checked {
+ .ams-radio__label .ams-radio__circle::after {
background-image: var(--ams-radio-circle-invalid-checked-background-image);
// Checked
.ams-radio__input:checked + .ams-radio__label {
.ams-radio__icon circle:nth-child(2) {
display: block;
}
}

// Disabled label
.ams-radio__input:disabled + .ams-radio__label:hover {
text-decoration: none;
}
// Disabled
.ams-radio__input:disabled + .ams-radio__label {
color: var(--ams-radio-disabled-color);
cursor: not-allowed;

.ams-radio__icon circle:first-child {
stroke: var(--ams-radio-icon-disabled-outer-stroke);
}

// Disabled checked
.ams-radio__input:disabled:checked {
+ .ams-radio__label .ams-radio__circle::after {
background-image: var(--ams-radio-circle-disabled-checked-background-image);
.ams-radio__icon circle:nth-child(2) {
fill: var(--ams-radio-icon-disabled-inner-fill);
}
}

// Disabled invalid unchecked
// Disabled invalid
.ams-radio__input[aria-invalid="true"]:disabled {
+ .ams-radio__label .ams-radio__circle::after {
// TODO: currently disabled invalid gets the same styling as disabled. This should get its own styling.
border-color: var(--ams-radio-circle-disabled-border-color);
+ .ams-radio__label {
.ams-radio__icon circle:first-child {
// TODO: currently disabled invalid gets the same styling as disabled. This should get its own styling.
stroke: var(--ams-radio-icon-disabled-invalid-outer-stroke);
}

.ams-radio__icon circle:nth-child(2) {
// TODO: currently disabled invalid gets the same styling as disabled. This should get its own styling.
fill: var(--ams-radio-icon-disabled-invalid-inner-fill);
}
}
}

// HOVER STATES
// HOVER

// Invalid unchecked hover
.ams-radio__input[aria-invalid="true"] + .ams-radio__label:hover .ams-radio__circle::after {
// TODO: this should be the (currently non-existent) dark red hover color
border-color: var(--ams-radio-circle-invalid-hover-border-color);
// Disabled label hover
.ams-radio__input:disabled + .ams-radio__label:hover {
text-decoration: none;
}

// Default checked hover
.ams-radio__input:checked + .ams-radio__label:hover .ams-radio__circle::after {
background-image: var(--ams-radio-circle-checked-hover-background-image);
}
// Invalid hover
.ams-radio__input[aria-invalid="true"] + .ams-radio__label:hover {
.ams-radio__icon circle:first-child {
// TODO: this should be the (currently non-existent) dark red hover color
stroke: var(--ams-radio-icon-invalid-hover-outer-stroke);
}

// Invalid checked hover
.ams-radio__input[aria-invalid="true"]:checked + .ams-radio__label:hover .ams-radio__circle::after {
// TODO: this should be the (currently non-existent) dark red hover color
background-image: var(--ams-radio-circle-invalid-checked-hover-background-image);
.ams-radio__icon circle:nth-child(2) {
// TODO: this should be the (currently non-existent) dark red hover color
fill: var(--ams-radio-icon-invalid-hover-inner-fill);
}
}

// Disabled checked hover
.ams-radio__input:disabled:checked + .ams-radio__label:hover .ams-radio__circle::after {
background-image: var(--ams-radio-circle-disabled-checked-hover-background-image);
// Disabled invalid hover
.ams-radio__input[aria-invalid="true"]:disabled + .ams-radio__label:hover {
.ams-radio__icon circle:first-child {
// TODO: currently disabled invalid gets the same styling as disabled. This should get its own styling.
stroke: var(--ams-radio-icon-disabled-invalid-hover-outer-stroke);
}

.ams-radio__icon circle:nth-child(2) {
// TODO: currently disabled invalid gets the same styling as disabled. This should get its own styling.
fill: var(--ams-radio-icon-disabled-invalid-hover-inner-fill);
}
}

// Disabled invalid unchecked hover
.ams-radio__input[aria-invalid="true"]:disabled + .ams-radio__label:hover .ams-radio__circle::after {
// TODO: currently disabled invalid gets the same styling as disabled. This should get its own styling.
border-color: var(--ams-radio-circle-disabled-border-color);
// FORCED COLORS

// Default
@media (forced-colors: active) {
.ams-radio__label,
.ams-radio__label:hover,
.ams-radio__input[aria-invalid="true"] + .ams-radio__label,
.ams-radio__input[aria-invalid="true"] + .ams-radio__label:hover {
.ams-radio__icon {
circle:first-child {
stroke: FieldText;
dlnr marked this conversation as resolved.
Show resolved Hide resolved
}

// These two selectors never target the same element, so it's safe to disable the linter here.
/* stylelint-disable-next-line no-descending-specificity */
circle:nth-child(2) {
fill: FieldText;
}
}
}
}

// DISABLED INVALID STATES
// Checked
@media (forced-colors: active) {
.ams-radio__input:checked + .ams-radio__label,
.ams-radio__input[aria-invalid="true"]:checked + .ams-radio__label:hover {
.ams-radio__icon {
circle:first-child {
stroke: ActiveText;
}

// These two selectors never target the same element, so it's safe to disable the linter here.
/* stylelint-disable-next-line no-descending-specificity */
circle:nth-child(2) {
fill: ActiveText;
}
}
}
}

// Disabled invalid checked
.ams-radio__input[aria-invalid="true"]:disabled:checked {
+ .ams-radio__label .ams-radio__circle::after {
// TODO: currently disabled invalid gets the same styling as disabled. This should get its own styling.
background-image: var(--ams-radio-circle-disabled-checked-background-image);
// Disabled
@media (forced-colors: active) {
.ams-radio__input:disabled + .ams-radio__label,
.ams-radio__input[aria-invalid="true"]:disabled + .ams-radio__label,
.ams-radio__input[aria-invalid="true"]:disabled + .ams-radio__label:hover {
.ams-radio__icon {
circle:first-child {
stroke: GrayText;
}

// These two selectors never target the same element, so it's safe to disable the linter here.
/* stylelint-disable-next-line no-descending-specificity */
circle:nth-child(2) {
fill: GrayText;
}
}
}
}
9 changes: 9 additions & 0 deletions packages/react/src/Radio/Radio.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FavouriteIcon } from '@amsterdam/design-system-react-icons'
import { render, screen } from '@testing-library/react'
import { createRef } from 'react'
import { Radio } from './Radio'
Expand Down Expand Up @@ -150,6 +151,14 @@ describe('Radio', () => {
expect(handleChange).toHaveBeenCalled()
})

it('shows a custom icon', () => {
const { container } = render(<Radio icon={<FavouriteIcon className="test-class" />} />)

const icon = container.querySelector('svg')

expect(icon).toHaveClass('test-class')
})

it('supports ForwardRef in React', () => {
const ref = createRef<HTMLInputElement>()

Expand Down
9 changes: 6 additions & 3 deletions packages/react/src/Radio/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
* Copyright Gemeente Amsterdam
*/

import { RadioIcon } from '@amsterdam/design-system-react-icons'
import clsx from 'clsx'
import { forwardRef, useId } from 'react'
import type { ForwardedRef, InputHTMLAttributes, PropsWithChildren } from 'react'
import type { ForwardedRef, InputHTMLAttributes, PropsWithChildren, ReactNode } from 'react'

export type RadioProps = {
/** An icon to display instead of the default icon. */
icon?: ReactNode
/** Whether the value fails a validation rule. */
invalid?: boolean
} & PropsWithChildren<Omit<InputHTMLAttributes<HTMLInputElement>, 'aria-invalid' | 'type'>>

export const Radio = forwardRef(
({ children, className, invalid, ...restProps }: RadioProps, ref: ForwardedRef<HTMLInputElement>) => {
({ children, className, icon, invalid, ...restProps }: RadioProps, ref: ForwardedRef<HTMLInputElement>) => {
const id = useId()

return (
Expand All @@ -29,7 +32,7 @@ export const Radio = forwardRef(
type="radio"
/>
<label className="ams-radio__label" htmlFor={id}>
<span className="ams-radio__circle" />
<span className="ams-radio__icon-container">{icon ?? <RadioIcon className="ams-radio__icon" />}</span>
{children}
</label>
</div>
Expand Down
1 change: 1 addition & 0 deletions proprietary/assets/icons/Radio.svg
VincentSmedinga marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading