🙈 Remove openspec from git tracking and add to .gitignore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Nathan Panchout
2026-01-29 14:19:28 +01:00
parent 1468973f81
commit 964dc94a04
8 changed files with 3 additions and 687 deletions

3
.gitignore vendored
View File

@@ -40,3 +40,6 @@ packages/tokens/src/lib/cunningham.ts
# Claude Code
CLAUDE.md
.claude*
# OpenSpec
openspec/

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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