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 { TangledStringRenderer } from "../renderers/TangledStringRenderer";
4import type { TangledStringRecord } from "../types/tangled";
5import { useAtProto } from "../providers/AtProtoProvider";
6
7/**
8 * Props for rendering Tangled String records.
9 */
10export interface TangledStringProps {
11 /** DID of the repository that stores the string record. */
12 did: string;
13 /** Record key within the `sh.tangled.string` collection. */
14 rkey: string;
15 /** Prefetched Tangled String record. When provided, skips fetching from the network. */
16 record?: TangledStringRecord;
17 /** Optional renderer override for custom presentation. */
18 renderer?: React.ComponentType<TangledStringRendererInjectedProps>;
19 /** Fallback node displayed before loading begins. */
20 fallback?: React.ReactNode;
21 /** Indicator node shown while data is loading. */
22 loadingIndicator?: React.ReactNode;
23 /** Preferred color scheme for theming. */
24 colorScheme?: "light" | "dark" | "system";
25}
26
27/**
28 * Values injected into custom Tangled String renderer implementations.
29 */
30export type TangledStringRendererInjectedProps = {
31 /** Loaded Tangled String record value. */
32 record: TangledStringRecord;
33 /** Indicates whether the record is currently loading. */
34 loading: boolean;
35 /** Fetch error, if any. */
36 error?: Error;
37 /** Preferred color scheme for downstream components. */
38 colorScheme?: "light" | "dark" | "system";
39 /** DID associated with the record. */
40 did: string;
41 /** Record key for the string. */
42 rkey: string;
43 /** Canonical external URL for linking to the string. */
44 canonicalUrl: string;
45};
46
47/** NSID for Tangled String records. */
48export const TANGLED_COLLECTION = "sh.tangled.string";
49
50/**
51 * Resolves a Tangled String record and renders it with optional overrides while computing a canonical link.
52 *
53 * @param did - DID whose Tangled String should be fetched.
54 * @param rkey - Record key within the Tangled String collection.
55 * @param renderer - Optional component override that will receive injected props.
56 * @param fallback - Node rendered before the first load begins.
57 * @param loadingIndicator - Node rendered while the Tangled String is loading.
58 * @param colorScheme - Preferred color scheme for theming the renderer.
59 * @returns A JSX subtree representing the Tangled String record with loading states handled.
60 */
61export const TangledString: React.FC<TangledStringProps> = React.memo(({
62 did,
63 rkey,
64 record,
65 renderer,
66 fallback,
67 loadingIndicator,
68 colorScheme,
69}) => {
70 const { tangledBaseUrl } = useAtProto();
71 const Comp: React.ComponentType<TangledStringRendererInjectedProps> =
72 renderer ?? ((props) => <TangledStringRenderer {...props} />);
73 const Wrapped: React.FC<{
74 record: TangledStringRecord;
75 loading: boolean;
76 error?: Error;
77 }> = (props) => (
78 <Comp
79 {...props}
80 colorScheme={colorScheme}
81 did={did}
82 rkey={rkey}
83 canonicalUrl={`${tangledBaseUrl}/strings/${did}/${encodeURIComponent(rkey)}`}
84 />
85 );
86
87 if (record !== undefined) {
88 return (
89 <AtProtoRecord<TangledStringRecord>
90 record={record}
91 renderer={Wrapped}
92 fallback={fallback}
93 loadingIndicator={loadingIndicator}
94 />
95 );
96 }
97
98 return (
99 <AtProtoRecord<TangledStringRecord>
100 did={did}
101 collection={TANGLED_COLLECTION}
102 rkey={rkey}
103 renderer={Wrapped}
104 fallback={fallback}
105 loadingIndicator={loadingIndicator}
106 />
107 );
108});
109
110export default TangledString;