@behindthemusictree/app-kit-ui 37.0.0 → 38.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/genre-tree/index.ts
21
21
  var genre_tree_exports = {};
22
22
  __export(genre_tree_exports, {
23
+ EntityDetailPanel: () => EntityDetailPanel,
23
24
  GenreDetailPanel: () => GenreDetailPanel,
24
25
  GenreInfoPanelBody: () => GenreInfoPanelBody,
25
26
  GenreNodeActionBand: () => GenreNodeActionBand,
@@ -39,6 +40,7 @@ __export(genre_tree_exports, {
39
40
  TrackPositionPlayPause: () => TrackPositionPlayPause,
40
41
  searchStatus: () => searchStatus,
41
42
  useArtistSearchGroup: () => useArtistSearchGroup,
43
+ useEntityPanel: () => useEntityPanel,
42
44
  useGenrePlaylistPlayback: () => useGenrePlaylistPlayback,
43
45
  useGenreRemoteSearchGroup: () => useGenreRemoteSearchGroup,
44
46
  useSongSearchGroup: () => useSongSearchGroup
@@ -294,7 +296,7 @@ function InternalErrorPopup({ errorCode, contactEmail, ...rest }) {
294
296
  " ",
295
297
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { href: `mailto:${contactEmail}`, className: "text-[var(--color-primary)] hover:text-[var(--color-primary-hover)]", children: contactEmail })
296
298
  ] }) }),
297
- errorCode && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "text-sm text-[var(--color-neutral-500)] bg-[var(--color-neutral-100)] p-3 rounded-lg border border-[var(--color-neutral-100)]", children: [
299
+ errorCode && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "text-body text-[var(--color-neutral-500)] bg-[var(--color-neutral-100)] p-3 rounded-lg border border-[var(--color-neutral-100)]", children: [
298
300
  "Error Code: ",
299
301
  errorCode
300
302
  ] })
@@ -784,7 +786,7 @@ function useArtistSearchGroup(getBaseUrl, text, onSelect, enabled = true) {
784
786
  artist.disambiguation
785
787
  ] })
786
788
  ] }),
787
- artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [
789
+ artist.matchedAlias && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "block truncate text-caption text-[var(--color-neutral-500)]", children: [
788
790
  "also known as ",
789
791
  artist.matchedAlias
790
792
  ] })
@@ -809,7 +811,7 @@ function useSongSearchGroup(getBaseUrl, text, onSelect, enabled = true) {
809
811
  key: song.uuid,
810
812
  content: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: (0, import_genre_tree7.isSongSearchResultPlayable)(song) ? void 0 : "opacity-60", children: [
811
813
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "block truncate font-medium", children: song.title }),
812
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: [song.artists.map((artist) => artist.name).join(", "), song.genre?.name ?? "No genre"].filter(Boolean).join(" \xB7 ") })
814
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "block truncate text-caption text-[var(--color-neutral-500)]", children: [song.artists.map((artist) => artist.name).join(", "), song.genre?.name ?? "No genre"].filter(Boolean).join(" \xB7 ") })
813
815
  ] }),
814
816
  onSelect: () => onSelect(song)
815
817
  })),
@@ -832,9 +834,9 @@ function useGenreRemoteSearchGroup(getBaseUrl, text, onSelect, { key = "genres",
832
834
  content: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
833
835
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "block truncate font-medium", children: [
834
836
  genre.name,
835
- genre.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ml-2 text-xs font-normal italic text-[var(--color-text-muted)]", children: "umbrella" })
837
+ genre.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ml-2 text-caption font-normal italic text-[var(--color-text-muted)]", children: "umbrella" })
836
838
  ] }),
837
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "block truncate text-xs text-[var(--color-neutral-500)]", children: describe?.(genre) ?? `${genre.treeName} \xB7 ${genre.root.name}` })
839
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "block truncate text-caption text-[var(--color-neutral-500)]", children: describe?.(genre) ?? `${genre.treeName} \xB7 ${genre.root.name}` })
838
840
  ] }),
839
841
  onSelect: () => onSelect(genre)
840
842
  })),
@@ -875,7 +877,7 @@ function GenreSearch({
875
877
  key: genrePlaylist.uuid,
876
878
  content: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
877
879
  genrePlaylist.name,
878
- genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "ml-2 text-xs italic text-[var(--color-text-muted)]", children: "umbrella" })
880
+ genrePlaylist.isUmbrella && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "ml-2 text-caption italic text-[var(--color-text-muted)]", children: "umbrella" })
879
881
  ] }),
880
882
  onSelect: () => select(genrePlaylist)
881
883
  }))
@@ -993,7 +995,7 @@ function Toast({ message, onClose, durationMs = 2200 }) {
993
995
  role: "status",
994
996
  "aria-live": "polite",
995
997
  "aria-atomic": "true",
996
- className: "pointer-events-auto w-full max-w-md rounded-md bg-[var(--color-surface)] px-4 py-4 text-sm text-[var(--color-text)] shadow-lg ring-1 ring-black/10",
998
+ className: "pointer-events-auto w-full max-w-md rounded-md bg-[var(--color-surface)] px-4 py-4 text-body text-[var(--color-text)] shadow-lg ring-1 ring-black/10",
997
999
  children: message
998
1000
  }
999
1001
  ) }),
@@ -1003,7 +1005,7 @@ function Toast({ message, onClose, durationMs = 2200 }) {
1003
1005
 
1004
1006
  // src/genre-tree/ShareDestinationButton.tsx
1005
1007
  var import_jsx_runtime16 = require("react/jsx-runtime");
1006
- var className = "group flex min-w-0 flex-1 flex-col items-center justify-center gap-1.5 rounded-xl px-2 py-2 text-xs text-[var(--color-text)] transition-colors hover:bg-[var(--color-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50";
1008
+ var className = "group flex min-w-0 flex-1 flex-col items-center justify-center gap-1.5 rounded-xl px-2 py-2 text-caption text-[var(--color-text)] transition-colors hover:bg-[var(--color-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50";
1007
1009
  function Contents({ icon, label }) {
1008
1010
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
1009
1011
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: icon }),
@@ -1033,9 +1035,9 @@ function SharePreviewCard({ target, url }) {
1033
1035
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "rounded-3xl bg-[var(--color-surface-muted)] p-4", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-h-32 overflow-hidden rounded-2xl bg-[var(--color-surface)] shadow-lg", children: [
1034
1036
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex w-28 shrink-0 items-center justify-center bg-[var(--color-primary)]/15 text-[var(--color-primary)]", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Icon, { "aria-hidden": "true", className: "h-10 w-10" }) }),
1035
1037
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-1 p-4", children: [
1036
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
1038
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-caption font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: typeLabel }),
1037
1039
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate text-base font-semibold text-[var(--color-text)]", title: target.name, children: target.name }),
1038
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate text-sm text-[var(--color-primary)]", title: url, children: url })
1040
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate text-body text-[var(--color-primary)]", title: url, children: url })
1039
1041
  ] })
1040
1042
  ] }) });
1041
1043
  }
@@ -1176,8 +1178,8 @@ function SharePopup({ target, url, onClose }) {
1176
1178
  }
1177
1179
  )
1178
1180
  ] }),
1179
- copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: "Could not copy the link." }),
1180
- shareError && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { role: "alert", className: "text-center text-sm text-[var(--color-red-500)]", children: shareError })
1181
+ copyStatus === "failed" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { role: "alert", className: "text-center text-body text-[var(--color-red-500)]", children: "Could not copy the link." }),
1182
+ shareError && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { role: "alert", className: "text-center text-body text-[var(--color-red-500)]", children: shareError })
1181
1183
  ] })
1182
1184
  }
1183
1185
  ),
@@ -1197,7 +1199,7 @@ function ShareActionButton({ target, url }) {
1197
1199
  {
1198
1200
  type: "button",
1199
1201
  onClick: openSharePopup,
1200
- className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
1202
+ className: "group flex flex-col items-center gap-1 w-16 text-caption text-center text-[var(--color-text)] focus-visible:outline-none",
1201
1203
  "aria-label": `Share ${target.name}`,
1202
1204
  title: `Share ${target.name}`,
1203
1205
  children: [
@@ -1279,7 +1281,7 @@ function GenreActionBand({
1279
1281
  disabled: item.disabled,
1280
1282
  title: item.title,
1281
1283
  onClick: item.onClick,
1282
- className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none disabled:opacity-50",
1284
+ className: "group flex flex-col items-center gap-1 w-16 text-caption text-center text-[var(--color-text)] focus-visible:outline-none disabled:opacity-50",
1283
1285
  children: [
1284
1286
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)] group-disabled:group-hover:bg-[var(--color-surface-muted)]", children: item.icon }),
1285
1287
  item.label
@@ -1298,7 +1300,7 @@ function GenreActionBand({
1298
1300
  "aria-label": "Admin actions",
1299
1301
  className: `px-3.5 py-3 ${items.length > 0 ? "border-t border-[var(--color-border)]" : ""}`,
1300
1302
  children: [
1301
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "mb-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
1303
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "mb-2 text-caption font-semibold uppercase tracking-wide text-[var(--color-text-muted)]", children: "Admin" }),
1302
1304
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex flex-wrap gap-1", children: renderItems(adminItems) })
1303
1305
  ]
1304
1306
  }
@@ -1330,7 +1332,7 @@ function ClampedText({ children, className: className2 = "" }) {
1330
1332
  type: "button",
1331
1333
  "aria-expanded": expanded,
1332
1334
  onClick: () => setExpanded((value) => !value),
1333
- className: "mt-1 text-xs text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]",
1335
+ className: "mt-1 text-caption text-[var(--color-text-muted)] underline hover:text-[var(--color-text)]",
1334
1336
  children: expanded ? "Show less" : "Show more"
1335
1337
  }
1336
1338
  )
@@ -1481,7 +1483,7 @@ function GenrePlaylistTracks({
1481
1483
  ".",
1482
1484
  " ",
1483
1485
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", className: "underline", onClick: () => void refetch(), children: "Retry" })
1484
- ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-[0.8125rem] leading-[1.45] [container-type:inline-size]", "aria-label": `${genreName} ${trackNoun.plural}`, children: [
1486
+ ] }) : tracks.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("ul", { className: "mt-1 max-h-64 overflow-y-auto text-body [container-type:inline-size]", "aria-label": `${genreName} ${trackNoun.plural}`, children: [
1485
1487
  tracks.map((track, index) => {
1486
1488
  const label = track.artists?.length ? `${track.title} \u2014 ${track.artists.map((artist2) => artist2.name).join(", ")}` : track.title;
1487
1489
  const isCurrent = playerTrackObject?.track.id === track.uuid;
@@ -1579,7 +1581,7 @@ function GenreDetailExtras({
1579
1581
  const { summary, summarySourceUrl, essentialSongs } = overview;
1580
1582
  const wikidataUrl = wikidataId ? `https://www.wikidata.org/wiki/${encodeURIComponent(wikidataId)}` : null;
1581
1583
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
1582
- (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-xs text-[var(--color-text)]", children: [
1584
+ (summarySourceUrl || wikidataUrl) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: "Links", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex flex-wrap gap-x-3 gap-y-1 text-caption text-[var(--color-text)]", children: [
1583
1585
  summarySourceUrl && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
1584
1586
  "a",
1585
1587
  {
@@ -1607,10 +1609,10 @@ function GenreDetailExtras({
1607
1609
  }
1608
1610
  )
1609
1611
  ] }) }),
1610
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ClampedText, { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1612
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: "Summary", children: renderSummary ? renderSummary(overview) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ClampedText, { className: "text-body text-[var(--color-text)]", children: summary ?? "\u2014" }, genreUuid) }),
1611
1613
  family,
1612
1614
  renderGenreDetailExtras?.(overview),
1613
- (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("ul", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-[0.8125rem] leading-[1.45] text-[var(--color-text)]", children: "\u2014" }) }),
1615
+ (renderEssentialTracks || essentialSongs) && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_ui6.PanelSection, { title: `Essential ${trackNoun.plural}`, children: renderEssentialTracks ? renderEssentialTracks(overview) : essentialSongs && essentialSongs.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("ul", { className: "text-body text-[var(--color-text)]", children: essentialSongs.map((track) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("li", { className: "py-1", children: track.title }, track.uuid)) }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-body text-[var(--color-text)]", children: "\u2014" }) }),
1614
1616
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1615
1617
  GenrePlaylistTracks,
1616
1618
  {
@@ -1676,7 +1678,7 @@ function GenreInfoPanelBody({
1676
1678
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(import_jsx_runtime25.Fragment, { children: [
1677
1679
  toolbar,
1678
1680
  genrePlaylist ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(GenreNodeActionBand, { ...band, genrePlaylist, playback }) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(GenreActionBand, { ...band }),
1679
- summary && catalogue && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "px-3.5 py-3 text-sm text-[var(--color-text)]", children: [
1681
+ summary && catalogue && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "px-3.5 py-3 text-body text-[var(--color-text)]", children: [
1680
1682
  /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { children: [
1681
1683
  "Catalogue ",
1682
1684
  noun.plural,
@@ -1766,7 +1768,7 @@ function useGenreDetailContent({
1766
1768
  if (isError) return {
1767
1769
  name: null,
1768
1770
  title: "Genre unavailable",
1769
- content: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { role: "alert", className: "p-4 text-sm text-[var(--color-text)]", children: "This genre could not be loaded." })
1771
+ content: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { role: "alert", className: "p-4 text-body text-[var(--color-text)]", children: "This genre could not be loaded." })
1770
1772
  };
1771
1773
  if (isPending || !detail || !view) return {
1772
1774
  name: null,
@@ -1902,7 +1904,7 @@ function ChangeParentPopup({
1902
1904
  return () => window.removeEventListener("keydown", handleKeyDown);
1903
1905
  }, [onClose]);
1904
1906
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(BasePopup_default, { title: `Change parent of ${genrePlaylist.name}`, onClose, showCancelButton: true, fitContent: true, children: [
1905
- /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { className: "mb-3 text-sm text-[var(--color-text-muted)]", children: [
1907
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { className: "mb-3 text-body text-[var(--color-text-muted)]", children: [
1906
1908
  "Current parent: ",
1907
1909
  genrePlaylist.parent?.name ?? "none"
1908
1910
  ] }),
@@ -1914,7 +1916,7 @@ function ChangeParentPopup({
1914
1916
  placeholder: "Search a new parent\u2026"
1915
1917
  }
1916
1918
  ) }),
1917
- formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { role: "alert", className: "mt-3 text-sm text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1919
+ formErrors.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { role: "alert", className: "mt-3 text-body text-[var(--color-red-500)]", children: formErrors.map(({ message }) => message).join(" ") })
1918
1920
  ] });
1919
1921
  }
1920
1922
 
@@ -2000,7 +2002,7 @@ function ExternalLink({ href, children }) {
2000
2002
  function ArtistPhoto({ image, name }) {
2001
2003
  return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("figure", { className: "flex flex-col gap-1.5 px-6 py-2.5", children: [
2002
2004
  /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("img", { src: image.url, alt: name, className: "h-64 w-full object-contain bg-[var(--color-surface-muted)] rounded" }),
2003
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("figcaption", { className: "text-xs text-[var(--color-text-muted)]", children: [
2005
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("figcaption", { className: "text-caption text-[var(--color-text-muted)]", children: [
2004
2006
  "Photo: ",
2005
2007
  /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ExternalLink, { href: image.pageUrl, children: image.author ?? "Wikimedia Commons" }),
2006
2008
  ",",
@@ -2199,7 +2201,7 @@ function TrackItem({
2199
2201
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex max-w-full items-center gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "title text-lg font-bold text-[var(--color-text)] max-w-full truncate", children: renderSongLink ? renderSongLink(track) : (0, import_genre_tree22.isSong)(track) ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(MusicBrainzRecordingLink, { song: track, className: "hover:underline focus-visible:underline", children: track.title }) : track.title }) }),
2200
2202
  track.artists && track.artists.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "artist text-base max-w-full truncate text-[var(--color-text-muted)]", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_genre_tree23.ArtistNames, { artists: track.artists, renderArtistLink }) }) : ""
2201
2203
  ] }),
2202
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-xs font-bold", children: track.genre.name }) : "" }),
2204
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "genre-name-container flex items-center justify-start w-[21.6%] min-w-0 shrink-0", children: track.genre ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "genre-name max-w-full whitespace-normal break-words p-1 text-caption font-bold", children: track.genre.name }) : "" }),
2203
2205
  renderDuration && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
2204
2206
  "div",
2205
2207
  {
@@ -2255,14 +2257,14 @@ function ArtistSongs({ artist, getBackendBaseUrl, renderArtistLink, renderSongLi
2255
2257
  {
2256
2258
  type: "button",
2257
2259
  onClick: () => void play(),
2258
- className: "flex w-fit items-center gap-1.5 px-3 py-1 text-xs font-medium bg-gray-900 text-white rounded-full hover:bg-gray-700",
2260
+ className: "flex w-fit items-center gap-1.5 px-3 py-1 text-caption font-medium bg-gray-900 text-white rounded-full hover:bg-gray-700",
2259
2261
  children: [
2260
2262
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react10.Play, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
2261
2263
  "Play all"
2262
2264
  ]
2263
2265
  }
2264
2266
  ),
2265
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-xs", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2267
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { role: "group", "aria-label": "Sort songs by", className: "flex gap-1 text-caption", children: SORTS.map((sort) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
2266
2268
  "button",
2267
2269
  {
2268
2270
  type: "button",
@@ -2383,7 +2385,7 @@ function SongHistory({ uuid, getBackendBaseUrl }) {
2383
2385
  if (history.status === "loading") body = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_ui14.Skeleton, { className: "h-10", "aria-busy": "true" });
2384
2386
  else if (history.status === "error") body = /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-red-600", children: "Could not load the history." });
2385
2387
  else if (history.status === "success")
2386
- body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("li", { className: "py-1.5 text-sm", children: [
2388
+ body = history.entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "text-[var(--color-text-muted)]", children: "No changes yet." }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("ul", { className: "divide-y divide-[var(--color-border)]", children: history.entries.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("li", { className: "py-1.5 text-body", children: [
2387
2389
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "font-medium", children: entry.action }),
2388
2390
  entry.oldValue !== null || entry.newValue !== null ? /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "text-[var(--color-text-muted)]", children: [
2389
2391
  " ",
@@ -2392,7 +2394,7 @@ function SongHistory({ uuid, getBackendBaseUrl }) {
2392
2394
  " \u2192 ",
2393
2395
  entry.newValue ?? "none"
2394
2396
  ] }) : null,
2395
- /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "block text-xs text-[var(--color-text-muted)]", children: [
2397
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { className: "block text-caption text-[var(--color-text-muted)]", children: [
2396
2398
  entry.actorPseudo ?? "Anonymous",
2397
2399
  " \xB7 ",
2398
2400
  dateFormat.format(new Date(entry.createdOn))
@@ -2410,15 +2412,8 @@ function SongHistory({ uuid, getBackendBaseUrl }) {
2410
2412
  }
2411
2413
  );
2412
2414
  }
2413
- function SongPanelContent({
2414
- song,
2415
- scope,
2416
- getBackendBaseUrl,
2417
- shareUrl,
2418
- renderArtistLink,
2419
- genrePath,
2420
- onSelectGenre
2421
- }) {
2415
+ function SongPanelContent(props) {
2416
+ const { song, scope, getBackendBaseUrl, shareUrl, renderArtistLink } = props;
2422
2417
  const { playNewTrackListFromTrackUuid } = (0, import_genre_tree26.useTrackList)();
2423
2418
  const { playerTrackObject, playState, handlePlayPauseAction } = (0, import_player11.usePlayer)();
2424
2419
  const unplayableReason = (0, import_genre_tree26.songUnplayableReason)(song);
@@ -2428,12 +2423,14 @@ function SongPanelContent({
2428
2423
  const musicBrainzUrl = (0, import_genre_tree26.musicBrainzRecordingUrl)(song);
2429
2424
  const fields = [];
2430
2425
  if (song.genre) {
2431
- const path = genrePath.length > 0 ? genrePath : null;
2426
+ const path = props.genrePath && props.genrePath.length > 0 ? props.genrePath : null;
2427
+ const { onSelectGenre } = props;
2428
+ const genre = song.genre;
2432
2429
  fields.push({
2433
2430
  label: "Genre",
2434
- value: path ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { children: [
2431
+ value: onSelectGenre ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("button", { type: "button", onClick: () => onSelectGenre(genre), className: "hover:underline", children: genre.name }) : path ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex flex-wrap items-center gap-x-1", children: path.map((genrePlaylist, index) => /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("span", { children: [
2435
2432
  index > 0 && /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { "aria-hidden": "true", children: "\u203A " }),
2436
- /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("button", { type: "button", onClick: () => onSelectGenre(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2433
+ /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("button", { type: "button", onClick: () => props.onSelectGenrePlaylist?.(genrePlaylist), className: "hover:underline", children: genrePlaylist.name })
2437
2434
  ] }, genrePlaylist.uuid)) }) : song.genre.name
2438
2435
  });
2439
2436
  } else {
@@ -2470,12 +2467,12 @@ function SongPanelContent({
2470
2467
  });
2471
2468
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(import_jsx_runtime37.Fragment, { children: [
2472
2469
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("div", { className: "genre-action-band border-b border-[var(--color-border)]", children: /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("div", { className: "flex flex-wrap gap-1 px-3.5 py-3", children: [
2473
- unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "flex items-center px-2.5 text-sm text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
2470
+ unplayableReason ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("p", { className: "flex items-center px-2.5 text-body text-[var(--color-text-muted)]", children: unplayableReason }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)(
2474
2471
  "button",
2475
2472
  {
2476
2473
  type: "button",
2477
2474
  onClick: () => isCurrent ? handlePlayPauseAction() : playNewTrackListFromTrackUuid(song, scope),
2478
- className: "group flex flex-col items-center gap-1 w-16 text-xs text-center text-[var(--color-text)] focus-visible:outline-none",
2475
+ className: "group flex flex-col items-center gap-1 w-16 text-caption text-center text-[var(--color-text)] focus-visible:outline-none",
2479
2476
  children: [
2480
2477
  /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { className: "flex items-center justify-center w-10 h-10 rounded-full group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)] bg-[var(--color-surface-muted)] transition-colors group-hover:bg-[var(--color-border)]", children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react11.Pause, { "aria-hidden": "true", className: "w-5 h-5" }) : /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(import_lucide_react11.Play, { "aria-hidden": "true", className: "w-5 h-5" }) }),
2481
2478
  isPlaying ? "Pause" : "Play"
@@ -2499,11 +2496,10 @@ function useSongPanel({
2499
2496
  getBackendBaseUrl,
2500
2497
  getShareUrl,
2501
2498
  renderArtistLink,
2502
- getGenrePath,
2503
- onSelectGenre,
2504
2499
  expanded,
2505
2500
  onExpandedChange,
2506
- onClose
2501
+ onClose,
2502
+ ...navigation
2507
2503
  }) {
2508
2504
  const result = (0, import_genre_tree27.useSong)(uuid, getBackendBaseUrl);
2509
2505
  const panel = { expanded, onExpandedChange, onClose };
@@ -2547,8 +2543,10 @@ function useSongPanel({
2547
2543
  getBackendBaseUrl,
2548
2544
  shareUrl: shareUrl ?? void 0,
2549
2545
  renderArtistLink,
2550
- genrePath: song.genre ? getGenrePath(song.genre) : [],
2551
- onSelectGenre
2546
+ ...navigation.getGenrePath ? {
2547
+ genrePath: song.genre ? navigation.getGenrePath(song.genre) : [],
2548
+ onSelectGenrePlaylist: navigation.onSelectGenrePlaylist
2549
+ } : { onSelectGenre: navigation.onSelectGenre }
2552
2550
  }
2553
2551
  )
2554
2552
  };
@@ -2556,6 +2554,37 @@ function useSongPanel({
2556
2554
  }
2557
2555
  }
2558
2556
 
2557
+ // src/genre-tree/useEntityPanel.tsx
2558
+ function useEntityPanel({
2559
+ scope,
2560
+ getBackendBaseUrl,
2561
+ getArtistBackendBaseUrl,
2562
+ getShareUrl,
2563
+ renderArtistLink,
2564
+ renderSongLink,
2565
+ song,
2566
+ artist,
2567
+ ...navigation
2568
+ }) {
2569
+ const songPanel = useSongPanel({
2570
+ ...song,
2571
+ scope,
2572
+ getBackendBaseUrl,
2573
+ getShareUrl,
2574
+ renderArtistLink,
2575
+ ...navigation
2576
+ });
2577
+ const artistPanel = useArtistPanel({
2578
+ ...artist,
2579
+ scope,
2580
+ getBackendBaseUrl: getArtistBackendBaseUrl ?? getBackendBaseUrl,
2581
+ getShareUrl,
2582
+ renderArtistLink,
2583
+ renderSongLink
2584
+ });
2585
+ return songPanel ?? artistPanel;
2586
+ }
2587
+
2559
2588
  // src/genre-tree/SizeMetricToggle.tsx
2560
2589
  var import_md = require("react-icons/md");
2561
2590
  var import_ui16 = require("@behindthemusictree/ui");
@@ -2738,20 +2767,6 @@ function GenreTreeView({
2738
2767
  const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react18.useState)(null);
2739
2768
  const artistBaseUrl = artistSearchBaseUrl ?? getBackendBaseUrl;
2740
2769
  const songBaseUrl = getBackendBaseUrl;
2741
- const artistPanel = useArtistPanel({
2742
- uuid: artistUuid,
2743
- scope,
2744
- getBackendBaseUrl: artistBaseUrl,
2745
- getShareUrl: getShareUrlRef.current,
2746
- renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name })),
2747
- expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2748
- onExpandedChange: (on) => {
2749
- if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
2750
- onArtistExpandedChange?.(on);
2751
- },
2752
- renderSongLink: resolvedRenderSongLink,
2753
- onClose: () => changeArtist(null)
2754
- });
2755
2770
  const resolvedRenderArtistLink = renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime40.jsx)("button", { type: "button", onClick: () => openArtist(artist.uuid), className: "hover:underline", children: artist.name }));
2756
2771
  const getGenrePath = (genre) => {
2757
2772
  const path = [];
@@ -2763,27 +2778,38 @@ function GenreTreeView({
2763
2778
  }
2764
2779
  return path;
2765
2780
  };
2766
- const songPanel = useSongPanel({
2767
- uuid: songUuid,
2781
+ const entityPanel = useEntityPanel({
2768
2782
  scope,
2769
2783
  getBackendBaseUrl: songBaseUrl,
2784
+ getArtistBackendBaseUrl: artistBaseUrl,
2770
2785
  getShareUrl: getShareUrlRef.current,
2771
2786
  renderArtistLink: resolvedRenderArtistLink,
2787
+ renderSongLink: resolvedRenderSongLink,
2788
+ song: {
2789
+ uuid: songUuid,
2790
+ expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
2791
+ onExpandedChange: (on) => {
2792
+ if (controlledSongExpanded === void 0) setExpandedSongUuid(on ? songUuid : null);
2793
+ onSongExpandedChange?.(on);
2794
+ },
2795
+ onClose: () => changeSong(null)
2796
+ },
2797
+ artist: {
2798
+ uuid: artistUuid,
2799
+ expanded: controlledArtistExpanded ?? (artistUuid !== null && expandedArtistUuid === artistUuid),
2800
+ onExpandedChange: (on) => {
2801
+ if (controlledArtistExpanded === void 0) setExpandedArtistUuid(on ? artistUuid : null);
2802
+ onArtistExpandedChange?.(on);
2803
+ },
2804
+ onClose: () => changeArtist(null)
2805
+ },
2772
2806
  getGenrePath,
2773
- onSelectGenre: (genrePlaylist2) => {
2807
+ onSelectGenrePlaylist: (genrePlaylist2) => {
2774
2808
  changeSong(null);
2775
2809
  selectGenrePlaylist(genrePlaylist2);
2776
- },
2777
- expanded: controlledSongExpanded ?? (songUuid !== null && expandedSongUuid === songUuid),
2778
- onExpandedChange: (on) => {
2779
- if (controlledSongExpanded === void 0) setExpandedSongUuid(on ? songUuid : null);
2780
- onSongExpandedChange?.(on);
2781
- },
2782
- onClose: () => changeSong(null)
2810
+ }
2783
2811
  });
2784
- const builtInSongPanel = overlayPanel || selectedGenreUuid !== null ? void 0 : songPanel;
2785
- const builtInArtistPanel = overlayPanel || selectedGenreUuid !== null || builtInSongPanel ? void 0 : artistPanel;
2786
- const builtInPanel = builtInSongPanel ?? builtInArtistPanel;
2812
+ const builtInPanel = overlayPanel || selectedGenreUuid !== null ? void 0 : entityPanel;
2787
2813
  const [uncontrolledCollapsed, setUncontrolledCollapsed] = (0, import_react18.useState)(false);
2788
2814
  const [uncontrolledExpanded, setUncontrolledExpanded] = (0, import_react18.useState)(false);
2789
2815
  const isCollapsed = collapsed ?? uncontrolledCollapsed;
@@ -2954,7 +2980,7 @@ function GenreTreeView({
2954
2980
  ) : null;
2955
2981
  const renderArtistFilter = () => {
2956
2982
  if (isLoading || !filterArtist) return null;
2957
- return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-sm", children: [
2983
+ return /* @__PURE__ */ (0, import_jsx_runtime40.jsxs)("span", { className: "inline-flex items-center overflow-hidden rounded-full bg-[var(--color-neutral-100)] text-body", children: [
2958
2984
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
2959
2985
  "button",
2960
2986
  {
@@ -3065,14 +3091,14 @@ function GenreTreeView({
3065
3091
  /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3066
3092
  "div",
3067
3093
  {
3068
- className: `actions-container absolute z-30 flex items-center gap-2 ${placement === "regions" ? "left-[calc(var(--layout-gutter)+var(--layout-panel-inset))] top-[calc(var(--layout-gutter)+var(--layout-panel-inset))] max-md:right-[calc(var(--layout-gutter)+var(--layout-panel-inset))] max-md:top-[var(--layout-band-top)] max-md:flex-col max-md:items-stretch" : "left-5 top-5"}`,
3094
+ className: `actions-container absolute z-30 flex items-center gap-2 ${placement === "regions" ? "left-[calc(var(--layout-gutter)+var(--layout-panel-inset))] top-[calc(var(--layout-gutter)+var(--layout-panel-inset))] md:left-[var(--layout-panel-inset)] md:top-[var(--layout-panel-inset)] max-md:right-[calc(var(--layout-gutter)+var(--layout-panel-inset))] max-md:top-[var(--layout-band-top)] max-md:flex-col max-md:items-stretch" : "left-5 top-5"}`,
3069
3095
  children: actions
3070
3096
  }
3071
3097
  ),
3072
3098
  !isLoading && !isError && filterArtist && /* @__PURE__ */ (0, import_jsx_runtime40.jsx)(
3073
3099
  "div",
3074
3100
  {
3075
- className: `absolute left-1/2 z-30 hidden -translate-x-1/2 md:block ${placement === "regions" ? "top-[calc(var(--layout-gutter)+var(--layout-panel-inset))]" : "top-5"}`,
3101
+ className: `absolute left-1/2 z-30 hidden -translate-x-1/2 md:block ${placement === "regions" ? "top-[calc(var(--layout-gutter)+var(--layout-panel-inset))] md:top-[var(--layout-panel-inset)]" : "top-5"}`,
3076
3102
  children: renderArtistFilter()
3077
3103
  }
3078
3104
  ),
@@ -3342,7 +3368,7 @@ function TrackListSidebar({
3342
3368
  /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "from h-auto flex flex-col justify-center items-center mr-2", children: "From" }),
3343
3369
  /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "name-container flex flex-col justify-center items-center text-[var(--color-text)] font-bold pr-2 max-w-[27.5rem]", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "name max-w-full truncate", children: trackList.origin.label }) })
3344
3370
  ] }),
3345
- /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "info flex items-center text-sm text-[var(--color-text-muted)]", children: [
3371
+ /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "info flex items-center text-body text-[var(--color-text-muted)]", children: [
3346
3372
  trackList.origin.type === import_genre_tree30.TrackListOriginType.GENRE_PLAYLIST ? "Genre \u2022 " : trackList.origin.type === import_genre_tree30.TrackListOriginType.ARTIST ? "Artist \u2022 " : `${trackNoun.singular} playlist \u2022 `,
3347
3373
  `${trackList.total} ${trackList.total > 1 ? trackNoun.plural : trackNoun.singular}`
3348
3374
  ] })
@@ -3652,8 +3678,72 @@ function Rating({ rating, handleChange }) {
3652
3678
  ) })
3653
3679
  ] }, i)) });
3654
3680
  }
3681
+
3682
+ // src/genre-tree/EntityDetailPanel.tsx
3683
+ var import_react21 = require("react");
3684
+ var import_ui19 = require("@behindthemusictree/ui");
3685
+ var import_jsx_runtime45 = require("react/jsx-runtime");
3686
+ function EntityDetailPanel({
3687
+ scope,
3688
+ getBackendBaseUrl,
3689
+ getArtistBackendBaseUrl,
3690
+ getShareUrl,
3691
+ songUuid,
3692
+ onSongUuidChange,
3693
+ artistUuid,
3694
+ onArtistUuidChange,
3695
+ renderArtistLink,
3696
+ renderSongLink,
3697
+ collapsed,
3698
+ edgeTab,
3699
+ ...navigation
3700
+ }) {
3701
+ const [expandedSongUuid, setExpandedSongUuid] = (0, import_react21.useState)(null);
3702
+ const [expandedArtistUuid, setExpandedArtistUuid] = (0, import_react21.useState)(null);
3703
+ const panel = useEntityPanel({
3704
+ scope,
3705
+ getBackendBaseUrl,
3706
+ getArtistBackendBaseUrl,
3707
+ getShareUrl,
3708
+ renderArtistLink: renderArtistLink ?? ((artist) => artist.isSpecialPurpose ? artist.name : /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("button", { type: "button", onClick: () => onArtistUuidChange(artist.uuid), className: "hover:underline", children: artist.name })),
3709
+ renderSongLink: renderSongLink ?? ((song) => /* @__PURE__ */ (0, import_jsx_runtime45.jsx)("button", { type: "button", onClick: () => onSongUuidChange(song.uuid), className: "hover:underline", children: song.title })),
3710
+ song: {
3711
+ uuid: songUuid,
3712
+ expanded: songUuid !== null && expandedSongUuid === songUuid,
3713
+ onExpandedChange: (on) => setExpandedSongUuid(on ? songUuid : null),
3714
+ onClose: () => onSongUuidChange(null)
3715
+ },
3716
+ artist: {
3717
+ uuid: artistUuid,
3718
+ expanded: artistUuid !== null && expandedArtistUuid === artistUuid,
3719
+ onExpandedChange: (on) => setExpandedArtistUuid(on ? artistUuid : null),
3720
+ onClose: () => onArtistUuidChange(null)
3721
+ },
3722
+ ...navigation
3723
+ });
3724
+ if (!panel) return null;
3725
+ return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(
3726
+ import_ui19.DetailPanel,
3727
+ {
3728
+ side: "left",
3729
+ title: panel.title,
3730
+ titleInSearch: true,
3731
+ onClose: panel.onClose,
3732
+ collapsed,
3733
+ expanded: panel.expanded,
3734
+ onExpandedChange: panel.onExpandedChange,
3735
+ aside: panel.extraContent,
3736
+ edgeTab,
3737
+ children: panel.extraContent && !panel.expanded ? /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)(import_jsx_runtime45.Fragment, { children: [
3738
+ panel.content,
3739
+ panel.extraContent
3740
+ ] }) : panel.content
3741
+ }
3742
+ );
3743
+ }
3655
3744
  // Annotate the CommonJS export names for ESM import in node:
3656
3745
  0 && (module.exports = {
3746
+ EntityDetailPanel,
3657
3747
  GenreDetailPanel,
3658
3748
  GenreInfoPanelBody,
3659
3749
  GenreNodeActionBand,
@@ -3673,6 +3763,7 @@ function Rating({ rating, handleChange }) {
3673
3763
  TrackPositionPlayPause,
3674
3764
  searchStatus,
3675
3765
  useArtistSearchGroup,
3766
+ useEntityPanel,
3676
3767
  useGenrePlaylistPlayback,
3677
3768
  useGenreRemoteSearchGroup,
3678
3769
  useSongSearchGroup