A React component library for rendering common AT Protocol records for applications such as Bluesky and Leaflet.
1import React from "react"; 2import type { ShTangledString } from "@atcute/tangled"; 3import { useAtProto } from "../providers/AtProtoProvider"; 4 5export type TangledStringRecord = ShTangledString.Main; 6 7export interface TangledStringRendererProps { 8 record: TangledStringRecord; 9 error?: Error; 10 loading: boolean; 11 did: string; 12 rkey: string; 13 canonicalUrl?: string; 14} 15 16export const TangledStringRenderer: React.FC<TangledStringRendererProps> = ({ 17 record, 18 error, 19 loading, 20 did, 21 rkey, 22 canonicalUrl, 23}) => { 24 const { tangledBaseUrl } = useAtProto(); 25 26 if (error) 27 return ( 28 <div style={{ padding: 8, color: "crimson" }}> 29 Failed to load snippet. 30 </div> 31 ); 32 if (loading && !record) return <div style={{ padding: 8 }}>Loading</div>; 33 34 const viewUrl = 35 canonicalUrl ?? 36 `${tangledBaseUrl}/strings/${did}/${encodeURIComponent(rkey)}`; 37 const timestamp = new Date(record.createdAt).toLocaleString(undefined, { 38 dateStyle: "medium", 39 timeStyle: "short", 40 }); 41 return ( 42 <div style={{ ...base.container, background: `var(--atproto-color-bg-elevated)`, borderWidth: "1px", borderStyle: "solid", borderColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}> 43 <div style={{ ...base.header, background: `var(--atproto-color-bg-elevated)`, borderBottomWidth: "1px", borderBottomStyle: "solid", borderBottomColor: `var(--atproto-color-border)` }}> 44 <strong style={{ ...base.filename, color: `var(--atproto-color-text)` }}> 45 {record.filename} 46 </strong> 47 <div style={{ ...base.headerRight }}> 48 <time 49 style={{ ...base.timestamp, color: `var(--atproto-color-text-secondary)` }} 50 dateTime={record.createdAt} 51 > 52 {timestamp} 53 </time> 54 <a 55 href={viewUrl} 56 target="_blank" 57 rel="noopener noreferrer" 58 style={{ ...base.headerLink, color: `var(--atproto-color-link)` }} 59 > 60 View on Tangled 61 </a> 62 </div> 63 </div> 64 {record.description && ( 65 <div style={{ ...base.description, background: `var(--atproto-color-bg)`, borderTopWidth: "1px", borderTopStyle: "solid", borderTopColor: `var(--atproto-color-border)`, borderBottomWidth: "1px", borderBottomStyle: "solid", borderBottomColor: `var(--atproto-color-border)`, color: `var(--atproto-color-text)` }}> 66 {record.description} 67 </div> 68 )} 69 <pre style={{ ...base.codeBlock, background: `var(--atproto-color-bg)`, color: `var(--atproto-color-text)`, borderTopWidth: "1px", borderTopStyle: "solid", borderTopColor: `var(--atproto-color-border)` }}> 70 <code>{record.contents}</code> 71 </pre> 72 </div> 73 ); 74}; 75 76const base: Record<string, React.CSSProperties> = { 77 container: { 78 fontFamily: "system-ui, sans-serif", 79 borderRadius: 6, 80 overflow: "hidden", 81 transition: 82 "background-color 180ms ease, border-color 180ms ease, color 180ms ease, box-shadow 180ms ease", 83 width: "100%", 84 }, 85 header: { 86 padding: "10px 16px", 87 display: "flex", 88 justifyContent: "space-between", 89 alignItems: "center", 90 gap: 12, 91 }, 92 headerRight: { 93 display: "flex", 94 alignItems: "center", 95 gap: 12, 96 flexWrap: "wrap", 97 justifyContent: "flex-end", 98 }, 99 filename: { 100 fontFamily: 101 'SFMono-Regular, ui-monospace, Menlo, Monaco, "Courier New", monospace', 102 fontSize: 13, 103 wordBreak: "break-all", 104 }, 105 timestamp: { 106 fontSize: 12, 107 }, 108 headerLink: { 109 fontSize: 12, 110 fontWeight: 600, 111 textDecoration: "none", 112 }, 113 description: { 114 padding: "10px 16px", 115 fontSize: 13, 116 borderTopWidth: "1px", 117 borderTopStyle: "solid", 118 borderTopColor: "transparent", 119 }, 120 codeBlock: { 121 margin: 0, 122 padding: "16px", 123 fontSize: 13, 124 overflowX: "auto", 125 borderTopWidth: "1px", 126 borderTopStyle: "solid", 127 borderTopColor: "transparent", 128 fontFamily: 129 'SFMono-Regular, ui-monospace, Menlo, Monaco, "Courier New", monospace', 130 }, 131}; 132 133export default TangledStringRenderer;