@behindthemusictree/app-kit-ui 29.1.0 → 31.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.
@@ -1,10 +1,11 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ComponentProps } from 'react';
3
3
  import { z } from 'zod';
4
- import { GenreTreeProps, FocusChangeReason, GenreTreeNode, GenreTreeAction, GenreTreeViewMode, GenreTreeViewport, InfoPanelSections, InfoPanelBody, WheelZoomMode } from '@behindthemusictree/genre-tree-view';
4
+ import { GenreTreeProps, FocusChangeReason, GenreTreeNode, GenreTreeAction, GenreTreeViewMode, GenreTreeViewport, WheelZoomMode } from '@behindthemusictree/genre-tree-view';
5
5
  export { FocusChangeReason, GenreTreeAction, GenreTreeOutlineSkeleton, GenreTreeSkeleton, GenreTreeViewMode, GenreTreeViewSkeleton, GenreTreeViewSkeletonProps, GenreTreeViewport, GenreTreeWheelSkeleton } from '@behindthemusictree/genre-tree-view';
6
- import { TrackBase, CriteriaOverview, GenreUuid, CriteriaPlaylistMinimum, RenderArtistLink, CriteriaPlaylistSimple, GenrePlaylistUuid, ArtistSearchResult, CriteriaMinimum } from '@behindthemusictree/app-kit/genre-tree';
6
+ import { TrackBase, CriteriaOverview, GenreUuid, CriteriaPlaylistMinimum, RenderArtistLink, RenderSongLink, CriteriaPlaylistSimple, GenrePlaylistUuid, ArtistSearchResult, CriteriaMinimum, SongSearchResult } from '@behindthemusictree/app-kit/genre-tree';
7
7
  import { Scope } from '@behindthemusictree/app-kit/transport';
8
+ import { DetailPanel } from '@behindthemusictree/ui';
8
9
 
9
10
  type GenrePlaylistTracksSlots = {
10
11
  /** Rendered first in each row, e.g. a selection checkbox. `index` is the row's position in the loaded list. */
@@ -30,6 +31,7 @@ type GenreDetailExtrasProps<O extends CriteriaOverview> = GenreDetailSlots<O> &
30
31
  criteriaOverviewSchema?: z.ZodType<O, unknown>;
31
32
  renderGenreDetailExtras?: (overview: O) => ReactNode;
32
33
  renderArtistLink?: RenderArtistLink;
34
+ renderSongLink?: RenderSongLink;
33
35
  /** When set, the tracks list shows only this artist's songs in the genre. */
34
36
  artist?: {
35
37
  uuid: string;
@@ -39,8 +41,24 @@ type GenreDetailExtrasProps<O extends CriteriaOverview> = GenreDetailSlots<O> &
39
41
  family: ReactNode;
40
42
  };
41
43
 
42
- /** Controlled expand state and danger-zone slot of the info panel, plus the generic overlay panel, as typed by genre-tree-view. */
43
- type InfoPanelCurationProps = Partial<Pick<GenreTreeProps, "renderDangerZone" | "expanded" | "onExpandedChange" | "collapsed" | "hideExpandButton" | "overlayPanel">>;
44
+ /** An artist or song detail the host renders in the shared DetailPanel in place of a genre's. */
45
+ interface OverlayPanel {
46
+ title: ReactNode;
47
+ content: ReactNode;
48
+ /** Second column, shown only while expanded. */
49
+ extraContent?: ReactNode;
50
+ expanded: boolean;
51
+ onExpandedChange: (expanded: boolean) => void;
52
+ onClose: () => void;
53
+ }
54
+ /** A host-supplied panel: expansion is optional because the host may not offer it. */
55
+ type HostOverlayPanel = Omit<OverlayPanel, "expanded" | "onExpandedChange"> & {
56
+ expanded?: boolean;
57
+ onExpandedChange?: (expanded: boolean) => void;
58
+ };
59
+
60
+ /** The host-owned detail panel's contract with the tree: the area the panel covers, and the node-style resolver the tree publishes back. */
61
+ type HostPanelProps = Partial<Pick<GenreTreeProps, "obscuredInset" | "onNodeStyleResolverChange">>;
44
62
 
45
63
  /** What a play button needs of a playlist; `criteria` is only read when playing one artist's songs in the genre. */
46
64
  type PlayableGenrePlaylistInput = CriteriaPlaylistMinimum & {
@@ -99,6 +117,12 @@ type GenreTreeShareTarget = {
99
117
  uuid: string;
100
118
  name: string;
101
119
  scope: Scope;
120
+ } | {
121
+ kind: "song";
122
+ uuid: string;
123
+ /** The song title. */
124
+ name: string;
125
+ scope: Scope;
102
126
  };
103
127
  /** Host applications own routing and return an absolute or relative URL for this target. */
104
128
  type GetGenreTreeShareUrl = (target: GenreTreeShareTarget) => string | null | undefined;
@@ -174,14 +198,35 @@ type GenreTreeViewProps<O extends CriteriaOverview = CriteriaOverview> = {
174
198
  /** Controlled expanded state of the artist panel. Uncontrolled, it resets to compact on each artist. */
175
199
  artistExpanded?: boolean;
176
200
  onArtistExpandedChange?: (expanded: boolean) => void;
177
- /** Resolves a selected genre or artist to a share URL using the host app's routes. */
201
+ /** Controlled song panel (a song uuid). Like the artist panel it shares the info panel's slot: opening a song clears
202
+ * the selected genre and the artist; selecting a genre closes it. A host `overlayPanel` takes precedence. */
203
+ songUuid?: string | null;
204
+ /** Called when the user opens a song (search pick, title link) or closes its panel. */
205
+ onSongUuidChange?: (uuid: string | null) => void;
206
+ /** Controlled expanded state of the song panel. Uncontrolled, it resets to compact on each song. */
207
+ songExpanded?: boolean;
208
+ onSongExpandedChange?: (expanded: boolean) => void;
209
+ /** Songs are searched by default; picking one opens its panel. */
210
+ songSearch?: boolean;
211
+ onSongSelect?: (song: SongSearchResult) => void;
212
+ /** When set, song titles in the info panel's track list render through it (e.g. a router `Link` to the song).
213
+ * By default they are buttons that open the song panel. */
214
+ renderSongLink?: RenderSongLink;
215
+ /** Resolves a selected genre, artist or song to a share URL using the host app's routes. */
178
216
  getShareUrl?: GetGenreTreeShareUrl;
179
- } & ArtistSearchProps & GenreDetailSlots<O> & GenrePlaylistTracksSlots & InfoPanelCurationProps & GenreFocusProps & {
217
+ } & ArtistSearchProps & GenreDetailSlots<O> & GenrePlaylistTracksSlots & GenreFocusProps & {
218
+ /** Appended to a selected genre's panel, e.g. destructive actions. */
219
+ renderDangerZone?: (node: GenreTreeNode) => ReactNode;
220
+ /** Controlled expanded state of the genre panel. Uncontrolled when omitted. */
221
+ expanded?: boolean;
222
+ onExpandedChange?: (expanded: boolean) => void;
223
+ /** A host panel rendered in the detail panel's slot; it takes precedence over the genre, song and artist panels. */
224
+ overlayPanel?: HostOverlayPanel;
180
225
  /** Hides the info panel behind its edge tab; uncontrolled when omitted. Only applies with `placement="regions"`. */
181
226
  collapsed?: boolean;
182
227
  onCollapsedChange?: (collapsed: boolean) => void;
183
228
  };
184
- declare function GenreTreeView<O extends CriteriaOverview = CriteriaOverview>({ scope, handleGenreCreationAction, handleGenreRenameAction, onParentChanged, getBackendBaseUrl, additionalActions, viewMode: controlledViewMode, selectedNodeId: controlledSelectedNodeId, onSelectedNodeIdChange, defaultViewport, viewportRestore, onViewportChange, readOnly, className, showDisplayOptions, showAddRoot, allowWheelRotation: controlledAllowWheelRotation, criteriaOverviewSchema, renderGenreDetailExtras, floatingControlsLeading, sizeMetric: controlledSizeMetric, onSizeMetricChange, renderArtistLink, artistUuid: controlledArtistUuid, onArtistUuidChange, artistExpanded: controlledArtistExpanded, onArtistExpandedChange, getShareUrl, artistSearch, onArtistSelect, artistSearchBaseUrl, renderSummary, renderEssentialTracks, renderSongLeading, renderSongActions, renderTracksToolbar, renderDangerZone, expanded, onExpandedChange, collapsed, onCollapsedChange, overlayPanel, placement, ...focus }: GenreTreeViewProps<O>): react.JSX.Element;
229
+ declare function GenreTreeView<O extends CriteriaOverview = CriteriaOverview>({ scope, handleGenreCreationAction, handleGenreRenameAction, onParentChanged, getBackendBaseUrl, additionalActions, viewMode: controlledViewMode, selectedNodeId: controlledSelectedNodeId, onSelectedNodeIdChange, defaultViewport, viewportRestore, onViewportChange, readOnly, className, showDisplayOptions, showAddRoot, allowWheelRotation: controlledAllowWheelRotation, criteriaOverviewSchema, renderGenreDetailExtras, floatingControlsLeading, sizeMetric: controlledSizeMetric, onSizeMetricChange, renderArtistLink, artistUuid: controlledArtistUuid, onArtistUuidChange, artistExpanded: controlledArtistExpanded, onArtistExpandedChange, getShareUrl, artistSearch, onArtistSelect, artistSearchBaseUrl, songUuid: controlledSongUuid, onSongUuidChange, songExpanded: controlledSongExpanded, onSongExpandedChange, songSearch, onSongSelect, renderSongLink, renderSummary, renderEssentialTracks, renderSongLeading, renderSongActions, renderTracksToolbar, renderDangerZone, expanded, onExpandedChange, collapsed, onCollapsedChange, overlayPanel, placement, ...focus }: GenreTreeViewProps<O>): react.JSX.Element;
185
230
 
186
231
  type GenreActionBandProps = {
187
232
  node: GenreTreeNode;
@@ -249,13 +294,12 @@ type GenreDetailPanelProps<O extends CriteriaOverview> = {
249
294
  collapsed?: boolean;
250
295
  expanded?: boolean;
251
296
  onExpandedChange?: (expanded: boolean) => void;
297
+ /** Tab on the panel's inner edge, for hosts that hide and show the panel. */
298
+ edgeTab?: ComponentProps<typeof DetailPanel>["edgeTab"];
252
299
  extras?: GenreInfoPanelBodyProps<O>["extras"];
253
300
  };
254
301
  /** A genre's detail panel driven by one uuid, with no tree behind it. The tree's own panel shares its body. */
255
- declare function GenreDetailPanel<O extends CriteriaOverview>({ genreUuid, scope, getBackendBaseUrl, onClose, onSelectGenre, onZoomIn, shareUrl, shareTarget, collapsed, expanded, onExpandedChange, extras, }: GenreDetailPanelProps<O>): react.JSX.Element;
256
-
257
- /** Gives the brand layout tokens a zero-width left column while no left detail panel is showing. */
258
- declare function useLeftDetailPanelLayout(isPanelShown: boolean, enabled?: boolean): void;
302
+ declare function GenreDetailPanel<O extends CriteriaOverview>({ genreUuid, scope, getBackendBaseUrl, onClose, onSelectGenre, onZoomIn, shareUrl, shareTarget, collapsed, expanded, onExpandedChange, edgeTab, extras, }: GenreDetailPanelProps<O>): react.JSX.Element;
259
303
 
260
304
  type GenrePlaylistTreePerRootProps<T extends TrackBase> = {
261
305
  scope: Scope;
@@ -267,19 +311,12 @@ type GenrePlaylistTreePerRootProps<T extends TrackBase> = {
267
311
  additionalActions?: (node: GenreTreeNode) => GenreTreeAction[];
268
312
  onNodeClick?: (node: GenreTreeNode) => void;
269
313
  onNodeHover?: (node: GenreTreeNode) => void;
270
- renderInfoPanelBody?: (node: GenreTreeNode, sections: InfoPanelSections) => InfoPanelBody;
271
314
  /** Overrides which node is shown highlighted, e.g. from a search selection. */
272
315
  selectedNodeId?: string | null;
273
316
  /** Fires when a user action changes the info panel's node, or `null` when it is closed. */
274
317
  onSelectedNodeChange?: (node: GenreTreeNode | null) => void;
275
- /** Hides the info panel's header close button. */
276
- hideInfoPanelClose?: boolean;
277
- /** Hides the info panel header's node name. */
278
- hideInfoPanelTitle?: boolean;
279
318
  /** When true, suppresses per-node create/rename affordances. Defaults to false. */
280
319
  readOnly?: boolean;
281
- /** When false, suppresses the hover toolbar on every node. Defaults to true. */
282
- showToolbar?: boolean;
283
320
  wheelZoom?: WheelZoomMode;
284
321
  /** Mount-only seed for the pan/zoom, replacing the initial fit. */
285
322
  defaultViewport?: GenreTreeViewport;
@@ -289,11 +326,12 @@ type GenrePlaylistTreePerRootProps<T extends TrackBase> = {
289
326
  onViewportChange?: (viewport: GenreTreeViewport) => void;
290
327
  /** What node play buttons play; defaults to each node's whole genre playlist. */
291
328
  playback?: GenrePlaylistPlayback;
292
- } & InfoPanelCurationProps;
293
- declare function GenrePlaylistTreePerRoot<T extends TrackBase>({ scope, className, rootUuid, genrePlaylistTreePerRoot, handleGenreCreationAction, handleGenreRenameAction, additionalActions, onNodeClick, onNodeHover, renderInfoPanelBody, selectedNodeId, onSelectedNodeChange, hideInfoPanelClose, hideInfoPanelTitle, readOnly, showToolbar, wheelZoom, defaultViewport, viewportRestore, onViewportChange, playback, ...infoPanelCuration }: GenrePlaylistTreePerRootProps<T>): react.JSX.Element;
329
+ } & HostPanelProps;
330
+ declare function GenrePlaylistTreePerRoot<T extends TrackBase>({ scope, className, rootUuid, genrePlaylistTreePerRoot, handleGenreCreationAction, handleGenreRenameAction, additionalActions, onNodeClick, onNodeHover, selectedNodeId, onSelectedNodeChange, readOnly, wheelZoom, defaultViewport, viewportRestore, onViewportChange, playback, ...hostPanel }: GenrePlaylistTreePerRootProps<T>): react.JSX.Element;
294
331
 
295
- type GenreSearchExtraResults = {
296
- /** Heading of this group; genre matches then get a "Genres" heading above it. */
332
+ type GenreSearchExtraGroup = {
333
+ key: string;
334
+ /** Heading of this group; genre matches then get a "Genres" heading too. */
297
335
  label: string;
298
336
  items: {
299
337
  key: string;
@@ -310,17 +348,17 @@ type GenreSearchProps<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple>
310
348
  onSelect: (genrePlaylist: T) => void;
311
349
  /** Name of the currently selected genre; fills the input whenever it changes. */
312
350
  selectedName?: string | null;
313
- /** When set, renders a "Previous" button left of the text; shown only when the host's CSS reveals `.genre-search-previous`. */
351
+ /** When set, renders a "Previous" button left of the text and makes room for it. */
314
352
  onPrevious?: () => void;
315
353
  /** Called by the clear button, after it empties the input. */
316
354
  onClear?: () => void;
317
355
  placeholder?: string;
318
356
  /** Called with what the user types, and with "" on clear or pick; never when `selectedName` refills the input. */
319
357
  onQueryChange?: (query: string) => void;
320
- /** A second result group (e.g. artists) the host resolves from `onQueryChange`. */
321
- extraResults?: GenreSearchExtraResults;
358
+ /** Further result groups (artists, songs) the host resolves from `onQueryChange`. */
359
+ extraGroups?: GenreSearchExtraGroup[];
322
360
  };
323
- declare function GenreSearch<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple>({ genrePlaylists, onSelect, selectedName, onClear, onPrevious, placeholder, onQueryChange, extraResults, }: GenreSearchProps<T>): react.JSX.Element;
361
+ declare function GenreSearch<T extends CriteriaPlaylistSimple = CriteriaPlaylistSimple>({ genrePlaylists, onSelect, selectedName, onClear, onPrevious, placeholder, onQueryChange, extraGroups, }: GenreSearchProps<T>): react.JSX.Element;
324
362
 
325
363
  interface TrackListSidebarProps<T extends TrackBase> {
326
364
  className?: string;
@@ -332,8 +370,10 @@ interface TrackListSidebarProps<T extends TrackBase> {
332
370
  onMinimize?: () => void;
333
371
  /** When set, artist names render through it (e.g. a router `Link`). */
334
372
  renderArtistLink?: RenderArtistLink;
373
+ /** When set, song titles render through it (e.g. open the song panel). */
374
+ renderSongLink?: RenderSongLink;
335
375
  }
336
- declare function TrackListSidebar<T extends TrackBase>({ className, renderDuration, renderActions, layout, isCollapsed, onExpand, onMinimize, renderArtistLink, }: TrackListSidebarProps<T>): react.JSX.Element | null;
376
+ declare function TrackListSidebar<T extends TrackBase>({ className, renderDuration, renderActions, layout, isCollapsed, onExpand, onMinimize, renderArtistLink, renderSongLink, }: TrackListSidebarProps<T>): react.JSX.Element | null;
337
377
 
338
378
  interface PlayerPanelProps {
339
379
  className?: string;
@@ -347,8 +387,10 @@ interface PlayerPanelProps {
347
387
  /** Width once extended, e.g. to stop short of a left-hand detail panel. */
348
388
  extendedWidth?: string;
349
389
  renderArtistLink?: RenderArtistLink;
390
+ /** When set, song titles in the tracklist render through it (e.g. open the song panel). */
391
+ renderSongLink?: RenderSongLink;
350
392
  }
351
- declare function PlayerPanel({ className, isVisible, expanded, onExpandedChange, extendedWidth, renderArtistLink, }: PlayerPanelProps): react.JSX.Element;
393
+ declare function PlayerPanel({ className, isVisible, expanded, onExpandedChange, extendedWidth, renderArtistLink, renderSongLink, }: PlayerPanelProps): react.JSX.Element;
352
394
 
353
395
  interface TrackItemProps<T extends TrackBase> {
354
396
  track: T;
@@ -357,8 +399,10 @@ interface TrackItemProps<T extends TrackBase> {
357
399
  renderActions?: (track: T) => ReactNode;
358
400
  /** When set, artist names render through it (e.g. a router `Link`). */
359
401
  renderArtistLink?: RenderArtistLink;
402
+ /** When set, a song title renders through it (e.g. opens the song panel) instead of linking to MusicBrainz. */
403
+ renderSongLink?: RenderSongLink;
360
404
  }
361
- declare function TrackItem<T extends TrackBase>({ track, position, renderDuration, renderActions, renderArtistLink, }: TrackItemProps<T>): react.JSX.Element;
405
+ declare function TrackItem<T extends TrackBase>({ track, position, renderDuration, renderActions, renderArtistLink, renderSongLink, }: TrackItemProps<T>): react.JSX.Element;
362
406
 
363
407
  interface TrackPositionPlayPauseProps {
364
408
  position: number;
@@ -374,23 +418,4 @@ interface RatingProps {
374
418
  }
375
419
  declare function Rating({ rating, handleChange }: RatingProps): react.JSX.Element;
376
420
 
377
- type PanelEdgeTabAction = {
378
- label: string;
379
- onClick: () => void;
380
- /** Reflected as `aria-expanded` on the button; omit when the button is not a disclosure. */
381
- expanded?: boolean;
382
- };
383
- interface PanelEdgeTabProps {
384
- /** Side of the screen the panel sits on; the tab hangs off the panel's inner edge. */
385
- side: "left" | "right";
386
- /** Button whose chevron points toward the screen edge. */
387
- edge: PanelEdgeTabAction | null;
388
- /** Button whose chevron points toward the center of the screen. */
389
- center: PanelEdgeTabAction | null;
390
- /** Id of the panel the buttons control, when the host can name it. */
391
- controls?: string;
392
- className?: string;
393
- }
394
- declare function PanelEdgeTab({ side, edge, center, controls, className }: PanelEdgeTabProps): react.JSX.Element | null;
395
-
396
- export { type ArtistSearchProps, GenreDetailPanel, type GenreDetailPanelProps, type GenreDetailSlots, type GenreFocusProps, GenreInfoPanelBody, type GenreInfoPanelBodyProps, GenreNodeActionBand, type GenrePlaylistPlayback, type GenrePlaylistTracksSlots, GenrePlaylistTreePerRoot, type GenrePlaylistTreePerRootProps, GenreSearch, type GenreSearchExtraResults, type GenreSearchProps, type GenreTreeShareTarget, GenreTreeView, type GenreTreeViewProps, type GetGenreTreeShareUrl, type InfoPanelCurationProps, PanelEdgeTab, type PanelEdgeTabAction, type PanelEdgeTabProps, type ParentChange, type PlayableGenrePlaylist, PlayerPanel, type PlayerPanelProps, Rating, type RatingProps, TrackItem, type TrackItemProps, TrackListSidebar, type TrackListSidebarProps, TrackPositionPlayPause, type TrackPositionPlayPauseProps, type UseGenrePlaylistPlaybackOptions, useGenrePlaylistPlayback, useLeftDetailPanelLayout };
421
+ export { type ArtistSearchProps, GenreDetailPanel, type GenreDetailPanelProps, type GenreDetailSlots, type GenreFocusProps, GenreInfoPanelBody, type GenreInfoPanelBodyProps, GenreNodeActionBand, type GenrePlaylistPlayback, type GenrePlaylistTracksSlots, GenrePlaylistTreePerRoot, type GenrePlaylistTreePerRootProps, GenreSearch, type GenreSearchExtraGroup, type GenreSearchProps, type GenreTreeShareTarget, GenreTreeView, type GenreTreeViewProps, type GetGenreTreeShareUrl, type ParentChange, type PlayableGenrePlaylist, PlayerPanel, type PlayerPanelProps, Rating, type RatingProps, TrackItem, type TrackItemProps, TrackListSidebar, type TrackListSidebarProps, TrackPositionPlayPause, type TrackPositionPlayPauseProps, type UseGenrePlaylistPlaybackOptions, useGenrePlaylistPlayback };
@@ -9,15 +9,13 @@ import {
9
9
  GenreTreeView,
10
10
  GenreTreeViewSkeleton,
11
11
  GenreTreeWheelSkeleton,
12
- PanelEdgeTab,
13
12
  PlayerPanel,
14
13
  Rating,
15
14
  TrackItem,
16
15
  TrackListSidebar,
17
16
  TrackPositionPlayPause,
18
- useGenrePlaylistPlayback,
19
- useLeftDetailPanelLayout
20
- } from "../chunk-36YVZ6JR.js";
17
+ useGenrePlaylistPlayback
18
+ } from "../chunk-AQASG5KT.js";
21
19
  import "../chunk-DKQQLLMY.js";
22
20
  export {
23
21
  GenreDetailPanel,
@@ -30,13 +28,11 @@ export {
30
28
  GenreTreeView,
31
29
  GenreTreeViewSkeleton,
32
30
  GenreTreeWheelSkeleton,
33
- PanelEdgeTab,
34
31
  PlayerPanel,
35
32
  Rating,
36
33
  TrackItem,
37
34
  TrackListSidebar,
38
35
  TrackPositionPlayPause,
39
- useGenrePlaylistPlayback,
40
- useLeftDetailPanelLayout
36
+ useGenrePlaylistPlayback
41
37
  };
42
38
  //# sourceMappingURL=index.js.map