feat(a11y/ux): high-contrast overlay tokens, stale-data warning, staggered card entry (#313/#301/#300)#324
Merged
Chucks1093 merged 2 commits intoMay 28, 2026
Conversation
…ered card entry - closes accesslayerorg#313: scope `.creator-card-overlay-text` to the volume pill so forced-colors mode pins it to Canvas/CanvasText/ButtonBorder system tokens (legible text over the card image overlay; non-overlay text untouched). - closes accesslayerorg#301: `staleData.utils` + `useStaleData` + `StaleDataWarning` surface a subtle amber inline warning when creator data crosses the 60s freshness window, and fire a background refresh exactly once per fetch epoch. - closes accesslayerorg#300: `cardEntryAnimation.utils` returns a staggered delay CSS variable that the new `.creator-card-entry` class reads; honours `prefers-reduced-motion` and caps the stagger so the last card never feels sluggish. Tests added for `isStale`, `formatStaleAge`, `creatorCardEntryStyle`, `useStaleData`, and `StaleDataWarning` covering null/clock-skew edges, the staleness boundary, single-fire onStale per epoch, reduced-motion, and the warning render contract. Co-Authored-By: Claude <noreply@anthropic.com>
…d entry animation Closes accesslayerorg#313, accesslayerorg#301, accesslayerorg#300. - accesslayerorg#313: overlay-text guardrail for forced-colors mode. The volume24h pill on CreatorCard now carries .creator-card-overlay-text; the global stylesheet pins that class to Canvas / CanvasText / ButtonBorder when (forced-colors: active) so the text stays legible over the image overlay in Windows High Contrast. Non-overlay text is not touched — the rule is scoped to .creator-card-overlay-text. - accesslayerorg#301: new stale-data detection helper + hook + warning component. src/utils/staleData.utils.ts (isStale, formatStaleAge) + src/hooks/ useStaleData.ts (with periodic re-eval scheduling and single-fire onStale per stale transition that resets when the data becomes fresh again) + src/components/common/StaleDataWarning.tsx (subtle amber pill, role=status / aria-live=polite). Wired into LandingPage with a 60s threshold; onStale triggers handleRetryCreatorFetch as a background refresh that resets the baseline. - accesslayerorg#300: card-entry animation helper. src/utils/cardEntryAnimation.utils exports creatorCardEntryStyle(index) which returns a CSS variable (--creator-card-entry-delay) capped at 360ms so the last card never feels sluggish. prefers-reduced-motion no-ops to 0ms; @media query in index.css disables the animation entirely under reduced motion. Pointer events stay enabled the whole time so cards are interactive almost immediately. Wired into LandingPage's success-path creator grid. Tests (18 new, all passing): - src/utils/__tests__/cardEntryAnimation.utils.test.ts (7). - src/utils/__tests__/staleData.utils.test.ts (9). - src/components/common/__tests__/StaleDataWarning.test.tsx (5). Repo verification: - pnpm test 145/146 (1 pre-existing CreatorInitialsAvatar failure disclosed below — same bug we've seen in the kalveen / precious accesslayer PRs). - pnpm lint clean - pnpm build clean Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@Mawuli-tech Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Chucks1093
pushed a commit
that referenced
this pull request
May 28, 2026
…with drag-to-dismiss Closes #315, #314. - #315 CreatorBio: gains collapsible + collapsedMaxLines + collapseThresholdChars props. When enabled on the profile variant and the bio is long enough (default >200 chars), the paragraph renders clamped with a focusable Show more / Show less toggle that carries aria-expanded + aria-controls so screen readers know the bio's collapsed state. Short bios are unaffected (no toggle, no clamp). The card variant ignores collapsible since it already clamps via maxLines. Wired into CreatorProfileHeader where the profile bio actually renders. - #314 BottomSheet: new mobile-first primitive built on Radix Dialog for the focus trap / role / Escape handling. Adds drag-to-dismiss via native pointer events: * The visual handle (BottomSheetHandle) registers itself with the sheet's content surface so a gesture that starts on the handle is always treated as 'grabbing the sheet'. * Otherwise the gesture is captured only when no inner scroller is engaged (walks up from the target, bails if any ancestor has scrollTop>0) — so a downward swipe on scrollable content scrolls instead of dismissing. * Dragging past dismissThresholdPx (default 96) dismisses by dispatching Escape so Radix's onOpenChange(false) pipeline runs. * Short / upward drags snap back via a brief transform reset. * The default close button always works as an alternative; pass enableDrag=false to make it the only path. Tests (12 new, all passing): - src/components/common/__tests__/CreatorBio.test.tsx (6 new cases): no toggle for short bio, no engagement on card variant, clamps + Show more wiring, toggles to Show less + removes clamp, custom collapseThresholdChars, custom collapsedMaxLines. - src/components/ui/bottom-sheet.test.tsx (6 cases): render + handle + close button, close-button dismissal, drag past threshold dismisses, short drag does not, upward drag clamps + never dismisses, enableDrag=false leaves close button as only path. Repo verification: - pnpm test 129/130 (1 pre-existing CreatorInitialsAvatar failure, same one disclosed in #310 / #311 / #324). - pnpm lint clean - pnpm build clean Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR closes #313, #301, #300 — three adjacent UX/accessibility issues on the creator marketplace. Batched in one PR because they share consumers (the LandingPage + the CreatorCard) and the helpers reinforce each other.
closes #313
closes #301
closes #300
What's in this PR
#313 — High-contrast guardrail for overlay text
The
volume24hpill onCreatorCardrenders text over the image overlay; the existingbg-slate-950/75+text-white/90lose contrast under Windows High Contrast /(forced-colors: active)..creator-card-overlay-textclass to that pill.src/index.css, a@media (forced-colors: active)block pins the class toCanvas/CanvasText/ButtonBorderand drops thebackdrop-filter. These are the system tokens browsers guarantee meet the active high-contrast theme's contrast ratio.forced-colorssystem tokens) rather than hardcoded colours.#301 — Stale-creator-data detection + warning
src/utils/staleData.utils.ts—isStale(lastFetchedAt, thresholdMs)returns{ stale, ageMs, msUntilStale }. Handles nullish input (always stale), clock skew (clamps to 0), non-positive thresholds (always stale).formatStaleAge(ageMs)— "Just updated" / "Updated Ns ago" / "Updated N min/hr/days ago"; safe forInfinityand negative inputs.src/hooks/useStaleData.ts— wraps the helper with periodic re-evaluation scheduled at the exact moment the threshold expires (no polling) and firesonStaleexactly once per stale transition (fresh → stale → fresh resets the latch so the next stale transition fires again).src/components/common/StaleDataWarning.tsx— subtle amber pill,role="status" aria-live="polite", returnsnullwhen not stale so callers can render it unconditionally.LandingPagewith a 60-second threshold;onStalefires the existinghandleRetryCreatorFetchto trigger a background refresh that resets the baseline (creatorsFetchedAt) and clears the warning automatically.#300 — Staggered card-entry animation
src/utils/cardEntryAnimation.utils.ts—creatorCardEntryStyle(index, options)returns astyleobject with a--creator-card-entry-delayCSS variable. Defaults:stepMs = 40,maxDelayMs = 360(so even with 100+ cards the last one isn't sluggish). Respectsprefers-reduced-motion(and accepts an explicit override for tests). Exports the matchingCREATOR_CARD_ENTRY_CLASS = 'creator-card-entry'.src/index.cssdefines thecreator-card-entrykeyframes (220ms ease-out, opacity 0→1 + 8px translateY) reading the delay variable. A@media (prefers-reduced-motion: reduce)rule disables the animation entirely.LandingPage's success-path creator grid; eachCreatorCardis wrapped in a<div>carrying the class + style.Tests
18 new vitest cases, all passing in the new files (
pnpm testreports 145/146 — see Disclosures below for the one pre-existing failure):src/utils/__tests__/cardEntryAnimation.utils.test.ts(7): exported class name, 0ms first card, stagger between consecutive cards,maxDelayMscap,prefers-reduced-motionshort-circuit, explicitdisabled, negative-index no-op.src/utils/__tests__/staleData.utils.test.ts(9): nullish = stale + infinite age, fresh within window, boundary, clock-skew clamp, non-positive threshold, default threshold, age formatter coverage, fallback for non-finite / negative inputs.src/components/common/__tests__/StaleDataWarning.test.tsx(5): renders null when fresh, standard copy when stale, age suffix,role="status"+aria-live="polite", custom message override.src/hooks/__tests__/useStaleData.test.tscases also pass — the hook is fully covered by the seven cases there.)Disclosures
src/components/common/__tests__/CreatorInitialsAvatar.test.tsxfails onmain— the test queriesgetByLabelText('Alex Rivers initials avatar')but the component renders the initials witharia-hidden="true". Reproducible on unmodified upstreammain; the bug was introduced by the same combination ofe92b472/0213bcdthat the kalveen feat(a11y/ui): main landmark, price-refresh state, no-script fallback, handle norm (#306/#305/#299/#298) #310 and precious feat(a11y/ui): profile header skeleton, accessible metric tooltips, bio clamp, onboarding placeholders (#291/#290/#282/#273) #311 accesslayer PRs already disclosed. Not touched here.handleRetryCreatorFetchrather than introducing a separate refresh code path. That's enough to satisfy the acceptance criterion ("a background refresh clears the warning once fresh data arrives") because a successful refetch setscreatorsFetchedAtagain, which resets the staleness baseline and the hook'shasFiredForCurrentEpochlatch. A dedicated debounced background refresher could land later if the team wants to decouple it from the user-initiated retry flow.