-
-
Notifications
You must be signed in to change notification settings - Fork 40
web-next: Profile hover cards for avatars, names, and mentions #276
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
e78bb58
Add HoverCard solid-ui primitive
dahlia e8bc77a
Add ActorHoverCard scaffolding
dahlia e0133f3
Wire ActorHoverCard onto avatar and name surfaces
dahlia 62a9f1c
Add mention hover cards + fix avatar trigger anchor rect
dahlia 6ef2179
Translate hover-card empty state in all locales
dahlia cf8e9b7
Cover article detail page and SmallProfileCard bio
dahlia 533feac
Support h-card mentions in hover-card hook
dahlia aa3d9ac
Fall back to username when display name is empty
dahlia da889f9
Add rel="noopener noreferrer" to external profile links
dahlia 10d647e
Add hover card to NoteHeader handle span
dahlia 747b1c4
Cover handle spans in the remaining post-card headers
dahlia e4dff98
Deduplicate follow-count rendering in ActorPreviewCard
dahlia b3a4619
Derive mention username from the URL path first
dahlia 3fc4f9c
Add actorByUrl GraphQL query
dahlia 761ee47
Resolve mention hover cards by URL instead of handle
dahlia 301b400
Use InternalLink in ActorPreviewCard for SPA navigation
dahlia d13b786
Disable popover auto-focus on mention hover preview
dahlia 32a1f23
Derive username from name only via innerHTML in SmallProfileCard
dahlia 20b4195
Drop window.setTimeout prefix in mentionHoverCards
dahlia 7648b97
Merge name and handle hover triggers in post headers
dahlia 7fda5ff
Type mention hover-card timers via ReturnType
dahlia e3ae3ce
Batch mention hover-card state transitions
dahlia fbfe8d6
Resolve protocol-relative mention hrefs
dahlia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { createSignal, type JSX, Show } from "solid-js"; | ||
| import { | ||
| HoverCard, | ||
| HoverCardContent, | ||
| HoverCardTrigger, | ||
| } from "~/components/ui/hover-card.tsx"; | ||
| import { cn } from "~/lib/utils.ts"; | ||
| import { ActorHoverCardLoader } from "./ActorHoverCardLoader.tsx"; | ||
|
|
||
| export interface ActorHoverCardProps { | ||
| /** Canonical fediverse handle (e.g., `@user@host`). */ | ||
| handle: string; | ||
| /** | ||
| * Extra classes for the trigger wrapper. Append `shrink-0` when wrapping a | ||
| * fixed-size avatar in a flex container so the wrapper itself does not | ||
| * collapse. | ||
| */ | ||
| class?: string; | ||
| children: JSX.Element; | ||
| } | ||
|
|
||
| export function ActorHoverCard(props: ActorHoverCardProps) { | ||
| const [open, setOpen] = createSignal(false); | ||
| return ( | ||
| <HoverCard open={open()} onOpenChange={setOpen}> | ||
| <HoverCardTrigger | ||
| as="span" | ||
| class={cn("inline-flex self-start", props.class)} | ||
| role="presentation" | ||
| tabIndex={-1} | ||
| > | ||
| {props.children} | ||
| </HoverCardTrigger> | ||
| <HoverCardContent> | ||
| <Show when={open()}> | ||
| <ActorHoverCardLoader handle={props.handle} /> | ||
| </Show> | ||
| </HoverCardContent> | ||
| </HoverCard> | ||
| ); | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import { graphql } from "relay-runtime"; | ||
| import { ErrorBoundary, type JSX, Show } from "solid-js"; | ||
| import { | ||
| createPreloadedQuery, | ||
| loadQuery, | ||
| useRelayEnvironment, | ||
| } from "solid-relay"; | ||
| import { useLingui } from "~/lib/i18n/macro.d.ts"; | ||
| import type { ActorHoverCardLoaderByHandleQuery } from "./__generated__/ActorHoverCardLoaderByHandleQuery.graphql.ts"; | ||
| import type { ActorHoverCardLoaderByUrlQuery } from "./__generated__/ActorHoverCardLoaderByUrlQuery.graphql.ts"; | ||
| import { ActorPreviewCard } from "./ActorPreviewCard.tsx"; | ||
| import { ActorPreviewSkeleton } from "./ActorPreviewSkeleton.tsx"; | ||
|
|
||
| const actorHoverCardLoaderByHandleQuery = graphql` | ||
| query ActorHoverCardLoaderByHandleQuery($handle: String!) { | ||
| actorByHandle(handle: $handle, allowLocalHandle: true) { | ||
| ...ActorPreviewCard_actor | ||
| } | ||
| } | ||
| `; | ||
|
|
||
| const actorHoverCardLoaderByUrlQuery = graphql` | ||
| query ActorHoverCardLoaderByUrlQuery($url: URL!) { | ||
| actorByUrl(url: $url) { | ||
| ...ActorPreviewCard_actor | ||
| } | ||
| } | ||
| `; | ||
|
|
||
| function Unavailable() { | ||
| const { t } = useLingui(); | ||
| return ( | ||
| <div class="p-4 text-sm text-muted-foreground"> | ||
| {t`Could not load profile.`} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| function withFallbacks(loaded: () => JSX.Element) { | ||
| return ( | ||
| <ErrorBoundary fallback={() => <Unavailable />}> | ||
| {loaded()} | ||
| </ErrorBoundary> | ||
| ); | ||
| } | ||
|
|
||
| export interface ActorHoverCardLoaderProps { | ||
| handle: string; | ||
| } | ||
|
|
||
| export function ActorHoverCardLoader(props: ActorHoverCardLoaderProps) { | ||
| const env = useRelayEnvironment(); | ||
| const data = createPreloadedQuery<ActorHoverCardLoaderByHandleQuery>( | ||
| actorHoverCardLoaderByHandleQuery, | ||
| () => | ||
| loadQuery(env(), actorHoverCardLoaderByHandleQuery, { | ||
| handle: props.handle, | ||
| }), | ||
| ); | ||
|
|
||
| return withFallbacks(() => ( | ||
| <Show when={data()} fallback={<ActorPreviewSkeleton />}> | ||
| {(loaded) => ( | ||
| <Show when={loaded().actorByHandle} fallback={<Unavailable />}> | ||
| {(actor) => <ActorPreviewCard $actor={actor()} />} | ||
| </Show> | ||
| )} | ||
| </Show> | ||
| )); | ||
| } | ||
|
|
||
| export interface ActorHoverCardLoaderByUrlProps { | ||
| url: string; | ||
| } | ||
|
|
||
| export function ActorHoverCardLoaderByUrl( | ||
| props: ActorHoverCardLoaderByUrlProps, | ||
| ) { | ||
| const env = useRelayEnvironment(); | ||
| const data = createPreloadedQuery<ActorHoverCardLoaderByUrlQuery>( | ||
| actorHoverCardLoaderByUrlQuery, | ||
| () => loadQuery(env(), actorHoverCardLoaderByUrlQuery, { url: props.url }), | ||
| ); | ||
|
|
||
| return withFallbacks(() => ( | ||
| <Show when={data()} fallback={<ActorPreviewSkeleton />}> | ||
| {(loaded) => ( | ||
| <Show when={loaded().actorByUrl} fallback={<Unavailable />}> | ||
| {(actor) => <ActorPreviewCard $actor={actor()} />} | ||
| </Show> | ||
| )} | ||
| </Show> | ||
| )); | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.