@puredesktop/puredesktop-ui-bridge 2.1.8 → 2.1.9
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/package.json +35 -5
- package/src/agents/mcpServerPresets.test.ts +105 -0
- package/src/agents/mcpServerPresets.ts +380 -0
- package/src/agents/runtime/index.ts +7 -0
- package/src/agents/runtime/mapAgentSnapshot.ts +11 -8
- package/src/agents/runtime/pendingAsks.test.ts +117 -0
- package/src/agents/runtime/pendingAsks.ts +110 -0
- package/src/bridge/agentScopeDefaults.ts +65 -0
- package/src/bridge/agentTypes.ts +36 -2
- package/src/bridge/agents.d.mts +6 -0
- package/src/bridge/agents.mjs +10 -0
- package/src/bridge/agents.ts +4 -0
- package/src/bridge/credentials.d.mts +62 -0
- package/src/bridge/credentials.mjs +53 -0
- package/src/bridge/events.d.mts +1 -0
- package/src/bridge/events.mjs +1 -0
- package/src/bridge/googleAuth.ts +7 -152
- package/src/bridge/mcpClient.d.mts +74 -0
- package/src/bridge/mcpClient.mjs +79 -0
- package/src/bridge/methods.d.mts +22 -0
- package/src/bridge/methods.mjs +23 -0
- package/src/bridge/notifications.d.mts +12 -0
- package/src/bridge/notifications.mjs +36 -0
- package/src/bridge/operations.d.mts +44 -0
- package/src/bridge/operations.mjs +40 -0
- package/src/bridge/react/usePlatformAgentSessionDrive.test.tsx +29 -10
- package/src/bridge/react/usePlatformAgentSessionDrive.tsx +10 -81
- package/src/bridge/react/usePlatformAppSettings.test.tsx +105 -105
- package/src/bridge/react/usePlatformAppSettings.tsx +104 -104
- package/src/bridge/react/usePlatformJsonStore.test.tsx +152 -152
- package/src/bridge/react/usePlatformJsonStore.tsx +149 -149
- package/src/bridge/react/usePlatformOperations.tsx +75 -0
- package/src/bridge/storage.d.mts +17 -4
- package/src/bridge/storage.mjs +6 -3
- package/src/components/agents/AgentComposer.tsx +10 -6
- package/src/components/agents/AgentDrawerPanel.test.tsx +2 -205
- package/src/components/agents/AgentDrawerPanel.tsx +23 -64
- package/src/components/agents/AgentMessageBubble.tsx +59 -33
- package/src/components/agents/AgentMessageList.tsx +4 -9
- package/src/components/agents/AgentQuestionPromptList.tsx +34 -212
- package/src/components/agents/AgentToolApprovalActions.tsx +13 -11
- package/src/components/agents/AgentToolPendingList.tsx +6 -6
- package/src/components/agents/AgentToolPendingReview.tsx +6 -6
- package/src/components/agents/QuestionRequestList.tsx +278 -0
- package/src/components/agents/agentPanelStyles.ts +9 -33
- package/src/components/agents/agentTypes.ts +25 -43
- package/src/components/agents/index.ts +4 -0
- package/src/components/assets/asset-library/sidebar/AssetLibraryList.tsx +10 -4
- package/src/components/chrome/WorkspaceTabStrip.tsx +4 -3
- package/src/components/common/chat/ChatBox.tsx +9 -9
- package/src/components/common/chat/ChatModelMenu.tsx +34 -4
- package/src/components/common/chat/ChatThread.tsx +23 -7
- package/src/components/common/connections/ProviderConnection.test.tsx +208 -0
- package/src/components/common/connections/ProviderConnection.tsx +277 -0
- package/src/components/common/connections/index.ts +5 -0
- package/src/components/common/containers/AppFrame.tsx +26 -19
- package/src/components/common/containers/AppHeader.tsx +20 -20
- package/src/components/common/documents/DocumentSwitcher.tsx +63 -58
- package/src/components/common/dropdown/MenuDropdownItem.tsx +14 -2
- package/src/components/common/dropdown/MenuPortal.tsx +3 -1
- package/src/components/common/dropdown/menuViewportPosition.test.ts +20 -10
- package/src/components/common/dropdown/menuViewportPosition.ts +35 -27
- package/src/components/common/overlays/Modal.tsx +13 -2
- package/src/components/editor/EditorLinkPromptDialog.tsx +1 -1
- package/src/ics/generateIcs.test.ts +153 -0
- package/src/ics/generateIcs.ts +197 -0
- package/src/index.ts +3 -0
- package/src/net/httpRetry.test.ts +117 -0
- package/src/net/httpRetry.ts +111 -0
- package/src/theme/appAccents.ts +6 -0
- package/src/theme/appIdentityCss.mjs +11 -0
- package/src/theme/appIdentityCss.ts +11 -0
- package/src/theme/themes/dark.ts +3 -1
- package/src/theme/themes/light.ts +3 -1
- package/src/bridge/googleProviderConfig.ts +0 -73
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
connectCredentialOAuth,
|
|
4
|
+
disconnectCredential,
|
|
5
|
+
readCredentialStatus,
|
|
6
|
+
setCredentialOAuthConfig,
|
|
7
|
+
type OAuthCredentialStatus,
|
|
8
|
+
} from '../../../bridge/credentials.mjs'
|
|
9
|
+
import { Button } from '../buttons/Button.js'
|
|
10
|
+
import { FormField } from '../inputs/FormField.js'
|
|
11
|
+
import { TextField } from '../inputs/TextField.js'
|
|
12
|
+
import {
|
|
13
|
+
ConnectionButtonRow,
|
|
14
|
+
ConnectionFieldGrid,
|
|
15
|
+
ConnectionPanel,
|
|
16
|
+
ConnectionPanelText,
|
|
17
|
+
ConnectionPanelTitle,
|
|
18
|
+
ConnectionTestResult,
|
|
19
|
+
} from './ConnectionSetup.js'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Bridge surface the component talks to. Overridable for tests; production
|
|
23
|
+
* defaults to the shell credentials bridge. The shell owns all state — this
|
|
24
|
+
* component never caches connection state and never sees secret material.
|
|
25
|
+
*/
|
|
26
|
+
export interface ProviderConnectionClient {
|
|
27
|
+
status(request: { id: string }): Promise<OAuthCredentialStatus>
|
|
28
|
+
setOAuthConfig(request: {
|
|
29
|
+
id: string
|
|
30
|
+
clientId: string
|
|
31
|
+
clientSecret: string
|
|
32
|
+
scopes?: string[]
|
|
33
|
+
}): Promise<unknown>
|
|
34
|
+
connectOAuth(request: {
|
|
35
|
+
id: string
|
|
36
|
+
scopes?: string[]
|
|
37
|
+
}): Promise<{ email?: string }>
|
|
38
|
+
disconnect(request: { id: string; forget?: boolean }): Promise<unknown>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const bridgeClient: ProviderConnectionClient = {
|
|
42
|
+
status: readCredentialStatus,
|
|
43
|
+
setOAuthConfig: setCredentialOAuthConfig,
|
|
44
|
+
connectOAuth: connectCredentialOAuth,
|
|
45
|
+
disconnect: disconnectCredential,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ProviderConnectionProps {
|
|
49
|
+
/** Shell credential id, e.g. `google.oauth`. */
|
|
50
|
+
credentialId: string
|
|
51
|
+
title: string
|
|
52
|
+
description?: string
|
|
53
|
+
/** Scopes this app needs; merged into the connection request. */
|
|
54
|
+
scopes?: string[]
|
|
55
|
+
/** "How to create an OAuth client" walkthrough for the unconfigured state. */
|
|
56
|
+
helpUrl?: string
|
|
57
|
+
/** Called after connect/disconnect so the host app can rebuild providers. */
|
|
58
|
+
onStatusChange?: (status: OAuthCredentialStatus) => void
|
|
59
|
+
client?: ProviderConnectionClient
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type Busy = 'idle' | 'saving' | 'connecting' | 'disconnecting'
|
|
63
|
+
|
|
64
|
+
export function ProviderConnection({
|
|
65
|
+
credentialId,
|
|
66
|
+
title,
|
|
67
|
+
description,
|
|
68
|
+
scopes,
|
|
69
|
+
helpUrl,
|
|
70
|
+
onStatusChange,
|
|
71
|
+
client = bridgeClient,
|
|
72
|
+
}: ProviderConnectionProps): React.ReactElement {
|
|
73
|
+
const [status, setStatus] = useState<OAuthCredentialStatus | null>(null)
|
|
74
|
+
const [clientIdDraft, setClientIdDraft] = useState('')
|
|
75
|
+
const [clientSecretDraft, setClientSecretDraft] = useState('')
|
|
76
|
+
const [busy, setBusy] = useState<Busy>('idle')
|
|
77
|
+
const [error, setError] = useState<string | null>(null)
|
|
78
|
+
|
|
79
|
+
const refresh = useCallback(async () => {
|
|
80
|
+
const next = await client.status({ id: credentialId })
|
|
81
|
+
setStatus(next)
|
|
82
|
+
onStatusChange?.(next)
|
|
83
|
+
return next
|
|
84
|
+
}, [client, credentialId, onStatusChange])
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
refresh().catch(cause =>
|
|
88
|
+
setError(cause instanceof Error ? cause.message : String(cause)),
|
|
89
|
+
)
|
|
90
|
+
}, [refresh])
|
|
91
|
+
|
|
92
|
+
const scopesRequest = useMemo(
|
|
93
|
+
() => (scopes?.length ? { scopes } : {}),
|
|
94
|
+
[scopes],
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const saveConfig = useCallback(async () => {
|
|
98
|
+
setBusy('saving')
|
|
99
|
+
setError(null)
|
|
100
|
+
try {
|
|
101
|
+
await client.setOAuthConfig({
|
|
102
|
+
id: credentialId,
|
|
103
|
+
clientId: clientIdDraft,
|
|
104
|
+
clientSecret: clientSecretDraft,
|
|
105
|
+
...scopesRequest,
|
|
106
|
+
})
|
|
107
|
+
// Write-only: the secret leaves the DOM the moment it is saved.
|
|
108
|
+
setClientSecretDraft('')
|
|
109
|
+
setClientIdDraft('')
|
|
110
|
+
await refresh()
|
|
111
|
+
} catch (cause) {
|
|
112
|
+
setError(cause instanceof Error ? cause.message : String(cause))
|
|
113
|
+
} finally {
|
|
114
|
+
setBusy('idle')
|
|
115
|
+
}
|
|
116
|
+
}, [client, credentialId, clientIdDraft, clientSecretDraft, refresh, scopesRequest])
|
|
117
|
+
|
|
118
|
+
const connect = useCallback(async () => {
|
|
119
|
+
setBusy('connecting')
|
|
120
|
+
setError(null)
|
|
121
|
+
try {
|
|
122
|
+
await client.connectOAuth({ id: credentialId, ...scopesRequest })
|
|
123
|
+
await refresh()
|
|
124
|
+
} catch (cause) {
|
|
125
|
+
setError(cause instanceof Error ? cause.message : String(cause))
|
|
126
|
+
} finally {
|
|
127
|
+
setBusy('idle')
|
|
128
|
+
}
|
|
129
|
+
}, [client, credentialId, refresh, scopesRequest])
|
|
130
|
+
|
|
131
|
+
const disconnect = useCallback(async () => {
|
|
132
|
+
setBusy('disconnecting')
|
|
133
|
+
setError(null)
|
|
134
|
+
try {
|
|
135
|
+
await client.disconnect({ id: credentialId })
|
|
136
|
+
await refresh()
|
|
137
|
+
} catch (cause) {
|
|
138
|
+
setError(cause instanceof Error ? cause.message : String(cause))
|
|
139
|
+
} finally {
|
|
140
|
+
setBusy('idle')
|
|
141
|
+
}
|
|
142
|
+
}, [client, credentialId, refresh])
|
|
143
|
+
|
|
144
|
+
const configured = status?.configured === true
|
|
145
|
+
const connected = status?.connected === true
|
|
146
|
+
const needsReconnect = status?.needsReconnect === true
|
|
147
|
+
const working = busy !== 'idle'
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<ConnectionPanel aria-busy={working}>
|
|
151
|
+
<ConnectionPanelTitle>{title}</ConnectionPanelTitle>
|
|
152
|
+
{description ? (
|
|
153
|
+
<ConnectionPanelText>{description}</ConnectionPanelText>
|
|
154
|
+
) : null}
|
|
155
|
+
|
|
156
|
+
{!configured ? (
|
|
157
|
+
<>
|
|
158
|
+
<ConnectionPanelText>
|
|
159
|
+
Paste the OAuth client id and secret from your Google Cloud
|
|
160
|
+
console.{' '}
|
|
161
|
+
{helpUrl ? (
|
|
162
|
+
<a href={helpUrl} target="_blank" rel="noreferrer">
|
|
163
|
+
How to create an OAuth client
|
|
164
|
+
</a>
|
|
165
|
+
) : null}
|
|
166
|
+
</ConnectionPanelText>
|
|
167
|
+
<ConnectionFieldGrid>
|
|
168
|
+
<FormField label="Client id" htmlFor={`${credentialId}-client-id`}>
|
|
169
|
+
<TextField
|
|
170
|
+
id={`${credentialId}-client-id`}
|
|
171
|
+
value={clientIdDraft}
|
|
172
|
+
autoComplete="off"
|
|
173
|
+
spellCheck={false}
|
|
174
|
+
disabled={working}
|
|
175
|
+
onChange={event => setClientIdDraft(event.target.value)}
|
|
176
|
+
/>
|
|
177
|
+
</FormField>
|
|
178
|
+
<FormField
|
|
179
|
+
label="Client secret"
|
|
180
|
+
htmlFor={`${credentialId}-client-secret`}
|
|
181
|
+
hint="Stored in the encrypted vault; never shown again."
|
|
182
|
+
>
|
|
183
|
+
<TextField
|
|
184
|
+
id={`${credentialId}-client-secret`}
|
|
185
|
+
type="password"
|
|
186
|
+
value={clientSecretDraft}
|
|
187
|
+
autoComplete="off"
|
|
188
|
+
spellCheck={false}
|
|
189
|
+
disabled={working}
|
|
190
|
+
onChange={event => setClientSecretDraft(event.target.value)}
|
|
191
|
+
/>
|
|
192
|
+
</FormField>
|
|
193
|
+
</ConnectionFieldGrid>
|
|
194
|
+
<ConnectionButtonRow>
|
|
195
|
+
<Button
|
|
196
|
+
type="button"
|
|
197
|
+
disabled={
|
|
198
|
+
working || !clientIdDraft.trim() || !clientSecretDraft.trim()
|
|
199
|
+
}
|
|
200
|
+
onClick={() => void saveConfig()}
|
|
201
|
+
>
|
|
202
|
+
{busy === 'saving' ? 'Saving…' : 'Save'}
|
|
203
|
+
</Button>
|
|
204
|
+
</ConnectionButtonRow>
|
|
205
|
+
</>
|
|
206
|
+
) : null}
|
|
207
|
+
|
|
208
|
+
{configured && !connected && !needsReconnect ? (
|
|
209
|
+
<ConnectionButtonRow>
|
|
210
|
+
<Button
|
|
211
|
+
type="button"
|
|
212
|
+
variant="subtle"
|
|
213
|
+
disabled={working}
|
|
214
|
+
onClick={() => void disconnect()}
|
|
215
|
+
>
|
|
216
|
+
Remove
|
|
217
|
+
</Button>
|
|
218
|
+
<Button
|
|
219
|
+
type="button"
|
|
220
|
+
disabled={working}
|
|
221
|
+
onClick={() => void connect()}
|
|
222
|
+
>
|
|
223
|
+
{busy === 'connecting' ? 'Waiting for browser…' : 'Sign in with Google'}
|
|
224
|
+
</Button>
|
|
225
|
+
</ConnectionButtonRow>
|
|
226
|
+
) : null}
|
|
227
|
+
|
|
228
|
+
{needsReconnect ? (
|
|
229
|
+
<ConnectionTestResult
|
|
230
|
+
tone="warning"
|
|
231
|
+
title="Reconnect required"
|
|
232
|
+
message={
|
|
233
|
+
status?.email
|
|
234
|
+
? `Access for ${status.email} has expired or was revoked.`
|
|
235
|
+
: 'Access has expired or was revoked.'
|
|
236
|
+
}
|
|
237
|
+
/>
|
|
238
|
+
) : null}
|
|
239
|
+
|
|
240
|
+
{configured && (connected || needsReconnect) ? (
|
|
241
|
+
<ConnectionButtonRow>
|
|
242
|
+
<Button
|
|
243
|
+
type="button"
|
|
244
|
+
variant="subtle"
|
|
245
|
+
disabled={working}
|
|
246
|
+
onClick={() => void disconnect()}
|
|
247
|
+
>
|
|
248
|
+
Disconnect
|
|
249
|
+
</Button>
|
|
250
|
+
<Button
|
|
251
|
+
type="button"
|
|
252
|
+
disabled={working}
|
|
253
|
+
onClick={() => void connect()}
|
|
254
|
+
>
|
|
255
|
+
{busy === 'connecting'
|
|
256
|
+
? 'Waiting for browser…'
|
|
257
|
+
: needsReconnect
|
|
258
|
+
? 'Reconnect'
|
|
259
|
+
: 'Reconnect account'}
|
|
260
|
+
</Button>
|
|
261
|
+
</ConnectionButtonRow>
|
|
262
|
+
) : null}
|
|
263
|
+
|
|
264
|
+
{connected ? (
|
|
265
|
+
<ConnectionTestResult
|
|
266
|
+
tone="success"
|
|
267
|
+
title={status?.email ? `Connected as ${status.email}` : 'Connected'}
|
|
268
|
+
message="Mail and Calendar share this connection."
|
|
269
|
+
/>
|
|
270
|
+
) : null}
|
|
271
|
+
|
|
272
|
+
{error ? (
|
|
273
|
+
<ConnectionTestResult tone="error" title="Something went wrong" message={error} />
|
|
274
|
+
) : null}
|
|
275
|
+
</ConnectionPanel>
|
|
276
|
+
)
|
|
277
|
+
}
|
|
@@ -15,14 +15,18 @@ export interface AppFrameProps
|
|
|
15
15
|
themeFromBridge?: boolean
|
|
16
16
|
/** Optional actions rendered in the app header's action slot. */
|
|
17
17
|
headerActions?: ReactNode
|
|
18
|
-
/** Open document's file name, shown beside the wordmark. */
|
|
19
|
-
headerDocumentName?: ReactNode
|
|
20
|
-
|
|
18
|
+
/** Open document's file name, shown beside the wordmark. */
|
|
19
|
+
headerDocumentName?: ReactNode
|
|
20
|
+
/** Hide the app wordmark when identity is already provided by host chrome. */
|
|
21
|
+
hideWordmark?: boolean
|
|
22
|
+
/** Optional app identity slug used before the shell bridge is ready. */
|
|
23
|
+
identityAppSlug?: string
|
|
24
|
+
}
|
|
21
25
|
|
|
22
26
|
const StyledRoot = styled.div`
|
|
23
27
|
display: flex;
|
|
24
28
|
flex-direction: column;
|
|
25
|
-
|
|
29
|
+
width: 100%;
|
|
26
30
|
height: 100%;
|
|
27
31
|
min-width: 0;
|
|
28
32
|
min-height: 0;
|
|
@@ -38,10 +42,12 @@ export function AppFrame({
|
|
|
38
42
|
children,
|
|
39
43
|
className,
|
|
40
44
|
themeFromBridge = true,
|
|
41
|
-
headerActions,
|
|
42
|
-
headerDocumentName,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
headerActions,
|
|
46
|
+
headerDocumentName,
|
|
47
|
+
hideWordmark = false,
|
|
48
|
+
identityAppSlug,
|
|
49
|
+
...rest
|
|
50
|
+
}: AppFrameProps): React.ReactElement {
|
|
45
51
|
const { meta, ready } = usePlatformBridge()
|
|
46
52
|
const { manifest } = usePlatformAppManifest(meta?.appSlug ?? null, ready)
|
|
47
53
|
|
|
@@ -51,17 +57,18 @@ export function AppFrame({
|
|
|
51
57
|
|
|
52
58
|
useEffect(() => {
|
|
53
59
|
const root = document.documentElement
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
const appSlug = meta?.appSlug ?? identityAppSlug
|
|
61
|
+
if (appSlug) {
|
|
62
|
+
root.dataset.app = appSlug
|
|
56
63
|
} else {
|
|
57
64
|
delete root.dataset.app
|
|
58
65
|
}
|
|
59
66
|
return () => {
|
|
60
|
-
if (root.dataset.app ===
|
|
67
|
+
if (root.dataset.app === appSlug) {
|
|
61
68
|
delete root.dataset.app
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
|
-
}, [meta?.appSlug])
|
|
71
|
+
}, [identityAppSlug, meta?.appSlug])
|
|
65
72
|
|
|
66
73
|
usePlatformThemeFromBridge(themeFromBridge)
|
|
67
74
|
const themeAttrs = usePlatformThemeDocumentAttrs(themeFromBridge)
|
|
@@ -76,13 +83,13 @@ export function AppFrame({
|
|
|
76
83
|
data-platform-surface-palette={themeAttrs.surfacePalette || undefined}
|
|
77
84
|
{...rest}
|
|
78
85
|
>
|
|
79
|
-
{manifest && (
|
|
80
|
-
<AppHeader
|
|
81
|
-
manifest={manifest}
|
|
82
|
-
actions={headerActions}
|
|
83
|
-
documentName={headerDocumentName}
|
|
84
|
-
/>
|
|
85
|
-
)}
|
|
86
|
+
{manifest && !hideWordmark && (
|
|
87
|
+
<AppHeader
|
|
88
|
+
manifest={manifest}
|
|
89
|
+
actions={headerActions}
|
|
90
|
+
documentName={headerDocumentName}
|
|
91
|
+
/>
|
|
92
|
+
)}
|
|
86
93
|
{children}
|
|
87
94
|
</StyledRoot>
|
|
88
95
|
</>
|
|
@@ -4,13 +4,13 @@ import type { PlatformAppManifest } from '../../../bridge/types.js'
|
|
|
4
4
|
|
|
5
5
|
export type { PlatformAppManifest }
|
|
6
6
|
|
|
7
|
-
export interface AppHeaderProps {
|
|
8
|
-
manifest: PlatformAppManifest
|
|
9
|
-
/** Optional slot for the app's tools — the header earns its row. */
|
|
10
|
-
actions?: React.ReactNode
|
|
11
|
-
/**
|
|
12
|
-
* Convention: the open document's file name, shown beside the wordmark
|
|
13
|
-
* (like any document-based app's window title).
|
|
7
|
+
export interface AppHeaderProps {
|
|
8
|
+
manifest: PlatformAppManifest
|
|
9
|
+
/** Optional slot for the app's tools — the header earns its row. */
|
|
10
|
+
actions?: React.ReactNode
|
|
11
|
+
/**
|
|
12
|
+
* Convention: the open document's file name, shown beside the wordmark
|
|
13
|
+
* (like any document-based app's window title).
|
|
14
14
|
*/
|
|
15
15
|
documentName?: React.ReactNode
|
|
16
16
|
}
|
|
@@ -66,19 +66,19 @@ const ActionSlot = styled.div`
|
|
|
66
66
|
|
|
67
67
|
export const APP_HEADER_ACTIONS_PORTAL_ID = 'purescience-app-header-actions'
|
|
68
68
|
|
|
69
|
-
export function AppHeader({
|
|
70
|
-
manifest,
|
|
71
|
-
actions,
|
|
72
|
-
documentName,
|
|
73
|
-
}: AppHeaderProps): React.ReactElement {
|
|
74
|
-
return (
|
|
75
|
-
<StyledHeader>
|
|
76
|
-
<PureAppWordmark
|
|
77
|
-
appName={manifest.navigationLabel ?? manifest.slug}
|
|
78
|
-
size="sm"
|
|
79
|
-
accentName
|
|
80
|
-
/>
|
|
81
|
-
{documentName ? <DocumentName>{documentName}</DocumentName> : null}
|
|
69
|
+
export function AppHeader({
|
|
70
|
+
manifest,
|
|
71
|
+
actions,
|
|
72
|
+
documentName,
|
|
73
|
+
}: AppHeaderProps): React.ReactElement {
|
|
74
|
+
return (
|
|
75
|
+
<StyledHeader>
|
|
76
|
+
<PureAppWordmark
|
|
77
|
+
appName={manifest.navigationLabel ?? manifest.slug}
|
|
78
|
+
size="sm"
|
|
79
|
+
accentName
|
|
80
|
+
/>
|
|
81
|
+
{documentName ? <DocumentName>{documentName}</DocumentName> : null}
|
|
82
82
|
<ActionSlot id={APP_HEADER_ACTIONS_PORTAL_ID}>{actions}</ActionSlot>
|
|
83
83
|
</StyledHeader>
|
|
84
84
|
)
|