Before calendar multi-select css update run
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
# Update — Calendar multi-select drag feedback (bug fixes)
|
||||
|
||||
**Audience:** the junior developer who implemented the calendar multi-select feature.
|
||||
**Area:** `frontend/` only. No backend changes. This is a small, focused follow-up — the feature works, these are two visual/interaction bugs in the *drag-to-select* behavior.
|
||||
|
||||
> Great work on the feature — bars, week numbers, bulk apply and the legend all look good. There are exactly **two** things to fix, described below. Please change **only** what's needed for these two; don't touch the save/bulk-apply logic, the bar rendering, or the modal behavior.
|
||||
|
||||
---
|
||||
|
||||
## The two bugs (what the user sees vs. what they want)
|
||||
|
||||
### Bug 1 — Days are NOT highlighted *while* you drag-select
|
||||
|
||||
**What happens now:** When you press and drag across days, the day cells stay white during the drag. The blue "selected" highlight (light blue background + blue border) only appears **after** you release the mouse and the "Plan N days" dialog opens.
|
||||
|
||||
**What we want:** As soon as you start dragging, every day in the current selection range should be highlighted **immediately and live**, updating as you drag over more days — and it should look just like the **hover** state of a single day. The highlight must be visible *during* the drag, before the dialog appears.
|
||||
|
||||
### Bug 2 — The day numbers get text-selected (blue "selecting text" boxes)
|
||||
|
||||
**What happens now:** While dragging, the browser selects the day-number text, so the numbers (1, 2, 3, …) get the blue "highlighted text" look — exactly like dragging to select text on a web page. You can see this on the numbers in both screenshots.
|
||||
|
||||
**What we want:** Dragging across the calendar must **never** select text. No day number (or any other calendar text) should get the text-selection highlight while dragging.
|
||||
|
||||
---
|
||||
|
||||
## Root cause (so the fix is obvious)
|
||||
|
||||
### Bug 1 root cause — in `src/pages/Calendar.tsx`
|
||||
|
||||
The `.calendar-day--selected` highlight is driven by the `selectedDates` state, which is passed down to `<CalendarWeek selectedDates={selectedDates} />`.
|
||||
|
||||
But `selectedDates` is **only written in the `pointerup` handler** (the `useEffect` around lines 29–49):
|
||||
|
||||
```ts
|
||||
const handlePointerUp = () => {
|
||||
if (isDragging && dragAnchor && dragCurrent) {
|
||||
const dates = datesInRange(dragAnchor, dragCurrent)
|
||||
setSelectedDates(new Set(dates)) // <-- only happens on RELEASE
|
||||
...
|
||||
setIsModalOpen(true)
|
||||
}
|
||||
setIsDragging(false)
|
||||
}
|
||||
```
|
||||
|
||||
During the drag itself, only `dragAnchor` and `dragCurrent` change (via `handlePointerEnter`). Because `selectedDates` is still empty until release, nothing is highlighted while dragging. That's the whole bug — it's a *timing* problem, not a styling problem.
|
||||
|
||||
### Bug 2 root cause — in `src/pages/calendar.css`
|
||||
|
||||
`.calendar-day` (and its `.calendar-day__number` child) have **no `user-select` rule**, so the browser's default text selection kicks in during a drag. Note the week-number cells (`.calendar__week-number`) already set `user-select: none` — the day cells just need the same.
|
||||
|
||||
---
|
||||
|
||||
## Exactly what to change
|
||||
|
||||
### Fix 1 — show the selection live during the drag (`src/pages/Calendar.tsx`)
|
||||
|
||||
Render the highlight from a **derived "preview" set** instead of only the committed `selectedDates`. While dragging, the preview is the range between the anchor and the current day; otherwise it's the committed selection.
|
||||
|
||||
Add this just before the `return` (near where `firstSelectedDate` is computed, ~line 184):
|
||||
|
||||
```ts
|
||||
// Dates to highlight in the grid. While dragging, show a live preview of the
|
||||
// range under the cursor; otherwise show the committed selection.
|
||||
const highlightedDates =
|
||||
isDragging && dragAnchor && dragCurrent
|
||||
? new Set(datesInRange(dragAnchor, dragCurrent))
|
||||
: selectedDates
|
||||
```
|
||||
|
||||
Then pass `highlightedDates` (not `selectedDates`) to the week:
|
||||
|
||||
```tsx
|
||||
<CalendarWeek
|
||||
...
|
||||
selectedDates={highlightedDates} // was: selectedDates
|
||||
...
|
||||
/>
|
||||
```
|
||||
|
||||
**Important — do NOT change `selectedDates` itself or how it's used elsewhere.** The committed `selectedDates` is what `handleSavePlannedDay` / `handleDeletePlannedDay` read on save. We are only changing **what gets rendered as highlighted**, not what gets saved. Leave the `pointerup` handler and the save logic exactly as they are.
|
||||
|
||||
**Make the highlight match hover.** The look is already close, but make sure `.calendar-day--selected` matches `.calendar-day:hover` so "selecting" and "hovering" look identical. In `src/pages/calendar.css` the hover rule is:
|
||||
|
||||
```css
|
||||
.calendar-day:hover {
|
||||
border-color: #0066cc;
|
||||
box-shadow: 0 2px 8px rgba(0, 102, 204, 0.1);
|
||||
background-color: #f9fbff;
|
||||
}
|
||||
```
|
||||
|
||||
The existing `.calendar-day--selected` (lines ~117–121) already uses the same three values, so this should need no change — just verify they stay in sync. Do **not** invent a new selected color; the requirement is "same as hover."
|
||||
|
||||
### Fix 2 — stop text selection while dragging (`src/pages/calendar.css`)
|
||||
|
||||
Add `user-select: none` to the day cell so dragging can never select the numbers. Put it on `.calendar-day` (it will also cover the `.calendar-day__number` child):
|
||||
|
||||
```css
|
||||
.calendar-day {
|
||||
/* ...existing rules... */
|
||||
user-select: none;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
}
|
||||
```
|
||||
|
||||
Keep both lines — Safari still needs the `-webkit-` prefix, and ESLint/CI won't complain about it. (If you prefer, you can instead add it once to the grid container `.calendar__grid` so it covers the whole calendar; either is acceptable, but `.calendar-day` is the minimal, targeted change.)
|
||||
|
||||
---
|
||||
|
||||
## What NOT to touch
|
||||
|
||||
- The bulk-apply / save loop in `handleSavePlannedDay`, and the delete logic.
|
||||
- The spanning-bar rendering, lane layout, or `CalendarWeek` props (other than the one value passed to `selectedDates`).
|
||||
- The modal, week-number gutter, or legend.
|
||||
- `selectedDates` state semantics — it stays the *committed* selection used for saving.
|
||||
|
||||
---
|
||||
|
||||
## How to verify (manual)
|
||||
|
||||
1. Turn on week numbers in Settings (optional) and open the calendar.
|
||||
2. Press on a day and drag across several days **without releasing**:
|
||||
- ✅ Every day under the drag is highlighted live (same look as hovering one day), updating as you move.
|
||||
- ✅ No day number gets the blue text-selection box; nothing on the calendar looks like selected text.
|
||||
3. Release the mouse: the "Plan N days" dialog opens and the same days stay highlighted (unchanged from today).
|
||||
4. Cancel/Save: highlight clears (unchanged from today).
|
||||
5. A single click still opens the single-day modal (unchanged).
|
||||
|
||||
---
|
||||
|
||||
## Tests (must stay green + add coverage)
|
||||
|
||||
CI gates from `frontend/` — all must pass:
|
||||
|
||||
```bash
|
||||
npx tsc --noEmit
|
||||
npm run lint # zero warnings
|
||||
npm run coverage # 90% gate
|
||||
```
|
||||
|
||||
Add a test in `src/pages/Calendar.test.tsx` (or the existing multi-select test) proving the **live** highlight:
|
||||
|
||||
- Fire `pointerDown` on one current-month day, then `pointerEnter` on a later day **without** firing `pointerup`.
|
||||
- Assert that the cells in that range now have the `calendar-day--selected` class **before** any `pointerup` — this is exactly the behavior that was missing.
|
||||
|
||||
(There is no clean unit assertion for "text isn't selectable" — that's enforced by the CSS rule; cover it in the manual check above. If you want a guard, a small test asserting `.calendar-day` has the expected class is enough; the `user-select` value itself isn't worth asserting in jsdom.)
|
||||
|
||||
Don't delete or weaken any existing calendar test to make things pass (see `AGENTS.md`). After the change, run `graphify update .`.
|
||||
|
||||
---
|
||||
|
||||
## Definition of done
|
||||
|
||||
- [ ] Dragging highlights the day range **live**, matching the hover look, before the dialog opens.
|
||||
- [ ] No text (day numbers or otherwise) gets selected while dragging.
|
||||
- [ ] Committed selection, save/bulk-apply, delete, modal, bars, week numbers and legend all behave exactly as before.
|
||||
- [ ] A test covers the live (pre-`pointerup`) highlight.
|
||||
- [ ] `tsc --noEmit`, `npm run lint` (0 warnings), `npm run coverage` (≥90%) pass; existing tests still green.
|
||||
- [ ] `graphify update .` run.
|
||||
```
|
||||
@@ -115,8 +115,13 @@
|
||||
}
|
||||
|
||||
.calendar-day--selected {
|
||||
background-color: #e3f2fd;
|
||||
background-color: #f9fbff;
|
||||
border-color: #0066cc;
|
||||
box-shadow: 0 2px 8px rgba(0, 102, 204, 0.1);
|
||||
}
|
||||
|
||||
.calendar-day--selected:hover {
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
|
||||
.calendar-day__events {
|
||||
@@ -257,6 +262,10 @@
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
background-color: #fafafa;
|
||||
border-right: 1px solid #d0d0d0;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.calendar__week-number {
|
||||
@@ -267,8 +276,11 @@
|
||||
font-weight: 600;
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
border-right: 1px solid #e0e0e0;
|
||||
border-right: 1px solid #d0d0d0;
|
||||
background-color: #fafafa;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
min-height: 84px;
|
||||
}
|
||||
|
||||
.calendar__week-number-text {
|
||||
|
||||
Reference in New Issue
Block a user