@workbench-kit/react 0.0.2-prototype.0.2.4 → 0.0.2-prototype.0.2.41
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.
- package/README.md +172 -0
- package/package.json +38 -21
- package/src/index.ts +74 -34
- package/src/jdw/JsonCodeEditorPane.tsx +7 -6
- package/src/jdw/builtins/renderBuiltinWidgetLeaf.tsx +1 -3
- package/src/jdw/createBuiltinJdwRegistry.ts +1 -4
- package/src/jdw/cssRenderBackend.tsx +1 -4
- package/src/jdw/fixtures/jdw-fixtures.ts +0 -15
- package/src/json-config/JsonConfigWorkbench.tsx +1 -1
- package/src/layout/index.ts +11 -2
- package/src/layout/panel/Panel.tsx +0 -3
- package/src/layout/panel/index.ts +0 -2
- package/src/layout/sidebar/SideBarTree.tsx +301 -0
- package/src/layout/sidebar/SidebarActionIconBar.tsx +3 -3
- package/src/layout/sidebar/WorkbenchSidebarActions.tsx +1 -1
- package/src/layout/sidebar/index.ts +13 -0
- package/src/modal/Modal.tsx +55 -25
- package/src/modal/ModalTitlebar.tsx +8 -8
- package/src/modal/modalPosition.ts +3 -6
- package/src/modal/useModalFocusTrap.ts +142 -0
- package/src/modal/useModalWindowFrame.ts +2 -2
- package/src/overlay/ContextMenu.tsx +133 -21
- package/src/overlay/context-menu-density.css +22 -0
- package/src/overlay/context-menu-item.ts +17 -0
- package/src/overlay/context-menu.css +6 -2
- package/src/overlay/index.ts +6 -1
- package/src/overlay/measureAnchoredOverlayPanel.ts +4 -6
- package/src/overlay/pointerPassthroughRegion.ts +17 -1
- package/src/overlay/usePointerPassthroughRegion.ts +35 -6
- package/src/primitives/absolute-box/AbsoluteBox.tsx +4 -4
- package/src/primitives/app-icon/AppIcon.tsx +33 -0
- package/src/primitives/app-icon/app-icon.css +28 -0
- package/src/primitives/button/Button.tsx +4 -4
- package/src/primitives/catalog-browse-card/CatalogBrowseCard.tsx +6 -6
- package/src/primitives/catalog-browse-pane/CatalogBrowsePane.tsx +39 -32
- package/src/primitives/catalog-browse-pane/catalog-browse-pane.css +4 -0
- package/src/primitives/catalog-filter-overlay/CatalogFilterOverlay.tsx +2 -2
- package/src/primitives/checkbox/Checkbox.tsx +1 -1
- package/src/primitives/chip/Chip.tsx +3 -12
- package/src/primitives/chip/index.ts +2 -2
- package/src/primitives/codicon/Codicon.tsx +1 -1
- package/src/primitives/file-icon/FileIcon.tsx +1 -1
- package/src/primitives/icon-button/IconButton.tsx +1 -1
- package/src/primitives/index.ts +7 -2
- package/src/primitives/library-detail-layout/LibraryDetailLayout.tsx +123 -9
- package/src/primitives/library-detail-layout/library-detail-layout.css +57 -0
- package/src/primitives/library-detail-layout/resolve-hero-cover-media.ts +80 -0
- package/src/primitives/library-facet-filter-panel/LibraryFacetFilterPanel.tsx +12 -12
- package/src/primitives/library-facet-filter-strip/LibraryFacetFilterStrip.tsx +4 -4
- package/src/primitives/number-input/NumberInput.tsx +5 -5
- package/src/primitives/primitives.css +1 -0
- package/src/primitives/record-media-hero/RecordMediaHero.tsx +10 -6
- package/src/primitives/scroll-area/ScrollArea.tsx +5 -5
- package/src/primitives/scroll-area-infinite-load/useScrollAreaInfiniteLoad.ts +5 -5
- package/src/primitives/searchable-multi-select/SearchableMultiSelect.tsx +7 -7
- package/src/primitives/select/Select.tsx +1 -13
- package/src/primitives/text-area/TextArea.tsx +1 -1
- package/src/primitives/text-input/TextInput.tsx +1 -1
- package/src/primitives/workbench-media-slot/WorkbenchMediaPlaceholder.tsx +2 -2
- package/src/primitives/workbench-media-slot/WorkbenchMediaSlot.tsx +16 -10
- package/src/primitives/workbench-thumbnail/WorkbenchThumbnail.tsx +4 -4
- package/src/scrollbars.css +2 -6
- package/src/styles/core.css +16 -0
- package/src/styles/foundation.css +4 -0
- package/src/styles/overlay.css +2 -0
- package/src/styles.css +1 -15
- package/src/utils/clamp.ts +4 -0
- package/src/utils/normalizeKeyToken.ts +13 -0
- package/src/utils/readNumber.ts +4 -0
- package/src/widget-tree/WidgetSourceEditor.tsx +0 -14
- package/src/widget-tree/WidgetTreeCanvasPreview.tsx +3 -10
- package/src/widget-tree/WidgetTreeLab.tsx +2 -23
- package/src/widget-tree/WidgetTreeSidePanel.tsx +0 -5
- package/src/widget-tree/WidgetTreeWorkspaceShell.tsx +2 -2
- package/src/widget-tree/index.ts +1 -5
- package/src/workbench/auth/WorkbenchLoginView.tsx +1 -2
- package/src/workbench/auth/auth-flows.css +14 -0
- package/src/workbench/chat/ChatCommandProposalCard.tsx +4 -4
- package/src/workbench/chat/ChatConversationBar.tsx +3 -3
- package/src/workbench/chat/ChatHistoryMenu.tsx +1 -1
- package/src/workbench/chat/ChatMessageCollapsible.tsx +4 -0
- package/src/workbench/chat/ChatMessageItem.tsx +68 -10
- package/src/workbench/chat/ChatMessageList.tsx +1 -1
- package/src/workbench/chat/ChatPanel.tsx +32 -23
- package/src/workbench/chat/ChatPhasedRunProgress.tsx +86 -13
- package/src/workbench/chat/ChatRenameDialog.tsx +1 -1
- package/src/workbench/chat/chat-conversation.css +2 -2
- package/src/workbench/chat/chat-ui.css +8 -0
- package/src/workbench/chat/chatConversation.ts +2 -2
- package/src/workbench/chat/chatMessageMeta.tsx +4 -4
- package/src/workbench/chat/index.ts +1 -0
- package/src/workbench/chat/types.ts +7 -7
- package/src/workbench/chat/useCancelRuntimeOnUnmount.ts +4 -4
- package/src/workbench/chat/useChatPrependPagination.ts +10 -10
- package/src/workbench/chat/useSlashCommandSuggest.ts +1 -1
- package/src/workbench/{WorkbenchModalPortal.tsx → chrome/WorkbenchModalPortal.tsx} +1 -1
- package/src/workbench/chrome/workbench-chrome.css +4 -0
- package/src/workbench/chrome/workbench-layout-regions.css +13 -0
- package/src/workbench/chrome/workbench-shell-titlebar.css +0 -8
- package/src/workbench/{CommandGroupShell.tsx → commands/CommandGroupShell.tsx} +2 -2
- package/src/workbench/{CommandList.tsx → commands/CommandList.tsx} +6 -5
- package/src/workbench/{CommandPalette.tsx → commands/CommandPalette.tsx} +16 -42
- package/src/workbench/{ConfirmationFlow.tsx → commands/ConfirmationFlow.tsx} +5 -5
- package/src/workbench/{ShortcutCommandBridge.tsx → commands/ShortcutCommandBridge.tsx} +17 -13
- package/src/workbench/commands/WorkbenchQuickOpen.tsx +462 -0
- package/src/workbench/{command-model.ts → commands/command-model.ts} +5 -1
- package/src/workbench/commands/command-ui.ts +3 -0
- package/src/workbench/{commands.ts → commands/commands.ts} +45 -1
- package/src/workbench/commands/createWorkspaceFilesQuickOpenProvider.ts +86 -0
- package/src/workbench/commands/quick-open-model.ts +66 -0
- package/src/workbench/{WorkbenchEditorTabs.tsx → editor/WorkbenchEditorTabs.tsx} +13 -3
- package/src/workbench/{editor-tabs-dnd.ts → editor/editor-tabs-dnd.ts} +1 -1
- package/src/workbench/{editorTabContextMenu.ts → editor/editorTabContextMenu.ts} +43 -4
- package/src/workbench/{useEditorTabsStripDnd.ts → editor/useEditorTabsStripDnd.ts} +1 -1
- package/src/workbench/{useWorkbenchEditorTabContextMenu.tsx → editor/useWorkbenchEditorTabContextMenu.tsx} +16 -2
- package/src/workbench/editor-tabs/index.ts +10 -9
- package/src/workbench/index.ts +114 -74
- package/src/workbench/management/CommandManagementSidebar.tsx +16 -30
- package/src/workbench/management/ExtensionManagementPanel.tsx +84 -46
- package/src/workbench/management/ExtensionManagementSidebar.tsx +79 -45
- package/src/workbench/management/IntegrationCommandActions.tsx +8 -1
- package/src/workbench/management/IntegrationSettingsSurface.tsx +2 -5
- package/src/workbench/management/IntegrationsShell.tsx +1 -1
- package/src/workbench/management/KeybindingCaptureField.tsx +1 -9
- package/src/workbench/management/WorkbenchDialogFrame.tsx +2 -2
- package/src/workbench/management/WorkbenchNotice.tsx +17 -12
- package/src/workbench/management/createWorkbenchNotify.tsx +88 -0
- package/src/workbench/management/extension-install-approval.ts +72 -0
- package/src/workbench/management/extension-management-filters.ts +38 -0
- package/src/workbench/management/index.ts +18 -3
- package/src/workbench/management/management.css +28 -0
- package/src/workbench/management/types.ts +23 -1
- package/src/workbench/management/useWorkbenchNotify.ts +10 -0
- package/src/workbench/{MarkdownPreview.tsx → markdown/MarkdownPreview.tsx} +22 -7
- package/src/workbench/markdown/markdownRemarkPlugins.ts +12 -0
- package/src/workbench/markdown/sanitizeMarkdownHref.ts +29 -0
- package/src/workbench/schema/workbenchDocumentRenderer.tsx +48 -121
- package/src/workbench/settings/StructuredDataForm.tsx +1 -4
- package/src/workbench/settings/StructuredDataSchemaPanel.tsx +39 -44
- package/src/workbench/settings/sectionedPanelScrollSpy.ts +0 -81
- package/src/workbench/settings/structuredDataFormModel.ts +2 -1
- package/src/workbench/settings/structuredDataSchema.ts +24 -13
- package/src/workbench/settings/structuredDataSchemaSection.ts +4 -0
- package/src/workbench/settings/structuredDataSchemaTable.ts +5 -2
- package/src/workbench/{ActivityBar.tsx → shell/ActivityBar.tsx} +8 -12
- package/src/workbench/{ArtifactShell.tsx → shell/ArtifactShell.tsx} +25 -25
- package/src/workbench/{SplitView.tsx → shell/SplitView.tsx} +3 -3
- package/src/workbench/{StatusBar.tsx → shell/StatusBar.tsx} +8 -8
- package/src/workbench/{StructuredArtifactEditor.tsx → shell/StructuredArtifactEditor.tsx} +9 -9
- package/src/workbench/{Timeline.tsx → shell/Timeline.tsx} +77 -20
- package/src/workbench/{WorkbenchCanvasShell.defaults.ts → shell/WorkbenchCanvasShell.defaults.ts} +1 -1
- package/src/workbench/{WorkbenchCanvasShell.model.ts → shell/WorkbenchCanvasShell.model.ts} +5 -5
- package/src/workbench/{WorkbenchCanvasShell.tsx → shell/WorkbenchCanvasShell.tsx} +6 -6
- package/src/workbench/{WorkbenchDesktopTitleBar.tsx → shell/WorkbenchDesktopTitleBar.tsx} +15 -15
- package/src/workbench/{WorkbenchShell.tsx → shell/WorkbenchShell.tsx} +45 -6
- package/src/workbench/{WorkbenchShellTitleBarLayoutControls.tsx → shell/WorkbenchShellTitleBarLayoutControls.tsx} +16 -19
- package/src/workbench/{WorkbenchStandaloneShell.tsx → shell/WorkbenchStandaloneShell.tsx} +15 -22
- package/src/workbench/{WorkbenchViewEditor.tsx → shell/WorkbenchViewEditor.tsx} +7 -7
- package/src/workbench/{WorkbenchViewSidebar.tsx → shell/WorkbenchViewSidebar.tsx} +7 -7
- package/src/workbench/{WorkbenchWindowChromeControls.tsx → shell/WorkbenchWindowChromeControls.tsx} +18 -15
- package/src/workbench/shell/activity-bar.css +47 -0
- package/src/workbench/{activityBarOrder.ts → shell/activityBarOrder.ts} +2 -1
- package/src/workbench/{shell.ts → shell/shell.ts} +20 -13
- package/src/workbench/{shellViewModel.ts → shell/shellViewModel.ts} +3 -3
- package/src/workbench/{standalone.ts → shell/standalone.ts} +14 -2
- package/src/workbench/{useDeferredWorkbenchMount.ts → shell/useDeferredWorkbenchMount.ts} +3 -3
- package/src/workbench/{useWorkbenchAppearanceDocumentSync.tsx → shell/useWorkbenchAppearanceDocumentSync.tsx} +1 -1
- package/src/workbench/{useWorkbenchSidebarActionBarDnd.ts → shell/useWorkbenchSidebarActionBarDnd.ts} +1 -1
- package/src/workbench/{useWorkbenchSidebarViewPlacementDropZone.ts → shell/useWorkbenchSidebarViewPlacementDropZone.ts} +2 -2
- package/src/workbench/{workbenchSidebarLayoutContext.ts → shell/workbenchSidebarLayoutContext.ts} +0 -6
- package/src/workbench/{workbenchViewRouteState.ts → shell/workbenchViewRouteState.ts} +4 -4
- package/src/workbench/{WorkbenchThemeProvider.tsx → theme/WorkbenchThemeProvider.tsx} +4 -4
- package/src/workbench/{theme.ts → theme/theme.ts} +1 -1
- package/src/workbench/{themePresets.ts → theme/themePresets.ts} +39 -64
- package/src/workbench/views/command-list-density.css +36 -0
- package/src/workbench/views/command-list.css +2 -0
- package/src/workbench/views/extensions-sidebar.css +2 -0
- package/src/workbench/views/view-hosts.css +3 -3
- package/src/workbench/workspace/WorkspaceEditorPanel.tsx +1 -1
- package/src/workbench/workspace/WorkspaceExplorer.tsx +105 -4
- package/src/workbench/workspace/createVirtualWorkspaceExplorerPort.ts +1 -14
- package/src/workbench/workspace/workspaceExplorerKeyboard.ts +109 -0
- package/src/workbench/workspace/workspaceJsonDiagnostics.ts +6 -5
- package/src/workbench-menu-surfaces.css +3 -57
- package/src/brand/TilepaperAppIcon.tsx +0 -181
- package/src/brand/index.ts +0 -2
- package/src/brand/tilepaper-app-icon-geometry.ts +0 -201
- package/src/primitives/absolute-box/index.ts +0 -1
- package/src/primitives/catalog-browse-card/index.ts +0 -1
- package/src/primitives/catalog-browse-pane/index.ts +0 -3
- package/src/primitives/catalog-filter-overlay/index.ts +0 -1
- package/src/primitives/codicon/index.ts +0 -1
- package/src/primitives/external-link-button/index.ts +0 -1
- package/src/primitives/library-detail-layout/index.ts +0 -1
- package/src/primitives/library-facet-filter-panel/index.ts +0 -1
- package/src/primitives/library-facet-filter-strip/index.ts +0 -1
- package/src/primitives/panel-loading/index.ts +0 -1
- package/src/primitives/scroll-area-infinite-load/index.ts +0 -2
- package/src/primitives/workbench-thumbnail/index.ts +0 -1
- package/src/test-utils/workbenchMonacoMock.tsx +0 -37
- package/src/workbench/jdw/jdw-editors.css +0 -4
- package/src/workbench/jdw/jdw.css +0 -2
- package/src/workbench/management/integration-command-action.ts +0 -25
- package/src/workbench/markdownRemarkPlugins.ts +0 -4
- package/src/workbench/workbench-chrome.css +0 -4
- /package/src/workbench/{WorkbenchPlatformContext.tsx → chrome/WorkbenchPlatformContext.tsx} +0 -0
- /package/src/workbench/{workbenchModalViewState.ts → chrome/workbenchModalViewState.ts} +0 -0
- /package/src/workbench/{workbenchOverlaysContext.tsx → chrome/workbenchOverlaysContext.tsx} +0 -0
- /package/src/workbench/{workbenchPlatformChrome.ts → chrome/workbenchPlatformChrome.ts} +0 -0
- /package/src/workbench/{command-execution-policy.ts → commands/command-execution-policy.ts} +0 -0
- /package/src/workbench/{keyboard.ts → commands/keyboard.ts} +0 -0
- /package/src/workbench/{workbenchContextMenu.ts → commands/workbenchContextMenu.ts} +0 -0
- /package/src/workbench/{appearanceLabels.ts → shell/appearanceLabels.ts} +0 -0
- /package/src/workbench/{hardReset.ts → shell/hardReset.ts} +0 -0
- /package/src/workbench/{shellPresets.ts → shell/shellPresets.ts} +0 -0
- /package/src/workbench/{shellState.ts → shell/shellState.ts} +0 -0
- /package/src/workbench/{sidebarViewPlacementDnd.ts → shell/sidebarViewPlacementDnd.ts} +0 -0
- /package/src/workbench/{status.ts → shell/status.ts} +0 -0
- /package/src/workbench/{useWorkbenchSidebarLayout.tsx → shell/useWorkbenchSidebarLayout.tsx} +0 -0
- /package/src/workbench/{useWorkbenchStandaloneShellStateSync.tsx → shell/useWorkbenchStandaloneShellStateSync.tsx} +0 -0
- /package/src/workbench/{workbenchStandaloneShellReactContext.tsx → shell/workbenchStandaloneShellReactContext.tsx} +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.ui-app-icon {
|
|
2
|
+
--ui-app-icon-size: 20px;
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
display: inline-flex;
|
|
5
|
+
flex: 0 0 auto;
|
|
6
|
+
align-items: center;
|
|
7
|
+
justify-content: center;
|
|
8
|
+
width: var(--ui-app-icon-size);
|
|
9
|
+
height: var(--ui-app-icon-size);
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
border-radius: var(--shell-radius-xs, var(--radius-sm, 2px));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.ui-app-icon[data-size='sm'] {
|
|
15
|
+
--ui-app-icon-size: 16px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.ui-app-icon[data-size='lg'] {
|
|
19
|
+
--ui-app-icon-size: 32px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.ui-app-icon__image,
|
|
23
|
+
.ui-app-icon > svg {
|
|
24
|
+
display: block;
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 100%;
|
|
27
|
+
object-fit: contain;
|
|
28
|
+
}
|
|
@@ -6,10 +6,10 @@ import { cx } from '../../utils/cx';
|
|
|
6
6
|
type ButtonVariant = 'default' | 'primary' | 'danger';
|
|
7
7
|
|
|
8
8
|
export interface ButtonProps extends ComponentPropsWithRef<'button'> {
|
|
9
|
-
block?: boolean
|
|
10
|
-
compact?: boolean
|
|
11
|
-
icon?: string
|
|
12
|
-
secondary?: boolean
|
|
9
|
+
block?: boolean;
|
|
10
|
+
compact?: boolean;
|
|
11
|
+
icon?: string;
|
|
12
|
+
secondary?: boolean;
|
|
13
13
|
variant?: ButtonVariant;
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -11,9 +11,9 @@ export interface CatalogBrowseCardProps extends Omit<
|
|
|
11
11
|
'children' | 'title'
|
|
12
12
|
> {
|
|
13
13
|
description?: ReactNode;
|
|
14
|
-
icon?: string
|
|
15
|
-
imageAlt?: string
|
|
16
|
-
imageUrl?: string | null
|
|
14
|
+
icon?: string;
|
|
15
|
+
imageAlt?: string;
|
|
16
|
+
imageUrl?: string | null;
|
|
17
17
|
label: ReactNode;
|
|
18
18
|
/**
|
|
19
19
|
* Custom media content for the icon/cover region. When set, replaces the
|
|
@@ -23,15 +23,15 @@ export interface CatalogBrowseCardProps extends Omit<
|
|
|
23
23
|
/** Overlay content inside the media region (badges, status chips, etc.). */
|
|
24
24
|
mediaOverlay?: ReactNode;
|
|
25
25
|
meta?: ReactNode;
|
|
26
|
-
selected?: boolean
|
|
26
|
+
selected?: boolean;
|
|
27
27
|
/**
|
|
28
28
|
* Secondary actions outside the primary hit target (for example Assign).
|
|
29
29
|
* When set, the root becomes a non-button container and click/drag handlers
|
|
30
30
|
* apply to the main control only.
|
|
31
31
|
*/
|
|
32
32
|
trailing?: ReactNode;
|
|
33
|
-
tooltip?: string
|
|
34
|
-
variant?: CatalogBrowseCardVariant
|
|
33
|
+
tooltip?: string;
|
|
34
|
+
variant?: CatalogBrowseCardVariant;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export function CatalogBrowseCard({
|
|
@@ -33,12 +33,12 @@ export type CatalogBrowseViewMode = 'grid' | 'list';
|
|
|
33
33
|
* Hosts with richer cards should pass `renderGridItem` / `renderListItem`.
|
|
34
34
|
*/
|
|
35
35
|
export interface CatalogBrowseItem {
|
|
36
|
-
readonly description?: string | null
|
|
36
|
+
readonly description?: string | null;
|
|
37
37
|
readonly id: string;
|
|
38
|
-
readonly imageAlt?: string
|
|
39
|
-
readonly imageUrl?: string | null
|
|
38
|
+
readonly imageAlt?: string;
|
|
39
|
+
readonly imageUrl?: string | null;
|
|
40
40
|
readonly label: string;
|
|
41
|
-
readonly meta?: string | null
|
|
41
|
+
readonly meta?: string | null;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export interface CatalogBrowseItemRenderState {
|
|
@@ -48,17 +48,17 @@ export interface CatalogBrowseItemRenderState {
|
|
|
48
48
|
export interface CatalogBrowsePaneProps extends Omit<ComponentPropsWithRef<'div'>, 'children'> {
|
|
49
49
|
readonly clearSearchLabel: string;
|
|
50
50
|
readonly emptyMessage: string;
|
|
51
|
-
readonly errorMessage?: string | null
|
|
51
|
+
readonly errorMessage?: string | null;
|
|
52
52
|
/** Slot between search and sort/view controls (facet trigger, chips, etc.). */
|
|
53
53
|
readonly facetStrip?: ReactNode;
|
|
54
|
-
readonly gridAriaLabel?: string
|
|
54
|
+
readonly gridAriaLabel?: string;
|
|
55
55
|
/** Visual label for the grid view-mode segment (string or icon node). */
|
|
56
56
|
readonly gridLabel?: ReactNode;
|
|
57
57
|
readonly hasMore: boolean;
|
|
58
58
|
readonly isLoading: boolean;
|
|
59
59
|
readonly isLoadingMore: boolean;
|
|
60
60
|
readonly items: ReadonlyArray<CatalogBrowseItem>;
|
|
61
|
-
readonly listAriaLabel?: string
|
|
61
|
+
readonly listAriaLabel?: string;
|
|
62
62
|
/** Visual label for the list view-mode segment (string or icon node). */
|
|
63
63
|
readonly listLabel?: ReactNode;
|
|
64
64
|
readonly loadingMessage: string;
|
|
@@ -66,11 +66,11 @@ export interface CatalogBrowsePaneProps extends Omit<ComponentPropsWithRef<'div'
|
|
|
66
66
|
((itemId: string, itemLabel: string, event: ReactMouseEvent<HTMLElement>) => void) | undefined;
|
|
67
67
|
readonly onLoadMore: () => void;
|
|
68
68
|
readonly onOpenItem: (itemId: string) => void;
|
|
69
|
-
readonly onRefresh?: (() => void) | null
|
|
69
|
+
readonly onRefresh?: (() => void) | null;
|
|
70
70
|
readonly onSearchQueryChange: (query: string) => void;
|
|
71
|
-
readonly onSortChange?: (
|
|
71
|
+
readonly onSortChange?: (sort: string) => void;
|
|
72
72
|
readonly onViewModeChange: (viewMode: CatalogBrowseViewMode) => void;
|
|
73
|
-
readonly refreshLabel?: string | null
|
|
73
|
+
readonly refreshLabel?: string | null;
|
|
74
74
|
readonly renderGridItem?:
|
|
75
75
|
((item: CatalogBrowseItem, state: CatalogBrowseItemRenderState) => ReactNode) | undefined;
|
|
76
76
|
readonly renderListItem?:
|
|
@@ -78,14 +78,14 @@ export interface CatalogBrowsePaneProps extends Omit<ComponentPropsWithRef<'div'
|
|
|
78
78
|
readonly searchAriaLabel: string;
|
|
79
79
|
readonly searchPlaceholder: string;
|
|
80
80
|
readonly searchQuery: string;
|
|
81
|
-
readonly selectedItemId?: string | null
|
|
82
|
-
readonly sort?: string
|
|
83
|
-
readonly sortAriaLabel?: string
|
|
84
|
-
readonly sortOptions?: ReadonlyArray<{ label: string; value: string }
|
|
81
|
+
readonly selectedItemId?: string | null;
|
|
82
|
+
readonly sort?: string;
|
|
83
|
+
readonly sortAriaLabel?: string;
|
|
84
|
+
readonly sortOptions?: ReadonlyArray<{ label: string; value: string }>;
|
|
85
85
|
/** Extra trailing controls after built-in sort / view mode (before refresh). */
|
|
86
86
|
readonly toolbarTrailing?: ReactNode;
|
|
87
87
|
readonly viewMode: CatalogBrowseViewMode;
|
|
88
|
-
readonly viewModeAriaLabel?: string
|
|
88
|
+
readonly viewModeAriaLabel?: string;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
export function CatalogBrowsePane({
|
|
@@ -240,25 +240,32 @@ export function CatalogBrowsePane({
|
|
|
240
240
|
>
|
|
241
241
|
{items.map((item) => {
|
|
242
242
|
const selected = selectedItemId === item.id;
|
|
243
|
-
if (renderGridItem) {
|
|
244
|
-
return <Fragment key={item.id}>{renderGridItem(item, { selected })}</Fragment>;
|
|
245
|
-
}
|
|
246
243
|
return (
|
|
247
|
-
<
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
imageUrl={item.imageUrl ?? null}
|
|
244
|
+
<div
|
|
245
|
+
className="ui-catalog-browse__item"
|
|
246
|
+
data-ui-catalog-browse-item-container={item.id}
|
|
251
247
|
key={item.id}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
248
|
+
role="listitem"
|
|
249
|
+
>
|
|
250
|
+
{renderGridItem ? (
|
|
251
|
+
renderGridItem(item, { selected })
|
|
252
|
+
) : (
|
|
253
|
+
<CatalogBrowseCard
|
|
254
|
+
data-ui-catalog-browse-item={item.id}
|
|
255
|
+
imageAlt={item.imageAlt ?? item.label}
|
|
256
|
+
imageUrl={item.imageUrl ?? null}
|
|
257
|
+
label={item.label}
|
|
258
|
+
meta={item.meta ?? undefined}
|
|
259
|
+
onClick={() => onOpenItem(item.id)}
|
|
260
|
+
onContextMenu={(event) => {
|
|
261
|
+
onItemContextMenu?.(item.id, item.label, event);
|
|
262
|
+
}}
|
|
263
|
+
selected={selected}
|
|
264
|
+
tooltip={item.label}
|
|
265
|
+
variant="cover"
|
|
266
|
+
/>
|
|
267
|
+
)}
|
|
268
|
+
</div>
|
|
262
269
|
);
|
|
263
270
|
})}
|
|
264
271
|
<BrowseScrollFooter
|
|
@@ -13,10 +13,10 @@ export interface CatalogFilterOverlayProps extends Omit<
|
|
|
13
13
|
* When true, Clear stays mounted and sized but is not actionable.
|
|
14
14
|
* Keeps the title row height stable across selection changes.
|
|
15
15
|
*/
|
|
16
|
-
readonly clearDisabled?: boolean
|
|
16
|
+
readonly clearDisabled?: boolean;
|
|
17
17
|
readonly clearLabel: string;
|
|
18
18
|
readonly onClear: () => void;
|
|
19
|
-
readonly ref?: Ref<HTMLDivElement
|
|
19
|
+
readonly ref?: Ref<HTMLDivElement>;
|
|
20
20
|
readonly title: ReactNode;
|
|
21
21
|
readonly titleId: string;
|
|
22
22
|
}
|
|
@@ -3,7 +3,7 @@ import type { ChangeEvent, ComponentPropsWithRef, ReactNode } from 'react';
|
|
|
3
3
|
import { cx } from '../../utils/cx';
|
|
4
4
|
|
|
5
5
|
export interface CheckboxProps extends Omit<ComponentPropsWithRef<'input'>, 'type'> {
|
|
6
|
-
label?: ReactNode
|
|
6
|
+
label?: ReactNode;
|
|
7
7
|
onCheckedChange?: (checked: boolean, event: ChangeEvent<HTMLInputElement>) => void;
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -4,15 +4,12 @@ import { Button } from '../button';
|
|
|
4
4
|
import { cx } from '../../utils/cx';
|
|
5
5
|
|
|
6
6
|
export interface ChipProps extends Omit<ComponentPropsWithRef<'button'>, 'children'> {
|
|
7
|
-
count?: number
|
|
7
|
+
count?: number;
|
|
8
8
|
label: ReactNode;
|
|
9
|
-
onDismiss?: (
|
|
9
|
+
onDismiss?: () => void;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Compact labeled control, optionally dismissible (close affordance).
|
|
14
|
-
* Preferred over layout-scoped `FilterChip`.
|
|
15
|
-
*/
|
|
12
|
+
/** Compact labeled control, optionally dismissible (close affordance). */
|
|
16
13
|
export function Chip({ className, count, label, onDismiss, type = 'button', ...props }: ChipProps) {
|
|
17
14
|
return (
|
|
18
15
|
<Button className={cx('ui-chip', 'ui-filter-chip', className)} type={type} {...props}>
|
|
@@ -35,9 +32,3 @@ export function Chip({ className, count, label, onDismiss, type = 'button', ...p
|
|
|
35
32
|
</Button>
|
|
36
33
|
);
|
|
37
34
|
}
|
|
38
|
-
|
|
39
|
-
/** @deprecated Use {@link Chip} / {@link ChipProps}. */
|
|
40
|
-
export const FilterChip = Chip;
|
|
41
|
-
|
|
42
|
-
/** @deprecated Use {@link ChipProps}. */
|
|
43
|
-
export type FilterChipProps = ChipProps;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { Chip
|
|
2
|
-
export type { ChipProps
|
|
1
|
+
export { Chip } from './Chip';
|
|
2
|
+
export type { ChipProps } from './Chip';
|
|
@@ -4,7 +4,7 @@ import { cxCodicon } from '../../utils/codicon';
|
|
|
4
4
|
export interface CodiconProps extends Omit<ComponentPropsWithRef<'i'>, 'children'> {
|
|
5
5
|
icon: string;
|
|
6
6
|
/** When set, exposes the icon to assistive tech instead of aria-hidden. */
|
|
7
|
-
label?: string
|
|
7
|
+
label?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/** VS Code codicon glyph (`codicon` font). Requires `@vscode/codicons` CSS in the app entry. Prefer `WorkbenchIcon` for new code. */
|
|
@@ -6,7 +6,7 @@ export const UI_FILE_ICON_CLASS = 'ui-file-icon';
|
|
|
6
6
|
|
|
7
7
|
export interface FileIconProps extends Omit<ComponentPropsWithRef<'i'>, 'children'> {
|
|
8
8
|
icon: string;
|
|
9
|
-
kind?: FileIconKind
|
|
9
|
+
kind?: FileIconKind;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function FileIcon({ className, icon, kind, ...props }: FileIconProps) {
|
|
@@ -7,7 +7,7 @@ import { cx } from '../../utils/cx';
|
|
|
7
7
|
type IconButtonVariant = 'default' | 'danger';
|
|
8
8
|
|
|
9
9
|
export interface IconButtonProps extends Omit<ComponentPropsWithRef<'button'>, 'children'> {
|
|
10
|
-
compact?: boolean
|
|
10
|
+
compact?: boolean;
|
|
11
11
|
icon: WorkbenchIconInput;
|
|
12
12
|
label: string;
|
|
13
13
|
variant?: IconButtonVariant;
|
package/src/primitives/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { AbsoluteBox } from './absolute-box/AbsoluteBox';
|
|
2
2
|
export type { AbsoluteBoxProps, WorkbenchRect } from './absolute-box/AbsoluteBox';
|
|
3
|
+
export { AppIcon } from './app-icon/AppIcon';
|
|
4
|
+
export type { AppIconProps, AppIconSize } from './app-icon/AppIcon';
|
|
3
5
|
export { Badge } from './badge/Badge';
|
|
4
6
|
export type { BadgeProps } from './badge/Badge';
|
|
5
7
|
export { Button } from './button/Button';
|
|
@@ -110,6 +112,11 @@ export type {
|
|
|
110
112
|
LibraryDetailLayoutMode,
|
|
111
113
|
LibraryDetailLayoutProps,
|
|
112
114
|
} from './library-detail-layout/LibraryDetailLayout';
|
|
115
|
+
export { resolveLibraryDetailHeroCoverMedia } from './library-detail-layout/resolve-hero-cover-media.js';
|
|
116
|
+
export type {
|
|
117
|
+
ResolveLibraryDetailHeroCoverMediaInput,
|
|
118
|
+
ResolvedLibraryDetailHeroCoverMedia,
|
|
119
|
+
} from './library-detail-layout/resolve-hero-cover-media.js';
|
|
113
120
|
export { ScrollAreaInfiniteSentinel } from './scroll-area-infinite-load/ScrollAreaInfiniteSentinel';
|
|
114
121
|
export type { ScrollAreaInfiniteSentinelProps } from './scroll-area-infinite-load/ScrollAreaInfiniteSentinel';
|
|
115
122
|
export { useScrollAreaInfiniteLoad } from './scroll-area-infinite-load/useScrollAreaInfiniteLoad';
|
|
@@ -145,7 +152,6 @@ export {
|
|
|
145
152
|
FilterBar,
|
|
146
153
|
FilterBarActiveChips,
|
|
147
154
|
FilterBarRow,
|
|
148
|
-
FilterChip,
|
|
149
155
|
HelpText,
|
|
150
156
|
Panel,
|
|
151
157
|
PanelBody,
|
|
@@ -164,7 +170,6 @@ export type {
|
|
|
164
170
|
FilterBarActiveChipsProps,
|
|
165
171
|
FilterBarProps,
|
|
166
172
|
FilterBarRowProps,
|
|
167
|
-
FilterChipProps,
|
|
168
173
|
HelpTextProps,
|
|
169
174
|
PanelBodyProps,
|
|
170
175
|
PanelFooterProps,
|
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
import './library-detail-layout.css';
|
|
2
|
-
import type
|
|
2
|
+
import { useEffect, useState, type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
import { cx } from '../../utils/cx';
|
|
5
5
|
import { WorkbenchMediaSlot } from '../workbench-media-slot';
|
|
6
6
|
import { RecordMediaHero } from '../record-media-hero';
|
|
7
7
|
import { ScrollArea } from '../scroll-area';
|
|
8
|
+
import { resolveLibraryDetailHeroCoverMedia } from './resolve-hero-cover-media.js';
|
|
8
9
|
|
|
9
|
-
export type LibraryDetailLayoutMode = 'background' | 'banner' | 'compact';
|
|
10
|
+
export type LibraryDetailLayoutMode = 'background' | 'banner' | 'compact' | 'hero-cover';
|
|
10
11
|
|
|
11
12
|
export interface LibraryDetailLayoutProps extends Omit<
|
|
12
13
|
ComponentPropsWithRef<'div'>,
|
|
13
14
|
'children' | 'title'
|
|
14
15
|
> {
|
|
15
16
|
readonly actions?: ReactNode;
|
|
16
|
-
|
|
17
|
+
/** Optional attribution / footer below the identity row (`hero-cover` mode). */
|
|
18
|
+
readonly attribution?: ReactNode;
|
|
19
|
+
readonly backgroundImageUrl?: string | null;
|
|
17
20
|
readonly children?: ReactNode;
|
|
18
|
-
readonly coverAlt?: string
|
|
19
|
-
readonly coverImageUrl?: string | null
|
|
21
|
+
readonly coverAlt?: string;
|
|
22
|
+
readonly coverImageUrl?: string | null;
|
|
20
23
|
readonly description?: ReactNode;
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Wide hero band URL for `hero-cover` mode. When omitted or identical to
|
|
26
|
+
* `coverImageUrl`, the cover is used as soft atmosphere behind the portrait.
|
|
27
|
+
*/
|
|
28
|
+
readonly heroImageUrl?: string | null;
|
|
29
|
+
readonly logoImageUrl?: string | null;
|
|
30
|
+
readonly mode?: LibraryDetailLayoutMode;
|
|
23
31
|
readonly summary?: ReactNode;
|
|
24
32
|
readonly title: ReactNode;
|
|
25
33
|
readonly toolbar?: ReactNode;
|
|
@@ -27,12 +35,14 @@ export interface LibraryDetailLayoutProps extends Omit<
|
|
|
27
35
|
|
|
28
36
|
export function LibraryDetailLayout({
|
|
29
37
|
actions,
|
|
38
|
+
attribution,
|
|
30
39
|
backgroundImageUrl = null,
|
|
31
40
|
children,
|
|
32
41
|
className,
|
|
33
42
|
coverAlt,
|
|
34
43
|
coverImageUrl = null,
|
|
35
44
|
description,
|
|
45
|
+
heroImageUrl = null,
|
|
36
46
|
logoImageUrl = null,
|
|
37
47
|
mode = 'banner',
|
|
38
48
|
summary,
|
|
@@ -41,7 +51,13 @@ export function LibraryDetailLayout({
|
|
|
41
51
|
...props
|
|
42
52
|
}: LibraryDetailLayoutProps): ReactNode {
|
|
43
53
|
const resolvedMode =
|
|
44
|
-
mode === 'background'
|
|
54
|
+
mode === 'background'
|
|
55
|
+
? 'background'
|
|
56
|
+
: mode === 'compact'
|
|
57
|
+
? 'compact'
|
|
58
|
+
: mode === 'hero-cover'
|
|
59
|
+
? 'hero-cover'
|
|
60
|
+
: 'banner';
|
|
45
61
|
|
|
46
62
|
return (
|
|
47
63
|
<div
|
|
@@ -54,7 +70,18 @@ export function LibraryDetailLayout({
|
|
|
54
70
|
{...props}
|
|
55
71
|
>
|
|
56
72
|
{toolbar}
|
|
57
|
-
{resolvedMode === '
|
|
73
|
+
{resolvedMode === 'hero-cover' ? (
|
|
74
|
+
<LibraryDetailHeroCover
|
|
75
|
+
actions={actions}
|
|
76
|
+
attribution={attribution}
|
|
77
|
+
coverAlt={coverAlt}
|
|
78
|
+
coverImageUrl={coverImageUrl}
|
|
79
|
+
description={description}
|
|
80
|
+
heroImageUrl={heroImageUrl ?? backgroundImageUrl}
|
|
81
|
+
summary={summary}
|
|
82
|
+
title={title}
|
|
83
|
+
/>
|
|
84
|
+
) : resolvedMode === 'background' ? (
|
|
58
85
|
<div className="ui-library-detail-layout__band" data-ui-library-detail-band="true">
|
|
59
86
|
<RecordMediaHero
|
|
60
87
|
alt={coverAlt}
|
|
@@ -105,6 +132,93 @@ export function LibraryDetailLayout({
|
|
|
105
132
|
);
|
|
106
133
|
}
|
|
107
134
|
|
|
135
|
+
function LibraryDetailHeroCover({
|
|
136
|
+
actions,
|
|
137
|
+
attribution,
|
|
138
|
+
coverAlt,
|
|
139
|
+
coverImageUrl,
|
|
140
|
+
description,
|
|
141
|
+
heroImageUrl,
|
|
142
|
+
summary,
|
|
143
|
+
title,
|
|
144
|
+
}: {
|
|
145
|
+
actions?: ReactNode;
|
|
146
|
+
attribution?: ReactNode;
|
|
147
|
+
coverAlt?: string;
|
|
148
|
+
coverImageUrl: string | null;
|
|
149
|
+
description?: ReactNode;
|
|
150
|
+
heroImageUrl: string | null;
|
|
151
|
+
summary?: ReactNode;
|
|
152
|
+
title: ReactNode;
|
|
153
|
+
}): ReactNode {
|
|
154
|
+
const [heroFailed, setHeroFailed] = useState(false);
|
|
155
|
+
const [coverFailed, setCoverFailed] = useState(false);
|
|
156
|
+
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
setHeroFailed(false);
|
|
159
|
+
}, [heroImageUrl]);
|
|
160
|
+
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
setCoverFailed(false);
|
|
163
|
+
}, [coverImageUrl]);
|
|
164
|
+
|
|
165
|
+
const media = resolveLibraryDetailHeroCoverMedia({
|
|
166
|
+
heroImageUrl,
|
|
167
|
+
coverImageUrl,
|
|
168
|
+
heroFailed,
|
|
169
|
+
coverFailed,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div className="ui-library-detail-layout__hero-cover" data-ui-library-detail-hero-cover="true">
|
|
174
|
+
<div className="ui-library-detail-layout__band" data-ui-library-detail-band="true">
|
|
175
|
+
<RecordMediaHero
|
|
176
|
+
alt={coverAlt}
|
|
177
|
+
className={cx(
|
|
178
|
+
'ui-library-detail-layout__band-media',
|
|
179
|
+
media.bandKind === 'atmosphere' && 'ui-library-detail-layout__band-media--atmosphere',
|
|
180
|
+
)}
|
|
181
|
+
data-atmosphere={media.bandKind === 'atmosphere' ? 'true' : undefined}
|
|
182
|
+
fallbackIcon="library"
|
|
183
|
+
imageUrl={media.bandImageUrl}
|
|
184
|
+
layout="background"
|
|
185
|
+
onImageError={() => {
|
|
186
|
+
if (media.bandKind === 'hero') {
|
|
187
|
+
setHeroFailed(true);
|
|
188
|
+
} else if (media.bandKind === 'atmosphere') {
|
|
189
|
+
setCoverFailed(true);
|
|
190
|
+
}
|
|
191
|
+
}}
|
|
192
|
+
/>
|
|
193
|
+
<div aria-hidden className="ui-library-detail-layout__band-overlay" />
|
|
194
|
+
</div>
|
|
195
|
+
<div className="ui-library-detail-layout__hero-cover-body">
|
|
196
|
+
{media.showPortraitCover ? (
|
|
197
|
+
<RecordMediaHero
|
|
198
|
+
alt={coverAlt}
|
|
199
|
+
className="ui-library-detail-layout__portrait-cover"
|
|
200
|
+
fallbackIcon="library"
|
|
201
|
+
imageUrl={media.resolvedCover}
|
|
202
|
+
layout="compact"
|
|
203
|
+
onImageError={() => setCoverFailed(true)}
|
|
204
|
+
/>
|
|
205
|
+
) : null}
|
|
206
|
+
<div className="ui-library-detail-layout__hero-content">
|
|
207
|
+
<div className="ui-library-detail-layout__title">{title}</div>
|
|
208
|
+
{summary ? <div className="ui-library-detail-layout__summary">{summary}</div> : null}
|
|
209
|
+
{actions ? <div className="ui-library-detail-layout__actions">{actions}</div> : null}
|
|
210
|
+
{description ? (
|
|
211
|
+
<div className="ui-library-detail-layout__description">{description}</div>
|
|
212
|
+
) : null}
|
|
213
|
+
{attribution ? (
|
|
214
|
+
<div className="ui-library-detail-layout__attribution">{attribution}</div>
|
|
215
|
+
) : null}
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
108
222
|
function LibraryDetailIdentity({
|
|
109
223
|
logoImageUrl,
|
|
110
224
|
summary,
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
--shell-library-capsule-list-height: 43px;
|
|
5
5
|
--shell-detail-hero-cover-width: 231px;
|
|
6
6
|
--shell-detail-hero-cover-height: calc(var(--shell-detail-hero-cover-width, 231px) * 43 / 116);
|
|
7
|
+
--shell-detail-portrait-cover-width: 120px;
|
|
8
|
+
--shell-detail-portrait-cover-height: 160px;
|
|
9
|
+
--shell-detail-atmosphere-blur: 18px;
|
|
10
|
+
--shell-detail-atmosphere-scale: 1.12;
|
|
11
|
+
--shell-detail-atmosphere-opacity: 0.55;
|
|
7
12
|
--shell-library-detail-logo-size: 56px;
|
|
8
13
|
--shell-library-detail-thumb-width: var(--shell-library-capsule-list-width, 116px);
|
|
9
14
|
--shell-library-detail-thumb-height: var(--shell-library-capsule-list-height, 43px);
|
|
@@ -167,3 +172,55 @@
|
|
|
167
172
|
aspect-ratio: var(--shell-library-capsule-aspect, 116 / 43);
|
|
168
173
|
flex: 0 0 auto;
|
|
169
174
|
}
|
|
175
|
+
|
|
176
|
+
.ui-library-detail-layout__hero-cover {
|
|
177
|
+
display: grid;
|
|
178
|
+
gap: 0;
|
|
179
|
+
min-width: 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.ui-library-detail-layout__hero-cover .ui-library-detail-layout__band {
|
|
183
|
+
border-bottom-left-radius: 0;
|
|
184
|
+
border-bottom-right-radius: 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.ui-library-detail-layout__band-media--atmosphere .ui-record-media-hero__image,
|
|
188
|
+
.ui-library-detail-layout__band-media[data-atmosphere='true'] .ui-record-media-hero__image {
|
|
189
|
+
opacity: var(--shell-detail-atmosphere-opacity, 0.55);
|
|
190
|
+
filter: blur(var(--shell-detail-atmosphere-blur, 18px));
|
|
191
|
+
transform: scale(var(--shell-detail-atmosphere-scale, 1.12));
|
|
192
|
+
object-fit: cover;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.ui-library-detail-layout__hero-cover-body {
|
|
196
|
+
display: flex;
|
|
197
|
+
gap: 16px;
|
|
198
|
+
align-items: flex-end;
|
|
199
|
+
margin-top: -48px;
|
|
200
|
+
padding: 0 16px 8px;
|
|
201
|
+
position: relative;
|
|
202
|
+
z-index: 2;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.ui-library-detail-layout__portrait-cover.ui-record-media-hero--compact {
|
|
206
|
+
box-sizing: border-box;
|
|
207
|
+
width: var(--shell-detail-portrait-cover-width, 120px);
|
|
208
|
+
min-width: var(--shell-detail-portrait-cover-width, 120px);
|
|
209
|
+
max-width: var(--shell-detail-portrait-cover-width, 120px);
|
|
210
|
+
height: var(--shell-detail-portrait-cover-height, 160px);
|
|
211
|
+
min-height: var(--shell-detail-portrait-cover-height, 160px);
|
|
212
|
+
max-height: var(--shell-detail-portrait-cover-height, 160px);
|
|
213
|
+
aspect-ratio: auto;
|
|
214
|
+
flex: 0 0 auto;
|
|
215
|
+
border: 1px solid
|
|
216
|
+
var(--shell-color-border-subtle, var(--color-border-subtle, rgba(255, 255, 255, 0.12)));
|
|
217
|
+
border-radius: var(--shell-radius-sm, 4px);
|
|
218
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
|
|
219
|
+
overflow: hidden;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.ui-library-detail-layout__attribution {
|
|
223
|
+
color: var(--shell-color-text-subtle, var(--color-text-subtle, var(--color-text-muted)));
|
|
224
|
+
font-size: var(--shell-font-size-caption, 0.75rem);
|
|
225
|
+
line-height: 1.4;
|
|
226
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure media resolution for LibraryDetailLayout `hero-cover` mode.
|
|
3
|
+
* Implements atmosphere-from-cover when no distinct wide hero URL succeeds.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ResolveLibraryDetailHeroCoverMediaInput {
|
|
7
|
+
readonly heroImageUrl?: string | null;
|
|
8
|
+
readonly coverImageUrl?: string | null;
|
|
9
|
+
readonly heroFailed?: boolean;
|
|
10
|
+
readonly coverFailed?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ResolvedLibraryDetailHeroCoverMedia {
|
|
14
|
+
/** Trimmed cover URL when load has not failed. */
|
|
15
|
+
readonly resolvedCover: string | null;
|
|
16
|
+
/** Distinct wide hero URL (differs from cover) when load has not failed. */
|
|
17
|
+
readonly resolvedHero: string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Soft backdrop URL when there is no successful distinct hero but cover is
|
|
20
|
+
* available. Must not be stretched as the sole full-bleed identity cover.
|
|
21
|
+
*/
|
|
22
|
+
readonly atmosphereUrl: string | null;
|
|
23
|
+
/** Band media to render: distinct hero, else atmosphere, else null (fallback). */
|
|
24
|
+
readonly bandImageUrl: string | null;
|
|
25
|
+
readonly bandKind: 'hero' | 'atmosphere' | 'fallback';
|
|
26
|
+
readonly showPortraitCover: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function trimUrl(value: string | null | undefined): string | null {
|
|
30
|
+
if (value === null || value === undefined) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const trimmed = value.trim();
|
|
34
|
+
return trimmed === '' ? null : trimmed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function resolveLibraryDetailHeroCoverMedia(
|
|
38
|
+
input: ResolveLibraryDetailHeroCoverMediaInput,
|
|
39
|
+
): ResolvedLibraryDetailHeroCoverMedia {
|
|
40
|
+
const coverSource = trimUrl(input.coverImageUrl);
|
|
41
|
+
const heroSource = trimUrl(input.heroImageUrl);
|
|
42
|
+
const heroFailed = input.heroFailed ?? false;
|
|
43
|
+
const coverFailed = input.coverFailed ?? false;
|
|
44
|
+
|
|
45
|
+
const resolvedCover = coverSource && !coverFailed ? coverSource : null;
|
|
46
|
+
const distinctHero = heroSource && coverSource && heroSource === coverSource ? null : heroSource;
|
|
47
|
+
const resolvedHero = distinctHero && !heroFailed ? distinctHero : null;
|
|
48
|
+
const atmosphereUrl = !resolvedHero && resolvedCover ? resolvedCover : null;
|
|
49
|
+
|
|
50
|
+
if (resolvedHero) {
|
|
51
|
+
return {
|
|
52
|
+
resolvedCover,
|
|
53
|
+
resolvedHero,
|
|
54
|
+
atmosphereUrl: null,
|
|
55
|
+
bandImageUrl: resolvedHero,
|
|
56
|
+
bandKind: 'hero',
|
|
57
|
+
showPortraitCover: resolvedCover !== null,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (atmosphereUrl) {
|
|
62
|
+
return {
|
|
63
|
+
resolvedCover,
|
|
64
|
+
resolvedHero: null,
|
|
65
|
+
atmosphereUrl,
|
|
66
|
+
bandImageUrl: atmosphereUrl,
|
|
67
|
+
bandKind: 'atmosphere',
|
|
68
|
+
showPortraitCover: true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
resolvedCover,
|
|
74
|
+
resolvedHero: null,
|
|
75
|
+
atmosphereUrl: null,
|
|
76
|
+
bandImageUrl: null,
|
|
77
|
+
bandKind: 'fallback',
|
|
78
|
+
showPortraitCover: false,
|
|
79
|
+
};
|
|
80
|
+
}
|