feat(a11y/ui): main landmark, price-refresh state, no-script fallback, handle norm (#306/#305/#299/#298)#310
Merged
Chucks1093 merged 1 commit intoMay 28, 2026
Conversation
…handle normalisation Closes accesslayerorg#306, accesslayerorg#305, accesslayerorg#299, accesslayerorg#298. - accesslayerorg#306: Demoted the LandingPage's outer <main> to a decorative <div> and scoped a real <main aria-label="Creator marketplace"> to the marketplace-and-below content. The page header (logo, hero copy) is now a sibling <header> rather than living inside <main>, so screen-reader landmark navigation jumps directly to the primary content. - accesslayerorg#305: Added isPriceRefreshing prop to CreatorCard. When true the price badge keeps its visible value but applies a muted style, renders a small spinner, sets aria-busy, and includes an sr-only "Refreshing price" hint. min-width + tabular-nums on the value preserve layout so the badge does not collapse or shift. LandingPage drives the flag from a simple 30-second simulator gated on prefers-reduced-motion until the real price-refresh pipeline lands. - accesslayerorg#299: Added a styled <noscript> block in index.html with inline CSS so it renders even with external CSS blocked. The Vite SPA leaves #root empty without JS, so this is the one place a no-script user can see context; one block covers every interactive surface (browse, search, wallet, buy/sell, prices, tx status). - accesslayerorg#298: New src/utils/handleDisplay.utils.ts exports formatCreatorHandle — trim, strip any leading @, lowercase, prepend one @. Applied in CreatorCard (instructorId + socialHandle, both display and CardMetaRow) and CreatorProfileHeader (subtitle + share title). Raw props are preserved for any equality / URL construction the caller does. 7 vitest cases cover casing, leading @, whitespace, nullish, lone @, and idempotence. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
@chigozirim007 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 #306, #305, #299, #298 — all four issues live in the same area of the marketplace UI (creator card / profile header / landing layout), so batching them in one PR is the natural shape.
closes #306
closes #305
closes #299
closes #298
What's in this PR
#306 — Main landmark for the creator marketplace
The previous structure had the entire LandingPage wrapped in
<main>, including the brand banner. That makes landmark navigation land on the page chrome instead of the content.<main>to a decorative<div>.<header>(<MarketplaceSection as="header">) — now a sibling of<main>, not a child of it.<main id="creator-marketplace-main" aria-label="Creator marketplace">that scopes to the search-and-filter bar, creator grid, profile pattern, and transaction timeline.<main>per page; verified bygrep -rn "<main".#305 — Loading state for the creator card price badge
isPriceRefreshing?: booleanprop onCreatorCard.aria-busy="true", and includes ansr-only"Refreshing price" hint.min-w-[6.5rem]container withtabular-nums, so the badge does not collapse or shift width during a refresh.LandingPagedrives the flag with a 30-second simulator (setInterval+setTimeout) gated onprefers-reduced-motion— so we don't surface a non-essential animation to users who opted out. Real pipeline (WebSocket / polling hook) lands as a follow-up; this PR is the UI half of the handshake.#299 — No-script fallback for key UI actions
The marketplace is a Vite SPA — without JavaScript the
#rootdiv stays empty and every interactive surface is unavailable.<noscript>block toindex.html. Inline CSS so it renders even when external CSS is blocked; tone matches the rest of the app (gradient bg, amber eyebrow, jakarta-feel sans-serif fallback).role="alert"so assistive tech announces the fallback if scripting later fails to load.#298 — Helper for creator handle display casing
src/utils/handleDisplay.utils.tsexportsformatCreatorHandle(raw):@, lowercase, prepend one@.""for empty / whitespace / nullish / lone-@input so callers can choose a placeholder.CreatorCard.tsx— instructorId display (@creatorfallback), socialHandle display row, and the bottom "Handle"CardMetaRow(value + title).CreatorProfileHeader.tsx— subtitle below name, and the Web Share APItitle(with the raw handle as a safe fallback if the helper returns "").src/utils/__tests__/handleDisplay.utils.test.ts: mixed-case lowercasing, stripping leading@, double-@preservation, whitespace trim, empty/whitespace/nullish, lone@, idempotence, and the no-mutation invariant.Verification
Disclosures (pre-existing on
main, unrelated to this PR)src/components/common/__tests__/CreatorInitialsAvatar.test.tsxfails onmain. The test queries forgetByLabelText('Alex Rivers initials avatar'), but the component renders the initials witharia-hidden="true"instead of anaria-labelon the wrapping element. Reproducible on the unmodified upstreammain(commitse92b472/0213bcdtouched the component and its test independently). I did not touch this file or its test.useAccount,useNetworkMismatch,useTransactionTelemetry) with no existing test scaffolding in the repo. Adding a component test would require mocking all of those, which is more scaffolding than the visual change warrants. The utility (formatCreatorHandle) is covered directly; the refresh-indicator rendering is verified by build + manual review.