Skip to content

Commit 81a4c1a

Browse files
Jeffrey Lauwersclaude
andcommitted
feat(EmailInput): opschonen CSS en Storybook toevoegen
- Verwijder dode dsn-email-input CSS klassen (component gebruikt dsn-text-input) - Voeg width variants tests toe - Voeg Storybook story, docs.mdx en docs.md toe Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent c2d2423 commit 81a4c1a

5 files changed

Lines changed: 250 additions & 81 deletions

File tree

Lines changed: 1 addition & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,4 @@
11
/**
22
* EmailInput Component Styles
3-
* Extends base text input styles for email input
3+
* Uses dsn-text-input styles from TextInput.css — no additional styles needed
44
*/
5-
6-
.dsn-email-input {
7-
font-family: var(--dsn-form-control-font-family);
8-
font-size: var(--dsn-form-control-font-size);
9-
font-weight: var(--dsn-form-control-font-weight);
10-
line-height: var(--dsn-form-control-line-height);
11-
color: var(--dsn-form-control-text-color);
12-
background-color: var(--dsn-form-control-bg);
13-
border: var(--dsn-form-control-border-width) solid
14-
var(--dsn-form-control-border-color);
15-
border-radius: var(--dsn-form-control-border-radius);
16-
padding-block: var(--dsn-form-control-padding-block);
17-
padding-inline: var(--dsn-form-control-padding-inline);
18-
transition:
19-
border-color var(--dsn-form-control-transition-speed)
20-
var(--dsn-form-control-transition-easing),
21-
background-color var(--dsn-form-control-transition-speed)
22-
var(--dsn-form-control-transition-easing),
23-
box-shadow var(--dsn-form-control-transition-speed)
24-
var(--dsn-form-control-transition-easing);
25-
}
26-
27-
.dsn-email-input::placeholder {
28-
color: var(--dsn-form-control-placeholder-text-color);
29-
opacity: 1;
30-
}
31-
32-
.dsn-email-input:hover:not(:disabled):not(:read-only) {
33-
border-color: var(--dsn-form-control-hover-border-color);
34-
background-color: var(--dsn-form-control-hover-bg);
35-
}
36-
37-
.dsn-email-input:focus {
38-
outline: var(--dsn-focus-outline-width) solid var(--dsn-focus-outline-color);
39-
outline-offset: var(--dsn-focus-outline-offset);
40-
border-color: var(--dsn-form-control-focus-border-color);
41-
background-color: var(--dsn-form-control-focus-bg);
42-
}
43-
44-
.dsn-email-input:disabled {
45-
border-color: var(--dsn-form-control-disabled-border-color);
46-
background-color: var(--dsn-form-control-disabled-bg);
47-
color: var(--dsn-form-control-disabled-text-color);
48-
opacity: var(--dsn-form-control-disabled-opacity);
49-
cursor: not-allowed;
50-
}
51-
52-
.dsn-email-input:read-only {
53-
border-color: var(--dsn-form-control-readonly-border-color);
54-
background-color: var(--dsn-form-control-readonly-bg);
55-
cursor: default;
56-
}
57-
58-
.dsn-email-input--invalid {
59-
border-color: var(--dsn-form-control-invalid-border-color);
60-
}
61-
62-
.dsn-email-input--invalid:focus {
63-
outline-color: var(--dsn-form-control-invalid-border-color);
64-
}
65-
66-
/* Width variants */
67-
.dsn-email-input--width-xs {
68-
width: var(--dsn-form-control-width-xs);
69-
}
70-
.dsn-email-input--width-sm {
71-
width: var(--dsn-form-control-width-sm);
72-
}
73-
.dsn-email-input--width-md {
74-
width: var(--dsn-form-control-width-md);
75-
}
76-
.dsn-email-input--width-lg {
77-
width: var(--dsn-form-control-width-lg);
78-
}
79-
.dsn-email-input--width-xl {
80-
width: var(--dsn-form-control-width-xl);
81-
}
82-
.dsn-email-input--width-full {
83-
width: 100%;
84-
}

packages/components-react/src/EmailInput/EmailInput.test.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ describe('EmailInput', () => {
8989
expect(screen.getByTestId('input')).toHaveAttribute('autocomplete', 'off');
9090
});
9191

92+
describe('width variants', () => {
93+
it.each(['xs', 'sm', 'md', 'lg', 'xl', 'full'] as const)(
94+
'applies width class for %s',
95+
(w) => {
96+
render(<EmailInput width={w} data-testid="input" />);
97+
expect(screen.getByTestId('input')).toHaveClass(
98+
`dsn-text-input--width-${w}`
99+
);
100+
}
101+
);
102+
});
103+
92104
describe('invalid state', () => {
93105
it('sets aria-invalid when invalid prop is true', () => {
94106
render(<EmailInput invalid data-testid="input" />);
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# EmailInput
2+
3+
Een invoerveld specifiek voor e-mailadressen.
4+
5+
## Doel
6+
7+
De EmailInput component is een gespecialiseerd invoerveld voor e-mailadressen. Het gebruikt `type="email"` voor ingebouwde browservalidatie en `inputmode="email"` voor een geoptimaliseerd toetsenbord op mobiel (met @-teken en .com direct beschikbaar). Browser-autocomplete staat standaard aan via `autocomplete="email"`, zodat gebruikers snel hun opgeslagen e-mailadres kunnen invullen.
8+
9+
<!-- VOORBEELD -->
10+
11+
## Use when
12+
13+
- Je een e-mailadres van de gebruiker nodig hebt, bijv. voor een inlogformulier, registratie of contactformulier.
14+
- Je wilt dat de browser het e-mailadres valideert en autocomplete aanbiedt.
15+
16+
## Don't use when
17+
18+
- Je meerdere e-mailadressen wilt invoeren — overweeg dan een TagInput of tekstinvoer met eigen validatie.
19+
- Het om een vrij tekstveld gaat — gebruik dan [TextInput](/docs/components-textinput--docs).
20+
21+
## Best practices
22+
23+
- **Gebruik een duidelijke placeholder.** Geef een voorbeeld e-mailadres (bijv. `naam@voorbeeld.nl`).
24+
- **Laat browser-autocomplete aan.** De standaard `autocomplete="email"` helpt gebruikers snel invullen. Zet alleen op `off` als daar een goede reden voor is.
25+
- **Combineer met FormField.** Gebruik altijd een label via `FormField` of `FormFieldLabel` voor toegankelijkheid.
26+
- **Geef validatie feedback.** Gebruik de `invalid` prop in combinatie met `aria-invalid` en een `FormFieldErrorMessage`.
27+
28+
## Accessibility
29+
30+
- `type="email"` zorgt voor ingebouwde browservalidatie en een geoptimaliseerd mobiel toetsenbord.
31+
- `inputmode="email"` geeft op mobiel een toetsenbord met @-teken en veelgebruikte domeinsuffixen.
32+
- `autocomplete="email"` maakt het voor gebruikers eenvoudig om hun e-mailadres in te vullen zonder te typen.
33+
34+
## States
35+
36+
- **Default**: Leeg, klaar voor invoer
37+
- **Filled**: Bevat een e-mailadres
38+
- **Focus**: Actief, gebruiker typt
39+
- **Hover**: Muis over het veld
40+
- **Disabled**: Niet beschikbaar voor invoer
41+
- **Read-only**: Waarde is zichtbaar maar niet aanpasbaar
42+
- **Invalid**: Validatiefout (bijv. ongeldig e-mailadres)
43+
44+
## Design tokens
45+
46+
EmailInput erft alle tokens van [TextInput](/docs/components-textinput--docs):
47+
48+
| Token | Beschrijving |
49+
| -------------------------------------------- | ------------------------- |
50+
| `--dsn-text-input-font-family` | Lettertypefamilie |
51+
| `--dsn-text-input-font-size` | Font size |
52+
| `--dsn-text-input-font-weight` | Font weight |
53+
| `--dsn-text-input-line-height` | Line height |
54+
| `--dsn-text-input-color` | Tekstkleur |
55+
| `--dsn-text-input-background-color` | Achtergrondkleur |
56+
| `--dsn-text-input-border-color` | Borderkleur default state |
57+
| `--dsn-text-input-border-width` | Dikte van de border |
58+
| `--dsn-text-input-border-radius` | Border radius |
59+
| `--dsn-text-input-padding-block-start` | Padding boven |
60+
| `--dsn-text-input-padding-block-end` | Padding onder |
61+
| `--dsn-text-input-hover-border-color` | Borderkleur hover state |
62+
| `--dsn-text-input-focus-border-color` | Borderkleur focus state |
63+
| `--dsn-text-input-disabled-background-color` | Achtergrondkleur disabled |
64+
| `--dsn-text-input-disabled-color` | Tekstkleur disabled |
65+
| `--dsn-text-input-invalid-border-color` | Borderkleur invalid state |
66+
| `--dsn-text-input-placeholder-color` | Placeholder tekstkleur |
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Meta, Story, Controls, Markdown } from '@storybook/blocks';
2+
import * as EmailInputStories from './EmailInput.stories';
3+
import docs from './EmailInput.docs.md?raw';
4+
5+
export const [intro, rest] = docs.split('<!-- VOORBEELD -->');
6+
7+
<Meta of={EmailInputStories} />
8+
9+
<Markdown>{intro}</Markdown>
10+
11+
## Voorbeeld
12+
13+
<Story of={EmailInputStories.Default} />
14+
15+
<Controls of={EmailInputStories.Default} />
16+
17+
<Markdown>{rest}</Markdown>
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import { EmailInput } from '@dsn/components-react';
3+
import DocsPage from './EmailInput.docs.mdx';
4+
5+
const meta: Meta<typeof EmailInput> = {
6+
title: 'Components/EmailInput',
7+
component: EmailInput,
8+
parameters: {
9+
docs: {
10+
page: DocsPage,
11+
},
12+
},
13+
argTypes: {
14+
placeholder: { control: 'text' },
15+
disabled: { control: 'boolean' },
16+
readOnly: { control: 'boolean' },
17+
invalid: { control: 'boolean' },
18+
required: { control: 'boolean' },
19+
width: {
20+
control: 'select',
21+
options: [undefined, 'xs', 'sm', 'md', 'lg', 'xl', 'full'],
22+
},
23+
},
24+
args: {
25+
placeholder: 'naam@voorbeeld.nl',
26+
disabled: false,
27+
readOnly: false,
28+
invalid: false,
29+
required: false,
30+
},
31+
};
32+
33+
export default meta;
34+
type Story = StoryObj<typeof EmailInput>;
35+
36+
export const Default: Story = {};
37+
38+
export const WithValue: Story = {
39+
name: 'With value',
40+
args: {
41+
defaultValue: 'jan@voorbeeld.nl',
42+
},
43+
};
44+
45+
export const Disabled: Story = {
46+
args: {
47+
disabled: true,
48+
value: 'jan@voorbeeld.nl',
49+
},
50+
};
51+
52+
export const ReadOnly: Story = {
53+
name: 'Read-only',
54+
args: {
55+
readOnly: true,
56+
value: 'jan@voorbeeld.nl',
57+
},
58+
};
59+
60+
export const Invalid: Story = {
61+
args: {
62+
invalid: true,
63+
value: 'geen-geldig-email',
64+
},
65+
};
66+
67+
export const Widths: Story = {
68+
name: 'Width variants',
69+
render: () => (
70+
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
71+
<EmailInput width="xs" placeholder="xs" />
72+
<EmailInput width="sm" placeholder="sm" />
73+
<EmailInput width="md" placeholder="md" />
74+
<EmailInput width="lg" placeholder="lg" />
75+
<EmailInput width="xl" placeholder="xl" />
76+
<EmailInput width="full" placeholder="full" />
77+
</div>
78+
),
79+
};
80+
81+
export const AllStates: Story = {
82+
name: 'All states',
83+
render: () => (
84+
<div
85+
style={{
86+
display: 'flex',
87+
flexDirection: 'column',
88+
gap: '1rem',
89+
maxWidth: '400px',
90+
}}
91+
>
92+
<div>
93+
<label
94+
style={{
95+
display: 'block',
96+
marginBottom: '0.5rem',
97+
fontWeight: 'bold',
98+
}}
99+
>
100+
Default
101+
</label>
102+
<EmailInput placeholder="naam@voorbeeld.nl" />
103+
</div>
104+
<div>
105+
<label
106+
style={{
107+
display: 'block',
108+
marginBottom: '0.5rem',
109+
fontWeight: 'bold',
110+
}}
111+
>
112+
With value
113+
</label>
114+
<EmailInput defaultValue="jan@voorbeeld.nl" />
115+
</div>
116+
<div>
117+
<label
118+
style={{
119+
display: 'block',
120+
marginBottom: '0.5rem',
121+
fontWeight: 'bold',
122+
}}
123+
>
124+
Disabled
125+
</label>
126+
<EmailInput disabled value="jan@voorbeeld.nl" />
127+
</div>
128+
<div>
129+
<label
130+
style={{
131+
display: 'block',
132+
marginBottom: '0.5rem',
133+
fontWeight: 'bold',
134+
}}
135+
>
136+
Read-only
137+
</label>
138+
<EmailInput readOnly value="jan@voorbeeld.nl" />
139+
</div>
140+
<div>
141+
<label
142+
style={{
143+
display: 'block',
144+
marginBottom: '0.5rem',
145+
fontWeight: 'bold',
146+
}}
147+
>
148+
Invalid
149+
</label>
150+
<EmailInput invalid value="geen-geldig-email" />
151+
</div>
152+
</div>
153+
),
154+
};

0 commit comments

Comments
 (0)