feat(focus): the game chips answer the pointer, as the dailies do
They are the same object twice — a pill carrying a game's colour, pressed or not, that you tap — so they get the same hover rather than a second one: a ring in the game's hue, a one-pixel lift, and the label coming up to full ink while the chip is not the pressed one. Sharing it meant `.tick-chip` becoming `.hue-chip`, and dropping the `data-done` attribute it keyed off. Both chips were already saying the same thing in `aria-pressed`, and a second attribute repeating it is one more thing to keep in step — a rule that reads the announced state cannot disagree with it. The focus strip needed a pixel of headroom for that lift. `scroll-x` sets `overflow-x: auto`, which computes the vertical axis to `auto` as well, so a chip that rises loses that pixel and its ring to the scroller's edge; `pt-1` is the room, and `mt` gives the same total gap back. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
858551ac98
commit
d3066aefdc
@@ -194,11 +194,11 @@ function TickChip({
|
|||||||
aria-pressed={isDone}
|
aria-pressed={isDone}
|
||||||
aria-label={`${ariaLabel}${isDone ? ", done today" : ", not done today"}`}
|
aria-label={`${ariaLabel}${isDone ? ", done today" : ", not done today"}`}
|
||||||
title={title}
|
title={title}
|
||||||
// Done-ness for the stylesheet: the chip's own colours are the game's
|
// `.hue-chip` is the shared hover: the chip's own colours are the game's
|
||||||
// hue and arrive inline, which no rule can override, so the hover has to
|
// hue and arrive inline, which no rule can override, so it is drawn from
|
||||||
// be drawn from what inline does not own — see `.tick-chip`.
|
// what inline does not own. `--hue` below is the only thing it needs;
|
||||||
data-done={isDone}
|
// pressed-ness it reads off `aria-pressed`, which is already up there.
|
||||||
className="tick-chip flex max-w-[15rem] items-center gap-2 rounded-full border px-3 py-1.5 text-xs font-medium"
|
className="hue-chip flex max-w-[15rem] items-center gap-2 rounded-full border px-3 py-1.5 text-xs font-medium"
|
||||||
style={{
|
style={{
|
||||||
["--hue" as string]: hue,
|
["--hue" as string]: hue,
|
||||||
borderColor: isDone ? hue : `color-mix(in srgb, ${hue} 20%, transparent)`,
|
borderColor: isDone ? hue : `color-mix(in srgb, ${hue} 20%, transparent)`,
|
||||||
@@ -222,7 +222,7 @@ function TickChip({
|
|||||||
opacity={isDone ? 1 : 0.25}
|
opacity={isDone ? 1 : 0.25}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<span className="tick-label truncate">{label}</span>
|
<span className="hue-chip-label truncate">{label}</span>
|
||||||
{streak > 1 && (
|
{streak > 1 && (
|
||||||
<span className="tnum shrink-0 text-[0.625rem] opacity-70">{streak}d</span>
|
<span className="tnum shrink-0 text-[0.625rem] opacity-70">{streak}d</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -61,7 +61,12 @@ export function GameFocus({
|
|||||||
<div
|
<div
|
||||||
role="group"
|
role="group"
|
||||||
aria-label="Focus on one game"
|
aria-label="Focus on one game"
|
||||||
className="scroll-x -mx-4 mt-2 flex gap-1.5 px-4 pb-1 lg:flex-wrap"
|
// `pt-1` is the lift's headroom, not spacing: `scroll-x` sets
|
||||||
|
// `overflow-x: auto`, which computes the vertical axis to `auto` as
|
||||||
|
// well, so a chip that rises a pixel under the cursor loses that pixel
|
||||||
|
// and its ring to the scroller's edge. `mt` gives the same total gap
|
||||||
|
// back.
|
||||||
|
className="scroll-x -mx-4 mt-1 flex gap-1.5 px-4 pb-1 pt-1 lg:flex-wrap"
|
||||||
>
|
>
|
||||||
<Chip
|
<Chip
|
||||||
label="All"
|
label="All"
|
||||||
@@ -120,8 +125,12 @@ function Chip({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
aria-pressed={on}
|
aria-pressed={on}
|
||||||
aria-label={`${ariaLabel ?? label}, ${count} outstanding`}
|
aria-label={`${ariaLabel ?? label}, ${count} outstanding`}
|
||||||
className="flex shrink-0 items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors"
|
// The dailies strip's hover, and for the same reason: these colours are a
|
||||||
|
// game's hue, so they arrive inline where no rule can reach them, and
|
||||||
|
// `.hue-chip` works off `--hue` and the `aria-pressed` above instead.
|
||||||
|
className="hue-chip flex shrink-0 items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium"
|
||||||
style={{
|
style={{
|
||||||
|
["--hue" as string]: hue,
|
||||||
borderColor: on ? hue : `color-mix(in srgb, ${hue} 30%, transparent)`,
|
borderColor: on ? hue : `color-mix(in srgb, ${hue} 30%, transparent)`,
|
||||||
color: on ? hue : "var(--color-muted)",
|
color: on ? hue : "var(--color-muted)",
|
||||||
background: on
|
background: on
|
||||||
@@ -131,7 +140,7 @@ function Chip({
|
|||||||
opacity: count === 0 && !on ? 0.5 : 1,
|
opacity: count === 0 && !on ? 0.5 : 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{label}
|
<span className="hue-chip-label">{label}</span>
|
||||||
<span className="tnum text-[0.625rem] opacity-70">{count}</span>
|
<span className="tnum text-[0.625rem] opacity-70">{count}</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
+23
-22
@@ -295,24 +295,25 @@ body {
|
|||||||
filter 130ms ease-out;
|
filter 130ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---- Today's dailies ---------------------------------------------------- */
|
/* ---- Chips coloured by a game hue --------------------------------------- */
|
||||||
/*
|
/*
|
||||||
* A daily chip is the fastest thing on the page to act on, and it was saying
|
* Today's dailies and the focus bar are the same object twice: a pill carrying
|
||||||
* none of that: the pointer stayed an arrow and nothing happened under it, so a
|
* a game's colour, pressed or not, that you tap. Both were saying none of that
|
||||||
* strip of tappable jobs read as a row of status badges.
|
* — the pointer stayed an arrow and nothing happened under it, so two strips of
|
||||||
|
* controls read as rows of status badges.
|
||||||
*
|
*
|
||||||
* The hover is drawn from what the component does *not* own. A chip's border,
|
* The hover has to be drawn from what the components do *not* own. A chip's
|
||||||
* ink and wash are mixed from the game's hue, which is data rather than a token
|
* border, ink and wash are mixed from the game's hue, which is data rather than
|
||||||
* and therefore arrives as an inline style — and no rule here can override one
|
* a token and therefore arrives as an inline style — and no rule here can
|
||||||
* of those. So the cursor, a ring in the same hue, a one-pixel lift and the
|
* override one of those. So a ring in the same hue, a one-pixel lift and the
|
||||||
* label coming up to full ink carry it instead. `--hue` and `data-done` are the
|
* label coming up to full ink carry it instead. `--hue` is the one thing a chip
|
||||||
* two things the chip hands over for that.
|
* has to hand over; `aria-pressed` it was already saying out loud.
|
||||||
*
|
*
|
||||||
* The label only brightens while the job is outstanding: on a finished chip the
|
* The label only brightens on a chip that is *not* pressed. On a pressed one
|
||||||
* ink *is* the game's hue, and overriding it with `ink` would trade the one
|
* the ink already *is* the game's hue, and overriding it with `ink` would trade
|
||||||
* thing that says which game for a hover state.
|
* the one thing saying which game for a hover state.
|
||||||
*/
|
*/
|
||||||
.tick-chip {
|
.hue-chip {
|
||||||
transition:
|
transition:
|
||||||
border-color 130ms ease-out,
|
border-color 130ms ease-out,
|
||||||
background-color 130ms ease-out,
|
background-color 130ms ease-out,
|
||||||
@@ -321,22 +322,22 @@ body {
|
|||||||
transform 120ms cubic-bezier(0.2, 0.7, 0.3, 1);
|
transform 120ms cubic-bezier(0.2, 0.7, 0.3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tick-label {
|
.hue-chip-label {
|
||||||
transition: color 130ms ease-out;
|
transition: color 130ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
.tick-chip:hover {
|
.hue-chip:hover {
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: 0 0 0 1px color-mix(in srgb, var(--hue) 45%, transparent);
|
box-shadow: 0 0 0 1px color-mix(in srgb, var(--hue) 45%, transparent);
|
||||||
}
|
}
|
||||||
.tick-chip[data-done="false"]:hover .tick-label {
|
.hue-chip[aria-pressed="false"]:hover .hue-chip-label {
|
||||||
color: var(--color-ink);
|
color: var(--color-ink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Press feedback on both mouse and touch, as the event row has. */
|
/* Press feedback on both mouse and touch, as the event row has. */
|
||||||
.tick-chip:active {
|
.hue-chip:active {
|
||||||
transform: scale(0.96);
|
transform: scale(0.96);
|
||||||
transition-duration: 60ms;
|
transition-duration: 60ms;
|
||||||
}
|
}
|
||||||
@@ -349,16 +350,16 @@ body {
|
|||||||
.row-summary,
|
.row-summary,
|
||||||
.row-check,
|
.row-check,
|
||||||
.meter-tick,
|
.meter-tick,
|
||||||
.tick-chip,
|
.hue-chip,
|
||||||
.tick-label {
|
.hue-chip-label {
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
.row-check:hover,
|
.row-check:hover,
|
||||||
.row-check:active,
|
.row-check:active,
|
||||||
.event-row:hover .row-open,
|
.event-row:hover .row-open,
|
||||||
.event-row:hover .row-rail,
|
.event-row:hover .row-rail,
|
||||||
.tick-chip:hover,
|
.hue-chip:hover,
|
||||||
.tick-chip:active {
|
.hue-chip:active {
|
||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user