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 { CurrentlyPlayingRenderer } from "../renderers/CurrentlyPlayingRenderer";
4import { useDidResolution } from "../hooks/useDidResolution";
5import type { TealActorStatusRecord } from "../types/teal";
6
7/**
8 * Props for rendering teal.fm currently playing status.
9 */
10export interface CurrentlyPlayingProps {
11 /** DID of the user whose currently playing status to display. */
12 did: string;
13 /** Record key within the `fm.teal.alpha.actor.status` collection (usually "self"). */
14 rkey?: string;
15 /** Prefetched teal.fm status record. When provided, skips fetching from the network. */
16 record?: TealActorStatusRecord;
17 /** Optional renderer override for custom presentation. */
18 renderer?: React.ComponentType<CurrentlyPlayingRendererInjectedProps>;
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 /** Auto-refresh music data and album art every 15 seconds. Defaults to true. */
26 autoRefresh?: boolean;
27}
28
29/**
30 * Values injected into custom currently playing renderer implementations.
31 */
32export type CurrentlyPlayingRendererInjectedProps = {
33 /** Loaded teal.fm status record value. */
34 record: TealActorStatusRecord;
35 /** Indicates whether the record is currently loading. */
36 loading: boolean;
37 /** Fetch error, if any. */
38 error?: Error;
39 /** Preferred color scheme for downstream components. */
40 colorScheme?: "light" | "dark" | "system";
41 /** DID associated with the record. */
42 did: string;
43 /** Record key for the status. */
44 rkey: string;
45 /** Auto-refresh music data and album art every 15 seconds. */
46 autoRefresh?: boolean;
47 /** Label to display. */
48 label?: string;
49 /** Refresh interval in milliseconds. */
50 refreshInterval?: number;
51 /** Handle to display in not listening state */
52 handle?: string;
53};
54
55/** NSID for teal.fm actor status records. */
56export const CURRENTLY_PLAYING_COLLECTION = "fm.teal.alpha.actor.status";
57
58/**
59 * Displays the currently playing track from teal.fm with auto-refresh.
60 *
61 * @param did - DID whose currently playing status should be fetched.
62 * @param rkey - Record key within the teal.fm status collection (defaults to "self").
63 * @param renderer - Optional component override that will receive injected props.
64 * @param fallback - Node rendered before the first load begins.
65 * @param loadingIndicator - Node rendered while the status is loading.
66 * @param colorScheme - Preferred color scheme for theming the renderer.
67 * @param autoRefresh - When true (default), refreshes album art and streaming platform links every 15 seconds.
68 * @returns A JSX subtree representing the currently playing track with loading states handled.
69 */
70export const CurrentlyPlaying: React.FC<CurrentlyPlayingProps> = React.memo(({
71 did,
72 rkey = "self",
73 record,
74 renderer,
75 fallback,
76 loadingIndicator,
77 colorScheme,
78 autoRefresh = true,
79}) => {
80 // Resolve handle from DID
81 const { handle } = useDidResolution(did);
82
83 const Comp: React.ComponentType<CurrentlyPlayingRendererInjectedProps> =
84 renderer ?? ((props) => <CurrentlyPlayingRenderer {...props} />);
85 const Wrapped: React.FC<{
86 record: TealActorStatusRecord;
87 loading: boolean;
88 error?: Error;
89 }> = (props) => (
90 <Comp
91 {...props}
92 colorScheme={colorScheme}
93 did={did}
94 rkey={rkey}
95 autoRefresh={autoRefresh}
96 label="CURRENTLY PLAYING"
97 refreshInterval={15000}
98 handle={handle}
99 />
100 );
101
102 if (record !== undefined) {
103 return (
104 <AtProtoRecord<TealActorStatusRecord>
105 record={record}
106 renderer={Wrapped}
107 fallback={fallback}
108 loadingIndicator={loadingIndicator}
109 />
110 );
111 }
112
113 return (
114 <AtProtoRecord<TealActorStatusRecord>
115 did={did}
116 collection={CURRENTLY_PLAYING_COLLECTION}
117 rkey={rkey}
118 renderer={Wrapped}
119 fallback={fallback}
120 loadingIndicator={loadingIndicator}
121 />
122 );
123});
124
125export default CurrentlyPlaying;