Path: blob/main/src/vs/workbench/test/browser/componentFixtures/baseUI.fixture.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { $ } from '../../../../base/browser/dom.js';6import { Codicon } from '../../../../base/common/codicons.js';7import { ThemeIcon } from '../../../../base/common/themables.js';8import { Action, Separator } from '../../../../base/common/actions.js';910// UI Components11import { Button, ButtonBar, ButtonWithDescription, unthemedButtonStyles } from '../../../../base/browser/ui/button/button.js';12import { Toggle, Checkbox, unthemedToggleStyles } from '../../../../base/browser/ui/toggle/toggle.js';13import { InputBox, MessageType, unthemedInboxStyles } from '../../../../base/browser/ui/inputbox/inputBox.js';14import { CountBadge } from '../../../../base/browser/ui/countBadge/countBadge.js';15import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';16import { ProgressBar } from '../../../../base/browser/ui/progressbar/progressbar.js';17import { HighlightedLabel } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';1819import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils.js';202122export default defineThemedFixtureGroup({23Buttons: defineComponentFixture({24labels: { kind: 'screenshot' },25render: renderButtons,26}),2728ButtonBar: defineComponentFixture({29labels: { kind: 'screenshot' },30render: renderButtonBar,31}),3233Toggles: defineComponentFixture({34labels: { kind: 'screenshot' },35render: renderToggles,36}),3738InputBoxes: defineComponentFixture({39labels: { kind: 'screenshot' },40render: renderInputBoxes,41}),4243CountBadges: defineComponentFixture({44labels: { kind: 'screenshot' },45render: renderCountBadges,46}),4748ActionBar: defineComponentFixture({49labels: { kind: 'screenshot' },50render: renderActionBar,51}),5253ProgressBars: defineComponentFixture({54labels: { kind: 'screenshot' },55render: renderProgressBars,56}),5758HighlightedLabels: defineComponentFixture({59labels: { kind: 'screenshot' },60render: renderHighlightedLabels,61}),62});636465// ============================================================================66// Styles (themed versions for fixture display)67// ============================================================================6869const themedButtonStyles = {70...unthemedButtonStyles,71buttonBackground: 'var(--vscode-button-background)',72buttonHoverBackground: 'var(--vscode-button-hoverBackground)',73buttonForeground: 'var(--vscode-button-foreground)',74buttonSecondaryBackground: 'var(--vscode-button-secondaryBackground)',75buttonSecondaryHoverBackground: 'var(--vscode-button-secondaryHoverBackground)',76buttonSecondaryForeground: 'var(--vscode-button-secondaryForeground)',77buttonBorder: 'var(--vscode-button-border)',78};7980const themedToggleStyles = {81...unthemedToggleStyles,82inputActiveOptionBorder: 'var(--vscode-inputOption-activeBorder)',83inputActiveOptionForeground: 'var(--vscode-inputOption-activeForeground)',84inputActiveOptionBackground: 'var(--vscode-inputOption-activeBackground)',85};8687const themedCheckboxStyles = {88checkboxBackground: 'var(--vscode-checkbox-background)',89checkboxBorder: 'var(--vscode-checkbox-border)',90checkboxForeground: 'var(--vscode-checkbox-foreground)',91checkboxDisabledBackground: undefined,92checkboxDisabledForeground: undefined,93};9495const themedInputBoxStyles = {96...unthemedInboxStyles,97inputBackground: 'var(--vscode-input-background)',98inputForeground: 'var(--vscode-input-foreground)',99inputBorder: 'var(--vscode-input-border)',100inputValidationInfoBackground: 'var(--vscode-inputValidation-infoBackground)',101inputValidationInfoBorder: 'var(--vscode-inputValidation-infoBorder)',102inputValidationWarningBackground: 'var(--vscode-inputValidation-warningBackground)',103inputValidationWarningBorder: 'var(--vscode-inputValidation-warningBorder)',104inputValidationErrorBackground: 'var(--vscode-inputValidation-errorBackground)',105inputValidationErrorBorder: 'var(--vscode-inputValidation-errorBorder)',106};107108const themedBadgeStyles = {109badgeBackground: 'var(--vscode-badge-background)',110badgeForeground: 'var(--vscode-badge-foreground)',111badgeBorder: undefined,112};113114const themedProgressBarOptions = {115progressBarBackground: 'var(--vscode-progressBar-background)',116};117118119// ============================================================================120// Buttons121// ============================================================================122123function renderButtons({ container, disposableStore }: ComponentFixtureContext): void {124container.style.padding = '16px';125container.style.display = 'flex';126container.style.flexDirection = 'column';127container.style.gap = '12px';128129// Section: Primary Buttons130const primarySection = $('div');131primarySection.style.display = 'flex';132primarySection.style.gap = '8px';133primarySection.style.alignItems = 'center';134container.appendChild(primarySection);135136const primaryButton = disposableStore.add(new Button(primarySection, { ...themedButtonStyles, title: 'Primary button' }));137primaryButton.label = 'Primary Button';138139const primaryIconButton = disposableStore.add(new Button(primarySection, { ...themedButtonStyles, title: 'With Icon', supportIcons: true }));140primaryIconButton.label = '$(add) Add Item';141142const smallButton = disposableStore.add(new Button(primarySection, { ...themedButtonStyles, title: 'Small button', small: true }));143smallButton.label = 'Small';144145// Section: Secondary Buttons146const secondarySection = $('div');147secondarySection.style.display = 'flex';148secondarySection.style.gap = '8px';149secondarySection.style.alignItems = 'center';150container.appendChild(secondarySection);151152const secondaryButton = disposableStore.add(new Button(secondarySection, { ...themedButtonStyles, secondary: true, title: 'Secondary button' }));153secondaryButton.label = 'Secondary Button';154155const secondaryIconButton = disposableStore.add(new Button(secondarySection, { ...themedButtonStyles, secondary: true, title: 'Cancel', supportIcons: true }));156secondaryIconButton.label = '$(close) Cancel';157158// Section: Disabled Buttons159const disabledSection = $('div');160disabledSection.style.display = 'flex';161disabledSection.style.gap = '8px';162disabledSection.style.alignItems = 'center';163container.appendChild(disabledSection);164165const disabledButton = disposableStore.add(new Button(disabledSection, { ...themedButtonStyles, title: 'Disabled', disabled: true }));166disabledButton.label = 'Disabled';167disabledButton.enabled = false;168169const disabledSecondary = disposableStore.add(new Button(disabledSection, { ...themedButtonStyles, secondary: true, title: 'Disabled Secondary', disabled: true }));170disabledSecondary.label = 'Disabled Secondary';171disabledSecondary.enabled = false;172}173174function renderButtonBar({ container, disposableStore }: ComponentFixtureContext): void {175container.style.padding = '16px';176container.style.display = 'flex';177container.style.flexDirection = 'column';178container.style.gap = '16px';179180// Button Bar181const barContainer = $('div');182container.appendChild(barContainer);183184const buttonBar = new ButtonBar(barContainer);185disposableStore.add(buttonBar);186187const okButton = buttonBar.addButton({ ...themedButtonStyles, title: 'OK' });188okButton.label = 'OK';189190const cancelButton = buttonBar.addButton({ ...themedButtonStyles, secondary: true, title: 'Cancel' });191cancelButton.label = 'Cancel';192193// Button with Description194const descContainer = $('div');195descContainer.style.width = '300px';196container.appendChild(descContainer);197198const buttonWithDesc = disposableStore.add(new ButtonWithDescription(descContainer, { ...themedButtonStyles, title: 'Install Extension', supportIcons: true }));199buttonWithDesc.label = '$(extensions) Install Extension';200buttonWithDesc.description = 'This will install the extension and enable it globally';201}202203204// ============================================================================205// Toggles and Checkboxes206// ============================================================================207208function renderToggles({ container, disposableStore }: ComponentFixtureContext): void {209container.style.padding = '16px';210container.style.display = 'flex';211container.style.flexDirection = 'column';212container.style.gap = '12px';213214// Toggles215const toggleSection = $('div');216toggleSection.style.display = 'flex';217toggleSection.style.gap = '16px';218toggleSection.style.alignItems = 'center';219container.appendChild(toggleSection);220221const toggle1 = disposableStore.add(new Toggle({222...themedToggleStyles,223title: 'Case Sensitive',224isChecked: false,225icon: Codicon.caseSensitive,226}));227toggleSection.appendChild(toggle1.domNode);228229const toggle2 = disposableStore.add(new Toggle({230...themedToggleStyles,231title: 'Whole Word',232isChecked: true,233icon: Codicon.wholeWord,234}));235toggleSection.appendChild(toggle2.domNode);236237const toggle3 = disposableStore.add(new Toggle({238...themedToggleStyles,239title: 'Use Regular Expression',240isChecked: false,241icon: Codicon.regex,242}));243toggleSection.appendChild(toggle3.domNode);244245// Checkboxes246const checkboxSection = $('div');247checkboxSection.style.display = 'flex';248checkboxSection.style.flexDirection = 'column';249checkboxSection.style.gap = '8px';250container.appendChild(checkboxSection);251252const createCheckboxRow = (label: string, checked: boolean) => {253const row = $('div');254row.style.display = 'flex';255row.style.alignItems = 'center';256row.style.gap = '8px';257258const checkbox = disposableStore.add(new Checkbox(label, checked, themedCheckboxStyles));259row.appendChild(checkbox.domNode);260261const labelEl = $('span');262labelEl.textContent = label;263labelEl.style.color = 'var(--vscode-foreground)';264row.appendChild(labelEl);265266return row;267};268269checkboxSection.appendChild(createCheckboxRow('Enable auto-save', true));270checkboxSection.appendChild(createCheckboxRow('Show line numbers', true));271checkboxSection.appendChild(createCheckboxRow('Word wrap', false));272}273274275// ============================================================================276// Input Boxes277// ============================================================================278279function renderInputBoxes({ container, disposableStore }: ComponentFixtureContext): void {280container.style.padding = '16px';281container.style.display = 'flex';282container.style.flexDirection = 'column';283container.style.gap = '16px';284container.style.width = '350px';285286// Input with value287const filledInput = disposableStore.add(new InputBox(container, undefined, {288placeholder: 'File path',289inputBoxStyles: themedInputBoxStyles,290}));291filledInput.value = '/src/vs/editor/browser';292293// Input with info validation294const infoInput = disposableStore.add(new InputBox(container, undefined, {295placeholder: 'Username',296inputBoxStyles: themedInputBoxStyles,297validationOptions: {298validation: (value) => value.length < 3 ? { content: 'Username must be at least 3 characters', type: MessageType.INFO } : null299}300}));301infoInput.value = 'ab';302infoInput.validate();303304// Input with warning validation305const warningInput = disposableStore.add(new InputBox(container, undefined, {306placeholder: 'Password',307inputBoxStyles: themedInputBoxStyles,308validationOptions: {309validation: (value) => value.length < 8 ? { content: 'Password should be at least 8 characters for security', type: MessageType.WARNING } : null310}311}));312warningInput.value = 'pass';313warningInput.validate();314315// Input with error validation316const errorInput = disposableStore.add(new InputBox(container, undefined, {317placeholder: 'Email address',318inputBoxStyles: themedInputBoxStyles,319validationOptions: {320validation: (value) => !value.includes('@') ? { content: 'Please enter a valid email address', type: MessageType.ERROR } : null321}322}));323errorInput.value = 'invalid-email';324errorInput.validate();325}326327328// ============================================================================329// Count Badges330// ============================================================================331332function renderCountBadges({ container }: ComponentFixtureContext): void {333container.style.padding = '16px';334container.style.display = 'flex';335container.style.gap = '12px';336container.style.alignItems = 'center';337338// Various badge counts339const counts = [1, 5, 12, 99, 999];340341for (const count of counts) {342const badgeContainer = $('div');343badgeContainer.style.display = 'flex';344badgeContainer.style.alignItems = 'center';345badgeContainer.style.gap = '8px';346347const label = $('span');348label.textContent = 'Issues';349label.style.color = 'var(--vscode-foreground)';350badgeContainer.appendChild(label);351352new CountBadge(badgeContainer, { count }, themedBadgeStyles);353container.appendChild(badgeContainer);354}355}356357358// ============================================================================359// Action Bar360// ============================================================================361362function renderActionBar({ container, disposableStore }: ComponentFixtureContext): void {363container.style.padding = '16px';364container.style.display = 'flex';365container.style.flexDirection = 'column';366container.style.gap = '16px';367368// Horizontal action bar369const horizontalLabel = $('div');370horizontalLabel.textContent = 'Horizontal Actions:';371horizontalLabel.style.color = 'var(--vscode-foreground)';372horizontalLabel.style.marginBottom = '4px';373container.appendChild(horizontalLabel);374375const horizontalContainer = $('div');376container.appendChild(horizontalContainer);377378const horizontalBar = disposableStore.add(new ActionBar(horizontalContainer, {379ariaLabel: 'Editor Actions',380}));381382horizontalBar.push([383new Action('editor.action.save', 'Save', ThemeIcon.asClassName(Codicon.save), true, async () => console.log('Save')),384new Action('editor.action.undo', 'Undo', ThemeIcon.asClassName(Codicon.discard), true, async () => console.log('Undo')),385new Action('editor.action.redo', 'Redo', ThemeIcon.asClassName(Codicon.redo), true, async () => console.log('Redo')),386new Separator(),387new Action('editor.action.find', 'Find', ThemeIcon.asClassName(Codicon.search), true, async () => console.log('Find')),388new Action('editor.action.replace', 'Replace', ThemeIcon.asClassName(Codicon.replaceAll), true, async () => console.log('Replace')),389]);390391// Action bar with disabled items392const mixedLabel = $('div');393mixedLabel.textContent = 'Mixed States:';394mixedLabel.style.color = 'var(--vscode-foreground)';395mixedLabel.style.marginBottom = '4px';396container.appendChild(mixedLabel);397398const mixedContainer = $('div');399container.appendChild(mixedContainer);400401const mixedBar = disposableStore.add(new ActionBar(mixedContainer, {402ariaLabel: 'Mixed Actions',403}));404405mixedBar.push([406new Action('action.enabled', 'Enabled', ThemeIcon.asClassName(Codicon.play), true, async () => { }),407new Action('action.disabled', 'Disabled', ThemeIcon.asClassName(Codicon.debugPause), false, async () => { }),408new Action('action.enabled2', 'Enabled', ThemeIcon.asClassName(Codicon.debugStop), true, async () => { }),409]);410}411412413// ============================================================================414// Progress Bar415// ============================================================================416417function renderProgressBars({ container, disposableStore }: ComponentFixtureContext): void {418container.style.padding = '16px';419container.style.display = 'flex';420container.style.flexDirection = 'column';421container.style.gap = '24px';422container.style.width = '400px';423424const createSection = (label: string) => {425const section = $('div');426const labelEl = $('div');427labelEl.textContent = label;428labelEl.style.color = 'var(--vscode-foreground)';429labelEl.style.marginBottom = '8px';430labelEl.style.fontSize = '12px';431section.appendChild(labelEl);432433// Progress bar container with proper constraints434const barContainer = $('div');435barContainer.style.position = 'relative';436barContainer.style.width = '100%';437barContainer.style.height = '4px';438barContainer.style.overflow = 'hidden';439section.appendChild(barContainer);440441container.appendChild(section);442return barContainer;443};444445// Discrete progress - 30%446const progress30Section = createSection('Discrete Progress - 30%');447const progress30Bar = disposableStore.add(new ProgressBar(progress30Section, themedProgressBarOptions));448progress30Bar.total(100);449progress30Bar.worked(30);450451// Discrete progress - 60%452const progress60Section = createSection('Discrete Progress - 60%');453const progress60Bar = disposableStore.add(new ProgressBar(progress60Section, themedProgressBarOptions));454progress60Bar.total(100);455progress60Bar.worked(60);456457// Discrete progress - 90%458const progress90Section = createSection('Discrete Progress - 90%');459const progress90Bar = disposableStore.add(new ProgressBar(progress90Section, themedProgressBarOptions));460progress90Bar.total(100);461progress90Bar.worked(90);462463// Completed progress464const doneSection = createSection('Completed (100%)');465const doneBar = disposableStore.add(new ProgressBar(doneSection, themedProgressBarOptions));466doneBar.total(100);467doneBar.worked(100);468}469470471// ============================================================================472// Highlighted Label473// ============================================================================474475function renderHighlightedLabels({ container }: ComponentFixtureContext): void {476container.style.padding = '16px';477container.style.display = 'flex';478container.style.flexDirection = 'column';479container.style.gap = '8px';480container.style.color = 'var(--vscode-foreground)';481482const createHighlightedLabel = (text: string, highlights: { start: number; end: number }[]) => {483const row = $('div');484row.style.display = 'flex';485row.style.alignItems = 'center';486row.style.gap = '8px';487488const labelContainer = $('div');489const label = new HighlightedLabel(labelContainer);490label.set(text, highlights);491row.appendChild(labelContainer);492493const queryLabel = $('span');494queryLabel.style.color = 'var(--vscode-descriptionForeground)';495queryLabel.style.fontSize = '12px';496queryLabel.textContent = `(matches highlighted)`;497row.appendChild(queryLabel);498499return row;500};501502// File search examples503container.appendChild(createHighlightedLabel('codeEditorWidget.ts', [{ start: 0, end: 4 }])); // "code"504container.appendChild(createHighlightedLabel('inlineCompletionsController.ts', [{ start: 6, end: 10 }])); // "Comp"505container.appendChild(createHighlightedLabel('diffEditorViewModel.ts', [{ start: 0, end: 4 }, { start: 10, end: 14 }])); // "diff" and "View"506container.appendChild(createHighlightedLabel('workbenchTestServices.ts', [{ start: 9, end: 13 }])); // "Test"507}508509510