🙈 Remove openspec from git tracking and add to .gitignore
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -40,3 +40,6 @@ packages/tokens/src/lib/cunningham.ts
|
||||
# Claude Code
|
||||
CLAUDE.md
|
||||
.claude*
|
||||
|
||||
# OpenSpec
|
||||
openspec/
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
## Context
|
||||
|
||||
Le design system Cunningham utilise actuellement un pattern "floating label" pour tous les champs de formulaire. Ce pattern est implémenté via le composant `LabelledBox` qui est utilisé par Input, TextArea, Select et DatePicker.
|
||||
|
||||
Architecture actuelle :
|
||||
```
|
||||
Field (wrapper: state, text, fullWidth)
|
||||
└── LabelledBox (gère le label flottant)
|
||||
└── Composant natif (input, textarea, etc.)
|
||||
```
|
||||
|
||||
Le `LabelledBox` gère :
|
||||
- Le positionnement du label (absolu, avec transition)
|
||||
- L'état `labelAsPlaceholder` (label en grand = placeholder, label petit = au-dessus)
|
||||
- Le CSS des transitions
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Ajouter une variante `classic` avec label externe et placeholder natif
|
||||
- Maintenir la rétrocompatibilité (floating = défaut)
|
||||
- Supporter la variante classic sur tous les composants concernés
|
||||
- Avoir un champ plus compact en mode classic
|
||||
|
||||
**Non-Goals:**
|
||||
- Changer le comportement par défaut des composants existants
|
||||
- Créer des composants séparés (ClassicInput, etc.)
|
||||
- Modifier Checkbox, Radio, Switch (déjà label externe)
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. Type `FieldVariant` partagé
|
||||
|
||||
Créer un type exporté dans `packages/react/src/components/Forms/types.ts` :
|
||||
|
||||
```typescript
|
||||
export type FieldVariant = "floating" | "classic";
|
||||
```
|
||||
|
||||
**Pourquoi** : Assure la cohérence du typage entre tous les composants.
|
||||
|
||||
### 2. Architecture en mode Classic
|
||||
|
||||
En mode classic, **le label est rendu EN DEHORS du wrapper bordé**, pas à l'intérieur :
|
||||
|
||||
```
|
||||
Field (wrapper)
|
||||
└── label.c__input__label (EN DEHORS - nouveau)
|
||||
└── div.c__input__wrapper (wrapper bordé)
|
||||
└── Composant natif (input)
|
||||
```
|
||||
|
||||
Vs mode floating (existant) :
|
||||
```
|
||||
Field (wrapper)
|
||||
└── div.c__input__wrapper (wrapper bordé)
|
||||
└── LabelledBox (gère le label flottant)
|
||||
└── Composant natif (input)
|
||||
```
|
||||
|
||||
**Pourquoi** : Le label doit être visuellement au-dessus de la bordure du champ, pas à l'intérieur.
|
||||
|
||||
### 3. CSS pour la variante classic
|
||||
|
||||
Chaque composant a sa propre classe label et un modifier `--classic` sur le wrapper :
|
||||
|
||||
```scss
|
||||
// Label externe
|
||||
.c__input__label {
|
||||
display: block;
|
||||
font-size: var(--c--components--forms-labelledbox--classic-label-font-size);
|
||||
color: var(--c--components--forms-labelledbox--label-color--small);
|
||||
margin-bottom: var(--c--components--forms-labelledbox--classic-label-margin-bottom);
|
||||
}
|
||||
|
||||
// Wrapper en mode classic
|
||||
.c__input__wrapper--classic {
|
||||
align-items: center; // Centrage vertical du contenu
|
||||
height: 2.75rem; // Hauteur réduite (vs 3.5rem en floating)
|
||||
}
|
||||
```
|
||||
|
||||
Classes par composant :
|
||||
- Input : `.c__input__label`, `.c__input__wrapper--classic`
|
||||
- TextArea : `.c__textarea__label`, `.c__textarea__wrapper--classic`
|
||||
- Select : `.c__select__label`, `.c__select--classic`
|
||||
- DatePicker : `.c__date-picker__label`, `.c__date-picker--classic`
|
||||
|
||||
### 4. Gestion du placeholder par composant
|
||||
|
||||
Chaque composant gère son placeholder selon la variante :
|
||||
|
||||
**Input / TextArea** :
|
||||
```typescript
|
||||
// Si variant="classic", passer placeholder au <input>/<textarea>
|
||||
// Si variant="floating", ne pas passer placeholder (ignoré)
|
||||
<input placeholder={variant === "classic" ? placeholder : undefined} />
|
||||
```
|
||||
|
||||
**Select** :
|
||||
```typescript
|
||||
// Si variant="classic" et pas de sélection, afficher placeholder stylisé
|
||||
{variant === "classic" && !selectedItem && placeholder && (
|
||||
<span className="c__select__placeholder">{placeholder}</span>
|
||||
)}
|
||||
```
|
||||
|
||||
### 5. Props ajoutées sur chaque composant
|
||||
|
||||
```typescript
|
||||
// Input, TextArea, Select, DatePicker, DateRangePicker
|
||||
type Props = {
|
||||
variant?: FieldVariant; // NOUVEAU - défaut: "floating"
|
||||
placeholder?: string; // Existe peut-être déjà, à vérifier
|
||||
// ... props existantes
|
||||
};
|
||||
```
|
||||
|
||||
### 6. Design tokens
|
||||
|
||||
Tokens existants réutilisés dans `LabelledBox/tokens.ts` :
|
||||
|
||||
```typescript
|
||||
"classic-label-margin-bottom": defaults.globals.spacings.xs,
|
||||
"classic-label-font-size": defaults.globals.font.sizes.s,
|
||||
```
|
||||
|
||||
Pour Select, token supplémentaire pour le placeholder :
|
||||
```typescript
|
||||
"placeholder-color": defaults.contextuals.content.semantic.neutral.tertiary,
|
||||
```
|
||||
|
||||
### 7. DateRangePicker spécifique
|
||||
|
||||
DateRangePicker a deux labels (startLabel, endLabel) affichés côte à côte au-dessus du wrapper :
|
||||
|
||||
```
|
||||
.c__date-picker__range__labels
|
||||
└── label (startLabel)
|
||||
└── spacer
|
||||
└── label (endLabel)
|
||||
.c__date-picker__wrapper
|
||||
└── DateFieldBox (start)
|
||||
└── separator
|
||||
└── DateFieldBox (end)
|
||||
```
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[Label externe vs LabelledBox]** → Duplication de la structure label. Mitigation : tokens CSS partagés pour la cohérence visuelle.
|
||||
|
||||
**[Hauteur réduite en classic]** → Les champs classic sont plus compacts (2.75rem vs 3.5rem). C'est intentionnel pour un look plus traditionnel.
|
||||
|
||||
**[Select placeholder vs Input placeholder]** → Le Select n'a pas de placeholder natif, on simule avec un span. Mitigation : même couleur via tokens.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Pas de migration nécessaire. Le changement est additif :
|
||||
- Nouveau comportement opt-in via `variant="classic"`
|
||||
- Comportement existant inchangé (floating par défaut)
|
||||
- Aucun breaking change
|
||||
|
||||
## Open Questions
|
||||
|
||||
Résolues :
|
||||
- ✅ Stories Storybook ajoutées pour chaque composant en mode classic
|
||||
- ✅ Tokens dans LabelledBox/tokens.ts (réutilisés par tous les composants)
|
||||
@@ -1,118 +0,0 @@
|
||||
# Proposal: Field Variant Classic
|
||||
|
||||
## Contexte
|
||||
|
||||
Actuellement, tous les champs de formulaire (Input, Select, TextArea, DatePicker) utilisent un pattern "floating label" : le label sert de placeholder quand le champ est vide, puis remonte au-dessus de la valeur quand l'utilisateur interagit avec le champ.
|
||||
|
||||
```
|
||||
État vide: État rempli:
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Your name │ │ Your name │ ← label (petit)
|
||||
│ │ │ Coucou │ ← valeur
|
||||
└─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
## Besoin
|
||||
|
||||
Ajouter une variante "classic" où :
|
||||
- Le label est toujours positionné au-dessus du champ
|
||||
- Le placeholder est un vrai placeholder HTML natif (séparé du label)
|
||||
|
||||
```
|
||||
Your name ← label externe (toujours visible)
|
||||
┌─────────────────┐
|
||||
│ Enter your name │ ← placeholder classique
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Décisions
|
||||
|
||||
### Approche d'implémentation : A + D
|
||||
|
||||
- **API publique (A)** : Chaque composant expose une prop `variant`
|
||||
- **Implémentation (D)** : La logique est centralisée dans `LabelledBox`
|
||||
|
||||
```tsx
|
||||
<Input variant="classic" label="Name" placeholder="Enter your name" />
|
||||
<Select variant="floating" label="Country" />
|
||||
```
|
||||
|
||||
### Nommage des variantes
|
||||
|
||||
| Variante | Description |
|
||||
|----------|-------------|
|
||||
| `"floating"` | Label qui sert de placeholder et remonte au focus (comportement actuel) |
|
||||
| `"classic"` | Label au-dessus, placeholder natif dans le champ |
|
||||
|
||||
### Variante par défaut
|
||||
|
||||
`"floating"` reste la variante par défaut pour assurer la rétrocompatibilité.
|
||||
|
||||
```tsx
|
||||
// Équivalent :
|
||||
<Input label="Name" />
|
||||
<Input label="Name" variant="floating" />
|
||||
```
|
||||
|
||||
### Comportement du placeholder
|
||||
|
||||
| Variante | Comportement |
|
||||
|----------|--------------|
|
||||
| `floating` | La prop `placeholder` est ignorée silencieusement (le label fait office de placeholder) |
|
||||
| `classic` | La prop `placeholder` est utilisée comme placeholder HTML natif (optionnel) |
|
||||
|
||||
### Select en mode classic
|
||||
|
||||
Quand aucune option n'est sélectionnée, un placeholder stylisé est affiché (même apparence grisée qu'un placeholder natif). Ce placeholder est optionnel.
|
||||
|
||||
```
|
||||
Country
|
||||
┌─────────────────────────────┐
|
||||
│ Select... ▼ │
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
## Composants concernés
|
||||
|
||||
- `Input`
|
||||
- `TextArea`
|
||||
- `Select` (mono et multi)
|
||||
- `DatePicker`
|
||||
- `DateRangePicker`
|
||||
|
||||
Note : Checkbox, Radio et Switch ont déjà un label externe, ils ne sont pas concernés.
|
||||
|
||||
## Non-objectifs
|
||||
|
||||
- Changer la variante par défaut (breaking change)
|
||||
- Créer des composants séparés (ClassicInput, etc.)
|
||||
- Ajouter un context/provider global pour la variante
|
||||
|
||||
## Impact technique
|
||||
|
||||
### LabelledBox
|
||||
|
||||
Le composant `LabelledBox` sera enrichi :
|
||||
|
||||
```tsx
|
||||
interface Props {
|
||||
label?: string;
|
||||
variant?: "floating" | "classic"; // NOUVEAU
|
||||
labelAsPlaceholder?: boolean; // Utilisé seulement si floating
|
||||
placeholder?: string; // NOUVEAU - pour classic
|
||||
// ... autres props existantes
|
||||
}
|
||||
```
|
||||
|
||||
### CSS
|
||||
|
||||
Nouveau mode CSS dans `LabelledBox/_index.scss` pour la variante classic :
|
||||
- Label toujours positionné au-dessus (pas de transition)
|
||||
- Pas de padding-top pour l'espace du label flottant
|
||||
|
||||
### Chaque composant
|
||||
|
||||
Chaque composant devra :
|
||||
1. Accepter la prop `variant`
|
||||
2. La passer à `LabelledBox`
|
||||
3. Gérer le placeholder selon la variante
|
||||
@@ -1,100 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Field variant prop
|
||||
|
||||
All form field components (Input, TextArea, Select, DatePicker, DateRangePicker) SHALL accept a `variant` prop with values `"floating"` or `"classic"`.
|
||||
|
||||
#### Scenario: Default variant is floating
|
||||
- **WHEN** a field component is rendered without a `variant` prop
|
||||
- **THEN** the component SHALL behave as `variant="floating"` (current behavior)
|
||||
|
||||
#### Scenario: Explicit floating variant
|
||||
- **WHEN** a field component is rendered with `variant="floating"`
|
||||
- **THEN** the component SHALL behave identically to without the prop
|
||||
|
||||
#### Scenario: Classic variant
|
||||
- **WHEN** a field component is rendered with `variant="classic"`
|
||||
- **THEN** the label SHALL be positioned above the field (static, no animation)
|
||||
- **AND** the placeholder SHALL be displayed inside the field (native behavior)
|
||||
|
||||
### Requirement: Floating variant label behavior
|
||||
|
||||
In floating variant, the label SHALL serve as a placeholder when the field is empty and move above the value when focused or filled.
|
||||
|
||||
#### Scenario: Empty field with floating variant
|
||||
- **WHEN** a field with `variant="floating"` is empty and not focused
|
||||
- **THEN** the label SHALL be displayed inside the field as a placeholder (large font)
|
||||
|
||||
#### Scenario: Focused field with floating variant
|
||||
- **WHEN** a field with `variant="floating"` receives focus
|
||||
- **THEN** the label SHALL animate to a smaller size above the input area
|
||||
|
||||
#### Scenario: Filled field with floating variant
|
||||
- **WHEN** a field with `variant="floating"` has a value
|
||||
- **THEN** the label SHALL remain in the small position above the value
|
||||
|
||||
#### Scenario: Placeholder prop ignored in floating variant
|
||||
- **WHEN** a field with `variant="floating"` receives a `placeholder` prop
|
||||
- **THEN** the placeholder prop SHALL be ignored silently
|
||||
- **AND** the label SHALL continue to serve as placeholder
|
||||
|
||||
### Requirement: Classic variant label behavior
|
||||
|
||||
In classic variant, the label SHALL always be positioned above the field (outside the bordered wrapper), with no animation.
|
||||
|
||||
#### Scenario: Empty field with classic variant
|
||||
- **WHEN** a field with `variant="classic"` is empty
|
||||
- **THEN** the label SHALL be displayed above the bordered field area (outside the border)
|
||||
- **AND** the placeholder (if provided) SHALL be displayed inside the field
|
||||
|
||||
#### Scenario: Focused field with classic variant
|
||||
- **WHEN** a field with `variant="classic"` receives focus
|
||||
- **THEN** the label SHALL remain in the same position above the field
|
||||
- **AND** the placeholder (if visible) SHALL remain visible until user types
|
||||
|
||||
#### Scenario: Classic variant without placeholder
|
||||
- **WHEN** a field with `variant="classic"` has no `placeholder` prop
|
||||
- **THEN** the label SHALL be displayed above the field
|
||||
- **AND** the field input area SHALL be empty (no placeholder text)
|
||||
|
||||
### Requirement: Classic variant compact height
|
||||
|
||||
Fields in classic variant SHALL have a reduced height compared to floating variant.
|
||||
|
||||
#### Scenario: Classic variant field height
|
||||
- **WHEN** a field is rendered with `variant="classic"`
|
||||
- **THEN** the field wrapper height SHALL be 2.75rem (44px)
|
||||
- **AND** the content SHALL be vertically centered within the wrapper
|
||||
|
||||
#### Scenario: Floating variant field height (unchanged)
|
||||
- **WHEN** a field is rendered with `variant="floating"` or without variant prop
|
||||
- **THEN** the field wrapper height SHALL remain 3.5rem (56px) as before
|
||||
|
||||
### Requirement: Select placeholder in classic variant
|
||||
|
||||
Select components in classic variant SHALL display a styled placeholder when no option is selected.
|
||||
|
||||
#### Scenario: Select with classic variant and no selection
|
||||
- **WHEN** a Select with `variant="classic"` has no selected option
|
||||
- **AND** a `placeholder` prop is provided
|
||||
- **THEN** the placeholder text SHALL be displayed inside the select field
|
||||
- **AND** the placeholder SHALL have a muted/gray appearance (same as native placeholder)
|
||||
|
||||
#### Scenario: Select with classic variant after selection
|
||||
- **WHEN** a Select with `variant="classic"` has a selected option
|
||||
- **THEN** the selected option label SHALL be displayed
|
||||
- **AND** the placeholder SHALL not be visible
|
||||
|
||||
#### Scenario: Select classic without placeholder prop
|
||||
- **WHEN** a Select with `variant="classic"` has no `placeholder` prop
|
||||
- **AND** no option is selected
|
||||
- **THEN** the select field SHALL appear empty (no placeholder text)
|
||||
|
||||
### Requirement: Backward compatibility
|
||||
|
||||
The addition of the `variant` prop SHALL NOT change the behavior of existing components without the prop.
|
||||
|
||||
#### Scenario: Existing code without variant prop
|
||||
- **WHEN** existing code uses Input, TextArea, Select, or DatePicker without `variant` prop
|
||||
- **THEN** the component SHALL render exactly as before (floating label behavior)
|
||||
- **AND** no visual or functional changes SHALL occur
|
||||
@@ -1,52 +0,0 @@
|
||||
## 1. Foundation
|
||||
|
||||
- [x] 1.1 Create `FieldVariant` type in `packages/react/src/components/Forms/types.ts`
|
||||
- [x] 1.2 Add design tokens for classic variant in `LabelledBox/tokens.ts`
|
||||
|
||||
## 2. LabelledBox Core
|
||||
|
||||
- [x] 2.1 Add `variant` prop to `LabelledBox` component interface
|
||||
- [x] 2.2 Update `LabelledBox` render logic to handle `variant="classic"` (ignore `labelAsPlaceholder`)
|
||||
- [x] 2.3 Add `.labelled-box--classic` CSS styles in `LabelledBox/_index.scss`
|
||||
- [x] 2.4 Add unit tests for `LabelledBox` with both variants
|
||||
|
||||
## 3. Input Component
|
||||
|
||||
- [x] 3.1 Add `variant` prop to `Input` component
|
||||
- [x] 3.2 Pass `variant` to `LabelledBox`
|
||||
- [x] 3.3 Handle `placeholder` prop based on variant (pass to native input only if classic)
|
||||
- [x] 3.4 Add unit tests for Input with classic variant
|
||||
- [x] 3.5 Add Storybook story for Input classic variant
|
||||
|
||||
## 4. TextArea Component
|
||||
|
||||
- [x] 4.1 Add `variant` prop to `TextArea` component
|
||||
- [x] 4.2 Pass `variant` to `LabelledBox`
|
||||
- [x] 4.3 Handle `placeholder` prop based on variant
|
||||
- [x] 4.4 Add unit tests for TextArea with classic variant
|
||||
- [x] 4.5 Add Storybook story for TextArea classic variant
|
||||
|
||||
## 5. Select Component
|
||||
|
||||
- [x] 5.1 Add `variant` and `placeholder` props to `Select` component interface
|
||||
- [x] 5.2 Pass `variant` to `LabelledBox` in `SelectMonoAux` and multi equivalents
|
||||
- [x] 5.3 Add styled placeholder rendering for classic variant when no selection
|
||||
- [x] 5.4 Add CSS for `.c__select__placeholder` (muted gray style)
|
||||
- [x] 5.5 Add unit tests for Select mono with classic variant
|
||||
- [x] 5.6 Add unit tests for Select multi with classic variant
|
||||
- [x] 5.7 Add Storybook stories for Select classic variant
|
||||
|
||||
## 6. DatePicker Components
|
||||
|
||||
- [x] 6.1 Add `variant` and `placeholder` props to `DatePicker` component
|
||||
- [x] 6.2 Pass `variant` to underlying field components
|
||||
- [x] 6.3 Handle placeholder in classic variant
|
||||
- [x] 6.4 Add unit tests for DatePicker with classic variant
|
||||
- [x] 6.5 Repeat for `DateRangePicker`
|
||||
- [x] 6.6 Add Storybook stories for DatePicker/DateRangePicker classic variant
|
||||
|
||||
## 7. Documentation & Finalization
|
||||
|
||||
- [x] 7.1 Update existing component documentation with variant prop
|
||||
- [x] 7.2 Run full test suite to ensure no regressions
|
||||
- [x] 7.3 Visual review of all components in both variants in Storybook
|
||||
@@ -1,130 +0,0 @@
|
||||
# Verification Report: field-variant-classic
|
||||
|
||||
## Summary
|
||||
|
||||
| Aspect | Status |
|
||||
|--------|--------|
|
||||
| **Completeness** | ✅ 31/32 tasks complete (96.9%) |
|
||||
| **Correctness** | ✅ All requirements implemented |
|
||||
| **Coherence** | ✅ Design decisions followed |
|
||||
| **Tests** | ✅ 414 tests passing |
|
||||
|
||||
## 1. Completeness
|
||||
|
||||
### Tasks Status
|
||||
- **Section 1 (Foundation)**: 2/2 ✅
|
||||
- **Section 2 (LabelledBox Core)**: 4/4 ✅
|
||||
- **Section 3 (Input Component)**: 5/5 ✅
|
||||
- **Section 4 (TextArea Component)**: 5/5 ✅
|
||||
- **Section 5 (Select Component)**: 7/7 ✅
|
||||
- **Section 6 (DatePicker Components)**: 6/6 ✅
|
||||
- **Section 7 (Documentation & Finalization)**: 2/3 ⚠️
|
||||
|
||||
### Remaining Task
|
||||
- **7.3 Visual review of all components in both variants in Storybook**: This is a manual task requiring human review in the browser.
|
||||
|
||||
## 2. Correctness
|
||||
|
||||
### Requirement: Field variant prop ✅
|
||||
All form components accept `variant` prop with `FieldVariant.Floating` or `FieldVariant.Classic` values.
|
||||
|
||||
| Scenario | Status | Evidence |
|
||||
|----------|--------|----------|
|
||||
| Default variant is floating | ✅ | Tests verify no visual change without prop |
|
||||
| Explicit floating variant | ✅ | Tests confirm identical behavior |
|
||||
| Classic variant | ✅ | Tests verify label above field, placeholder displayed |
|
||||
|
||||
### Requirement: Floating variant label behavior ✅
|
||||
| Scenario | Status | Evidence |
|
||||
|----------|--------|----------|
|
||||
| Empty field placeholder | ✅ | LabelledBox shows label as placeholder |
|
||||
| Focused field animation | ✅ | Label animates to small position |
|
||||
| Filled field label | ✅ | Label stays small above value |
|
||||
| Placeholder prop ignored | ✅ | Tests verify placeholder="" in floating |
|
||||
|
||||
### Requirement: Classic variant label behavior ✅
|
||||
| Scenario | Status | Evidence |
|
||||
|----------|--------|----------|
|
||||
| Label above field | ✅ | Label rendered outside wrapper with `.c__input__label` etc. |
|
||||
| Placeholder support | ✅ | Native placeholder passed to input |
|
||||
| Static label on focus | ✅ | Tests verify no class change on interaction |
|
||||
| No placeholder case | ✅ | Field empty when no placeholder prop |
|
||||
|
||||
### Requirement: Classic variant compact height ✅
|
||||
| Scenario | Status | Evidence |
|
||||
|----------|--------|----------|
|
||||
| Classic height 2.75rem | ✅ | CSS `--classic-height: 2.75rem` applied |
|
||||
| Floating height 3.5rem | ✅ | Unchanged from original |
|
||||
|
||||
### Requirement: Select placeholder in classic variant ✅
|
||||
| Scenario | Status | Evidence |
|
||||
|----------|--------|----------|
|
||||
| Placeholder with no selection | ✅ | `.c__select__placeholder` rendered |
|
||||
| Placeholder hidden after selection | ✅ | Tests verify placeholder disappears |
|
||||
| No placeholder case | ✅ | Select appears empty |
|
||||
|
||||
### Requirement: Backward compatibility ✅
|
||||
| Scenario | Status | Evidence |
|
||||
|----------|--------|----------|
|
||||
| Existing code unchanged | ✅ | All existing tests pass without modification |
|
||||
|
||||
### Additional Features Implemented
|
||||
- **`hideLabel` prop**: Added to Input, TextArea, DatePicker, DateRangePicker for accessibility
|
||||
- **`FieldVariant` enum**: Converted from type union to enum for better type safety
|
||||
|
||||
## 3. Coherence
|
||||
|
||||
### Design Decisions Verified
|
||||
|
||||
| Decision | Implementation | Status |
|
||||
|----------|---------------|--------|
|
||||
| Type `FieldVariant` partagé | `enum FieldVariant` in `types.ts` | ✅ |
|
||||
| Label outside wrapper in classic | Each component renders label before wrapper | ✅ |
|
||||
| CSS classes per component | `.c__input__label`, `.c__textarea__label`, etc. | ✅ |
|
||||
| Placeholder handling per variant | `placeholder={isClassic ? props.placeholder : ""}` | ✅ |
|
||||
| Design tokens | Tokens in `LabelledBox/tokens.ts` reused | ✅ |
|
||||
| DateRangePicker labels | `.c__date-picker__range__labels` with both labels | ✅ |
|
||||
|
||||
### Architecture Compliance
|
||||
The implementation follows the design document's architecture:
|
||||
```
|
||||
Classic mode:
|
||||
Field (wrapper)
|
||||
└── label.c__input__label (OUTSIDE - new)
|
||||
└── div.c__input__wrapper--classic (bordered wrapper)
|
||||
└── Native component (input)
|
||||
```
|
||||
|
||||
## 4. Test Coverage
|
||||
|
||||
| Component | Variant Tests | hideLabel Tests |
|
||||
|-----------|--------------|-----------------|
|
||||
| Input | ✅ 4 tests | ✅ 2 tests |
|
||||
| TextArea | ✅ 4 tests | ✅ 2 tests |
|
||||
| Select (mono) | ✅ 5 tests | N/A (already had) |
|
||||
| Select (multi) | ✅ 5 tests | N/A (already had) |
|
||||
| DatePicker | ✅ 3 tests | ✅ 2 tests |
|
||||
| DateRangePicker | ✅ 3 tests | ✅ 2 tests |
|
||||
| LabelledBox | ✅ 3 tests | N/A |
|
||||
|
||||
**Total: 414 tests passing**
|
||||
|
||||
## 5. Stories Coverage
|
||||
|
||||
All components have Storybook stories for:
|
||||
- Classic variant basic usage
|
||||
- Classic variant with placeholder
|
||||
- hideLabel in floating variant
|
||||
- hideLabel in classic variant
|
||||
|
||||
## Conclusion
|
||||
|
||||
The implementation is **ready for final review**. All requirements from the spec are correctly implemented, design decisions were followed, and the codebase maintains backward compatibility.
|
||||
|
||||
The only remaining item (7.3) is a manual visual review in Storybook, which requires human verification in the browser.
|
||||
|
||||
### Recommended Next Steps
|
||||
1. Run `yarn dev` and visually verify all components in Storybook
|
||||
2. Check both variants (floating/classic) render correctly
|
||||
3. Verify hideLabel works as expected (visually hidden but accessible)
|
||||
4. Archive the change after visual review passes
|
||||
@@ -1,20 +0,0 @@
|
||||
schema: spec-driven
|
||||
|
||||
# Project context (optional)
|
||||
# This is shown to AI when creating artifacts.
|
||||
# Add your tech stack, conventions, style guides, domain knowledge, etc.
|
||||
# Example:
|
||||
# context: |
|
||||
# Tech stack: TypeScript, React, Node.js
|
||||
# We use conventional commits
|
||||
# Domain: e-commerce platform
|
||||
|
||||
# Per-artifact rules (optional)
|
||||
# Add custom rules for specific artifacts.
|
||||
# Example:
|
||||
# rules:
|
||||
# proposal:
|
||||
# - Keep proposals under 500 words
|
||||
# - Always include a "Non-goals" section
|
||||
# tasks:
|
||||
# - Break tasks into chunks of max 2 hours
|
||||
@@ -1,100 +0,0 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Field variant prop
|
||||
|
||||
All form field components (Input, TextArea, Select, DatePicker, DateRangePicker) SHALL accept a `variant` prop with values `"floating"` or `"classic"`.
|
||||
|
||||
#### Scenario: Default variant is floating
|
||||
- **WHEN** a field component is rendered without a `variant` prop
|
||||
- **THEN** the component SHALL behave as `variant="floating"` (current behavior)
|
||||
|
||||
#### Scenario: Explicit floating variant
|
||||
- **WHEN** a field component is rendered with `variant="floating"`
|
||||
- **THEN** the component SHALL behave identically to without the prop
|
||||
|
||||
#### Scenario: Classic variant
|
||||
- **WHEN** a field component is rendered with `variant="classic"`
|
||||
- **THEN** the label SHALL be positioned above the field (static, no animation)
|
||||
- **AND** the placeholder SHALL be displayed inside the field (native behavior)
|
||||
|
||||
### Requirement: Floating variant label behavior
|
||||
|
||||
In floating variant, the label SHALL serve as a placeholder when the field is empty and move above the value when focused or filled.
|
||||
|
||||
#### Scenario: Empty field with floating variant
|
||||
- **WHEN** a field with `variant="floating"` is empty and not focused
|
||||
- **THEN** the label SHALL be displayed inside the field as a placeholder (large font)
|
||||
|
||||
#### Scenario: Focused field with floating variant
|
||||
- **WHEN** a field with `variant="floating"` receives focus
|
||||
- **THEN** the label SHALL animate to a smaller size above the input area
|
||||
|
||||
#### Scenario: Filled field with floating variant
|
||||
- **WHEN** a field with `variant="floating"` has a value
|
||||
- **THEN** the label SHALL remain in the small position above the value
|
||||
|
||||
#### Scenario: Placeholder prop ignored in floating variant
|
||||
- **WHEN** a field with `variant="floating"` receives a `placeholder` prop
|
||||
- **THEN** the placeholder prop SHALL be ignored silently
|
||||
- **AND** the label SHALL continue to serve as placeholder
|
||||
|
||||
### Requirement: Classic variant label behavior
|
||||
|
||||
In classic variant, the label SHALL always be positioned above the field (outside the bordered wrapper), with no animation.
|
||||
|
||||
#### Scenario: Empty field with classic variant
|
||||
- **WHEN** a field with `variant="classic"` is empty
|
||||
- **THEN** the label SHALL be displayed above the bordered field area (outside the border)
|
||||
- **AND** the placeholder (if provided) SHALL be displayed inside the field
|
||||
|
||||
#### Scenario: Focused field with classic variant
|
||||
- **WHEN** a field with `variant="classic"` receives focus
|
||||
- **THEN** the label SHALL remain in the same position above the field
|
||||
- **AND** the placeholder (if visible) SHALL remain visible until user types
|
||||
|
||||
#### Scenario: Classic variant without placeholder
|
||||
- **WHEN** a field with `variant="classic"` has no `placeholder` prop
|
||||
- **THEN** the label SHALL be displayed above the field
|
||||
- **AND** the field input area SHALL be empty (no placeholder text)
|
||||
|
||||
### Requirement: Classic variant compact height
|
||||
|
||||
Fields in classic variant SHALL have a reduced height compared to floating variant.
|
||||
|
||||
#### Scenario: Classic variant field height
|
||||
- **WHEN** a field is rendered with `variant="classic"`
|
||||
- **THEN** the field wrapper height SHALL be 2.75rem (44px)
|
||||
- **AND** the content SHALL be vertically centered within the wrapper
|
||||
|
||||
#### Scenario: Floating variant field height (unchanged)
|
||||
- **WHEN** a field is rendered with `variant="floating"` or without variant prop
|
||||
- **THEN** the field wrapper height SHALL remain 3.5rem (56px) as before
|
||||
|
||||
### Requirement: Select placeholder in classic variant
|
||||
|
||||
Select components in classic variant SHALL display a styled placeholder when no option is selected.
|
||||
|
||||
#### Scenario: Select with classic variant and no selection
|
||||
- **WHEN** a Select with `variant="classic"` has no selected option
|
||||
- **AND** a `placeholder` prop is provided
|
||||
- **THEN** the placeholder text SHALL be displayed inside the select field
|
||||
- **AND** the placeholder SHALL have a muted/gray appearance (same as native placeholder)
|
||||
|
||||
#### Scenario: Select with classic variant after selection
|
||||
- **WHEN** a Select with `variant="classic"` has a selected option
|
||||
- **THEN** the selected option label SHALL be displayed
|
||||
- **AND** the placeholder SHALL not be visible
|
||||
|
||||
#### Scenario: Select classic without placeholder prop
|
||||
- **WHEN** a Select with `variant="classic"` has no `placeholder` prop
|
||||
- **AND** no option is selected
|
||||
- **THEN** the select field SHALL appear empty (no placeholder text)
|
||||
|
||||
### Requirement: Backward compatibility
|
||||
|
||||
The addition of the `variant` prop SHALL NOT change the behavior of existing components without the prop.
|
||||
|
||||
#### Scenario: Existing code without variant prop
|
||||
- **WHEN** existing code uses Input, TextArea, Select, or DatePicker without `variant` prop
|
||||
- **THEN** the component SHALL render exactly as before (floating label behavior)
|
||||
- **AND** no visual or functional changes SHALL occur
|
||||
Reference in New Issue
Block a user