Skip to content

feat(a11y/ui): profile header skeleton, accessible metric tooltips, bio clamp, onboarding placeholders (#291/#290/#282/#273)#311

Merged
Chucks1093 merged 1 commit into
accesslayerorg:mainfrom
pre-cious-Igwealor:feat/profile-skeleton-bio-clamp-tooltip-pending
May 28, 2026
Merged

feat(a11y/ui): profile header skeleton, accessible metric tooltips, bio clamp, onboarding placeholders (#291/#290/#282/#273)#311
Chucks1093 merged 1 commit into
accesslayerorg:mainfrom
pre-cious-Igwealor:feat/profile-skeleton-bio-clamp-tooltip-pending

Conversation

@pre-cious-Igwealor
Copy link
Copy Markdown
Contributor

@pre-cious-Igwealor pre-cious-Igwealor commented May 28, 2026

This PR closes #291, #290, #282, #273 — all four are creator-profile presentation/accessibility issues in adjacent components, so one PR keeps the diff coherent.

closes #291
closes #290
closes #282
closes #273

What's in this PR

#273 — Loading skeleton for the creator profile header

  • New CreatorProfileHeaderSkeleton (exported alongside CreatorSkeleton in src/components/common/CreatorSkeleton.tsx).
  • Block dimensions match CreatorProfileHeader's populated layout exactly: avatar size-24 md:size-32 rounded-2xl border-4, name h-9 md:h-12 w-3/4 max-w-md, handle h-6 w-1/2 max-w-xs, and a hidden md:block share-button block — so no visible layout shift when real data resolves.
  • role="status" + sr-only label announces "Loading creator profile" to assistive tech.
  • Reduced-motion is respected via the shared skeletonBlockClass (which already does motion-reduce:); disableShimmer is forwarded for callers that disable shimmer higher up the tree.
  • Wired into LandingPage — when isLoading is true, the header section renders the skeleton instead of the populated CreatorProfileHeader.

#290 — Accessible tooltip trigger for creator metric explanations

The existing Tooltip wraps content in a non-focusable <div> and toggles via group-hover / group-focus-within. That's effectively hover-only — keyboard users can't reach the explanation on a plain label.

  • New AccessibleInfoTrigger component:
    • The trigger is a real <button type="button"> with a metric-specific aria-label ("Explanation for: Audience"), so it's focusable and announced.
    • On focus / hover / click, an absolutely-positioned role="tooltip" element appears and the trigger gets aria-describedby={tooltipId} + aria-expanded={true}.
    • Escape dismisses while open, blur / mouse-leave also dismisses.
    • Trigger keeps a stable visual footprint; tooltip is absolutely positioned and doesn't shift surrounding layout.
  • MiniStatChip accepts an optional explanation prop and renders the trigger when supplied.
  • Applied to the LandingPage's profile-stat chips: Status / Audience / Access.

#282 — Helper for truncating long creator bio text to max display lines

  • CreatorBio gains a maxLines?: number | null prop. Default 3 on the card variant; the profile variant always shows the full bio so the truncation stays purely cosmetic and the full text remains accessible on the creator profile page. Short bios are unaffected.
  • The implementation maps maxLines 1..6 to a fixed Tailwind line-clamp-N class (capping higher values at line-clamp-6 to keep card heights bounded). Fixed class names are used so Tailwind's JIT keeps the utilities in the produced CSS.
  • The full bio is preserved in the title attribute when clamped, so accessibility tooling and browser tooltips can read the unclamped text.
  • Helper lives in src/utils/lineClamp.utils.ts rather than in the component file so the component keeps a clean Fast-Refresh boundary (one component per file).

#291 — Placeholder copy for creator profile with pending onboarding

  • New PendingOnboardingPlaceholder component with inline and card variants and bio / links / stats / overview section presets. Centralising the copy keeps tone consistent across every empty-state surface — change once, propagates everywhere.
  • CreatorBio accepts isOnboardingPending. When true and the bio is empty, the fallback swaps to the pending copy ("This creator is still setting up their profile. Bio coming soon.") and the aria-label flips to "Bio pending — onboarding in progress" so screen readers communicate the cause of the empty state.

Verification

$ pnpm install         # ok
$ pnpm test            # 110 passed | 1 failed (pre-existing, see disclosures)
$ pnpm lint            # clean
$ pnpm build           # clean (vite build, 7.27s)

14 new vitest cases, all passing:

  • CreatorBio.test.tsx (added): 11 lineClampClassFor cases (profile, disabled, 1..6, cap), 5 clamp-integration cases (default 3-line, override, null disables, profile ignores, title preserves full text), 3 onboarding-pending cases (fallback swap, default branch, ignored when bio present).
  • AccessibleInfoTrigger.test.tsx (new): focusable label, focus reveals tooltip + aria-describedby + aria-expanded, blur hides, Escape dismisses, click toggles back off, default-label fallback.

Disclosures (pre-existing on main, unrelated to this PR)

  1. src/components/common/__tests__/CreatorInitialsAvatar.test.tsx fails on main. The test calls getByLabelText('Alex Rivers initials avatar') but the component renders the initials with aria-hidden="true" (no matching aria-label). Reproducible on unmodified upstream main; the failing component / test were modified independently of this PR (commits e92b472 / 0213bcd). Not touched here.
  2. PendingOnboardingPlaceholder ships as a new component but is only consumed through CreatorBio's isOnboardingPending flag in this PR. Wiring it into CreatorProfileInfoGrid and the social links list would require those components to know about onboarding state, which they currently don't — that surface area is a logical follow-up, not part of the four issues' scope.

…lamp, pending-onboarding placeholders

Closes accesslayerorg#291, accesslayerorg#290, accesslayerorg#282, accesslayerorg#273.

- accesslayerorg#273: New CreatorProfileHeaderSkeleton (in CreatorSkeleton.tsx) renders
  avatar (size-24 / md:size-32), name (h-9 / md:h-12), and handle (h-6)
  block placeholders that align with CreatorProfileHeader's populated
  layout so no layout shift on resolve. role="status" + sr-only label
  announce the loading state; reduced-motion is respected via the shared
  skeleton block class. Wired into LandingPage with isLoading.
- accesslayerorg#290: New AccessibleInfoTrigger component — a focusable <button> with
  aria-describedby + aria-expanded + Escape-to-dismiss. Replaces the
  hover-only pattern that left keyboard users stranded. MiniStatChip now
  accepts an optional  prop; the LandingPage's profile-stat
  chips (Status / Audience / Access) carry metric explanations.
- accesslayerorg#282: CreatorBio gains a maxLines prop (default 3 on card, ignored on
  profile). Implementation lives in src/utils/lineClamp.utils so the
  component file stays Fast-Refresh-friendly. Full bio is kept in the
  title attribute when clamped so screen readers and tooltips read the
  unclamped text; the profile page is unaffected.
- accesslayerorg#291: New PendingOnboardingPlaceholder component for blank profile
  sections during onboarding (bio / links / stats / overview variants).
  CreatorBio also gains isOnboardingPending that swaps its empty-bio
  fallback for the pending copy.

Tests (14 new, all passing):
- CreatorBio.test.tsx: 11 lineClampClassFor cases (profile, disabled,
  1..6, cap), 5 clamp-integration cases, 3 onboarding-pending cases.
- AccessibleInfoTrigger.test.tsx: focusable label, focus reveals tooltip
  with aria-describedby, blur hides, Escape dismisses, click toggles,
  default label fallback.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented May 28, 2026

@pre-cious-Igwealor 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! 🚀

Learn more about application limits

@Chucks1093 Chucks1093 merged commit 8c2d5b8 into accesslayerorg:main May 28, 2026
1 check passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants