A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.
1import React, { useMemo } from 'react'; 2import { AtProtoRecord } from '../core/AtProtoRecord'; 3import { LeafletDocumentRenderer, type LeafletDocumentRendererProps } from '../renderers/LeafletDocumentRenderer'; 4import type { LeafletDocumentRecord, LeafletPublicationRecord } from '../types/leaflet'; 5import type { ColorSchemePreference } from '../hooks/useColorScheme'; 6import { parseAtUri, toBlueskyPostUrl, leafletRkeyUrl, normalizeLeafletBasePath } from '../utils/at-uri'; 7import { useAtProtoRecord } from '../hooks/useAtProtoRecord'; 8 9/** 10 * Props for rendering a Leaflet document record. 11 */ 12export interface LeafletDocumentProps { 13 /** 14 * DID of the Leaflet publisher. 15 */ 16 did: string; 17 /** 18 * Record key of the document within the Leaflet collection. 19 */ 20 rkey: string; 21 /** 22 * Optional custom renderer for advanced layouts. 23 */ 24 renderer?: React.ComponentType<LeafletDocumentRendererInjectedProps>; 25 /** 26 * React node rendered before data begins loading. 27 */ 28 fallback?: React.ReactNode; 29 /** 30 * Indicator rendered while data is being fetched from the PDS. 31 */ 32 loadingIndicator?: React.ReactNode; 33 /** 34 * Preferred color scheme to forward to the renderer. 35 */ 36 colorScheme?: ColorSchemePreference; 37} 38 39/** 40 * Props provided to renderer overrides for Leaflet documents. 41 */ 42export type LeafletDocumentRendererInjectedProps = LeafletDocumentRendererProps; 43 44/** NSID for Leaflet document records. */ 45export const LEAFLET_DOCUMENT_COLLECTION = 'pub.leaflet.document'; 46 47/** 48 * Loads a Leaflet document along with its associated publication record and renders it 49 * using the provided or default renderer. 50 * 51 * @param did - DID of the Leaflet publisher. 52 * @param rkey - Record key of the Leaflet document. 53 * @param renderer - Optional renderer override used in place of the default. 54 * @param fallback - Node rendered before loading begins. 55 * @param loadingIndicator - Node rendered while the document or publication records are loading. 56 * @param colorScheme - Preferred color scheme forwarded to the renderer. 57 * @returns A JSX subtree that renders a Leaflet document with contextual metadata. 58 */ 59export const LeafletDocument: React.FC<LeafletDocumentProps> = ({ did, rkey, renderer, fallback, loadingIndicator, colorScheme }) => { 60 const Comp: React.ComponentType<LeafletDocumentRendererInjectedProps> = renderer ?? ((props) => <LeafletDocumentRenderer {...props} />); 61 62 const Wrapped: React.FC<{ record: LeafletDocumentRecord; loading: boolean; error?: Error }> = (props) => { 63 const publicationUri = useMemo(() => parseAtUri(props.record.publication), [props.record.publication]); 64 const { record: publicationRecord } = useAtProtoRecord<LeafletPublicationRecord>({ 65 did: publicationUri?.did, 66 collection: publicationUri?.collection ?? 'pub.leaflet.publication', 67 rkey: publicationUri?.rkey ?? '' 68 }); 69 const publicationBaseUrl = normalizeLeafletBasePath(publicationRecord?.base_path); 70 const canonicalUrl = resolveCanonicalUrl(props.record, did, rkey, publicationRecord?.base_path); 71 return ( 72 <Comp 73 {...props} 74 colorScheme={colorScheme} 75 did={did} 76 rkey={rkey} 77 canonicalUrl={canonicalUrl} 78 publicationBaseUrl={publicationBaseUrl} 79 publicationRecord={publicationRecord} 80 /> 81 ); 82 }; 83 84 return ( 85 <AtProtoRecord<LeafletDocumentRecord> 86 did={did} 87 collection={LEAFLET_DOCUMENT_COLLECTION} 88 rkey={rkey} 89 renderer={Wrapped} 90 fallback={fallback} 91 loadingIndicator={loadingIndicator} 92 /> 93 ); 94}; 95 96/** 97 * Determines the best canonical URL to expose for a Leaflet document. 98 * 99 * @param record - Leaflet document record under review. 100 * @param did - Publisher DID. 101 * @param rkey - Record key for the document. 102 * @param publicationBasePath - Optional base path configured by the publication. 103 * @returns A URL to use for canonical links. 104 */ 105function resolveCanonicalUrl(record: LeafletDocumentRecord, did: string, rkey: string, publicationBasePath?: string): string { 106 const publicationUrl = leafletRkeyUrl(publicationBasePath, rkey); 107 if (publicationUrl) return publicationUrl; 108 const postUri = record.postRef?.uri; 109 if (postUri) { 110 const parsed = parseAtUri(postUri); 111 const href = parsed ? toBlueskyPostUrl(parsed) : undefined; 112 if (href) return href; 113 } 114 return `at://${encodeURIComponent(did)}/${LEAFLET_DOCUMENT_COLLECTION}/${encodeURIComponent(rkey)}`; 115} 116 117export default LeafletDocument;