Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/test/browser/componentFixtures/baseUI.fixture.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { $ } from '../../../../base/browser/dom.js';
7
import { Codicon } from '../../../../base/common/codicons.js';
8
import { ThemeIcon } from '../../../../base/common/themables.js';
9
import { Action, Separator } from '../../../../base/common/actions.js';
10
11
// UI Components
12
import { Button, ButtonBar, ButtonWithDescription, unthemedButtonStyles } from '../../../../base/browser/ui/button/button.js';
13
import { Toggle, Checkbox, unthemedToggleStyles } from '../../../../base/browser/ui/toggle/toggle.js';
14
import { InputBox, MessageType, unthemedInboxStyles } from '../../../../base/browser/ui/inputbox/inputBox.js';
15
import { CountBadge } from '../../../../base/browser/ui/countBadge/countBadge.js';
16
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
17
import { ProgressBar } from '../../../../base/browser/ui/progressbar/progressbar.js';
18
import { HighlightedLabel } from '../../../../base/browser/ui/highlightedlabel/highlightedLabel.js';
19
20
import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils.js';
21
22
23
export default defineThemedFixtureGroup({
24
Buttons: defineComponentFixture({
25
labels: { kind: 'screenshot' },
26
render: renderButtons,
27
}),
28
29
ButtonBar: defineComponentFixture({
30
labels: { kind: 'screenshot' },
31
render: renderButtonBar,
32
}),
33
34
Toggles: defineComponentFixture({
35
labels: { kind: 'screenshot' },
36
render: renderToggles,
37
}),
38
39
InputBoxes: defineComponentFixture({
40
labels: { kind: 'screenshot' },
41
render: renderInputBoxes,
42
}),
43
44
CountBadges: defineComponentFixture({
45
labels: { kind: 'screenshot' },
46
render: renderCountBadges,
47
}),
48
49
ActionBar: defineComponentFixture({
50
labels: { kind: 'screenshot' },
51
render: renderActionBar,
52
}),
53
54
ProgressBars: defineComponentFixture({
55
labels: { kind: 'screenshot' },
56
render: renderProgressBars,
57
}),
58
59
HighlightedLabels: defineComponentFixture({
60
labels: { kind: 'screenshot' },
61
render: renderHighlightedLabels,
62
}),
63
});
64
65
66
// ============================================================================
67
// Styles (themed versions for fixture display)
68
// ============================================================================
69
70
const themedButtonStyles = {
71
...unthemedButtonStyles,
72
buttonBackground: 'var(--vscode-button-background)',
73
buttonHoverBackground: 'var(--vscode-button-hoverBackground)',
74
buttonForeground: 'var(--vscode-button-foreground)',
75
buttonSecondaryBackground: 'var(--vscode-button-secondaryBackground)',
76
buttonSecondaryHoverBackground: 'var(--vscode-button-secondaryHoverBackground)',
77
buttonSecondaryForeground: 'var(--vscode-button-secondaryForeground)',
78
buttonBorder: 'var(--vscode-button-border)',
79
};
80
81
const themedToggleStyles = {
82
...unthemedToggleStyles,
83
inputActiveOptionBorder: 'var(--vscode-inputOption-activeBorder)',
84
inputActiveOptionForeground: 'var(--vscode-inputOption-activeForeground)',
85
inputActiveOptionBackground: 'var(--vscode-inputOption-activeBackground)',
86
};
87
88
const themedCheckboxStyles = {
89
checkboxBackground: 'var(--vscode-checkbox-background)',
90
checkboxBorder: 'var(--vscode-checkbox-border)',
91
checkboxForeground: 'var(--vscode-checkbox-foreground)',
92
checkboxDisabledBackground: undefined,
93
checkboxDisabledForeground: undefined,
94
};
95
96
const themedInputBoxStyles = {
97
...unthemedInboxStyles,
98
inputBackground: 'var(--vscode-input-background)',
99
inputForeground: 'var(--vscode-input-foreground)',
100
inputBorder: 'var(--vscode-input-border)',
101
inputValidationInfoBackground: 'var(--vscode-inputValidation-infoBackground)',
102
inputValidationInfoBorder: 'var(--vscode-inputValidation-infoBorder)',
103
inputValidationWarningBackground: 'var(--vscode-inputValidation-warningBackground)',
104
inputValidationWarningBorder: 'var(--vscode-inputValidation-warningBorder)',
105
inputValidationErrorBackground: 'var(--vscode-inputValidation-errorBackground)',
106
inputValidationErrorBorder: 'var(--vscode-inputValidation-errorBorder)',
107
};
108
109
const themedBadgeStyles = {
110
badgeBackground: 'var(--vscode-badge-background)',
111
badgeForeground: 'var(--vscode-badge-foreground)',
112
badgeBorder: undefined,
113
};
114
115
const themedProgressBarOptions = {
116
progressBarBackground: 'var(--vscode-progressBar-background)',
117
};
118
119
120
// ============================================================================
121
// Buttons
122
// ============================================================================
123
124
function renderButtons({ container, disposableStore }: ComponentFixtureContext): void {
125
container.style.padding = '16px';
126
container.style.display = 'flex';
127
container.style.flexDirection = 'column';
128
container.style.gap = '12px';
129
130
// Section: Primary Buttons
131
const primarySection = $('div');
132
primarySection.style.display = 'flex';
133
primarySection.style.gap = '8px';
134
primarySection.style.alignItems = 'center';
135
container.appendChild(primarySection);
136
137
const primaryButton = disposableStore.add(new Button(primarySection, { ...themedButtonStyles, title: 'Primary button' }));
138
primaryButton.label = 'Primary Button';
139
140
const primaryIconButton = disposableStore.add(new Button(primarySection, { ...themedButtonStyles, title: 'With Icon', supportIcons: true }));
141
primaryIconButton.label = '$(add) Add Item';
142
143
const smallButton = disposableStore.add(new Button(primarySection, { ...themedButtonStyles, title: 'Small button', small: true }));
144
smallButton.label = 'Small';
145
146
// Section: Secondary Buttons
147
const secondarySection = $('div');
148
secondarySection.style.display = 'flex';
149
secondarySection.style.gap = '8px';
150
secondarySection.style.alignItems = 'center';
151
container.appendChild(secondarySection);
152
153
const secondaryButton = disposableStore.add(new Button(secondarySection, { ...themedButtonStyles, secondary: true, title: 'Secondary button' }));
154
secondaryButton.label = 'Secondary Button';
155
156
const secondaryIconButton = disposableStore.add(new Button(secondarySection, { ...themedButtonStyles, secondary: true, title: 'Cancel', supportIcons: true }));
157
secondaryIconButton.label = '$(close) Cancel';
158
159
// Section: Disabled Buttons
160
const disabledSection = $('div');
161
disabledSection.style.display = 'flex';
162
disabledSection.style.gap = '8px';
163
disabledSection.style.alignItems = 'center';
164
container.appendChild(disabledSection);
165
166
const disabledButton = disposableStore.add(new Button(disabledSection, { ...themedButtonStyles, title: 'Disabled', disabled: true }));
167
disabledButton.label = 'Disabled';
168
disabledButton.enabled = false;
169
170
const disabledSecondary = disposableStore.add(new Button(disabledSection, { ...themedButtonStyles, secondary: true, title: 'Disabled Secondary', disabled: true }));
171
disabledSecondary.label = 'Disabled Secondary';
172
disabledSecondary.enabled = false;
173
}
174
175
function renderButtonBar({ container, disposableStore }: ComponentFixtureContext): void {
176
container.style.padding = '16px';
177
container.style.display = 'flex';
178
container.style.flexDirection = 'column';
179
container.style.gap = '16px';
180
181
// Button Bar
182
const barContainer = $('div');
183
container.appendChild(barContainer);
184
185
const buttonBar = new ButtonBar(barContainer);
186
disposableStore.add(buttonBar);
187
188
const okButton = buttonBar.addButton({ ...themedButtonStyles, title: 'OK' });
189
okButton.label = 'OK';
190
191
const cancelButton = buttonBar.addButton({ ...themedButtonStyles, secondary: true, title: 'Cancel' });
192
cancelButton.label = 'Cancel';
193
194
// Button with Description
195
const descContainer = $('div');
196
descContainer.style.width = '300px';
197
container.appendChild(descContainer);
198
199
const buttonWithDesc = disposableStore.add(new ButtonWithDescription(descContainer, { ...themedButtonStyles, title: 'Install Extension', supportIcons: true }));
200
buttonWithDesc.label = '$(extensions) Install Extension';
201
buttonWithDesc.description = 'This will install the extension and enable it globally';
202
}
203
204
205
// ============================================================================
206
// Toggles and Checkboxes
207
// ============================================================================
208
209
function renderToggles({ container, disposableStore }: ComponentFixtureContext): void {
210
container.style.padding = '16px';
211
container.style.display = 'flex';
212
container.style.flexDirection = 'column';
213
container.style.gap = '12px';
214
215
// Toggles
216
const toggleSection = $('div');
217
toggleSection.style.display = 'flex';
218
toggleSection.style.gap = '16px';
219
toggleSection.style.alignItems = 'center';
220
container.appendChild(toggleSection);
221
222
const toggle1 = disposableStore.add(new Toggle({
223
...themedToggleStyles,
224
title: 'Case Sensitive',
225
isChecked: false,
226
icon: Codicon.caseSensitive,
227
}));
228
toggleSection.appendChild(toggle1.domNode);
229
230
const toggle2 = disposableStore.add(new Toggle({
231
...themedToggleStyles,
232
title: 'Whole Word',
233
isChecked: true,
234
icon: Codicon.wholeWord,
235
}));
236
toggleSection.appendChild(toggle2.domNode);
237
238
const toggle3 = disposableStore.add(new Toggle({
239
...themedToggleStyles,
240
title: 'Use Regular Expression',
241
isChecked: false,
242
icon: Codicon.regex,
243
}));
244
toggleSection.appendChild(toggle3.domNode);
245
246
// Checkboxes
247
const checkboxSection = $('div');
248
checkboxSection.style.display = 'flex';
249
checkboxSection.style.flexDirection = 'column';
250
checkboxSection.style.gap = '8px';
251
container.appendChild(checkboxSection);
252
253
const createCheckboxRow = (label: string, checked: boolean) => {
254
const row = $('div');
255
row.style.display = 'flex';
256
row.style.alignItems = 'center';
257
row.style.gap = '8px';
258
259
const checkbox = disposableStore.add(new Checkbox(label, checked, themedCheckboxStyles));
260
row.appendChild(checkbox.domNode);
261
262
const labelEl = $('span');
263
labelEl.textContent = label;
264
labelEl.style.color = 'var(--vscode-foreground)';
265
row.appendChild(labelEl);
266
267
return row;
268
};
269
270
checkboxSection.appendChild(createCheckboxRow('Enable auto-save', true));
271
checkboxSection.appendChild(createCheckboxRow('Show line numbers', true));
272
checkboxSection.appendChild(createCheckboxRow('Word wrap', false));
273
}
274
275
276
// ============================================================================
277
// Input Boxes
278
// ============================================================================
279
280
function renderInputBoxes({ container, disposableStore }: ComponentFixtureContext): void {
281
container.style.padding = '16px';
282
container.style.display = 'flex';
283
container.style.flexDirection = 'column';
284
container.style.gap = '16px';
285
container.style.width = '350px';
286
287
// Input with value
288
const filledInput = disposableStore.add(new InputBox(container, undefined, {
289
placeholder: 'File path',
290
inputBoxStyles: themedInputBoxStyles,
291
}));
292
filledInput.value = '/src/vs/editor/browser';
293
294
// Input with info validation
295
const infoInput = disposableStore.add(new InputBox(container, undefined, {
296
placeholder: 'Username',
297
inputBoxStyles: themedInputBoxStyles,
298
validationOptions: {
299
validation: (value) => value.length < 3 ? { content: 'Username must be at least 3 characters', type: MessageType.INFO } : null
300
}
301
}));
302
infoInput.value = 'ab';
303
infoInput.validate();
304
305
// Input with warning validation
306
const warningInput = disposableStore.add(new InputBox(container, undefined, {
307
placeholder: 'Password',
308
inputBoxStyles: themedInputBoxStyles,
309
validationOptions: {
310
validation: (value) => value.length < 8 ? { content: 'Password should be at least 8 characters for security', type: MessageType.WARNING } : null
311
}
312
}));
313
warningInput.value = 'pass';
314
warningInput.validate();
315
316
// Input with error validation
317
const errorInput = disposableStore.add(new InputBox(container, undefined, {
318
placeholder: 'Email address',
319
inputBoxStyles: themedInputBoxStyles,
320
validationOptions: {
321
validation: (value) => !value.includes('@') ? { content: 'Please enter a valid email address', type: MessageType.ERROR } : null
322
}
323
}));
324
errorInput.value = 'invalid-email';
325
errorInput.validate();
326
}
327
328
329
// ============================================================================
330
// Count Badges
331
// ============================================================================
332
333
function renderCountBadges({ container }: ComponentFixtureContext): void {
334
container.style.padding = '16px';
335
container.style.display = 'flex';
336
container.style.gap = '12px';
337
container.style.alignItems = 'center';
338
339
// Various badge counts
340
const counts = [1, 5, 12, 99, 999];
341
342
for (const count of counts) {
343
const badgeContainer = $('div');
344
badgeContainer.style.display = 'flex';
345
badgeContainer.style.alignItems = 'center';
346
badgeContainer.style.gap = '8px';
347
348
const label = $('span');
349
label.textContent = 'Issues';
350
label.style.color = 'var(--vscode-foreground)';
351
badgeContainer.appendChild(label);
352
353
new CountBadge(badgeContainer, { count }, themedBadgeStyles);
354
container.appendChild(badgeContainer);
355
}
356
}
357
358
359
// ============================================================================
360
// Action Bar
361
// ============================================================================
362
363
function renderActionBar({ container, disposableStore }: ComponentFixtureContext): void {
364
container.style.padding = '16px';
365
container.style.display = 'flex';
366
container.style.flexDirection = 'column';
367
container.style.gap = '16px';
368
369
// Horizontal action bar
370
const horizontalLabel = $('div');
371
horizontalLabel.textContent = 'Horizontal Actions:';
372
horizontalLabel.style.color = 'var(--vscode-foreground)';
373
horizontalLabel.style.marginBottom = '4px';
374
container.appendChild(horizontalLabel);
375
376
const horizontalContainer = $('div');
377
container.appendChild(horizontalContainer);
378
379
const horizontalBar = disposableStore.add(new ActionBar(horizontalContainer, {
380
ariaLabel: 'Editor Actions',
381
}));
382
383
horizontalBar.push([
384
new Action('editor.action.save', 'Save', ThemeIcon.asClassName(Codicon.save), true, async () => console.log('Save')),
385
new Action('editor.action.undo', 'Undo', ThemeIcon.asClassName(Codicon.discard), true, async () => console.log('Undo')),
386
new Action('editor.action.redo', 'Redo', ThemeIcon.asClassName(Codicon.redo), true, async () => console.log('Redo')),
387
new Separator(),
388
new Action('editor.action.find', 'Find', ThemeIcon.asClassName(Codicon.search), true, async () => console.log('Find')),
389
new Action('editor.action.replace', 'Replace', ThemeIcon.asClassName(Codicon.replaceAll), true, async () => console.log('Replace')),
390
]);
391
392
// Action bar with disabled items
393
const mixedLabel = $('div');
394
mixedLabel.textContent = 'Mixed States:';
395
mixedLabel.style.color = 'var(--vscode-foreground)';
396
mixedLabel.style.marginBottom = '4px';
397
container.appendChild(mixedLabel);
398
399
const mixedContainer = $('div');
400
container.appendChild(mixedContainer);
401
402
const mixedBar = disposableStore.add(new ActionBar(mixedContainer, {
403
ariaLabel: 'Mixed Actions',
404
}));
405
406
mixedBar.push([
407
new Action('action.enabled', 'Enabled', ThemeIcon.asClassName(Codicon.play), true, async () => { }),
408
new Action('action.disabled', 'Disabled', ThemeIcon.asClassName(Codicon.debugPause), false, async () => { }),
409
new Action('action.enabled2', 'Enabled', ThemeIcon.asClassName(Codicon.debugStop), true, async () => { }),
410
]);
411
}
412
413
414
// ============================================================================
415
// Progress Bar
416
// ============================================================================
417
418
function renderProgressBars({ container, disposableStore }: ComponentFixtureContext): void {
419
container.style.padding = '16px';
420
container.style.display = 'flex';
421
container.style.flexDirection = 'column';
422
container.style.gap = '24px';
423
container.style.width = '400px';
424
425
const createSection = (label: string) => {
426
const section = $('div');
427
const labelEl = $('div');
428
labelEl.textContent = label;
429
labelEl.style.color = 'var(--vscode-foreground)';
430
labelEl.style.marginBottom = '8px';
431
labelEl.style.fontSize = '12px';
432
section.appendChild(labelEl);
433
434
// Progress bar container with proper constraints
435
const barContainer = $('div');
436
barContainer.style.position = 'relative';
437
barContainer.style.width = '100%';
438
barContainer.style.height = '4px';
439
barContainer.style.overflow = 'hidden';
440
section.appendChild(barContainer);
441
442
container.appendChild(section);
443
return barContainer;
444
};
445
446
// Discrete progress - 30%
447
const progress30Section = createSection('Discrete Progress - 30%');
448
const progress30Bar = disposableStore.add(new ProgressBar(progress30Section, themedProgressBarOptions));
449
progress30Bar.total(100);
450
progress30Bar.worked(30);
451
452
// Discrete progress - 60%
453
const progress60Section = createSection('Discrete Progress - 60%');
454
const progress60Bar = disposableStore.add(new ProgressBar(progress60Section, themedProgressBarOptions));
455
progress60Bar.total(100);
456
progress60Bar.worked(60);
457
458
// Discrete progress - 90%
459
const progress90Section = createSection('Discrete Progress - 90%');
460
const progress90Bar = disposableStore.add(new ProgressBar(progress90Section, themedProgressBarOptions));
461
progress90Bar.total(100);
462
progress90Bar.worked(90);
463
464
// Completed progress
465
const doneSection = createSection('Completed (100%)');
466
const doneBar = disposableStore.add(new ProgressBar(doneSection, themedProgressBarOptions));
467
doneBar.total(100);
468
doneBar.worked(100);
469
}
470
471
472
// ============================================================================
473
// Highlighted Label
474
// ============================================================================
475
476
function renderHighlightedLabels({ container }: ComponentFixtureContext): void {
477
container.style.padding = '16px';
478
container.style.display = 'flex';
479
container.style.flexDirection = 'column';
480
container.style.gap = '8px';
481
container.style.color = 'var(--vscode-foreground)';
482
483
const createHighlightedLabel = (text: string, highlights: { start: number; end: number }[]) => {
484
const row = $('div');
485
row.style.display = 'flex';
486
row.style.alignItems = 'center';
487
row.style.gap = '8px';
488
489
const labelContainer = $('div');
490
const label = new HighlightedLabel(labelContainer);
491
label.set(text, highlights);
492
row.appendChild(labelContainer);
493
494
const queryLabel = $('span');
495
queryLabel.style.color = 'var(--vscode-descriptionForeground)';
496
queryLabel.style.fontSize = '12px';
497
queryLabel.textContent = `(matches highlighted)`;
498
row.appendChild(queryLabel);
499
500
return row;
501
};
502
503
// File search examples
504
container.appendChild(createHighlightedLabel('codeEditorWidget.ts', [{ start: 0, end: 4 }])); // "code"
505
container.appendChild(createHighlightedLabel('inlineCompletionsController.ts', [{ start: 6, end: 10 }])); // "Comp"
506
container.appendChild(createHighlightedLabel('diffEditorViewModel.ts', [{ start: 0, end: 4 }, { start: 10, end: 14 }])); // "diff" and "View"
507
container.appendChild(createHighlightedLabel('workbenchTestServices.ts', [{ start: 9, end: 13 }])); // "Test"
508
}
509
510