docs: add custom dropdowns and date pickers design

This commit is contained in:
Your Name
2026-02-17 22:15:10 +02:00
parent 64f04db2f2
commit 9602630f18

View File

@@ -0,0 +1,107 @@
# Custom Dropdowns & Date Pickers Design
**Goal:** Replace all native `<select>` and `<input type="date">` elements with custom Vue 3 components that match ZeroClock's dark UI.
**Architecture:** Two reusable components (`AppSelect`, `AppDatePicker`) using `<Teleport to="body">` for overflow-safe positioning. Drop-in replacements — no store or logic changes needed.
---
## Inventory
### Native `<select>` (6 instances)
1. **Timer.vue** — Project selector (`selectedProject`, nullable, disabled when running)
2. **Timer.vue** — Task selector (`selectedTask`, nullable, disabled when running or no project)
3. **Projects.vue** — Client selector in dialog (`formData.client_id`, optional "No client")
4. **Entries.vue** — Project filter (`filterProject`, nullable "All Projects")
5. **Entries.vue** — Project selector in edit dialog (`editForm.project_id`, required)
6. **Invoices.vue** — Client selector in create form (`createForm.client_id`, required)
### Native `<input type="date">` (6 instances)
1. **Entries.vue** — Start Date filter
2. **Entries.vue** — End Date filter
3. **Reports.vue** — Start Date
4. **Reports.vue** — End Date
5. **Invoices.vue** — Invoice Date (required)
6. **Invoices.vue** — Due Date (optional)
---
## Component 1: AppSelect
### Trigger
- Styled like existing inputs: `bg-bg-inset`, `border-border-subtle`, `rounded-xl`
- Shows selected label or placeholder in `text-text-tertiary`
- `ChevronDown` icon (Lucide) on right, rotates 180deg when open
- Disabled state: `opacity-40`, `cursor-not-allowed`
### Dropdown Panel
- Teleported to `<body>`, positioned via `getBoundingClientRect()`
- `bg-bg-surface`, `border border-border-visible`, `rounded-xl`, shadow
- Max-height with overflow-y scroll
- Options: hover `bg-bg-elevated`, selected item shows `Check` icon in accent color
- Animate in: scale + fade (150ms)
### API
```vue
<AppSelect
v-model="selectedProject"
:options="activeProjects"
label-key="name"
value-key="id"
placeholder="Select project"
:disabled="timerStore.isRunning"
/>
```
### Behavior
- Click trigger toggles open/close
- Click option selects and closes
- Click outside closes
- Keyboard: Arrow keys navigate, Enter selects, Escape closes
- Tab moves focus away and closes
---
## Component 2: AppDatePicker
### Trigger
- Text input showing formatted date ("Feb 17, 2026") or placeholder
- `Calendar` icon (Lucide) on right
- Same input styling as AppSelect trigger
### Calendar Popover
- Teleported to `<body>`, positioned below trigger
- Header: `ChevronLeft`/`ChevronRight` arrows, center "Month Year" label
- 7-column grid: Mon-Sun header row, day cells
- Prev/next month padding days in `text-text-tertiary`
- Today: ring/border in amber accent
- Selected: solid amber accent background, white text
- Hover: `bg-bg-elevated`
- Click day: selects, closes popover, updates model
### API
```vue
<AppDatePicker
v-model="startDate"
placeholder="Start date"
:required="true"
/>
```
### Behavior
- v-model is `YYYY-MM-DD` string (same format as native date input)
- Click trigger opens calendar
- Click outside closes
- Keyboard: Arrow keys navigate days, Enter selects, Escape closes
- Month navigation via chevron buttons
---
## Integration
Pure visual swap — replace each native element with the custom component, keep the same `v-model` binding. No store or routing changes.
**Files created:** `src/components/AppSelect.vue`, `src/components/AppDatePicker.vue`
**Files modified:** Timer.vue, Projects.vue, Entries.vue, Invoices.vue, Reports.vue
The `datetime-local` input in Entries.vue edit dialog stays as-is (not in scope).