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 all 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
211 changes: 122 additions & 89 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,165 @@
gap: var(--ams-radio-gap);
line-height: var(--ams-radio-line-height);
outline-offset: var(--ams-radio-outline-offset);
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: none;
inline-size: var(--ams-radio-icon-container-inline-size);
}

.ams-radio__circle::after {
border-color: var(--ams-radio-circle-hover-border-color);
}
.ams-radio__circle {
fill: none;
stroke: var(--ams-radio-circle-stroke);
stroke-width: 0.125rem;
}

.ams-radio__checked-indicator {
display: none;
fill: var(--ams-radio-checked-indicator-fill);
}

// Default hover
.ams-radio__label:hover {
color: var(--ams-radio-hover-color);
text-decoration-line: var(--ams-radio-hover-text-decoration-line);

.ams-radio__circle {
stroke: var(--ams-radio-circle-hover-stroke);
}

.ams-radio__checked-indicator {
fill: var(--ams-radio-checked-indicator-hover-fill);
}
}

// Default checked
.ams-radio__input:checked {
+ .ams-radio__label .ams-radio__circle::after {
background-image: var(--ams-radio-circle-checked-background-image);
// Invalid
.ams-radio__input[aria-invalid="true"] + .ams-radio__label {
.ams-radio__circle {
stroke: var(--ams-radio-circle-invalid-stroke);
}

.ams-radio__checked-indicator {
fill: var(--ams-radio-checked-indicator-invalid-fill);
}
}

// Invalid unchecked
.ams-radio__input[aria-invalid="true"] {
+ .ams-radio__label .ams-radio__circle::after {
border-color: var(--ams-radio-circle-invalid-border-color);
// Checked
.ams-radio__input:checked + .ams-radio__label {
.ams-radio__checked-indicator {
display: block;
}
}

// Disabled unchecked
.ams-radio__input:disabled {
+ .ams-radio__label {
color: var(--ams-radio-disabled-color);
cursor: not-allowed;
// Disabled
.ams-radio__input:disabled + .ams-radio__label {
color: var(--ams-radio-disabled-color);
cursor: not-allowed;

.ams-radio__circle::after {
border-color: var(--ams-radio-circle-disabled-border-color);
border-width: var(--ams-radio-circle-disabled-border-width);
}
.ams-radio__circle {
stroke: var(--ams-radio-circle-disabled-stroke);
}

.ams-radio__checked-indicator {
fill: var(--ams-radio-checked-indicator-disabled-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);
// Disabled invalid
.ams-radio__input[aria-invalid="true"]:disabled + .ams-radio__label {
.ams-radio__circle {
// TODO: currently disabled invalid gets the same styling as disabled. This should get its own styling.
stroke: var(--ams-radio-circle-disabled-invalid-stroke);
}

.ams-radio__checked-indicator {
// TODO: currently disabled invalid gets the same styling as disabled. This should get its own styling.
fill: var(--ams-radio-checked-indicator-disabled-invalid-fill);
}
}

// Disabled label
// HOVER

// Disabled label hover
.ams-radio__input:disabled + .ams-radio__label:hover {
text-decoration: none;
}

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

// Disabled invalid unchecked
.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__checked-indicator {
// TODO: this should be the (currently non-existent) dark red hover color
fill: var(--ams-radio-checked-indicator-invalid-hover-fill);
}
}

// HOVER STATES
// Disabled invalid hover
.ams-radio__input[aria-invalid="true"]:disabled + .ams-radio__label:hover {
.ams-radio__circle {
// TODO: currently disabled invalid gets the same styling as disabled. This should get its own styling.
stroke: var(--ams-radio-circle-disabled-invalid-hover-stroke);
}

// 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);
.ams-radio__checked-indicator {
// TODO: currently disabled invalid gets the same styling as disabled. This should get its own styling.
fill: var(--ams-radio-checked-indicator-disabled-invalid-hover-fill);
}
}

// 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);
}
// FORCED COLORS

// 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);
}
// 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__circle {
stroke: FieldText;
}

// 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);
.ams-radio__checked-indicator {
fill: FieldText;
}
}
}

// 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);
// 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__circle {
stroke: ActiveText;
}

.ams-radio__checked-indicator {
fill: ActiveText;
}
}
}

// DISABLED INVALID STATES
// 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__circle {
stroke: GrayText;
}

// 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);
.ams-radio__checked-indicator {
fill: GrayText;
}
}
}
15 changes: 14 additions & 1 deletion 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 All @@ -19,16 +20,20 @@ describe('Radio', () => {
expect(label).toBeVisible()
})

it('renders a design system BEM class name', () => {
it('renders design system BEM class names', () => {
const { container } = render(<Radio />)

const wrapper = container.querySelector(':only-child')
const input = screen.getByRole('radio')
const label = container.querySelector('label')
const circle = container.querySelector('.ams-radio__circle')
const indicator = container.querySelector('.ams-radio__checked-indicator')

expect(wrapper).toHaveClass('ams-radio')
expect(input).toHaveClass('ams-radio__input')
expect(label).toHaveClass('ams-radio__label')
expect(circle).toBeInTheDocument()
expect(indicator).toBeInTheDocument()
})

it('renders an additional class name', () => {
Expand Down Expand Up @@ -150,6 +155,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 />}</span>
{children}
</label>
</div>
Expand Down
1 change: 1 addition & 0 deletions proprietary/assets/icons/Radio.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions proprietary/react-icons/.svgrrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ module.exports = {
focusable: 'false',
},
typescript: true,
svgoConfig: './svgo.config.mjs',
}
15 changes: 15 additions & 0 deletions proprietary/react-icons/svgo.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
prefixIds: {
// Do not change class names defined in the SVGs
prefixClassNames: false,
},
},
},
},
],
}
Loading