@workbench-kit/react 0.0.1-prototype.0 → 0.0.2-prototype.0.1.2

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 (155) hide show
  1. package/package.json +33 -9
  2. package/src/index.ts +545 -43
  3. package/src/jdw/JdwPreview.tsx +38 -0
  4. package/src/jdw/JsonCodeEditorPane.tsx +274 -0
  5. package/src/jdw/builtins/renderBuiltinWidgetLeaf.tsx +29 -0
  6. package/src/jdw/builtins/renderBuiltinWidgetNode.tsx +126 -0
  7. package/src/jdw/createBuiltinJdwRegistry.ts +111 -0
  8. package/src/jdw/cssRenderBackend.tsx +134 -0
  9. package/src/jdw/document.ts +38 -0
  10. package/src/jdw/fixtures/jdw-fixtures.ts +52 -0
  11. package/src/jdw/fixtures/jdw-sample-screens.ts +522 -0
  12. package/src/jdw/index.ts +45 -0
  13. package/src/jdw/parse.ts +1 -0
  14. package/src/jdw/renderJdw.tsx +32 -0
  15. package/src/json-config/JsonConfigWorkbench.tsx +316 -0
  16. package/src/json-config/index.ts +6 -0
  17. package/src/json-config/json-config-editor-state.ts +42 -0
  18. package/src/layout/Panel.tsx +146 -1
  19. package/src/layout/SideBarViewFrame.tsx +367 -328
  20. package/src/layout/WorkbenchCanvas.tsx +849 -0
  21. package/src/layout/WorkbenchFullscreen.tsx +368 -0
  22. package/src/layout/WorkbenchLayout.tsx +5 -0
  23. package/src/layout/WorkbenchLayoutBase.tsx +294 -0
  24. package/src/layout/WorkbenchPropertyPanel.tsx +497 -0
  25. package/src/layout/WorkbenchSidebarActions.tsx +274 -0
  26. package/src/layout/WorkbenchTree.tsx +351 -0
  27. package/src/layout/layoutHelpers.ts +25 -0
  28. package/src/modal/ConfirmDialog.tsx +61 -53
  29. package/src/modal/Modal.tsx +295 -35
  30. package/src/overlay/ContextMenu.tsx +146 -145
  31. package/src/primitives/AbsoluteBox.tsx +46 -0
  32. package/src/primitives/Badge.tsx +12 -12
  33. package/src/primitives/Button.tsx +44 -14
  34. package/src/primitives/Checkbox.tsx +11 -5
  35. package/src/primitives/EmptyState.tsx +27 -26
  36. package/src/primitives/Field.tsx +38 -38
  37. package/src/primitives/IconButton.tsx +5 -2
  38. package/src/primitives/List.tsx +111 -0
  39. package/src/primitives/NumberInput.tsx +38 -0
  40. package/src/primitives/ScrollArea.tsx +42 -0
  41. package/src/primitives/Select.tsx +1 -12
  42. package/src/primitives/StatusBar.tsx +46 -0
  43. package/src/primitives/TextArea.tsx +36 -0
  44. package/src/primitives/TextInput.tsx +11 -2
  45. package/src/primitives/WorkbenchChrome.tsx +424 -0
  46. package/src/primitives/WorkbenchEditor.tsx +394 -0
  47. package/src/primitives/index.ts +105 -0
  48. package/src/primitives/select/Select.tsx +266 -0
  49. package/src/primitives/select/index.ts +2 -0
  50. package/src/primitives/select/options.ts +38 -0
  51. package/src/primitives/select/overlay.ts +63 -0
  52. package/src/primitives/select/select.app.css +9 -0
  53. package/src/primitives/select/select.css +140 -0
  54. package/src/primitives/select/types.ts +18 -0
  55. package/src/primitives/styles.css +955 -0
  56. package/src/scrollbars.css +69 -0
  57. package/src/styles.css +7738 -1762
  58. package/src/utils/codicon.ts +17 -0
  59. package/src/widget-asset/WidgetAssetEditor.tsx +140 -0
  60. package/src/widget-asset/WidgetAssetMetadataForm.tsx +82 -0
  61. package/src/widget-asset/WidgetAssetModeControls.tsx +42 -0
  62. package/src/widget-asset/WidgetAssetSourceEditor.tsx +50 -0
  63. package/src/widget-asset/WidgetAssetWorkbench.tsx +112 -0
  64. package/src/widget-asset/index.ts +17 -0
  65. package/src/widget-asset/widget-asset-document.ts +37 -0
  66. package/src/widget-asset/widget-asset-mode.ts +3 -0
  67. package/src/widget-studio/builtin-widget-asset-catalog.ts +12 -0
  68. package/src/widget-studio/create-widget-studio-workspace-editor.tsx +79 -0
  69. package/src/widget-studio/index.ts +7 -0
  70. package/src/widget-studio/resolve-widget-studio-asset-catalog.ts +26 -0
  71. package/src/widget-tree/WidgetAssetPalette.tsx +77 -0
  72. package/src/widget-tree/WidgetInspectorPanel.tsx +266 -0
  73. package/src/widget-tree/WidgetSourceEditor.tsx +56 -0
  74. package/src/widget-tree/WidgetTreeLab.tsx +272 -0
  75. package/src/widget-tree/WidgetTreeModeControls.tsx +44 -0
  76. package/src/widget-tree/WidgetTreeSidePanel.tsx +49 -0
  77. package/src/widget-tree/WidgetTreeView.tsx +97 -0
  78. package/src/widget-tree/WidgetTreeWorkbench.tsx +111 -0
  79. package/src/widget-tree/WidgetTreeWorkspaceShell.tsx +154 -0
  80. package/src/widget-tree/demo-registry.ts +32 -0
  81. package/src/widget-tree/demo-widget-assets.ts +4 -0
  82. package/src/widget-tree/index.ts +27 -0
  83. package/src/widget-tree/widget-tree-document.ts +20 -0
  84. package/src/widget-tree/widget-tree-layout.ts +31 -0
  85. package/src/widget-tree/widget-tree-mode.ts +7 -0
  86. package/src/widget-tree/widget-tree-play-helpers.ts +74 -0
  87. package/src/workbench/ActivityBar.tsx +52 -52
  88. package/src/workbench/ArtifactShell.tsx +397 -0
  89. package/src/workbench/CommandPalette.tsx +819 -0
  90. package/src/workbench/ConfirmationFlow.tsx +245 -0
  91. package/src/workbench/MarkdownPreview.tsx +151 -0
  92. package/src/workbench/ShortcutCommandBridge.tsx +368 -0
  93. package/src/workbench/SplitView.tsx +136 -136
  94. package/src/workbench/StatusBar.tsx +178 -123
  95. package/src/workbench/StructuredArtifactEditor.tsx +205 -0
  96. package/src/workbench/Timeline.tsx +302 -0
  97. package/src/workbench/WorkbenchCanvasShell.tsx +941 -0
  98. package/src/workbench/WorkbenchShell.tsx +74 -70
  99. package/src/workbench/WorkbenchStandaloneShell.tsx +318 -291
  100. package/src/workbench/auth/WorkbenchAuthGate.tsx +61 -0
  101. package/src/workbench/auth/WorkbenchLoginView.tsx +635 -0
  102. package/src/workbench/auth/index.ts +19 -0
  103. package/src/workbench/chat/ChatComposer.tsx +165 -148
  104. package/src/workbench/chat/ChatMessageItem.tsx +10 -1
  105. package/src/workbench/chat/index.ts +9 -0
  106. package/src/workbench/chat/slashCommand.ts +28 -0
  107. package/src/workbench/chat/useChatRuntimeState.ts +76 -0
  108. package/src/workbench/chat/useSlashCommandSuggest.ts +101 -0
  109. package/src/workbench/commands.ts +520 -490
  110. package/src/workbench/index.ts +443 -98
  111. package/src/workbench/keyboard.ts +53 -0
  112. package/src/workbench/schema/index.ts +48 -0
  113. package/src/workbench/schema/workbenchDocument.ts +31 -0
  114. package/src/workbench/schema/workbenchDocumentActions.ts +6 -0
  115. package/src/workbench/schema/workbenchDocumentAdapter.ts +9 -0
  116. package/src/workbench/schema/workbenchDocumentPatch.ts +13 -0
  117. package/src/workbench/schema/workbenchDocumentRenderer.tsx +702 -0
  118. package/src/workbench/settings/NavigationPanel.tsx +47 -0
  119. package/src/workbench/settings/SchemaForm.tsx +420 -0
  120. package/src/workbench/settings/SectionedPanel.tsx +415 -0
  121. package/src/workbench/settings/StructuredDataForm.tsx +539 -0
  122. package/src/workbench/settings/StructuredDataSchemaPanel.tsx +559 -0
  123. package/src/workbench/settings/StructuredDataSchemaPanelEmbed.tsx +21 -0
  124. package/src/workbench/settings/StructuredDataSchemaPanelFrame.tsx +76 -0
  125. package/src/workbench/settings/StructuredDataTableView.tsx +127 -0
  126. package/src/workbench/settings/WorkbenchPanelRegion.tsx +46 -0
  127. package/src/workbench/settings/WorkbenchSettingsModal.tsx +21 -12
  128. package/src/workbench/settings/WorkbenchSettingsNav.tsx +38 -22
  129. package/src/workbench/settings/index.ts +135 -0
  130. package/src/workbench/settings/panel-region.css +21 -0
  131. package/src/workbench/settings/sectionedPanelScrollSpy.ts +143 -0
  132. package/src/workbench/settings/structured-data-schema-panel.css +330 -0
  133. package/src/workbench/settings/structured-data-table-view.css +85 -0
  134. package/src/workbench/settings/structuredDataSchema.ts +886 -0
  135. package/src/workbench/shell.ts +15 -0
  136. package/src/workbench/shellState.ts +203 -203
  137. package/src/workbench/standalone.ts +101 -94
  138. package/src/workbench/status.ts +129 -0
  139. package/src/workbench/theme.ts +44 -0
  140. package/src/workbench/workspace/MultiProviderExplorer.tsx +625 -0
  141. package/src/workbench/workspace/WorkspaceDraftsContext.tsx +111 -0
  142. package/src/workbench/workspace/WorkspaceEditor.tsx +47 -12
  143. package/src/workbench/workspace/WorkspaceEditorPanel.tsx +334 -353
  144. package/src/workbench/workspace/WorkspaceExplorer.tsx +532 -524
  145. package/src/workbench/workspace/WorkspaceFileIcon.tsx +32 -2
  146. package/src/workbench/workspace/WorkspaceSearchPanel.tsx +113 -113
  147. package/src/workbench/workspace/WorkspaceSearchResults.tsx +47 -47
  148. package/src/workbench/workspace/index.ts +156 -98
  149. package/src/workbench/workspace/mimeType.ts +45 -0
  150. package/src/workbench/workspace/path.ts +10 -10
  151. package/src/workbench/workspace/search.ts +6 -6
  152. package/src/workbench/workspace/tree.ts +1 -1
  153. package/src/workbench/workspace/types.ts +10 -10
  154. package/src/workbench/workspace/useVirtualWorkspace.ts +111 -98
  155. package/src/workbench/workspace/workspaceJsonDiagnostics.ts +83 -0
@@ -0,0 +1,61 @@
1
+ import type { ReactNode } from 'react';
2
+ import { cx } from '../../utils/cx';
3
+ import { WorkbenchLoginView, type WorkbenchLoginViewProps } from './WorkbenchLoginView';
4
+
5
+ export type WorkbenchAuthStatus = 'authenticated' | 'unauthenticated' | 'loading' | 'expired';
6
+
7
+ export interface WorkbenchAuthGateProps {
8
+ authStatus: WorkbenchAuthStatus;
9
+ children: ReactNode;
10
+ className?: string;
11
+ expiredLabel?: ReactNode;
12
+ loadingLabel?: ReactNode;
13
+ loginViewProps?: WorkbenchLoginViewProps;
14
+ renderLoading?: () => ReactNode;
15
+ renderLogin?: (
16
+ authStatus: Exclude<WorkbenchAuthStatus, 'authenticated' | 'loading'>,
17
+ ) => ReactNode;
18
+ }
19
+
20
+ export function WorkbenchAuthGate({
21
+ authStatus,
22
+ children,
23
+ className,
24
+ expiredLabel = 'Your session expired. Sign in again to continue.',
25
+ loadingLabel = 'Checking session...',
26
+ loginViewProps,
27
+ renderLoading,
28
+ renderLogin,
29
+ }: WorkbenchAuthGateProps) {
30
+ if (authStatus === 'authenticated') {
31
+ return <>{children}</>;
32
+ }
33
+
34
+ if (authStatus === 'loading') {
35
+ return (
36
+ <div className={cx('workbench-auth-gate', className)}>
37
+ {renderLoading?.() ?? (
38
+ <div className="workbench-auth-gate__loading" role="status">
39
+ <i className="codicon codicon-loading codicon-modifier-spin" aria-hidden />
40
+ <span>{loadingLabel}</span>
41
+ </div>
42
+ )}
43
+ </div>
44
+ );
45
+ }
46
+
47
+ return (
48
+ <div className={cx('workbench-auth-gate', className)}>
49
+ {renderLogin?.(authStatus) ?? (
50
+ <WorkbenchLoginView
51
+ {...loginViewProps}
52
+ error={
53
+ authStatus === 'expired'
54
+ ? (loginViewProps?.error ?? expiredLabel)
55
+ : loginViewProps?.error
56
+ }
57
+ />
58
+ )}
59
+ </div>
60
+ );
61
+ }
@@ -0,0 +1,635 @@
1
+ import { useId, useState, type FormEvent, type ReactNode } from 'react';
2
+ import { Button } from '../../primitives/Button';
3
+ import { TextInput } from '../../primitives/TextInput';
4
+ import { cx } from '../../utils/cx';
5
+
6
+ export interface WorkbenchLoginCredentials {
7
+ identifier: string;
8
+ password: string;
9
+ }
10
+
11
+ export interface WorkbenchLoginSubmitContext {
12
+ credentials: WorkbenchLoginCredentials;
13
+ event: FormEvent<HTMLFormElement>;
14
+ }
15
+
16
+ export interface WorkbenchSignUpCredentials {
17
+ displayName: string;
18
+ identifier: string;
19
+ password: string;
20
+ passwordConfirmation: string;
21
+ }
22
+
23
+ export interface WorkbenchSignUpSubmitContext {
24
+ credentials: WorkbenchSignUpCredentials;
25
+ event: FormEvent<HTMLFormElement>;
26
+ }
27
+
28
+ export interface WorkbenchPasswordResetCredentials {
29
+ identifier: string;
30
+ }
31
+
32
+ export interface WorkbenchPasswordResetSubmitContext {
33
+ credentials: WorkbenchPasswordResetCredentials;
34
+ event: FormEvent<HTMLFormElement>;
35
+ }
36
+
37
+ interface WorkbenchAuthFrameProps {
38
+ children: ReactNode;
39
+ formLabel: string;
40
+ onSubmit: (event: FormEvent<HTMLFormElement>) => void;
41
+ submitDisabled: boolean;
42
+ actions?: ReactNode;
43
+ brandMark?: ReactNode;
44
+ busy?: boolean;
45
+ busyLabel?: ReactNode;
46
+ className?: string;
47
+ error?: ReactNode;
48
+ footerBrand?: ReactNode;
49
+ productName?: ReactNode;
50
+ secondaryActions?: ReactNode;
51
+ statusLabel?: ReactNode;
52
+ submitLabel?: ReactNode;
53
+ }
54
+
55
+ interface WorkbenchAuthTextFieldProps {
56
+ id: string;
57
+ label: ReactNode;
58
+ autoComplete?: string;
59
+ disabled?: boolean;
60
+ placeholder?: string;
61
+ type?: string;
62
+ value: string;
63
+ onValueChange: (value: string) => void;
64
+ }
65
+
66
+ export interface WorkbenchLoginViewProps {
67
+ actions?: ReactNode;
68
+ brandMark?: ReactNode;
69
+ busy?: boolean;
70
+ busyLabel?: ReactNode;
71
+ className?: string;
72
+ defaultIdentifier?: string;
73
+ defaultPassword?: string;
74
+ disabled?: boolean;
75
+ error?: ReactNode;
76
+ footerBrand?: ReactNode;
77
+ identifier?: string;
78
+ identifierAutoComplete?: string;
79
+ identifierLabel?: ReactNode;
80
+ identifierPlaceholder?: string;
81
+ loginLabel?: string;
82
+ onIdentifierChange?: (value: string) => void;
83
+ onPasswordChange?: (value: string) => void;
84
+ onSubmit?: (context: WorkbenchLoginSubmitContext) => void;
85
+ password?: string;
86
+ passwordAutoComplete?: string;
87
+ passwordLabel?: ReactNode;
88
+ passwordPlaceholder?: string;
89
+ productName?: ReactNode;
90
+ requireCredentials?: boolean;
91
+ secondaryActions?: ReactNode;
92
+ statusLabel?: ReactNode;
93
+ submitLabel?: ReactNode;
94
+ }
95
+
96
+ export interface WorkbenchSignUpViewProps {
97
+ actions?: ReactNode;
98
+ brandMark?: ReactNode;
99
+ busy?: boolean;
100
+ busyLabel?: ReactNode;
101
+ className?: string;
102
+ defaultDisplayName?: string;
103
+ defaultIdentifier?: string;
104
+ defaultPassword?: string;
105
+ defaultPasswordConfirmation?: string;
106
+ disabled?: boolean;
107
+ displayName?: string;
108
+ displayNameAutoComplete?: string;
109
+ displayNameLabel?: ReactNode;
110
+ displayNamePlaceholder?: string;
111
+ error?: ReactNode;
112
+ footerBrand?: ReactNode;
113
+ identifier?: string;
114
+ identifierAutoComplete?: string;
115
+ identifierLabel?: ReactNode;
116
+ identifierPlaceholder?: string;
117
+ onDisplayNameChange?: (value: string) => void;
118
+ onIdentifierChange?: (value: string) => void;
119
+ onPasswordChange?: (value: string) => void;
120
+ onPasswordConfirmationChange?: (value: string) => void;
121
+ onSubmit?: (context: WorkbenchSignUpSubmitContext) => void;
122
+ password?: string;
123
+ passwordAutoComplete?: string;
124
+ passwordConfirmation?: string;
125
+ passwordConfirmationAutoComplete?: string;
126
+ passwordConfirmationLabel?: ReactNode;
127
+ passwordConfirmationPlaceholder?: string;
128
+ passwordLabel?: ReactNode;
129
+ passwordMismatchLabel?: ReactNode;
130
+ passwordPlaceholder?: string;
131
+ productName?: ReactNode;
132
+ requireCredentials?: boolean;
133
+ requirePasswordConfirmation?: boolean;
134
+ secondaryActions?: ReactNode;
135
+ signUpLabel?: string;
136
+ statusLabel?: ReactNode;
137
+ submitLabel?: ReactNode;
138
+ }
139
+
140
+ export interface WorkbenchPasswordResetViewProps {
141
+ actions?: ReactNode;
142
+ brandMark?: ReactNode;
143
+ busy?: boolean;
144
+ busyLabel?: ReactNode;
145
+ className?: string;
146
+ defaultIdentifier?: string;
147
+ disabled?: boolean;
148
+ error?: ReactNode;
149
+ findPasswordLabel?: string;
150
+ footerBrand?: ReactNode;
151
+ identifier?: string;
152
+ identifierAutoComplete?: string;
153
+ identifierLabel?: ReactNode;
154
+ identifierPlaceholder?: string;
155
+ onIdentifierChange?: (value: string) => void;
156
+ onSubmit?: (context: WorkbenchPasswordResetSubmitContext) => void;
157
+ productName?: ReactNode;
158
+ requireCredentials?: boolean;
159
+ secondaryActions?: ReactNode;
160
+ statusLabel?: ReactNode;
161
+ submitLabel?: ReactNode;
162
+ }
163
+
164
+ export function WorkbenchLoginBrandMark() {
165
+ return (
166
+ <div className="workbench-login-brand-mark" aria-hidden>
167
+ <span className="workbench-login-brand-mark__ring workbench-login-brand-mark__ring--primary" />
168
+ <span className="workbench-login-brand-mark__ring workbench-login-brand-mark__ring--secondary" />
169
+ <span className="workbench-login-brand-mark__ring workbench-login-brand-mark__ring--tertiary" />
170
+ <span className="workbench-login-brand-mark__ring workbench-login-brand-mark__ring--small" />
171
+ </div>
172
+ );
173
+ }
174
+
175
+ export function WorkbenchLoginView({
176
+ actions,
177
+ brandMark,
178
+ busy = false,
179
+ busyLabel = 'Signing in...',
180
+ className,
181
+ defaultIdentifier = '',
182
+ defaultPassword = '',
183
+ disabled = false,
184
+ error,
185
+ footerBrand,
186
+ identifier,
187
+ identifierAutoComplete = 'username',
188
+ identifierLabel = 'Email',
189
+ identifierPlaceholder,
190
+ loginLabel = 'Sign in',
191
+ onIdentifierChange,
192
+ onPasswordChange,
193
+ onSubmit,
194
+ password,
195
+ passwordAutoComplete = 'current-password',
196
+ passwordLabel = 'Password',
197
+ passwordPlaceholder,
198
+ productName = 'Sign in',
199
+ requireCredentials = true,
200
+ secondaryActions,
201
+ statusLabel,
202
+ submitLabel = 'Sign in',
203
+ }: WorkbenchLoginViewProps) {
204
+ const generatedId = useId().replace(/:/g, '');
205
+ const identifierId = `${generatedId}-signin-identifier`;
206
+ const passwordId = `${generatedId}-signin-password`;
207
+ const [uncontrolledIdentifier, setUncontrolledIdentifier] = useState(defaultIdentifier);
208
+ const [uncontrolledPassword, setUncontrolledPassword] = useState(defaultPassword);
209
+ const resolvedIdentifier = identifier ?? uncontrolledIdentifier;
210
+ const resolvedPassword = password ?? uncontrolledPassword;
211
+ const isLocked = disabled || busy;
212
+ const hasCredentials = Boolean(resolvedIdentifier.trim()) && Boolean(resolvedPassword);
213
+ const isSubmitDisabled = isLocked || (requireCredentials && !hasCredentials);
214
+
215
+ const handleIdentifierChange = (value: string) => {
216
+ if (identifier === undefined) {
217
+ setUncontrolledIdentifier(value);
218
+ }
219
+
220
+ onIdentifierChange?.(value);
221
+ };
222
+
223
+ const handlePasswordChange = (value: string) => {
224
+ if (password === undefined) {
225
+ setUncontrolledPassword(value);
226
+ }
227
+
228
+ onPasswordChange?.(value);
229
+ };
230
+
231
+ const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
232
+ event.preventDefault();
233
+
234
+ if (isSubmitDisabled) return;
235
+
236
+ onSubmit?.({
237
+ credentials: {
238
+ identifier: resolvedIdentifier,
239
+ password: resolvedPassword,
240
+ },
241
+ event,
242
+ });
243
+ };
244
+
245
+ return (
246
+ <WorkbenchAuthFrame
247
+ actions={actions}
248
+ brandMark={brandMark}
249
+ busy={busy}
250
+ busyLabel={busyLabel}
251
+ className={className}
252
+ error={error}
253
+ footerBrand={footerBrand}
254
+ formLabel={loginLabel}
255
+ productName={productName}
256
+ secondaryActions={secondaryActions}
257
+ statusLabel={statusLabel}
258
+ submitDisabled={isSubmitDisabled}
259
+ submitLabel={submitLabel}
260
+ onSubmit={handleSubmit}
261
+ >
262
+ <WorkbenchAuthTextField
263
+ id={identifierId}
264
+ autoComplete={identifierAutoComplete}
265
+ disabled={isLocked}
266
+ label={identifierLabel}
267
+ placeholder={identifierPlaceholder}
268
+ value={resolvedIdentifier}
269
+ onValueChange={handleIdentifierChange}
270
+ />
271
+ <WorkbenchAuthTextField
272
+ id={passwordId}
273
+ autoComplete={passwordAutoComplete}
274
+ disabled={isLocked}
275
+ label={passwordLabel}
276
+ placeholder={passwordPlaceholder}
277
+ type="password"
278
+ value={resolvedPassword}
279
+ onValueChange={handlePasswordChange}
280
+ />
281
+ </WorkbenchAuthFrame>
282
+ );
283
+ }
284
+
285
+ export function WorkbenchSignUpView({
286
+ actions,
287
+ brandMark,
288
+ busy = false,
289
+ busyLabel = 'Creating account...',
290
+ className,
291
+ defaultDisplayName = '',
292
+ defaultIdentifier = '',
293
+ defaultPassword = '',
294
+ defaultPasswordConfirmation = '',
295
+ disabled = false,
296
+ displayName,
297
+ displayNameAutoComplete = 'name',
298
+ displayNameLabel = 'Name',
299
+ displayNamePlaceholder,
300
+ error,
301
+ footerBrand,
302
+ identifier,
303
+ identifierAutoComplete = 'email',
304
+ identifierLabel = 'Email',
305
+ identifierPlaceholder,
306
+ onDisplayNameChange,
307
+ onIdentifierChange,
308
+ onPasswordChange,
309
+ onPasswordConfirmationChange,
310
+ onSubmit,
311
+ password,
312
+ passwordAutoComplete = 'new-password',
313
+ passwordConfirmation,
314
+ passwordConfirmationAutoComplete = 'new-password',
315
+ passwordConfirmationLabel = 'Confirm password',
316
+ passwordConfirmationPlaceholder,
317
+ passwordLabel = 'Password',
318
+ passwordMismatchLabel = 'Passwords do not match.',
319
+ passwordPlaceholder,
320
+ productName = 'Create account',
321
+ requireCredentials = true,
322
+ requirePasswordConfirmation = true,
323
+ secondaryActions,
324
+ signUpLabel = 'Create account',
325
+ statusLabel,
326
+ submitLabel = 'Create account',
327
+ }: WorkbenchSignUpViewProps) {
328
+ const generatedId = useId().replace(/:/g, '');
329
+ const displayNameId = `${generatedId}-signup-display-name`;
330
+ const identifierId = `${generatedId}-signup-identifier`;
331
+ const passwordId = `${generatedId}-signup-password`;
332
+ const passwordConfirmationId = `${generatedId}-signup-password-confirmation`;
333
+ const [uncontrolledDisplayName, setUncontrolledDisplayName] = useState(defaultDisplayName);
334
+ const [uncontrolledIdentifier, setUncontrolledIdentifier] = useState(defaultIdentifier);
335
+ const [uncontrolledPassword, setUncontrolledPassword] = useState(defaultPassword);
336
+ const [uncontrolledPasswordConfirmation, setUncontrolledPasswordConfirmation] = useState(
337
+ defaultPasswordConfirmation,
338
+ );
339
+ const resolvedDisplayName = displayName ?? uncontrolledDisplayName;
340
+ const resolvedIdentifier = identifier ?? uncontrolledIdentifier;
341
+ const resolvedPassword = password ?? uncontrolledPassword;
342
+ const resolvedPasswordConfirmation = passwordConfirmation ?? uncontrolledPasswordConfirmation;
343
+ const isLocked = disabled || busy;
344
+ const passwordsAreComparable = Boolean(resolvedPassword && resolvedPasswordConfirmation);
345
+ const hasPasswordMismatch =
346
+ requirePasswordConfirmation &&
347
+ passwordsAreComparable &&
348
+ resolvedPassword !== resolvedPasswordConfirmation;
349
+ const hasCredentials =
350
+ Boolean(resolvedDisplayName.trim()) &&
351
+ Boolean(resolvedIdentifier.trim()) &&
352
+ Boolean(resolvedPassword) &&
353
+ (!requirePasswordConfirmation || Boolean(resolvedPasswordConfirmation));
354
+ const isSubmitDisabled =
355
+ isLocked || hasPasswordMismatch || (requireCredentials && !hasCredentials);
356
+ const resolvedError = error ?? (hasPasswordMismatch ? passwordMismatchLabel : undefined);
357
+
358
+ const handleDisplayNameChange = (value: string) => {
359
+ if (displayName === undefined) {
360
+ setUncontrolledDisplayName(value);
361
+ }
362
+
363
+ onDisplayNameChange?.(value);
364
+ };
365
+
366
+ const handleIdentifierChange = (value: string) => {
367
+ if (identifier === undefined) {
368
+ setUncontrolledIdentifier(value);
369
+ }
370
+
371
+ onIdentifierChange?.(value);
372
+ };
373
+
374
+ const handlePasswordChange = (value: string) => {
375
+ if (password === undefined) {
376
+ setUncontrolledPassword(value);
377
+ }
378
+
379
+ onPasswordChange?.(value);
380
+ };
381
+
382
+ const handlePasswordConfirmationChange = (value: string) => {
383
+ if (passwordConfirmation === undefined) {
384
+ setUncontrolledPasswordConfirmation(value);
385
+ }
386
+
387
+ onPasswordConfirmationChange?.(value);
388
+ };
389
+
390
+ const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
391
+ event.preventDefault();
392
+
393
+ if (isSubmitDisabled) return;
394
+
395
+ onSubmit?.({
396
+ credentials: {
397
+ displayName: resolvedDisplayName,
398
+ identifier: resolvedIdentifier,
399
+ password: resolvedPassword,
400
+ passwordConfirmation: resolvedPasswordConfirmation,
401
+ },
402
+ event,
403
+ });
404
+ };
405
+
406
+ return (
407
+ <WorkbenchAuthFrame
408
+ actions={actions}
409
+ brandMark={brandMark}
410
+ busy={busy}
411
+ busyLabel={busyLabel}
412
+ className={className}
413
+ error={resolvedError}
414
+ footerBrand={footerBrand}
415
+ formLabel={signUpLabel}
416
+ productName={productName}
417
+ secondaryActions={secondaryActions}
418
+ statusLabel={statusLabel}
419
+ submitDisabled={isSubmitDisabled}
420
+ submitLabel={submitLabel}
421
+ onSubmit={handleSubmit}
422
+ >
423
+ <WorkbenchAuthTextField
424
+ id={displayNameId}
425
+ autoComplete={displayNameAutoComplete}
426
+ disabled={isLocked}
427
+ label={displayNameLabel}
428
+ placeholder={displayNamePlaceholder}
429
+ value={resolvedDisplayName}
430
+ onValueChange={handleDisplayNameChange}
431
+ />
432
+ <WorkbenchAuthTextField
433
+ id={identifierId}
434
+ autoComplete={identifierAutoComplete}
435
+ disabled={isLocked}
436
+ label={identifierLabel}
437
+ placeholder={identifierPlaceholder}
438
+ value={resolvedIdentifier}
439
+ onValueChange={handleIdentifierChange}
440
+ />
441
+ <WorkbenchAuthTextField
442
+ id={passwordId}
443
+ autoComplete={passwordAutoComplete}
444
+ disabled={isLocked}
445
+ label={passwordLabel}
446
+ placeholder={passwordPlaceholder}
447
+ type="password"
448
+ value={resolvedPassword}
449
+ onValueChange={handlePasswordChange}
450
+ />
451
+ {requirePasswordConfirmation ? (
452
+ <WorkbenchAuthTextField
453
+ id={passwordConfirmationId}
454
+ autoComplete={passwordConfirmationAutoComplete}
455
+ disabled={isLocked}
456
+ label={passwordConfirmationLabel}
457
+ placeholder={passwordConfirmationPlaceholder}
458
+ type="password"
459
+ value={resolvedPasswordConfirmation}
460
+ onValueChange={handlePasswordConfirmationChange}
461
+ />
462
+ ) : null}
463
+ </WorkbenchAuthFrame>
464
+ );
465
+ }
466
+
467
+ export function WorkbenchPasswordResetView({
468
+ actions,
469
+ brandMark,
470
+ busy = false,
471
+ busyLabel = 'Sending...',
472
+ className,
473
+ defaultIdentifier = '',
474
+ disabled = false,
475
+ error,
476
+ findPasswordLabel = 'Find password',
477
+ footerBrand,
478
+ identifier,
479
+ identifierAutoComplete = 'email',
480
+ identifierLabel = 'Email',
481
+ identifierPlaceholder,
482
+ onIdentifierChange,
483
+ onSubmit,
484
+ productName = 'Find password',
485
+ requireCredentials = true,
486
+ secondaryActions,
487
+ statusLabel,
488
+ submitLabel = 'Send reset link',
489
+ }: WorkbenchPasswordResetViewProps) {
490
+ const generatedId = useId().replace(/:/g, '');
491
+ const identifierId = `${generatedId}-password-reset-identifier`;
492
+ const [uncontrolledIdentifier, setUncontrolledIdentifier] = useState(defaultIdentifier);
493
+ const resolvedIdentifier = identifier ?? uncontrolledIdentifier;
494
+ const isLocked = disabled || busy;
495
+ const hasCredentials = Boolean(resolvedIdentifier.trim());
496
+ const isSubmitDisabled = isLocked || (requireCredentials && !hasCredentials);
497
+
498
+ const handleIdentifierChange = (value: string) => {
499
+ if (identifier === undefined) {
500
+ setUncontrolledIdentifier(value);
501
+ }
502
+
503
+ onIdentifierChange?.(value);
504
+ };
505
+
506
+ const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
507
+ event.preventDefault();
508
+
509
+ if (isSubmitDisabled) return;
510
+
511
+ onSubmit?.({
512
+ credentials: {
513
+ identifier: resolvedIdentifier,
514
+ },
515
+ event,
516
+ });
517
+ };
518
+
519
+ return (
520
+ <WorkbenchAuthFrame
521
+ actions={actions}
522
+ brandMark={brandMark}
523
+ busy={busy}
524
+ busyLabel={busyLabel}
525
+ className={className}
526
+ error={error}
527
+ footerBrand={footerBrand}
528
+ formLabel={findPasswordLabel}
529
+ productName={productName}
530
+ secondaryActions={secondaryActions}
531
+ statusLabel={statusLabel}
532
+ submitDisabled={isSubmitDisabled}
533
+ submitLabel={submitLabel}
534
+ onSubmit={handleSubmit}
535
+ >
536
+ <WorkbenchAuthTextField
537
+ id={identifierId}
538
+ autoComplete={identifierAutoComplete}
539
+ disabled={isLocked}
540
+ label={identifierLabel}
541
+ placeholder={identifierPlaceholder}
542
+ value={resolvedIdentifier}
543
+ onValueChange={handleIdentifierChange}
544
+ />
545
+ </WorkbenchAuthFrame>
546
+ );
547
+ }
548
+
549
+ function WorkbenchAuthFrame({
550
+ actions,
551
+ brandMark,
552
+ busy = false,
553
+ busyLabel,
554
+ children,
555
+ className,
556
+ error,
557
+ footerBrand,
558
+ formLabel,
559
+ onSubmit,
560
+ productName,
561
+ secondaryActions,
562
+ statusLabel,
563
+ submitDisabled,
564
+ submitLabel,
565
+ }: WorkbenchAuthFrameProps) {
566
+ return (
567
+ <section
568
+ className={cx('workbench-login-view', className)}
569
+ aria-label={formLabel}
570
+ aria-busy={busy || undefined}
571
+ >
572
+ <div className="workbench-login-view__brand-pane">
573
+ <div className="workbench-login-view__mark">{brandMark ?? <WorkbenchLoginBrandMark />}</div>
574
+ {footerBrand ? (
575
+ <div className="workbench-login-view__footer-brand">{footerBrand}</div>
576
+ ) : null}
577
+ </div>
578
+
579
+ <form className="workbench-login-view__form" onSubmit={onSubmit}>
580
+ <div className="workbench-login-view__heading">
581
+ <h1 className="workbench-login-view__title">{productName}</h1>
582
+ {statusLabel ? <p className="workbench-login-view__status">{statusLabel}</p> : null}
583
+ </div>
584
+
585
+ <div className="workbench-login-view__fields">{children}</div>
586
+
587
+ {error ? (
588
+ <div className="workbench-login-view__error" role="alert">
589
+ {error}
590
+ </div>
591
+ ) : null}
592
+
593
+ <div className="workbench-login-view__actions">
594
+ {actions}
595
+ <Button disabled={submitDisabled} variant="primary" type="submit">
596
+ {busy ? busyLabel : submitLabel}
597
+ </Button>
598
+ </div>
599
+
600
+ {secondaryActions ? (
601
+ <div className="workbench-login-view__secondary-actions">{secondaryActions}</div>
602
+ ) : null}
603
+ </form>
604
+ </section>
605
+ );
606
+ }
607
+
608
+ function WorkbenchAuthTextField({
609
+ id,
610
+ autoComplete,
611
+ disabled,
612
+ label,
613
+ placeholder,
614
+ type,
615
+ value,
616
+ onValueChange,
617
+ }: WorkbenchAuthTextFieldProps) {
618
+ return (
619
+ <div className="workbench-login-view__field">
620
+ <label className="workbench-login-view__label" htmlFor={id}>
621
+ {label}
622
+ </label>
623
+ <TextInput
624
+ id={id}
625
+ autoComplete={autoComplete}
626
+ controlWidth="full"
627
+ disabled={disabled}
628
+ placeholder={placeholder}
629
+ type={type}
630
+ value={value}
631
+ onValueChange={onValueChange}
632
+ />
633
+ </div>
634
+ );
635
+ }
@@ -0,0 +1,19 @@
1
+ export { WorkbenchAuthGate } from './WorkbenchAuthGate';
2
+ export type { WorkbenchAuthGateProps, WorkbenchAuthStatus } from './WorkbenchAuthGate';
3
+ export {
4
+ WorkbenchLoginBrandMark,
5
+ WorkbenchLoginView,
6
+ WorkbenchPasswordResetView,
7
+ WorkbenchSignUpView,
8
+ } from './WorkbenchLoginView';
9
+ export type {
10
+ WorkbenchLoginCredentials,
11
+ WorkbenchLoginSubmitContext,
12
+ WorkbenchLoginViewProps,
13
+ WorkbenchPasswordResetCredentials,
14
+ WorkbenchPasswordResetSubmitContext,
15
+ WorkbenchPasswordResetViewProps,
16
+ WorkbenchSignUpCredentials,
17
+ WorkbenchSignUpSubmitContext,
18
+ WorkbenchSignUpViewProps,
19
+ } from './WorkbenchLoginView';