@workbench-kit/react 0.0.2-prototype.0.2.2 → 0.0.2-prototype.0.2.21
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 +125 -0
- package/package.json +27 -20
- package/src/index.ts +60 -29
- 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/json-config/JsonConfigWorkbench.tsx +1 -1
- package/src/layout/index.ts +11 -0
- 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 +112 -5
- package/src/overlay/context-menu.css +4 -2
- package/src/overlay/measureAnchoredOverlayPanel.ts +4 -6
- package/src/overlay/usePointerPassthroughRegion.ts +2 -0
- package/src/primitives/absolute-box/AbsoluteBox.tsx +4 -4
- 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 +15 -15
- package/src/primitives/catalog-filter-overlay/CatalogFilterOverlay.tsx +2 -2
- package/src/primitives/checkbox/Checkbox.tsx +1 -1
- package/src/primitives/chip/Chip.tsx +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 +5 -0
- package/src/primitives/library-detail-layout/LibraryDetailLayout.tsx +123 -9
- package/src/primitives/library-detail-layout/index.ts +5 -0
- 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/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/styles.css +1 -2
- package/src/test-utils/workbenchMonacoMock.tsx +20 -0
- 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/WidgetTreeCanvasPreview.tsx +1 -4
- package/src/widget-tree/WidgetTreeWorkspaceShell.tsx +2 -2
- 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/{CommandGroupShell.tsx → commands/CommandGroupShell.tsx} +2 -2
- package/src/workbench/{CommandList.tsx → commands/CommandList.tsx} +5 -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} +1 -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} +1 -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} +1 -1
- package/src/workbench/{editor-tabs-dnd.ts → editor/editor-tabs-dnd.ts} +1 -1
- package/src/workbench/{editorTabContextMenu.ts → editor/editorTabContextMenu.ts} +2 -2
- package/src/workbench/{useEditorTabsStripDnd.ts → editor/useEditorTabsStripDnd.ts} +1 -1
- package/src/workbench/{useWorkbenchEditorTabContextMenu.tsx → editor/useWorkbenchEditorTabContextMenu.tsx} +1 -1
- package/src/workbench/editor-tabs/index.ts +9 -9
- package/src/workbench/index.ts +110 -74
- package/src/workbench/management/ExtensionManagementPanel.tsx +32 -36
- package/src/workbench/management/ExtensionManagementSidebar.tsx +19 -35
- 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 +2 -5
- 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 +14 -0
- package/src/workbench/management/management.css +28 -0
- package/src/workbench/management/types.ts +14 -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/settings/StructuredDataForm.tsx +1 -4
- package/src/workbench/settings/structuredDataFormModel.ts +2 -1
- package/src/workbench/{ActivityBar.tsx → shell/ActivityBar.tsx} +8 -8
- 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} +4 -4
- 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/{shell.ts → shell/shell.ts} +18 -12
- 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/{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} +3 -2
- package/src/workbench/views/view-hosts.css +3 -3
- package/src/workbench/workspace/WorkspaceEditorPanel.tsx +1 -1
- package/src/workbench/workspace/WorkspaceExplorer.tsx +104 -3
- package/src/workbench/workspace/workspaceExplorerKeyboard.ts +109 -0
- package/src/workbench/workspace/workspaceJsonDiagnostics.ts +6 -5
- package/src/workbench/jdw/jdw-editors.css +0 -4
- package/src/workbench/jdw/jdw.css +0 -2
- 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/{activityBarOrder.ts → shell/activityBarOrder.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/{workbenchSidebarLayoutContext.ts → shell/workbenchSidebarLayoutContext.ts} +0 -0
- /package/src/workbench/{workbenchStandaloneShellReactContext.tsx → shell/workbenchStandaloneShellReactContext.tsx} +0 -0
|
@@ -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
|
+
}
|
|
@@ -38,7 +38,7 @@ export interface LibraryFacetSection {
|
|
|
38
38
|
* Uncontrolled initial collapsed state when `collapsibleSections` is true.
|
|
39
39
|
* When omitted, sections start expanded.
|
|
40
40
|
*/
|
|
41
|
-
readonly defaultCollapsed?: boolean
|
|
41
|
+
readonly defaultCollapsed?: boolean;
|
|
42
42
|
readonly fields: ReadonlyArray<LibraryFacetField>;
|
|
43
43
|
readonly id: string;
|
|
44
44
|
}
|
|
@@ -46,33 +46,33 @@ export interface LibraryFacetSection {
|
|
|
46
46
|
export interface LibraryFacetFilterPanelLabels {
|
|
47
47
|
readonly clearAll: string;
|
|
48
48
|
readonly clearChipAria: (chipLabel: string) => string;
|
|
49
|
-
readonly empty?: string
|
|
49
|
+
readonly empty?: string;
|
|
50
50
|
/** Aria/placeholder for searchable facet input. Default: "Search options". */
|
|
51
|
-
readonly filterOptions?: string
|
|
51
|
+
readonly filterOptions?: string;
|
|
52
52
|
/** Shown when option filter matches nothing. Default: "No matching options". */
|
|
53
|
-
readonly noMatchingOptions?: string
|
|
53
|
+
readonly noMatchingOptions?: string;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export interface LibraryFacetFilterPanelProps {
|
|
57
|
-
readonly activeChips?: ReadonlyArray<LibraryFacetActiveChip
|
|
58
|
-
readonly className?: string
|
|
57
|
+
readonly activeChips?: ReadonlyArray<LibraryFacetActiveChip>;
|
|
58
|
+
readonly className?: string;
|
|
59
59
|
/**
|
|
60
60
|
* When true, section headers toggle collapse.
|
|
61
61
|
* Default false — keeps all sections expanded.
|
|
62
62
|
*/
|
|
63
|
-
readonly collapsibleSections?: boolean
|
|
64
|
-
readonly description?: string
|
|
63
|
+
readonly collapsibleSections?: boolean;
|
|
64
|
+
readonly description?: string;
|
|
65
65
|
readonly labels: LibraryFacetFilterPanelLabels;
|
|
66
66
|
/** Required when `showActiveChips` is true (chip strip Clear). Host may own Clear otherwise. */
|
|
67
|
-
readonly onClearAll?: (
|
|
68
|
-
readonly onRemoveChip?: (
|
|
67
|
+
readonly onClearAll?: () => void;
|
|
68
|
+
readonly onRemoveChip?: (chipId: string) => void;
|
|
69
69
|
readonly onToggleFacetValue: (
|
|
70
70
|
fieldId: string,
|
|
71
71
|
value: string,
|
|
72
72
|
kind: LibraryFacetFieldKind,
|
|
73
73
|
) => void;
|
|
74
74
|
readonly resolveFieldLabel: (fieldId: string) => string;
|
|
75
|
-
readonly resolveSectionLabel?: (
|
|
75
|
+
readonly resolveSectionLabel?: (sectionId: string) => string;
|
|
76
76
|
readonly sections: ReadonlyArray<LibraryFacetSection>;
|
|
77
77
|
readonly selectedValues: Readonly<Record<string, readonly string[]>>;
|
|
78
78
|
/**
|
|
@@ -80,7 +80,7 @@ export interface LibraryFacetFilterPanelProps {
|
|
|
80
80
|
* selection). Hosts should place Clear in their own header chrome.
|
|
81
81
|
* Default true for backward compatibility.
|
|
82
82
|
*/
|
|
83
|
-
readonly showActiveChips?: boolean
|
|
83
|
+
readonly showActiveChips?: boolean;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
function sectionHasSelection(
|
|
@@ -26,7 +26,7 @@ export type LibraryFacetFieldKind = 'single-select' | 'multi-select';
|
|
|
26
26
|
export type LibraryFacetFieldPresentation = 'checklist' | 'searchable-multi';
|
|
27
27
|
|
|
28
28
|
export interface LibraryFacetFieldOption {
|
|
29
|
-
readonly count?: number
|
|
29
|
+
readonly count?: number;
|
|
30
30
|
readonly label: string;
|
|
31
31
|
readonly value: string;
|
|
32
32
|
}
|
|
@@ -43,7 +43,7 @@ export interface LibraryFacetField {
|
|
|
43
43
|
* Panel presentation hint. When omitted, long multi-select lists use
|
|
44
44
|
* `searchable-multi` and short lists use `checklist`.
|
|
45
45
|
*/
|
|
46
|
-
readonly presentation?: LibraryFacetFieldPresentation
|
|
46
|
+
readonly presentation?: LibraryFacetFieldPresentation;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export interface LibraryFacetActiveChip {
|
|
@@ -61,9 +61,9 @@ export interface LibraryFacetFilterStripProps {
|
|
|
61
61
|
* When set with `onOpenMoreFilters`, replaces cascade show-more/less with a
|
|
62
62
|
* menu item that opens the fuller facet panel/dialog.
|
|
63
63
|
*/
|
|
64
|
-
readonly moreFiltersLabel?: string
|
|
64
|
+
readonly moreFiltersLabel?: string;
|
|
65
65
|
readonly onClearAll: () => void;
|
|
66
|
-
readonly onOpenMoreFilters?: (
|
|
66
|
+
readonly onOpenMoreFilters?: () => void;
|
|
67
67
|
readonly onShowLess: () => void;
|
|
68
68
|
readonly onShowMore: () => void;
|
|
69
69
|
readonly onToggleFacetValue: (
|
|
@@ -17,16 +17,16 @@ export interface NumberInputProps extends Omit<
|
|
|
17
17
|
TextInputProps,
|
|
18
18
|
'defaultValue' | 'onValueChange' | 'type' | 'value'
|
|
19
19
|
> {
|
|
20
|
-
defaultValue?: number
|
|
20
|
+
defaultValue?: number;
|
|
21
21
|
/**
|
|
22
22
|
* When true, `undefined` renders as an empty field. Clearing the input calls
|
|
23
23
|
* `onEmptyValue` (preferred) instead of `onValueChange`.
|
|
24
24
|
*/
|
|
25
|
-
nullable?: boolean
|
|
25
|
+
nullable?: boolean;
|
|
26
26
|
/** Fired when a nullable field is cleared. */
|
|
27
|
-
onEmptyValue?: (
|
|
28
|
-
onValueChange?: (
|
|
29
|
-
value?: number
|
|
27
|
+
onEmptyValue?: () => void;
|
|
28
|
+
onValueChange?: (value: number, event: ChangeEvent<HTMLInputElement>) => void;
|
|
29
|
+
value?: number;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function resolveStep(step: TextInputProps['step']): number {
|
|
@@ -9,12 +9,14 @@ import { WorkbenchMediaSlot } from '../workbench-media-slot';
|
|
|
9
9
|
export type RecordMediaHeroLayout = 'banner' | 'compact' | 'background';
|
|
10
10
|
|
|
11
11
|
export interface RecordMediaHeroProps extends ComponentPropsWithRef<'div'> {
|
|
12
|
-
alt?: string
|
|
13
|
-
fallbackIcon?: string
|
|
14
|
-
imageUrl?: string | null
|
|
15
|
-
layout?: RecordMediaHeroLayout
|
|
16
|
-
logoUrl?: string | null
|
|
17
|
-
maxWidth?: number | string
|
|
12
|
+
alt?: string;
|
|
13
|
+
fallbackIcon?: string;
|
|
14
|
+
imageUrl?: string | null;
|
|
15
|
+
layout?: RecordMediaHeroLayout;
|
|
16
|
+
logoUrl?: string | null;
|
|
17
|
+
maxWidth?: number | string;
|
|
18
|
+
/** Forwarded when the primary hero/cover image fails to load. */
|
|
19
|
+
onImageError?: () => void;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
function resolveMaxWidth(maxWidth: number | string | undefined): CSSProperties['maxWidth'] {
|
|
@@ -33,6 +35,7 @@ export function RecordMediaHero({
|
|
|
33
35
|
layout = 'banner',
|
|
34
36
|
logoUrl = null,
|
|
35
37
|
maxWidth,
|
|
38
|
+
onImageError,
|
|
36
39
|
style,
|
|
37
40
|
...props
|
|
38
41
|
}: RecordMediaHeroProps) {
|
|
@@ -66,6 +69,7 @@ export function RecordMediaHero({
|
|
|
66
69
|
imageClassName="ui-record-media-hero__image"
|
|
67
70
|
imageUrl={imageUrl}
|
|
68
71
|
loading="eager"
|
|
72
|
+
onImageError={onImageError}
|
|
69
73
|
/>
|
|
70
74
|
{logoMedia.shouldShowImage ? (
|
|
71
75
|
<img
|
|
@@ -19,11 +19,11 @@ export type ScrollAreaOrientation = 'both' | 'horizontal' | 'vertical';
|
|
|
19
19
|
export type ScrollAreaScrollbarVisibility = 'auto' | 'hidden' | 'overlay';
|
|
20
20
|
|
|
21
21
|
export interface ScrollAreaProps extends Omit<ComponentPropsWithRef<'div'>, 'children'> {
|
|
22
|
-
as?: ElementType
|
|
23
|
-
children?: ReactNode
|
|
24
|
-
gutter?: ScrollAreaGutter
|
|
25
|
-
orientation?: ScrollAreaOrientation
|
|
26
|
-
scrollbars?: ScrollAreaScrollbarVisibility
|
|
22
|
+
as?: ElementType;
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
gutter?: ScrollAreaGutter;
|
|
25
|
+
orientation?: ScrollAreaOrientation;
|
|
26
|
+
scrollbars?: ScrollAreaScrollbarVisibility;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
type OverlayThumb = {
|
|
@@ -3,13 +3,13 @@ import { useEffect, useRef, type RefObject } from 'react';
|
|
|
3
3
|
const DEFAULT_ROOT_MARGIN = '0px 0px 120px 0px';
|
|
4
4
|
|
|
5
5
|
export interface UseScrollAreaInfiniteLoadOptions {
|
|
6
|
-
enabled?: boolean
|
|
6
|
+
enabled?: boolean;
|
|
7
7
|
hasMore: boolean;
|
|
8
|
-
isLoading?: boolean
|
|
9
|
-
isLoadingMore?: boolean
|
|
8
|
+
isLoading?: boolean;
|
|
9
|
+
isLoadingMore?: boolean;
|
|
10
10
|
onLoadMore: () => void | Promise<void>;
|
|
11
|
-
rootMargin?: string
|
|
12
|
-
scrollAreaRef?: RefObject<HTMLElement | null
|
|
11
|
+
rootMargin?: string;
|
|
12
|
+
scrollAreaRef?: RefObject<HTMLElement | null>;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface UseScrollAreaInfiniteLoadResult {
|
|
@@ -25,24 +25,24 @@ import {
|
|
|
25
25
|
} from './overlay';
|
|
26
26
|
|
|
27
27
|
export interface SearchableMultiSelectOption {
|
|
28
|
-
readonly count?: number
|
|
29
|
-
readonly disabled?: boolean
|
|
28
|
+
readonly count?: number;
|
|
29
|
+
readonly disabled?: boolean;
|
|
30
30
|
readonly label: string;
|
|
31
31
|
readonly value: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export interface SearchableMultiSelectProps {
|
|
35
35
|
readonly 'aria-label'?: string | undefined;
|
|
36
|
-
readonly className?: string
|
|
37
|
-
readonly disabled?: boolean
|
|
36
|
+
readonly className?: string;
|
|
37
|
+
readonly disabled?: boolean;
|
|
38
38
|
/** Shown when the query matches no options. Default: "No matching options". */
|
|
39
|
-
readonly emptyMessage?: string
|
|
39
|
+
readonly emptyMessage?: string;
|
|
40
40
|
/** Aria label for dismissible selected chips. Default: "Remove {label}". */
|
|
41
|
-
readonly getRemoveChipAriaLabel?: (
|
|
41
|
+
readonly getRemoveChipAriaLabel?: (optionLabel: string) => string;
|
|
42
42
|
readonly onValueToggle: (value: string) => void;
|
|
43
43
|
readonly options: ReadonlyArray<SearchableMultiSelectOption>;
|
|
44
44
|
/** Search input placeholder. Default: "Search". */
|
|
45
|
-
readonly searchPlaceholder?: string
|
|
45
|
+
readonly searchPlaceholder?: string;
|
|
46
46
|
readonly selectedValues: readonly string[];
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -16,6 +16,7 @@ import type { ControlWidth } from '../text-input/TextInput';
|
|
|
16
16
|
import { cxCodicon } from '../../utils/codicon';
|
|
17
17
|
import { cx } from '../../utils/cx';
|
|
18
18
|
import { getEnabledOptionIndex, parseOptions } from './options';
|
|
19
|
+
import { resolvePortalContainer } from '../searchable-multi-select/overlay';
|
|
19
20
|
import { isTriggerVisible, measureOverlayPosition, overlayListboxStyle } from './overlay';
|
|
20
21
|
import type { OverlayPosition, ParsedOption } from './types';
|
|
21
22
|
|
|
@@ -24,19 +25,6 @@ export interface SelectProps extends ComponentPropsWithRef<'select'> {
|
|
|
24
25
|
onValueChange?: (value: string, event: ChangeEvent<HTMLSelectElement>) => void;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
/**
|
|
28
|
-
* Portaling straight to `document.body` escapes the workbench root (`[data-theme-preset]`),
|
|
29
|
-
* which re-declares theme tokens locally and shadows whatever a contributed theme overrides
|
|
30
|
-
* on `document.documentElement`. Prefer themed hosts / overlay roots when present.
|
|
31
|
-
*/
|
|
32
|
-
function resolvePortalContainer(trigger: HTMLElement | null): HTMLElement {
|
|
33
|
-
return (
|
|
34
|
-
trigger?.closest<HTMLElement>(
|
|
35
|
-
'[data-theme-preset], [data-theme], .ui-workbench-host-root, .ide-workbench-overlays',
|
|
36
|
-
) ?? document.body
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
28
|
export function Select({
|
|
41
29
|
className,
|
|
42
30
|
controlWidth = 'default',
|
|
@@ -7,7 +7,7 @@ type ControlWidth = 'default' | 'wide' | 'full';
|
|
|
7
7
|
export interface TextAreaProps extends ComponentPropsWithRef<'textarea'> {
|
|
8
8
|
controlWidth?: ControlWidth;
|
|
9
9
|
monospace?: boolean;
|
|
10
|
-
resize?: CSSProperties['resize']
|
|
10
|
+
resize?: CSSProperties['resize'];
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function TextArea({
|
|
@@ -7,7 +7,7 @@ export type ControlWidth = 'default' | 'wide' | 'full';
|
|
|
7
7
|
export interface TextInputProps extends ComponentPropsWithRef<'input'> {
|
|
8
8
|
controlWidth?: ControlWidth;
|
|
9
9
|
monospace?: boolean;
|
|
10
|
-
onValueChange?: (
|
|
10
|
+
onValueChange?: (value: string, event: ChangeEvent<HTMLInputElement>) => void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function TextInput({
|
|
@@ -4,8 +4,8 @@ import { cxCodicon } from '../../utils/codicon';
|
|
|
4
4
|
import { cx } from '../../utils/cx';
|
|
5
5
|
|
|
6
6
|
export interface WorkbenchMediaPlaceholderProps extends ComponentPropsWithRef<'span'> {
|
|
7
|
-
icon?: string
|
|
8
|
-
iconClassName?: string
|
|
7
|
+
icon?: string;
|
|
8
|
+
iconClassName?: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -6,20 +6,22 @@ import { useWorkbenchMediaImage } from '../../utils/useWorkbenchMediaImage';
|
|
|
6
6
|
import { WorkbenchMediaPlaceholder } from './WorkbenchMediaPlaceholder';
|
|
7
7
|
|
|
8
8
|
export interface WorkbenchMediaSlotProps extends Omit<ComponentPropsWithRef<'span'>, 'children'> {
|
|
9
|
-
alt?: string
|
|
10
|
-
as?: 'div' | 'span'
|
|
11
|
-
fallback?: ReactNode
|
|
12
|
-
fallbackClassName?: string
|
|
13
|
-
fallbackIcon?: string
|
|
9
|
+
alt?: string;
|
|
10
|
+
as?: 'div' | 'span';
|
|
11
|
+
fallback?: ReactNode;
|
|
12
|
+
fallbackClassName?: string;
|
|
13
|
+
fallbackIcon?: string;
|
|
14
14
|
/** Stretch to fill the parent box (hero/cover surfaces). */
|
|
15
|
-
fill?: boolean
|
|
16
|
-
imageClassName?: string
|
|
17
|
-
imageUrl?: string | null
|
|
15
|
+
fill?: boolean;
|
|
16
|
+
imageClassName?: string;
|
|
17
|
+
imageUrl?: string | null;
|
|
18
18
|
/**
|
|
19
19
|
* Native image loading hint. Defaults to `lazy` for list/thumbnail surfaces.
|
|
20
20
|
* Pass `eager` for above-the-fold heroes and detail identity art.
|
|
21
21
|
*/
|
|
22
|
-
loading?: 'eager' | 'lazy'
|
|
22
|
+
loading?: 'eager' | 'lazy';
|
|
23
|
+
/** Optional host callback after the slot marks the current URL as failed. */
|
|
24
|
+
onImageError?: () => void;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export function WorkbenchMediaSlot({
|
|
@@ -33,6 +35,7 @@ export function WorkbenchMediaSlot({
|
|
|
33
35
|
imageClassName,
|
|
34
36
|
imageUrl = null,
|
|
35
37
|
loading = 'lazy',
|
|
38
|
+
onImageError,
|
|
36
39
|
...props
|
|
37
40
|
}: WorkbenchMediaSlotProps) {
|
|
38
41
|
const media = useWorkbenchMediaImage(imageUrl);
|
|
@@ -47,7 +50,10 @@ export function WorkbenchMediaSlot({
|
|
|
47
50
|
alt={alt ?? ''}
|
|
48
51
|
className={cx('ui-workbench-media-slot__image', imageClassName)}
|
|
49
52
|
loading={loading}
|
|
50
|
-
onError={
|
|
53
|
+
onError={() => {
|
|
54
|
+
media.onImageError();
|
|
55
|
+
onImageError?.();
|
|
56
|
+
}}
|
|
51
57
|
src={media.imageSrc}
|
|
52
58
|
/>
|
|
53
59
|
) : (
|