diff --git a/.changeset/shiny-dryers-count.md b/.changeset/shiny-dryers-count.md new file mode 100644 index 0000000000..727a9af1e1 --- /dev/null +++ b/.changeset/shiny-dryers-count.md @@ -0,0 +1,8 @@ +--- +"@digdir/designsystemet-css": major +"@digdir/designsystemet-react": major +--- + +Radio + Checkbox: +- Use `label` prop instead of `children` as label text +- Remove `Radio.Group` and `Checkbox.Group` and use `Fieldset` instead diff --git a/apps/theme/components/Previews/Components/Components.tsx b/apps/theme/components/Previews/Components/Components.tsx index 5cb80a806c..faa5e397a4 100644 --- a/apps/theme/components/Previews/Components/Components.tsx +++ b/apps/theme/components/Previews/Components/Components.tsx @@ -35,7 +35,6 @@ import { useState } from 'react'; import classes from './Components.module.css'; export const Components = () => { - const [radioValue, setRadioValue] = useState('vanilje'); const [currentPage, setCurrentPage] = useState(1); const pagination = usePagination({ currentPage, @@ -47,21 +46,15 @@ export const Components = () => { return (
- - En kilo poteter - To liter Farris - - Blomkål - - - Pizza - - - Tre liter lettmelk - - 2kg smågodt - 10 poser med Smash - +
+ + + + + + + +
@@ -205,19 +198,16 @@ export const Components = () => {
- setRadioValue(e)} > - Vanilje - Jordbær - Sjokolade - Jeg spiser ikke iskrem - + + + + +
diff --git a/packages/css/checkbox.css b/packages/css/checkbox.css deleted file mode 100644 index dec68939ee..0000000000 --- a/packages/css/checkbox.css +++ /dev/null @@ -1,221 +0,0 @@ -.ds-checkbox { - --dsc-checkbox-size: 1.75rem; - --dsc-checkbox-focus-border-width: 3px; - --dsc-checkbox-background: var(--ds-color-neutral-background-default); - --dsc-checkbox-border-color: var(--ds-color-neutral-border-default); - --dsc-checkbox-border__hover--size: calc(var(--ds-spacing-3) / 2); - --dsc-checkbox-border__hover: 0 0 0 var(--dsc-checkbox-border__hover--size) var(--ds-color-accent-surface-hover); - --dsc-checkbox-check_color: transparent; - - display: grid; -} - -.ds-checkbox:has(.ds-checkbox__label) { - grid-template-columns: var(--dsc-checkbox-size) auto; - gap: var(--ds-spacing-2); -} - -/* Checkbox */ -.ds-checkbox__input { - position: relative; - width: var(--dsc-checkbox-size); - height: var(--dsc-checkbox-size); - z-index: 1; - appearance: none; - margin: 0; - align-self: center; - outline: none; - cursor: pointer; - box-shadow: inset 0 0 0 2px var(--dsc-checkbox-border-color); - background: var(--dsc-checkbox-background); - border-radius: var(--ds-border-radius-sm); -} - -.ds-checkbox__input::before { - position: absolute; - content: ''; - display: block; - width: 2.75rem; - height: 2.75rem; - transform: translate(-50%, -50%); - top: 50%; - left: 50%; - cursor: pointer; - border-radius: var(--ds-border-radius-sm); -} - -.ds-checkbox__label { - /* min-height: var(--ds-sizing-10); */ - min-width: min-content; - display: inline-flex; - flex-direction: row; - gap: var(--ds-spacing-1); - align-items: center; - cursor: pointer; -} - -.ds-checkbox__description { - margin-top: calc(var(--ds-spacing-2) * -1); - color: var(--ds-color-neutral-text-subtle); - grid-column: 2; -} - -.ds-checkbox--readonly > .ds-checkbox__label, -.ds-checkbox--readonly > .ds-checkbox__input, -.ds-checkbox--readonly > .ds-checkbox__input::before { - cursor: default; -} - -.ds-checkbox__input:disabled, -.ds-checkbox__input:disabled ~ .ds-checkbox__label, -.ds-checkbox__input:disabled::before { - cursor: not-allowed; -} - -/* .ds-checkbox__input:focus-visible { - outline-offset: 3px; - outline: var(--dsc-checkbox-focus-border-width) solid var(--ds-color-focus-outer); - box-shadow: - 0 0 0 var(--dsc-checkbox-focus-border-width) var(--ds-color-focus-inner), - inset 0 0 0 2px var(--dsc-checkbox-border-color); - } */ - -.ds-checkbox__input::after { - content: ''; - width: 100%; - height: 100%; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background-color: var(--dsc-checkbox-check_color); - mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 23 23' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M18.5509 6.32414C18.9414 6.71467 18.9414 7.34783 18.5509 7.73836L10.5821 15.7071C10.1916 16.0976 9.55842 16.0976 9.16789 15.7071L4.94914 11.4884C4.55862 11.0978 4.55862 10.4647 4.94914 10.0741C5.33967 9.68362 5.97283 9.68362 6.36336 10.0741L9.875 13.5858L17.1366 6.32414C17.5272 5.93362 18.1603 5.93362 18.5509 6.32414Z' fill='white'/%3E%3C/svg%3E%0A"); -} - -.ds-checkbox__input:checked { - --dsc-checkbox-border-color: var(--ds-color-accent-base-default); - --dsc-checkbox-background: var(--ds-color-accent-base-default); - --dsc-checkbox-check_color: var(--ds-color-accent-contrast-default); - - background: var(--dsc-checkbox-background); -} - -.ds-checkbox__input:indeterminate { - --dsc-checkbox-border-color: var(--ds-color-accent-base-default); - --dsc-checkbox-background: var(--ds-color-accent-base-default); - --dsc-checkbox-check_color: var(--ds-color-accent-contrast-default); -} - -.ds-checkbox__input:indeterminate::after { - mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 23 23' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.25 11.25C4.25 10.4216 4.92157 9.75 5.75 9.75H16.75C17.5784 9.75 18.25 10.4216 18.25 11.25C18.25 12.0784 17.5784 12.75 16.75 12.75H5.75C4.92157 12.75 4.25 12.0784 4.25 11.25Z' fill='white' /%3E%3C/svg%3E%0A"); -} - -.ds-checkbox--readonly > .ds-checkbox__input { - --dsc-checkbox-border-color: var(--ds-color-neutral-border-subtle); - --dsc-checkbox-background: var(--ds-color-neutral-background-subtle); -} - -.ds-checkbox__input:disabled, -.ds-checkbox__input:disabled ~ .ds-checkbox__label, -.ds-checkbox__input:disabled ~ .ds-checkbox__description { - opacity: var(--ds-disabled-opacity); -} - -/* .ds-checkbox__input:checked:not(:focus-visible) { - box-shadow: inset 0 0 0 2px var(--dsc-checkbox-border-color); - } */ - -.ds-checkbox:has(.ds-checkbox__input:focus-visible) { - --dsc-focus-border-width: 3px; - - outline: var(--dsc-focus-border-width) solid var(--ds-color-focus-outer); - outline-offset: var(--dsc-focus-border-width); - box-shadow: 0 0 0 var(--dsc-focus-border-width) var(--ds-color-focus-inner); - border-radius: var(--ds-border-radius-sm); -} - -.ds-checkbox--readonly > .ds-checkbox__input:checked { - --dsc-checkbox-check_color: var(--ds-color-neutral-text-subtle); - - background: var(--dsc-checkbox-background); -} - -.ds-checkbox--readonly > .ds-checkbox__input:indeterminate { - --dsc-checkbox-check_color: var(--ds-color-neutral-text-subtle); - - background: var(--dsc-checkbox-background); -} - -.ds-checkbox--error > .ds-checkbox__input:not(:disabled, :focus-visible) { - --dsc-checkbox-border-color: var(--ds-color-danger-border-default); -} - -/* Only use hover for non-touch devices to prevent sticky-hovering - "input:not(:read-only)" does not work so using ".container:not(.readonly) >" instead */ -@media (hover: hover) and (pointer: fine) { - .ds-checkbox:not(.ds-checkbox--readonly) .ds-checkbox__input:not(:disabled) ~ .ds-checkbox__label:hover, - .ds-checkbox:not(.ds-checkbox--readonly) .ds-checkbox__input:hover:not(:disabled) ~ .ds-checkbox__label { - color: var(--ds-color-accent-text-subtle); - } - - .ds-checkbox:not(.ds-checkbox--readonly) .ds-checkbox__input:hover:not(:checked, :disabled, :focus-visible) { - --dsc-checkbox-border-color: var(--ds-color-accent-border-strong); - - box-shadow: var(--dsc-checkbox-border__hover), inset 0 0 0 2px var(--dsc-checkbox-border-color); - } - - .ds-checkbox:not(.ds-checkbox--readonly) .ds-checkbox__input:indeterminate:hover:not(:focus-visible) { - --dsc-checkbox-border-color: var(--ds-color-accent-border-strong); - - box-shadow: var(--dsc-checkbox-border__hover); - } - - .ds-checkbox:not(.ds-checkbox--readonly) .ds-checkbox__input:hover:checked:not(:disabled, :focus-visible) { - box-shadow: var(--dsc-checkbox-border__hover); - } -} - -/** Sizing */ - -.ds-checkbox--sm { - --dsc-checkbox-size: var(--ds-sizing-5); - - /* min-height: var(--ds-sizing-10); */ -} - -.ds-checkbox--md { - --dsc-checkbox-size: var(--ds-sizing-6); - - /* min-height: var(--ds-sizing-11); */ -} - -.ds-checkbox--lg { - --dsc-checkbox-size: var(--ds-sizing-7); - - /* min-height: var(--ds-sizing-12); */ -} - -.ds-checkbox__group { - --dsc-checkbox-group-gap: var(--ds-spacing-4); - - display: flex; - flex-direction: column; - gap: var(--dsc-checkbox-group-gap); - width: fit-content; -} - -.ds-checkbox__group--sm { - --dsc-checkbox-group-gap: var(--ds-spacing-3); - - margin-top: calc(var(--ds-spacing-1) * -1); -} - -.ds-checkbox__group--md { - --dsc-checkbox-group-gap: var(--ds-spacing-4); -} - -.ds-checkbox__group--lg { - --dsc-checkbox-group-gap: var(--ds-spacing-5); - - margin-top: var(--ds-spacing-1); -} diff --git a/packages/css/field.css b/packages/css/field.css index 14d1368623..77252a1207 100644 --- a/packages/css/field.css +++ b/packages/css/field.css @@ -1,7 +1,71 @@ .ds-field { - display: contents; + align-items: start; + display: flex; + flex-direction: column; + gap: var(--ds-spacing-2); - & > * + * { - margin-top: var(--ds-spacing-2); + @composes ds-body-text--md from './base/base.css'; + + &[data-size='sm'] { + @composes ds-body-text--sm from './base/base.css'; + } + + &[data-size='lg'] { + @composes ds-body-text--lg from './base/base.css'; + } + + & [data-field='description'] { + color: var(--ds-color-neutral-text-subtle); /* TODO: Change to opacity or color-mix(currentColor, trasparent) to ensure contrast when parent element color changes? */ + } + + /** + * States + */ + &:has([aria-disabled='true'], :disabled) > * { + cursor: not-allowed; + opacity: var(--ds-disabled-opacity); + } + + &:has([aria-readonly='true'], [readonly]) label { + --dsc-label--readonly: ; /* Activate lock icon for label when readonly */ + } + + /** + * Toggle inputs + */ + &:has(input:is([type='radio'], [type='checkbox'])) { + border-radius: var(--ds-border-radius-md); + display: grid; + grid-template-columns: auto 1fr; + row-gap: 0; + width: fit-content; /* Rather do display: grid + width: fit-content than display: inline-grid to encourage stacked radios */ + + & > * { + grid-column: 2; /* Only allow input in column 1 */ + } + + & label { + --dsc-label--readonly: initial; /* Never show lock icon on toggle inputs */ + font-weight: var(--ds-font-weight-regular); + } + + & input { + grid-column: 1; /* Always place input in column 1 */ + grid-row: 1; /* Always place input in row 1 */ + outline: none; + box-shadow: none; + } + + &:not(:has([readonly], [aria-disabled='true'], :disabled)) label { + cursor: pointer; + } + + &:has(input:focus-visible) { + @composes ds-focus--visible from './base/base.css'; + } + + &:has(input:only-child) { + gap: 0; /* No gap only with aria-label/aria-labelledby */ + } } } diff --git a/packages/css/fieldset.css b/packages/css/fieldset.css index 6413d4a5dd..27b704e518 100644 --- a/packages/css/fieldset.css +++ b/packages/css/fieldset.css @@ -1,44 +1,31 @@ .ds-fieldset { - --dsc-fieldset-icon-size: 1.2em; - --dsc-fieldset-gap: var(--ds-spacing-2); - - margin: 0; - padding: 0; border: 0; + margin: 0; min-width: 0; - - & > :not(:last-child) { - margin-bottom: var(--ds-spacing-2); /* Use margin as fieldset does not play nice with display: flex */ - } - - & > legend { - display: inline-flex; - } - - & > legend:empty { - display: none; - } + padding: 0; &[data-hidelegend] > legend, &[data-hidelegend] > legend + p { @composes ds-sr-only from './base/base.css'; } - &[data-readonly] > legend::before { - content: ''; - background: currentcolor; - height: var(--dsc-fieldset-icon-size); - mask: center / contain no-repeat - url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' viewBox='0 0 24 24'%3E%3Cpath fill-rule='evenodd' d='M12 2.25A4.75 4.75 0 0 0 7.25 7v2.25H7A1.75 1.75 0 0 0 5.25 11v9c0 .41.34.75.75.75h12a.75.75 0 0 0 .75-.75v-9A1.75 1.75 0 0 0 17 9.25h-.25V7A4.75 4.75 0 0 0 12 2.25m3.25 7V7a3.25 3.25 0 0 0-6.5 0v2.25zM12 13a1.5 1.5 0 0 0-.75 2.8V17a.75.75 0 0 0 1.5 0v-1.2A1.5 1.5 0 0 0 12 13'/%3E%3C/svg%3E"); - width: var(--dsc-fieldset-icon-size); + /* Add lock icon to legend when only containing readonly inputs */ + &:has([readonly]):not(:has(:read-write)) > legend { + --dsc-label--readonly: ; /* Using technique https://css-tricks.com/the-css-custom-property-toggle-trick/ */ + } + + /* Stack everything that is not directly after legend */ + & > * + * { + margin-block-start: var(--ds-spacing-4); } & > legend + p { color: var(--ds-color-neutral-text-subtle); + margin-block: 0; } &:disabled > legend, &:disabled > legend + p { - color: var(--ds-color-neutral-border-subtle); + opacity: var(--ds-disabled-opacity); } } diff --git a/packages/css/index.css b/packages/css/index.css index 77adbf78c7..ad4f21ace8 100644 --- a/packages/css/index.css +++ b/packages/css/index.css @@ -10,17 +10,15 @@ @import url('./paragraph.css') layer(ds.typography); @import url('./validation-message.css') layer(ds.typography); @import url('./button.css') layer(ds.components); +@import url('./field.css') layer(ds.components); +@import url('./input.css') layer(ds.components); @import url('./alert.css') layer(ds.components); @import url('./popover.css') layer(ds.components); @import url('./skiplink.css') layer(ds.components); @import url('./accordion.css') layer(ds.components); -@import url('./field.css') layer(ds.components); @import url('./switch.css') layer(ds.components); -@import url('./checkbox.css') layer(ds.components); -@import url('./radio.css') layer(ds.components); @import url('./search.css') layer(ds.components); @import url('./textfield.css') layer(ds.components); -@import url('./input.css') layer(ds.components); @import url('./helptext.css') layer(ds.components); @import url('./modal.css') layer(ds.components); @import url('./list.css') layer(ds.components); diff --git a/packages/css/input.css b/packages/css/input.css index fe0f25421b..832503208e 100644 --- a/packages/css/input.css +++ b/packages/css/input.css @@ -173,11 +173,6 @@ } } -/* Change cursor on wrapping