Skip to content

Commit 3e47fca

Browse files
Merge pull request #2 from jeffreylauwers/feature/password-input
feat(PasswordInput): opschonen en Storybook toevoegen
2 parents 110e5b5 + c3af120 commit 3e47fca

6 files changed

Lines changed: 255 additions & 93 deletions

File tree

Lines changed: 1 addition & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,4 @@
11
/**
22
* PasswordInput Component Styles
3-
* Extends base text input styles for password input
3+
* Uses dsn-text-input styles from TextInput.css — no additional styles needed
44
*/
5-
6-
.dsn-password-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-password-input::placeholder {
28-
color: var(--dsn-form-control-placeholder-text-color);
29-
opacity: 1;
30-
}
31-
32-
.dsn-password-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-password-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-password-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-password-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-password-input--invalid {
59-
border-color: var(--dsn-form-control-invalid-border-color);
60-
}
61-
62-
.dsn-password-input--invalid:focus {
63-
outline-color: var(--dsn-form-control-invalid-border-color);
64-
}
65-
66-
/* Width variants */
67-
.dsn-password-input--width-xs {
68-
width: var(--dsn-form-control-width-xs);
69-
}
70-
.dsn-password-input--width-sm {
71-
width: var(--dsn-form-control-width-sm);
72-
}
73-
.dsn-password-input--width-md {
74-
width: var(--dsn-form-control-width-md);
75-
}
76-
.dsn-password-input--width-lg {
77-
width: var(--dsn-form-control-width-lg);
78-
}
79-
.dsn-password-input--width-xl {
80-
width: var(--dsn-form-control-width-xl);
81-
}
82-
.dsn-password-input--width-full {
83-
width: 100%;
84-
}

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,10 @@ describe('PasswordInput', () => {
2727
expect(screen.getByTestId('input')).toHaveClass('dsn-text-input');
2828
});
2929

30-
it('always has dsn-password-input class for extra padding', () => {
31-
render(<PasswordInput data-testid="input" />);
32-
expect(screen.getByTestId('input')).toHaveClass('dsn-password-input');
33-
});
34-
3530
it('applies custom className', () => {
3631
render(<PasswordInput className="custom" data-testid="input" />);
3732
const el = screen.getByTestId('input');
3833
expect(el).toHaveClass('dsn-text-input');
39-
expect(el).toHaveClass('dsn-password-input');
4034
expect(el).toHaveClass('custom');
4135
});
4236

@@ -50,13 +44,13 @@ describe('PasswordInput', () => {
5044
render(
5145
<PasswordInput
5246
id="password"
53-
placeholder="Enter password"
47+
placeholder="Wachtwoord"
5448
data-testid="input"
5549
/>
5650
);
5751
const el = screen.getByTestId('input');
5852
expect(el).toHaveAttribute('id', 'password');
59-
expect(el).toHaveAttribute('placeholder', 'Enter password');
53+
expect(el).toHaveAttribute('placeholder', 'Wachtwoord');
6054
});
6155

6256
it('accepts value prop', () => {
@@ -130,6 +124,18 @@ describe('PasswordInput', () => {
130124
});
131125
});
132126

127+
describe('width variants', () => {
128+
it.each(['xs', 'sm', 'md', 'lg', 'xl', 'full'] as const)(
129+
'applies width class for %s',
130+
(w) => {
131+
render(<PasswordInput width={w} data-testid="input" />);
132+
expect(screen.getByTestId('input')).toHaveClass(
133+
`dsn-text-input--width-${w}`
134+
);
135+
}
136+
);
137+
});
138+
133139
describe('invalid state', () => {
134140
it('sets aria-invalid when invalid prop is true', () => {
135141
render(<PasswordInput invalid data-testid="input" />);

packages/components-react/src/PasswordInput/PasswordInput.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22
import { classNames, FormControlWidth } from '@dsn/core';
3-
import './PasswordInput.css';
3+
import '../TextInput/TextInput.css';
44

55
export type PasswordAutocomplete = 'current-password' | 'new-password' | 'off';
66

@@ -9,7 +9,10 @@ export interface PasswordInputProps extends Omit<
99
'type'
1010
> {
1111
/**
12-
* Autocomplete value for password managers
12+
* Autocomplete hint for password managers
13+
* - 'current-password': voor inlogformulieren (default)
14+
* - 'new-password': voor registratie- en wachtwoord wijzigformulieren
15+
* - 'off': autocomplete uitschakelen
1316
* @default 'current-password'
1417
*/
1518
passwordAutocomplete?: PasswordAutocomplete;
@@ -34,7 +37,9 @@ export interface PasswordInputProps extends Omit<
3437

3538
/**
3639
* Password Input component
37-
* Password input with extra padding for password manager icons
40+
* Uses type="password" for secure text entry.
41+
* Het tonen/verbergen van het wachtwoord is bewust niet ingebouwd in dit component —
42+
* dat patroon wordt separaat gedefinieerd via een Button naast het invoerveld.
3843
*
3944
* @example
4045
* ```tsx
@@ -69,7 +74,6 @@ export const PasswordInput = React.forwardRef<
6974
) => {
7075
const classes = classNames(
7176
'dsn-text-input',
72-
'dsn-password-input',
7377
width && `dsn-text-input--width-${width}`,
7478
className
7579
);
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# PasswordInput
2+
3+
Een invoerveld specifiek voor wachtwoorden.
4+
5+
## Doel
6+
7+
De PasswordInput component is een gespecialiseerd invoerveld voor wachtwoorden. Het gebruikt `type="password"` zodat de ingevoerde tekst wordt gemaskeerd. Via de `passwordAutocomplete` prop geef je wachtwoordmanagers een hint: gebruik `current-password` bij inlogformulieren en `new-password` bij registratie- of wachtwoord wijzigformulieren.
8+
9+
Het tonen en verbergen van het ingevulde wachtwoord is bewust **niet** ingebouwd in dit component. Dit patroon wordt separaat gedefinieerd via een Button naast het invoerveld.
10+
11+
<!-- VOORBEELD -->
12+
13+
## Use when
14+
15+
- Je een wachtwoord van de gebruiker nodig hebt, bijv. bij inloggen of registreren.
16+
- Je het ingevoerde wachtwoord wilt maskeren.
17+
18+
## Don't use when
19+
20+
- Het om een pincode of numerieke code gaat — gebruik dan [NumberInput](/docs/components-numberinput--docs).
21+
- Het om vrije tekst gaat — gebruik dan [TextInput](/docs/components-textinput--docs).
22+
23+
## Best practices
24+
25+
- **Gebruik `passwordAutocomplete` correct.** Stel `current-password` in bij inloggen en `new-password` bij registratie of wachtwoord wijzigen. Dit helpt wachtwoordmanagers de juiste actie te nemen.
26+
- **Voeg een toon/verberg-knop toe via een apart patroon.** Dit component bevat bewust geen toggle — gebruik hiervoor een Button naast het invoerveld.
27+
- **Combineer met FormField.** Gebruik altijd een label via `FormField` of `FormFieldLabel` voor toegankelijkheid.
28+
- **Geef validatie feedback.** Gebruik de `invalid` prop in combinatie met `aria-invalid` en een `FormFieldErrorMessage`.
29+
30+
## Accessibility
31+
32+
- `type="password"` maskeert de invoer visueel en verhindert dat de waarde zichtbaar is in de browser history.
33+
- `autocomplete="current-password"` of `autocomplete="new-password"` helpt wachtwoordmanagers en screen readers begrijpen wat het veld verwacht.
34+
- Voeg bij wachtwoordvereisten altijd een beschrijving toe via `aria-describedby` en `FormFieldDescription`.
35+
36+
## States
37+
38+
- **Default**: Leeg, klaar voor invoer
39+
- **Focus**: Actief, gebruiker typt
40+
- **Hover**: Muis over het veld
41+
- **Disabled**: Niet beschikbaar voor invoer
42+
- **Read-only**: Waarde is zichtbaar maar niet aanpasbaar
43+
- **Invalid**: Validatiefout (bijv. wachtwoord te kort of voldoet niet aan vereisten)
44+
45+
## Design tokens
46+
47+
PasswordInput erft alle tokens van [TextInput](/docs/components-textinput--docs):
48+
49+
| Token | Beschrijving |
50+
| -------------------------------------------- | ------------------------- |
51+
| `--dsn-text-input-font-family` | Lettertypefamilie |
52+
| `--dsn-text-input-font-size` | Font size |
53+
| `--dsn-text-input-font-weight` | Font weight |
54+
| `--dsn-text-input-line-height` | Line height |
55+
| `--dsn-text-input-color` | Tekstkleur |
56+
| `--dsn-text-input-background-color` | Achtergrondkleur |
57+
| `--dsn-text-input-border-color` | Borderkleur default state |
58+
| `--dsn-text-input-border-width` | Dikte van de border |
59+
| `--dsn-text-input-border-radius` | Border radius |
60+
| `--dsn-text-input-padding-block-start` | Padding boven |
61+
| `--dsn-text-input-padding-block-end` | Padding onder |
62+
| `--dsn-text-input-hover-border-color` | Borderkleur hover state |
63+
| `--dsn-text-input-focus-border-color` | Borderkleur focus state |
64+
| `--dsn-text-input-disabled-background-color` | Achtergrondkleur disabled |
65+
| `--dsn-text-input-disabled-color` | Tekstkleur disabled |
66+
| `--dsn-text-input-invalid-border-color` | Borderkleur invalid state |
67+
| `--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 PasswordInputStories from './PasswordInput.stories';
3+
import docs from './PasswordInput.docs.md?raw';
4+
5+
export const [intro, rest] = docs.split('<!-- VOORBEELD -->');
6+
7+
<Meta of={PasswordInputStories} />
8+
9+
<Markdown>{intro}</Markdown>
10+
11+
## Voorbeeld
12+
13+
<Story of={PasswordInputStories.Default} />
14+
15+
<Controls of={PasswordInputStories.Default} />
16+
17+
<Markdown>{rest}</Markdown>

0 commit comments

Comments
 (0)