docs: add custom dropdowns and date pickers design
This commit is contained in:
107
docs/plans/2026-02-17-custom-dropdowns-datepickers-design.md
Normal file
107
docs/plans/2026-02-17-custom-dropdowns-datepickers-design.md
Normal 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).
|
||||||
Reference in New Issue
Block a user