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: input #2550

Merged
merged 30 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d88664d
feat: input component
eirikbacker Oct 1, 2024
595f45a
fix(Input): simplify css
eirikbacker Oct 1, 2024
f13fe2f
chore(Input): cleanup
eirikbacker Oct 1, 2024
7dbee38
fix: support all input types
eirikbacker Oct 2, 2024
a0be3a4
fix(Input): component tokens
eirikbacker Oct 3, 2024
59bffd8
Merge branch 'next' into feat/input
eirikbacker Oct 3, 2024
3584e7d
fix(Input): add switch
eirikbacker Oct 3, 2024
42a3bd8
fix(Input): switch with check and sizing
eirikbacker Oct 3, 2024
e227add
chore(Input): update tests
eirikbacker Oct 3, 2024
049b85b
docs(Input): add role to storybook
eirikbacker Oct 3, 2024
dbfe153
Merge branch 'next' into feat/input
eirikbacker Oct 3, 2024
101503b
fix(Input): sizing
eirikbacker Oct 3, 2024
5cce130
fix(Input): prepare for usage on other elements
eirikbacker Oct 3, 2024
a11dba9
chore: pr review
eirikbacker Oct 8, 2024
d4daa93
docs: start implementing states view
eirikbacker Oct 8, 2024
45fce70
fix(Input): remove hover
eirikbacker Oct 8, 2024
1f7ddf8
fix(Input): paint check with gradients
eirikbacker Oct 10, 2024
092e2d7
Merge branch 'next' into feat/input
eirikbacker Oct 10, 2024
f5e4063
fix(Input): import from base.css
eirikbacker Oct 10, 2024
95ba87a
chore(Input): update imports
eirikbacker Oct 10, 2024
3511fa2
fix(Input): accent color
eirikbacker Oct 10, 2024
e71c484
fix(Input): tmp disable color-contrast a11y check for readonly elements
eirikbacker Oct 10, 2024
9eea57e
Merge branch 'next' into feat/input
eirikbacker Oct 10, 2024
f63b446
chore: fix typo
eirikbacker Oct 10, 2024
c690c90
Merge branch 'next' into feat/input
eirikbacker Oct 10, 2024
2a4bfb4
Merge branch 'next' into feat/input
eirikbacker Oct 10, 2024
59f3eea
chore: lint
eirikbacker Oct 10, 2024
5d3307a
fix(Input): simplify
eirikbacker Oct 10, 2024
96bfa21
fix(Input): simplify type
eirikbacker Oct 10, 2024
9663981
docs: simplify preview story
eirikbacker Oct 11, 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
1 change: 1 addition & 0 deletions packages/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
@import url('./search.css') layer(ds.components);
@import url('./select.css') layer(ds.components);
@import url('./textfield.css') layer(ds.components);
@import url('./input.css') layer(ds.components);
@import url('./textarea.css') layer(ds.components);
@import url('./helptext.css') layer(ds.components);
@import url('./modal.css') layer(ds.components);
Expand Down
242 changes: 242 additions & 0 deletions packages/css/input.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
.ds-input {
--dsc-input-border-color--checked: var(--ds-color-accent-base-default);
--dsc-input-border-color--invalid: var(--ds-color-danger-border-default);
--dsc-input-border-color--readonly: var(--ds-color-neutral-border-subtle);
--dsc-input-border-color: var(--ds-color-neutral-border-default);
--dsc-input-background--checked: var(--dsc-input-border-color--checked);
--dsc-input-background--invalid: var(--dsc-input-border-color--invalid);
--dsc-input-background--readonly: var(--ds-color-neutral-background-subtle);
--dsc-input-background--switch: var(--ds-color-neutral-border-default);
--dsc-input-background: var(--ds-color-neutral-background-default);
--dsc-input-border-width--toggle: 2px;
--dsc-input-border-width: 1px;
--dsc-input-color--checked: var(--ds-color-accent-contrast-default);
--dsc-input-color--invalid: var(--ds-color-danger-contrast-default);
--dsc-input-color--readonly: var(--ds-color-neutral-border-default);
--dsc-input-color: var(--ds-color-neutral-text-default);
--dsc-input-stroke: 0.05em;
--dsc-input-padding: var(--ds-spacing-2) var(--ds-spacing-3);
--dsc-input-size--switch: var(--ds-sizing-7);
--dsc-input-size--toggle: var(--ds-sizing-6);
--dsc-input-size: var(--ds-sizing-12);

/* Checkmark with antialiasing is achieved by percentages 48% / 50% / 52% */
--check-left: calc(var(--dsc-input-stroke) / 2) calc(66.66% + var(--dsc-input-stroke) / 2) / 33.33% 33.33% no-repeat content-box
linear-gradient(
45deg,
transparent calc(50% - var(--dsc-input-stroke)),
currentcolor calc(48% - var(--dsc-input-stroke)),
currentcolor calc(50% + var(--dsc-input-stroke)),
transparent calc(52% + var(--dsc-input-stroke))
);
--check-right: calc(100% - var(--dsc-input-stroke) / 2) / 66.66% 66.66% no-repeat content-box
linear-gradient(
-45deg,
transparent calc(50% - var(--dsc-input-stroke)),
currentcolor calc(48% - var(--dsc-input-stroke)),
currentcolor calc(50% + var(--dsc-input-stroke)),
transparent calc(52% + var(--dsc-input-stroke))
);

appearance: none;
background: var(--dsc-input-background);
border-radius: var(--ds-border-radius-md);
border: var(--dsc-input-affix-border, var(--dsc-input-border-width) solid var(--dsc-input-border-color)); /* Inherit from .ds-input-addons if present */
box-shadow: var(--dsc-input-box-shadow);
box-sizing: border-box;
color: var(--dsc-input-color);
font-family: inherit;
margin: 0; /* Reset native margin on checkbox and radio */
padding: var(--dsc-input-padding);
position: relative; /* Ensure foucs outline renders on top */

@composes ds-body-text--md from './base/base.css';
@composes ds-focus from './base/base.css';

/* Change switch background with low specificity to allow states to overwrite */
&:where([role='switch']) {
--dsc-input-background: var(--dsc-input-background--switch);
}

&:not(textarea) {
height: var(--dsc-input-size);
}

&:not([size]) {
width: 100%;
}

/**
* States
*/
&:checked,
&:indeterminate {
--dsc-input-border-color: var(--dsc-input-border-color--checked);
--dsc-input-background: var(--dsc-input-background--checked);
--dsc-input-color: var(--dsc-input-color--checked);
}

&:disabled,
&[aria-disabled='true'] {
cursor: not-allowed;
opacity: var(--ds-disabled-opacity);
}

&[aria-invalid='true'] {
--dsc-input-border-color: var(--dsc-input-border-color--invalid);
--dsc-input-background--checked: var(--dsc-input-background--invalid);
--dsc-input-color--checked: var(--dsc-input-color--invalid);
}

/* Using attribute [readonly] since pseudo selector :read-only is always true for checkbox, radio and select */
&[readonly] {
--dsc-input-border-color: var(--dsc-input-border-color--readonly);
--dsc-input-background: var(--dsc-input-background--readonly);
--dsc-input-color: var(--dsc-input-color--readonly);
}

/**
* Sizes
*/
&[data-size='sm'] {
@composes ds-body-text--sm from './base/base.css';

--dsc-input-padding: var(--ds-spacing-1) var(--ds-spacing-2);
--dsc-input-size--switch: var(--ds-sizing-6);
--dsc-input-size--toggle: var(--ds-sizing-5);
--dsc-input-size: var(--ds-sizing-10);
}

&[data-size='lg'] {
@composes ds-body-text--lg from './base/base.css';

--dsc-input-padding: var(--ds-spacing-3) var(--ds-spacing-4);
--dsc-input-size--switch: var(--ds-sizing-8);
--dsc-input-size--toggle: var(--ds-sizing-7);
--dsc-input-size: var(--ds-sizing-14);
}

/**
* Toggle inputs
*/
&:read-only:not([readonly], [aria-disabled='true'], :disabled) {
cursor: pointer;
}

&[type='checkbox'],
&[type='radio'] {
--dsc-input-border-width: var(--dsc-input-border-width--toggle);
--dsc-input-padding: calc(var(--ds-sizing-1) / 2);
--dsc-input-size: var(--dsc-input-size--toggle);

flex-shrink: 0; /* Never shrink a toggle input */
width: var(--dsc-input-size);
}

&[type='radio'] {
border-radius: var(--ds-border-radius-full);
}

&[type='radio']:checked {
background: radial-gradient(circle closest-side, currentcolor 45%, transparent 50%), var(--dsc-input-background);
}

&[type='checkbox']:checked {
background: var(--check-left), var(--check-right), var(--dsc-input-background);
}

&[type='checkbox']:indeterminate {
background: center / contain no-repeat content-box
linear-gradient(
transparent calc(48% - var(--dsc-input-stroke)),
currentcolor calc(50% - var(--dsc-input-stroke)),
currentcolor calc(50% + var(--dsc-input-stroke)),
transparent calc(52% + var(--dsc-input-stroke))
), var(--dsc-input-background);
}

/**
* Switch
*/
&[role='switch']:is([type='radio'], [type='checkbox']) {
--dsc-input-color: transparent; /* Hide checkmark */
--dsc-input-padding: var(--ds-sizing-1);
--dsc-input-size: var(--dsc-input-size--switch);
--circle-color: var(--dsc-input-color--checked);
--circle-position: left;

border-radius: var(--ds-border-radius-full);
padding-left: var(--dsc-input-size); /* Push checkmark to right side */
transition: 0.2s background-position;
width: calc((var(--dsc-input-size) - var(--dsc-input-border-width)) * 2); /* Subtract border-width to make background-image math correct */
background: var(--check-left), var(--check-right), radial-gradient(circle closest-side, var(--circle-color) 95%, transparent 100%) var(--circle-position) /
50% 100% no-repeat padding-box, var(--dsc-input-background);

&:checked {
--dsc-input-color: var(--dsc-input-border-color);
--circle-position: right;
}

&[readonly] {
--circle-color: var(--dsc-input-color--readonly);
}
}
}

/* Change cursor on wrapping <label> */
label:has(input:is([type='checkbox'], [type='radio']):not(:disabled, [aria-disabled], [readonly])) {
cursor: pointer;
}

/**
* Affix
*/
.ds-input-affix {
--dsc-input-affix-border-radius: var(--ds-border-radius-md);
--dsc-input-affix-border: 1px solid var(--ds-color-neutral-border-default);
--dsc-input-affix-padding-inline: var(--ds-spacing-4);

align-items: center;
background: var(--ds-color-neutral-background-subtle);
border-radius: var(--dsc-input-affix-border-radius);
box-sizing: border-box;
color: var(--ds-color-neutral-text-subtle);
display: inline-flex; /* Using inline-flex to match native inline-block behaviour of <input> */
gap: var(--dsc-input-affix-padding-inline);
padding-inline: var(--dsc-input-affix-padding-inline);
position: relative;
white-space: nowrap;
width: fit-content;

/* Using ::before to make input border overlap addons border */
&::before {
border-radius: inherit;
border: var(--dsc-input-affix-border);
content: '';
inset: 0;
pointer-events: none;
position: absolute;
}

@composes ds-body-text--md from './base/base.css';

/* Using double selector to ensure we win specificity */
.ds-input.ds-input {
align-self: stretch;
border-radius: 0;
flex: 1 1 auto;
height: auto;
}

&:has([data-size='sm']) {
@composes ds-body-text--sm from './base/base.css';

--dsc-input-affix-padding-inline: var(--ds-spacing-3);
}

&:has([data-size='lg']) {
@composes ds-body-text--lg from './base/base.css';

--dsc-input-affix-padding-inline: var(--ds-spacing-5);
}
}
81 changes: 81 additions & 0 deletions packages/react/src/components/form/Input/Input.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Meta, Canvas, Controls, Primary } from '@storybook/blocks';

import { Alert } from '../../Alert';
import * as InputStories from './Input.stories';

<Meta of={InputStories} />

<Alert color="warning">Input er under utvikling og burde ikke tas i bruk enda. Bruk heller Textfield komponenten.</Alert>
<br />

# Input

`Input` kalles tekstfelt på norsk. Det er et inndatafelt som for eksempel gir brukerne mulighet til å skrive korte tekster eller tall.

<Primary />
<Controls />

## Prefix/Suffix

Prefixer og suffixer er nyttige for å vise enheter, valuta eller andre typer informasjon som er relevant for feltet.
Du skal **ikke** bruke disse alene, siden skjermlesere ikke leser dem opp.
Det er viktig at samme informasjon som vises i prefixet eller suffixet også er inkludert i ledeteksten.

<Canvas of={InputStories.Adornments} />

## Kontrollert

<Canvas of={InputStories.Controlled} />

## Html Size

<Canvas of={InputStories.HtmlSize} />

## Retningslinjer for når du skal bruke `Input`

Vi bruker tekstfelt når vi vil gi brukeren mulighet til å skrive tekst/svar på maks. én linje, Det kan for eksempel være navn eller telefonnummer.

Passer til å

- gi mulighet for korte tekster eller svar
- legge inn tall, for eksempel et telefonnummer

Passer ikke til å

- gi lengre svar, bruk heller `Textarea`
- legge inn formaterte data, som markdown
<br />

### Plassering av ledeteksten

Ledeteksten og en eventuell beskrivelse skal alltid stå over tekstfeltet. Da er de lette å se på små skjermer og hindres ikke av eventuelle feilmeldinger.

### Unngå plassholdertekster

Plassholdertekster forsvinner når brukerne skriver i feltet. Det er derfor bedre å inkludere hint og viktig informasjon i selve ledeteksten eller den tilhørende beskrivelsen.

### Tilpass bredden på tekstfeltet

Tilpass bredden til det brukerne skal skrive inn, kort bredde til telefonnummer og bredere til stedsnavn. Ulik bredde på feltene gjør det enklere å navigere i skjemaer som har mange felter.

### Inndata og formatering

- Bruk `autoComplete` for felter som mottar personlig informasjon. Hvis feltet skal be om personopplysninger om en annen person enn brukeren, må du skru `autoComplete` av (WCAG 1.3.5).
- Bruk gjerne inndatatyper som viser hva du ber om, for eksempel telefonnummer og e-post. Slike inndatatyper gir mobilbrukere et tastatur som passer til det de skal angi, for eksempel et numerisk tastatur for telefonnummer, men de kan også utløse validering på klientsiden.
- Godta det meste av inndata fra brukerne, så lenge det er forståelig. Eksempler kan være kontonummer med punktum, telefonnummer med mellomrom eller mellomrom på slutten av en e-postadresse.
- Pass på at brukerne ser inndata som formateres automatisk, men uten at det forstyrrer dem mens de fyller ut.
- Ikke bruk bare store bokstaver eller kursiv tekst i ledeteksten. Det er vanskelig å lese.

## Tekst i komponenten

Det skal alltid være ledetekst på `Input`. I spesielle tilfeller kan vi skjule ledeteksten med `hidelabel`. Det kan for eksempel være i tabeller, hvis feltet får ledeteksten fra tabelloverskriften. Selv om vi har tenkt å skjule ledeteksten, må vi alltid skrive en ledetekst som gir mening, siden den leses opp av skjermlesere.

## Tilgjengelighet

### Ikke bruk deaktiverte felt

Ikke bruk deaktivert tilstand (disabled state) på tekstfelt. Tenk heller over om du trenger å vise feltet i det hele tatt, eller om du heller kan skrive informasjonen ut i ren tekst eller bruke Read Only.

### Prefiks og suffiks

Prefiks og suffiks er et ekstra visuelt hjelpemiddel, som blir ignorert av skjermlesere. Vi må alltid ha en beskrivende ledetekst. Prefiks og suffiks er plassert utenfor inndatafeltene de tilhører. Da unngår vi at de ikke skaper trøbbel i noen nettlesere, som kan sette inn et ikon i inndatafeltet (for eksempel ikoner for å vise eller lage passord).
Loading
Loading