A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.
1import React from 'react';
2import { AtProtoRecord } from '../core/AtProtoRecord';
3import { BlueskyProfileRenderer } from '../renderers/BlueskyProfileRenderer';
4import type { ProfileRecord } from '../types/bluesky';
5import { useBlob } from '../hooks/useBlob';
6import { getAvatarCid } from '../utils/profile';
7import { useDidResolution } from '../hooks/useDidResolution';
8import { formatDidForLabel } from '../utils/at-uri';
9
10/**
11 * Props used to render a Bluesky actor profile record.
12 */
13export interface BlueskyProfileProps {
14 /**
15 * DID of the target actor whose profile should be loaded.
16 */
17 did: string;
18 /**
19 * Record key within the profile collection. Typically `'self'`.
20 */
21 rkey?: string;
22 /**
23 * Optional renderer override for custom presentation.
24 */
25 renderer?: React.ComponentType<BlueskyProfileRendererInjectedProps>;
26 /**
27 * Fallback node shown before a request begins yielding data.
28 */
29 fallback?: React.ReactNode;
30 /**
31 * Loading indicator shown during in-flight fetches.
32 */
33 loadingIndicator?: React.ReactNode;
34 /**
35 * Pre-resolved handle to display when available externally.
36 */
37 handle?: string;
38 /**
39 * Preferred color scheme forwarded to renderer implementations.
40 */
41 colorScheme?: 'light' | 'dark' | 'system';
42}
43
44/**
45 * Props injected into custom profile renderer implementations.
46 */
47export type BlueskyProfileRendererInjectedProps = {
48 /**
49 * Loaded profile record value.
50 */
51 record: ProfileRecord;
52 /**
53 * Indicates whether the record is currently being fetched.
54 */
55 loading: boolean;
56 /**
57 * Any error encountered while fetching the profile.
58 */
59 error?: Error;
60 /**
61 * DID associated with the profile.
62 */
63 did: string;
64 /**
65 * Human-readable handle for the DID, when known.
66 */
67 handle?: string;
68 /**
69 * Blob URL for the user's avatar, when available.
70 */
71 avatarUrl?: string;
72 /**
73 * Preferred color scheme for theming downstream components.
74 */
75 colorScheme?: 'light' | 'dark' | 'system';
76};
77
78/** NSID for the canonical Bluesky profile collection. */
79export const BLUESKY_PROFILE_COLLECTION = 'app.bsky.actor.profile';
80
81/**
82 * Fetches and renders a Bluesky actor profile, optionally injecting custom presentation
83 * and providing avatar resolution support.
84 *
85 * @param did - DID whose profile record should be fetched.
86 * @param rkey - Record key within the profile collection (default `'self'`).
87 * @param renderer - Optional component override for custom rendering.
88 * @param fallback - Node rendered prior to loading state initialization.
89 * @param loadingIndicator - Node rendered while the profile request is in-flight.
90 * @param handle - Optional pre-resolved handle to display.
91 * @param colorScheme - Preferred color scheme forwarded to the renderer.
92 * @returns A rendered profile component with loading/error states handled.
93 */
94export const BlueskyProfile: React.FC<BlueskyProfileProps> = ({ did: handleOrDid, rkey = 'self', renderer, fallback, loadingIndicator, handle, colorScheme }) => {
95 const Component: React.ComponentType<BlueskyProfileRendererInjectedProps> = renderer ?? ((props) => <BlueskyProfileRenderer {...props} />);
96 const { did, handle: resolvedHandle } = useDidResolution(handleOrDid);
97 const repoIdentifier = did ?? handleOrDid;
98 const effectiveHandle = handle ?? resolvedHandle ?? (handleOrDid.startsWith('did:') ? formatDidForLabel(repoIdentifier) : handleOrDid);
99
100 const Wrapped: React.FC<{ record: ProfileRecord; loading: boolean; error?: Error }> = (props) => {
101 const avatarCid = getAvatarCid(props.record);
102 const { url: avatarUrl } = useBlob(repoIdentifier, avatarCid);
103 return <Component {...props} did={repoIdentifier} handle={effectiveHandle} avatarUrl={avatarUrl} colorScheme={colorScheme} />;
104 };
105 return (
106 <AtProtoRecord<ProfileRecord>
107 did={repoIdentifier}
108 collection={BLUESKY_PROFILE_COLLECTION}
109 rkey={rkey}
110 renderer={Wrapped}
111 fallback={fallback}
112 loadingIndicator={loadingIndicator}
113 />
114 );
115};
116
117export default BlueskyProfile;