@workbench-kit/react 0.0.1-prototype.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/package.json +62 -0
  2. package/src/index.ts +43 -0
  3. package/src/layout/Panel.tsx +27 -0
  4. package/src/layout/SideBarViewFrame.tsx +328 -0
  5. package/src/modal/ConfirmDialog.tsx +53 -0
  6. package/src/modal/Modal.tsx +110 -0
  7. package/src/overlay/ContextMenu.tsx +145 -0
  8. package/src/primitives/Badge.tsx +12 -0
  9. package/src/primitives/Button.tsx +14 -0
  10. package/src/primitives/Checkbox.tsx +15 -0
  11. package/src/primitives/EmptyState.tsx +26 -0
  12. package/src/primitives/Field.tsx +38 -0
  13. package/src/primitives/IconButton.tsx +32 -0
  14. package/src/primitives/Select.tsx +12 -0
  15. package/src/primitives/TextInput.tsx +24 -0
  16. package/src/primitives/Toolbar.tsx +8 -0
  17. package/src/styles.css +1762 -0
  18. package/src/utils/cx.ts +3 -0
  19. package/src/workbench/ActivityBar.tsx +52 -0
  20. package/src/workbench/SplitView.tsx +136 -0
  21. package/src/workbench/StatusBar.tsx +123 -0
  22. package/src/workbench/WorkbenchShell.tsx +70 -0
  23. package/src/workbench/WorkbenchStandaloneShell.tsx +291 -0
  24. package/src/workbench/chat/ChatComposer.tsx +148 -0
  25. package/src/workbench/chat/ChatMessageItem.tsx +35 -0
  26. package/src/workbench/chat/ChatMessageList.tsx +49 -0
  27. package/src/workbench/chat/ChatPanel.tsx +55 -0
  28. package/src/workbench/chat/index.ts +9 -0
  29. package/src/workbench/chat/types.ts +10 -0
  30. package/src/workbench/commands.ts +490 -0
  31. package/src/workbench/index.ts +98 -0
  32. package/src/workbench/settings/WorkbenchSettingsModal.tsx +188 -0
  33. package/src/workbench/settings/WorkbenchSettingsNav.tsx +41 -0
  34. package/src/workbench/settings/WorkbenchSettingsSection.tsx +30 -0
  35. package/src/workbench/settings/index.ts +7 -0
  36. package/src/workbench/settings/types.ts +16 -0
  37. package/src/workbench/shellState.ts +203 -0
  38. package/src/workbench/standalone.ts +94 -0
  39. package/src/workbench/workspace/WorkspaceEditor.tsx +226 -0
  40. package/src/workbench/workspace/WorkspaceEditorPanel.tsx +353 -0
  41. package/src/workbench/workspace/WorkspaceExplorer.tsx +524 -0
  42. package/src/workbench/workspace/WorkspaceFileIcon.tsx +149 -0
  43. package/src/workbench/workspace/WorkspaceHighlightedText.tsx +22 -0
  44. package/src/workbench/workspace/WorkspaceSearchPanel.tsx +113 -0
  45. package/src/workbench/workspace/WorkspaceSearchResults.tsx +47 -0
  46. package/src/workbench/workspace/index.ts +98 -0
  47. package/src/workbench/workspace/path.ts +10 -0
  48. package/src/workbench/workspace/search.ts +6 -0
  49. package/src/workbench/workspace/tree.ts +1 -0
  50. package/src/workbench/workspace/types.ts +10 -0
  51. package/src/workbench/workspace/useVirtualWorkspace.ts +98 -0
@@ -0,0 +1,145 @@
1
+ import { useEffect, useRef, useState, type ReactNode } from 'react';
2
+ import { cx } from '../utils/cx';
3
+
4
+ export type ContextMenuItem =
5
+ | {
6
+ type: 'separator';
7
+ id?: string;
8
+ }
9
+ | {
10
+ type?: 'item';
11
+ id?: string;
12
+ label: ReactNode;
13
+ icon?: string;
14
+ shortcut?: ReactNode;
15
+ disabled?: boolean;
16
+ danger?: boolean;
17
+ onSelect: () => void;
18
+ };
19
+
20
+ export interface ContextMenuProps {
21
+ ariaLabel?: string;
22
+ className?: string;
23
+ items: ContextMenuItem[];
24
+ x: number;
25
+ y: number;
26
+ onClose: () => void;
27
+ }
28
+
29
+ function itemKey(item: ContextMenuItem, index: number): string {
30
+ return item.id ?? `${item.type ?? 'item'}-${index}`;
31
+ }
32
+
33
+ export function ContextMenu({
34
+ ariaLabel = 'Context menu',
35
+ className,
36
+ items,
37
+ x,
38
+ y,
39
+ onClose,
40
+ }: ContextMenuProps) {
41
+ const ref = useRef<HTMLDivElement>(null);
42
+ const [position, setPosition] = useState({ x, y });
43
+
44
+ useEffect(() => {
45
+ setPosition({ x, y });
46
+ }, [x, y]);
47
+
48
+ useEffect(() => {
49
+ const menu = ref.current;
50
+ if (!menu || typeof window === 'undefined') return;
51
+
52
+ const frame = window.requestAnimationFrame(() => {
53
+ const rect = menu.getBoundingClientRect();
54
+ setPosition({
55
+ x: Math.max(4, Math.min(x, window.innerWidth - rect.width - 4)),
56
+ y: Math.max(4, Math.min(y, window.innerHeight - rect.height - 4)),
57
+ });
58
+ });
59
+
60
+ return () => window.cancelAnimationFrame(frame);
61
+ }, [items.length, x, y]);
62
+
63
+ useEffect(() => {
64
+ const handleKeyDown = (event: KeyboardEvent) => {
65
+ if (event.key === 'Escape') {
66
+ event.preventDefault();
67
+ onClose();
68
+ }
69
+ };
70
+
71
+ const handlePointerDown = (event: PointerEvent) => {
72
+ if (ref.current?.contains(event.target as Node)) return;
73
+ onClose();
74
+ };
75
+
76
+ const handleContextMenu = (event: MouseEvent) => {
77
+ if (ref.current?.contains(event.target as Node)) {
78
+ event.preventDefault();
79
+ return;
80
+ }
81
+ onClose();
82
+ };
83
+
84
+ window.addEventListener('keydown', handleKeyDown);
85
+ window.addEventListener('pointerdown', handlePointerDown, true);
86
+ window.addEventListener('contextmenu', handleContextMenu, true);
87
+ window.addEventListener('resize', onClose);
88
+ window.addEventListener('scroll', onClose, true);
89
+
90
+ return () => {
91
+ window.removeEventListener('keydown', handleKeyDown);
92
+ window.removeEventListener('pointerdown', handlePointerDown, true);
93
+ window.removeEventListener('contextmenu', handleContextMenu, true);
94
+ window.removeEventListener('resize', onClose);
95
+ window.removeEventListener('scroll', onClose, true);
96
+ };
97
+ }, [onClose]);
98
+
99
+ useEffect(() => {
100
+ ref.current?.querySelector<HTMLButtonElement>('.ui-context-menu__item:not(:disabled)')?.focus();
101
+ }, []);
102
+
103
+ if (items.length === 0) return null;
104
+
105
+ return (
106
+ <div
107
+ ref={ref}
108
+ aria-label={ariaLabel}
109
+ className={cx('ui-context-menu', className)}
110
+ role="menu"
111
+ style={{
112
+ left: position.x,
113
+ top: position.y,
114
+ }}
115
+ onContextMenu={(event) => event.preventDefault()}
116
+ >
117
+ {items.map((item, index) =>
118
+ item.type === 'separator' ? (
119
+ <div key={itemKey(item, index)} className="ui-context-menu__separator" role="separator" />
120
+ ) : (
121
+ <button
122
+ key={itemKey(item, index)}
123
+ className="ui-context-menu__item"
124
+ data-danger={item.danger ? 'true' : undefined}
125
+ disabled={item.disabled}
126
+ role="menuitem"
127
+ type="button"
128
+ onClick={() => {
129
+ item.onSelect();
130
+ onClose();
131
+ }}
132
+ >
133
+ <span className="ui-context-menu__icon">
134
+ {item.icon ? <i className={`codicon ${item.icon}`} /> : null}
135
+ </span>
136
+ <span className="ui-context-menu__label">{item.label}</span>
137
+ {item.shortcut ? (
138
+ <span className="ui-context-menu__shortcut">{item.shortcut}</span>
139
+ ) : null}
140
+ </button>
141
+ ),
142
+ )}
143
+ </div>
144
+ );
145
+ }
@@ -0,0 +1,12 @@
1
+ import type { ComponentPropsWithRef } from 'react';
2
+ import { cx } from '../utils/cx';
3
+
4
+ type BadgeVariant = 'accent' | 'muted' | 'danger';
5
+
6
+ export interface BadgeProps extends ComponentPropsWithRef<'span'> {
7
+ variant?: BadgeVariant;
8
+ }
9
+
10
+ export function Badge({ className, variant = 'accent', ...props }: BadgeProps) {
11
+ return <span className={cx('ui-badge', className)} data-variant={variant} {...props} />;
12
+ }
@@ -0,0 +1,14 @@
1
+ import type { ComponentPropsWithRef } from 'react';
2
+ import { cx } from '../utils/cx';
3
+
4
+ type ButtonVariant = 'default' | 'primary' | 'danger';
5
+
6
+ export interface ButtonProps extends ComponentPropsWithRef<'button'> {
7
+ variant?: ButtonVariant;
8
+ }
9
+
10
+ export function Button({ className, type = 'button', variant = 'default', ...props }: ButtonProps) {
11
+ return (
12
+ <button className={cx('ui-button', className)} data-variant={variant} type={type} {...props} />
13
+ );
14
+ }
@@ -0,0 +1,15 @@
1
+ import type { ComponentPropsWithRef, ReactNode } from 'react';
2
+ import { cx } from '../utils/cx';
3
+
4
+ export interface CheckboxProps extends Omit<ComponentPropsWithRef<'input'>, 'type'> {
5
+ label: ReactNode;
6
+ }
7
+
8
+ export function Checkbox({ className, label, ...props }: CheckboxProps) {
9
+ return (
10
+ <label className={cx('ui-checkbox', className)}>
11
+ <input type="checkbox" {...props} />
12
+ <span>{label}</span>
13
+ </label>
14
+ );
15
+ }
@@ -0,0 +1,26 @@
1
+ import type { ComponentPropsWithRef, ReactNode } from 'react';
2
+ import { cx } from '../utils/cx';
3
+
4
+ export interface EmptyStateProps extends ComponentPropsWithRef<'div'> {
5
+ compact?: boolean;
6
+ icon: string;
7
+ children: ReactNode;
8
+ }
9
+
10
+ export function EmptyState({
11
+ children,
12
+ className,
13
+ compact = false,
14
+ icon,
15
+ ...props
16
+ }: EmptyStateProps) {
17
+ return (
18
+ <div
19
+ className={cx('ui-empty-state', compact && 'ui-empty-state--compact', className)}
20
+ {...props}
21
+ >
22
+ <i className={`codicon ${icon}`} />
23
+ <span>{children}</span>
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,38 @@
1
+ import type { ReactNode } from 'react';
2
+ import { cx } from '../utils/cx';
3
+
4
+ export interface FieldProps {
5
+ children: ReactNode;
6
+ className?: string;
7
+ description?: ReactNode;
8
+ htmlFor?: string;
9
+ inline?: boolean;
10
+ label?: ReactNode;
11
+ }
12
+
13
+ export function Field({
14
+ label,
15
+ children,
16
+ className,
17
+ description,
18
+ htmlFor,
19
+ inline = false,
20
+ }: FieldProps) {
21
+ const labelElement = !label ? null : htmlFor ? (
22
+ <label className="ui-field__label" htmlFor={htmlFor}>
23
+ {label}
24
+ </label>
25
+ ) : (
26
+ <span className="ui-field__label">{label}</span>
27
+ );
28
+
29
+ return (
30
+ <div className={cx('ui-field', inline && 'ui-field--inline', className)}>
31
+ <div>
32
+ {labelElement}
33
+ {description && <div className="ui-field__description">{description}</div>}
34
+ </div>
35
+ {children}
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,32 @@
1
+ import type { ComponentPropsWithRef } from 'react';
2
+ import { cx } from '../utils/cx';
3
+
4
+ type IconButtonVariant = 'default' | 'danger';
5
+
6
+ export interface IconButtonProps extends Omit<ComponentPropsWithRef<'button'>, 'children'> {
7
+ icon: string;
8
+ label: string;
9
+ variant?: IconButtonVariant;
10
+ }
11
+
12
+ export function IconButton({
13
+ className,
14
+ icon,
15
+ label,
16
+ type = 'button',
17
+ variant = 'default',
18
+ ...props
19
+ }: IconButtonProps) {
20
+ return (
21
+ <button
22
+ aria-label={label}
23
+ className={cx('ui-icon-button', className)}
24
+ data-variant={variant}
25
+ title={label}
26
+ type={type}
27
+ {...props}
28
+ >
29
+ <i className={`codicon ${icon}`} />
30
+ </button>
31
+ );
32
+ }
@@ -0,0 +1,12 @@
1
+ import type { ComponentPropsWithRef } from 'react';
2
+ import { cx } from '../utils/cx';
3
+
4
+ type ControlWidth = 'default' | 'wide' | 'full';
5
+
6
+ export interface SelectProps extends ComponentPropsWithRef<'select'> {
7
+ controlWidth?: ControlWidth;
8
+ }
9
+
10
+ export function Select({ className, controlWidth = 'default', ...props }: SelectProps) {
11
+ return <select className={cx('ui-select', className)} data-width={controlWidth} {...props} />;
12
+ }
@@ -0,0 +1,24 @@
1
+ import type { ComponentPropsWithRef } from 'react';
2
+ import { cx } from '../utils/cx';
3
+
4
+ type ControlWidth = 'default' | 'wide' | 'full';
5
+
6
+ export interface TextInputProps extends ComponentPropsWithRef<'input'> {
7
+ controlWidth?: ControlWidth;
8
+ monospace?: boolean;
9
+ }
10
+
11
+ export function TextInput({
12
+ className,
13
+ controlWidth = 'default',
14
+ monospace = false,
15
+ ...props
16
+ }: TextInputProps) {
17
+ return (
18
+ <input
19
+ className={cx('ui-input', monospace && 'ui-input--monospace', className)}
20
+ data-width={controlWidth}
21
+ {...props}
22
+ />
23
+ );
24
+ }
@@ -0,0 +1,8 @@
1
+ import type { ComponentPropsWithRef } from 'react';
2
+ import { cx } from '../utils/cx';
3
+
4
+ export type ToolbarProps = ComponentPropsWithRef<'div'>;
5
+
6
+ export function Toolbar({ className, ...props }: ToolbarProps) {
7
+ return <div className={cx('ui-toolbar', className)} {...props} />;
8
+ }