@adea-ai/ui 0.96.0 → 0.97.1
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/dist/components/composites/about-dialog/about-dialog.d.ts +17 -0
- package/dist/components/composites/about-dialog/about-dialog.d.ts.map +1 -0
- package/dist/components/composites/about-dialog/about-dialog.js +167 -0
- package/dist/components/composites/about-dialog/about-dialog.js.map +1 -0
- package/dist/components/composites/about-dialog/index.d.ts +3 -0
- package/dist/components/composites/about-dialog/index.d.ts.map +1 -0
- package/dist/components/composites/about-dialog/index.js +2 -0
- package/dist/components/composites/account-menu/account-menu.d.ts +20 -1
- package/dist/components/composites/account-menu/account-menu.d.ts.map +1 -1
- package/dist/components/composites/account-menu/account-menu.js +103 -50
- package/dist/components/composites/account-menu/account-menu.js.map +1 -1
- package/dist/components/composites/account-menu/index.d.ts +1 -1
- package/dist/components/composites/account-menu/index.d.ts.map +1 -1
- package/dist/components/composites/account-menu/index.js +2 -2
- package/dist/components/composites/help-center/help-center.d.ts +19 -0
- package/dist/components/composites/help-center/help-center.d.ts.map +1 -0
- package/dist/components/composites/help-center/help-center.js +135 -0
- package/dist/components/composites/help-center/help-center.js.map +1 -0
- package/dist/components/composites/help-center/index.d.ts +3 -0
- package/dist/components/composites/help-center/index.d.ts.map +1 -0
- package/dist/components/composites/help-center/index.js +2 -0
- package/dist/components/composites/update-dialog/update-dialog.d.ts +11 -1
- package/dist/components/composites/update-dialog/update-dialog.d.ts.map +1 -1
- package/dist/components/composites/update-dialog/update-dialog.js +209 -78
- package/dist/components/composites/update-dialog/update-dialog.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/r/about-dialog.json +26 -0
- package/dist/r/help-center.json +28 -0
- package/dist/r/registry.json +55 -0
- package/dist/r/src/components/composites/about-dialog/about-dialog.tsx +161 -0
- package/dist/r/src/components/composites/about-dialog/index.ts +2 -0
- package/dist/r/src/components/composites/account-menu/account-menu.tsx +76 -13
- package/dist/r/src/components/composites/account-menu/index.ts +6 -1
- package/dist/r/src/components/composites/help-center/help-center.tsx +104 -0
- package/dist/r/src/components/composites/help-center/index.ts +2 -0
- package/dist/r/src/components/composites/update-dialog/update-dialog.tsx +200 -27
- package/dist/r/update-dialog.json +1 -0
- package/package.json +1 -1
- package/registry.json +55 -0
- package/src/components/composites/about-dialog/about-dialog.tsx +161 -0
- package/src/components/composites/about-dialog/index.ts +2 -0
- package/src/components/composites/account-menu/account-menu.tsx +76 -13
- package/src/components/composites/account-menu/index.ts +6 -1
- package/src/components/composites/help-center/help-center.tsx +104 -0
- package/src/components/composites/help-center/index.ts +2 -0
- package/src/components/composites/update-dialog/update-dialog.tsx +200 -27
- package/src/index.ts +2 -0
package/package.json
CHANGED
package/registry.json
CHANGED
|
@@ -1783,6 +1783,32 @@
|
|
|
1783
1783
|
}
|
|
1784
1784
|
]
|
|
1785
1785
|
},
|
|
1786
|
+
{
|
|
1787
|
+
"name": "about-dialog",
|
|
1788
|
+
"type": "registry:ui",
|
|
1789
|
+
"title": "About Dialog",
|
|
1790
|
+
"description": "Shared app identity and version support dialog.",
|
|
1791
|
+
"dependencies": [
|
|
1792
|
+
"lucide-solid@^1.48.0"
|
|
1793
|
+
],
|
|
1794
|
+
"registryDependencies": [
|
|
1795
|
+
"@adea-ai/ui/action-button",
|
|
1796
|
+
"@adea-ai/ui/alert",
|
|
1797
|
+
"@adea-ai/ui/dialog"
|
|
1798
|
+
],
|
|
1799
|
+
"files": [
|
|
1800
|
+
{
|
|
1801
|
+
"path": "src/components/composites/about-dialog/about-dialog.tsx",
|
|
1802
|
+
"type": "registry:ui",
|
|
1803
|
+
"target": "components/composites/about-dialog/about-dialog.tsx"
|
|
1804
|
+
},
|
|
1805
|
+
{
|
|
1806
|
+
"path": "src/components/composites/about-dialog/index.ts",
|
|
1807
|
+
"type": "registry:ui",
|
|
1808
|
+
"target": "components/composites/about-dialog/index.ts"
|
|
1809
|
+
}
|
|
1810
|
+
]
|
|
1811
|
+
},
|
|
1786
1812
|
{
|
|
1787
1813
|
"name": "account-menu",
|
|
1788
1814
|
"type": "registry:ui",
|
|
@@ -1931,6 +1957,34 @@
|
|
|
1931
1957
|
}
|
|
1932
1958
|
]
|
|
1933
1959
|
},
|
|
1960
|
+
{
|
|
1961
|
+
"name": "help-center",
|
|
1962
|
+
"type": "registry:ui",
|
|
1963
|
+
"title": "Help Center",
|
|
1964
|
+
"description": "Shared keyboard shortcuts and project resource page.",
|
|
1965
|
+
"dependencies": [
|
|
1966
|
+
"lucide-solid@^1.48.0"
|
|
1967
|
+
],
|
|
1968
|
+
"registryDependencies": [
|
|
1969
|
+
"@adea-ai/ui/action-button",
|
|
1970
|
+
"@adea-ai/ui/alert",
|
|
1971
|
+
"@adea-ai/ui/card",
|
|
1972
|
+
"@adea-ai/ui/kbd",
|
|
1973
|
+
"lib"
|
|
1974
|
+
],
|
|
1975
|
+
"files": [
|
|
1976
|
+
{
|
|
1977
|
+
"path": "src/components/composites/help-center/help-center.tsx",
|
|
1978
|
+
"type": "registry:ui",
|
|
1979
|
+
"target": "components/composites/help-center/help-center.tsx"
|
|
1980
|
+
},
|
|
1981
|
+
{
|
|
1982
|
+
"path": "src/components/composites/help-center/index.ts",
|
|
1983
|
+
"type": "registry:ui",
|
|
1984
|
+
"target": "components/composites/help-center/index.ts"
|
|
1985
|
+
}
|
|
1986
|
+
]
|
|
1987
|
+
},
|
|
1934
1988
|
{
|
|
1935
1989
|
"name": "list-row",
|
|
1936
1990
|
"type": "registry:ui",
|
|
@@ -2081,6 +2135,7 @@
|
|
|
2081
2135
|
"lucide-solid@^1.48.0"
|
|
2082
2136
|
],
|
|
2083
2137
|
"registryDependencies": [
|
|
2138
|
+
"@adea-ai/ui/action-button",
|
|
2084
2139
|
"@adea-ai/ui/badge",
|
|
2085
2140
|
"@adea-ai/ui/button",
|
|
2086
2141
|
"@adea-ai/ui/dialog",
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createEffect,
|
|
3
|
+
createMemo,
|
|
4
|
+
createSignal,
|
|
5
|
+
on,
|
|
6
|
+
onCleanup,
|
|
7
|
+
Show,
|
|
8
|
+
type Accessor,
|
|
9
|
+
} from 'solid-js'
|
|
10
|
+
import { ExternalLink } from 'lucide-solid'
|
|
11
|
+
import { ActionButton } from '../action-button'
|
|
12
|
+
import { Alert, AlertDescription } from '../../ui/alert'
|
|
13
|
+
import { Dialog, DialogContent, DialogTitle } from '../../ui/dialog'
|
|
14
|
+
import { createDialogFocusRestoration } from '../../ui/dialog/dialog'
|
|
15
|
+
|
|
16
|
+
export type AboutDialogProps = {
|
|
17
|
+
appName: string
|
|
18
|
+
appIcon: string
|
|
19
|
+
version?: string
|
|
20
|
+
platform: string
|
|
21
|
+
copyright: string
|
|
22
|
+
sourceUrl: string
|
|
23
|
+
open: boolean
|
|
24
|
+
onOpenChange: (open: boolean) => void
|
|
25
|
+
copyVersionInfo?: (text: string) => Promise<void>
|
|
26
|
+
openExternal?: (url: string) => Promise<void>
|
|
27
|
+
restoreFocusRef?: Accessor<HTMLElement | undefined>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Compact app identity and support details, with clipboard and native-browser adapters. */
|
|
31
|
+
export function AboutDialog(props: AboutDialogProps) {
|
|
32
|
+
const versionLabel = () => (props.version ? `Version ${props.version}` : 'Version unavailable')
|
|
33
|
+
const versionInfo = createMemo(() =>
|
|
34
|
+
[props.appName, versionLabel(), `Platform: ${props.platform}`].join('\n')
|
|
35
|
+
)
|
|
36
|
+
const [copied, setCopied] = createSignal(false)
|
|
37
|
+
const [error, setError] = createSignal('')
|
|
38
|
+
const [copying, setCopying] = createSignal(false)
|
|
39
|
+
let disposed = false
|
|
40
|
+
let epoch = 0
|
|
41
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
42
|
+
onCleanup(() => {
|
|
43
|
+
disposed = true
|
|
44
|
+
epoch += 1
|
|
45
|
+
clearTimeout(timer)
|
|
46
|
+
})
|
|
47
|
+
createEffect(
|
|
48
|
+
on(
|
|
49
|
+
() => props.open,
|
|
50
|
+
() => {
|
|
51
|
+
epoch += 1
|
|
52
|
+
clearTimeout(timer)
|
|
53
|
+
setCopied(false)
|
|
54
|
+
setError('')
|
|
55
|
+
setCopying(false)
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
const focus = createDialogFocusRestoration({
|
|
60
|
+
open: () => props.open,
|
|
61
|
+
restoreFocusRef: props.restoreFocusRef,
|
|
62
|
+
})
|
|
63
|
+
const copy = async () => {
|
|
64
|
+
const owner = epoch
|
|
65
|
+
const active = () => !disposed && props.open && owner === epoch
|
|
66
|
+
setError('')
|
|
67
|
+
setCopied(false)
|
|
68
|
+
setCopying(true)
|
|
69
|
+
try {
|
|
70
|
+
if (props.copyVersionInfo) await props.copyVersionInfo(versionInfo())
|
|
71
|
+
else await navigator.clipboard.writeText(versionInfo())
|
|
72
|
+
if (!active()) return
|
|
73
|
+
setCopied(true)
|
|
74
|
+
clearTimeout(timer)
|
|
75
|
+
timer = setTimeout(() => {
|
|
76
|
+
if (active()) setCopied(false)
|
|
77
|
+
}, 2000)
|
|
78
|
+
} catch {
|
|
79
|
+
if (active()) setError('Could not copy version info. Please try again.')
|
|
80
|
+
} finally {
|
|
81
|
+
if (active()) setCopying(false)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const openSource = async () => {
|
|
85
|
+
const owner = epoch
|
|
86
|
+
setError('')
|
|
87
|
+
try {
|
|
88
|
+
await props.openExternal?.(props.sourceUrl)
|
|
89
|
+
} catch {
|
|
90
|
+
if (!disposed && props.open && owner === epoch)
|
|
91
|
+
setError('Could not open the source repository. Please try again.')
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return (
|
|
95
|
+
<Dialog open={props.open} onOpenChange={props.onOpenChange}>
|
|
96
|
+
<DialogContent
|
|
97
|
+
onKeyDown={(event: KeyboardEvent) => {
|
|
98
|
+
// Tooltip help on the identity actions cannot trap Escape in this popup.
|
|
99
|
+
if (event.key === 'Escape' && !event.defaultPrevented) {
|
|
100
|
+
event.preventDefault()
|
|
101
|
+
props.onOpenChange(false)
|
|
102
|
+
}
|
|
103
|
+
}}
|
|
104
|
+
aria-label={`About ${props.appName}`}
|
|
105
|
+
aria-labelledby=""
|
|
106
|
+
aria-describedby={undefined}
|
|
107
|
+
onOpenAutoFocus={(event) => {
|
|
108
|
+
focus.onOpenAutoFocus(event)
|
|
109
|
+
// Let the identity be read before focus opens explanatory action help.
|
|
110
|
+
if (!event.defaultPrevented && event.currentTarget instanceof HTMLElement) {
|
|
111
|
+
event.preventDefault()
|
|
112
|
+
event.currentTarget.focus({ preventScroll: true })
|
|
113
|
+
}
|
|
114
|
+
}}
|
|
115
|
+
onCloseAutoFocus={focus.onCloseAutoFocus}
|
|
116
|
+
>
|
|
117
|
+
<div class="flex flex-col items-center gap-3 px-6 py-8 text-center">
|
|
118
|
+
<img src={props.appIcon} alt="" class="size-16" aria-hidden="true" />
|
|
119
|
+
<DialogTitle>{props.appName}</DialogTitle>
|
|
120
|
+
<p class="text-sm text-primary">{versionLabel()}</p>
|
|
121
|
+
<p class="text-xs text-muted-foreground">{props.copyright}</p>
|
|
122
|
+
<div class="flex flex-wrap items-center justify-center gap-3">
|
|
123
|
+
<ActionButton
|
|
124
|
+
type="button"
|
|
125
|
+
variant="outline"
|
|
126
|
+
size="sm"
|
|
127
|
+
busy={copying()}
|
|
128
|
+
tooltip="Copy the app version and runtime details for a support report."
|
|
129
|
+
onClick={() => void copy()}
|
|
130
|
+
>
|
|
131
|
+
{copied() ? 'Copied' : 'Copy version info'}
|
|
132
|
+
</ActionButton>
|
|
133
|
+
<ActionButton
|
|
134
|
+
as="a"
|
|
135
|
+
variant="link"
|
|
136
|
+
size="sm"
|
|
137
|
+
href={props.sourceUrl}
|
|
138
|
+
target="_blank"
|
|
139
|
+
rel="noopener noreferrer"
|
|
140
|
+
tooltip={`Open the ${props.appName} source repository in your browser.`}
|
|
141
|
+
onClick={(event) => {
|
|
142
|
+
if (props.openExternal) {
|
|
143
|
+
event.preventDefault()
|
|
144
|
+
void openSource()
|
|
145
|
+
}
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
<ExternalLink aria-hidden="true" />
|
|
149
|
+
View source
|
|
150
|
+
</ActionButton>
|
|
151
|
+
</div>
|
|
152
|
+
<Show when={error()}>
|
|
153
|
+
<Alert variant="destructive" role="alert">
|
|
154
|
+
<AlertDescription>{error()}</AlertDescription>
|
|
155
|
+
</Alert>
|
|
156
|
+
</Show>
|
|
157
|
+
</div>
|
|
158
|
+
</DialogContent>
|
|
159
|
+
</Dialog>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
@@ -56,6 +56,12 @@ export type AccountMenuItem = {
|
|
|
56
56
|
|
|
57
57
|
export type AccountMenuProps = {
|
|
58
58
|
class?: string
|
|
59
|
+
/** Anchor a rail menu beside its trigger, or keep the default upward placement. */
|
|
60
|
+
placement?: ComponentProps<typeof DropdownMenu>['placement']
|
|
61
|
+
/** Space between the trigger and menu, in pixels. */
|
|
62
|
+
gutter?: ComponentProps<typeof DropdownMenu>['gutter']
|
|
63
|
+
/** Hide the menu pointer when aligning it beside a compact rail. */
|
|
64
|
+
hideArrow?: boolean
|
|
59
65
|
/** The trigger size. Icon-only menus default to the standard icon size. */
|
|
60
66
|
size?: ButtonProps['size']
|
|
61
67
|
/** The entries above the separator. */
|
|
@@ -64,6 +70,8 @@ export type AccountMenuProps = {
|
|
|
64
70
|
platform?: 'desktop' | 'web'
|
|
65
71
|
/** Whether a session exists. Decides the sign-in/sign-out row. */
|
|
66
72
|
authenticated: boolean
|
|
73
|
+
/** Local-only applications can omit the session actions altogether. */
|
|
74
|
+
showSession?: boolean
|
|
67
75
|
/** Disable the session row while a sign-in or sign-out is in flight. */
|
|
68
76
|
busy?: boolean
|
|
69
77
|
/** The name shown on the trigger's accessible label. */
|
|
@@ -86,10 +94,14 @@ const FALLBACK_ICONS: Record<string, typeof Settings2> = {
|
|
|
86
94
|
export function AccountMenu(props: AccountMenuProps) {
|
|
87
95
|
const [local, rest] = splitProps(props, [
|
|
88
96
|
'class',
|
|
97
|
+
'placement',
|
|
98
|
+
'gutter',
|
|
99
|
+
'hideArrow',
|
|
89
100
|
'size',
|
|
90
101
|
'items',
|
|
91
102
|
'platform',
|
|
92
103
|
'authenticated',
|
|
104
|
+
'showSession',
|
|
93
105
|
'busy',
|
|
94
106
|
'label',
|
|
95
107
|
'onSignIn',
|
|
@@ -119,6 +131,8 @@ export function AccountMenu(props: AccountMenuProps) {
|
|
|
119
131
|
<Tooltip open={tooltipOpen()} onOpenChange={(open) => setTooltipOpen(open && !menuOpen())}>
|
|
120
132
|
<DropdownMenu
|
|
121
133
|
modal={false}
|
|
134
|
+
placement={local.placement ?? 'top-start'}
|
|
135
|
+
gutter={local.gutter}
|
|
122
136
|
onOpenChange={(open) => {
|
|
123
137
|
setMenuOpen(open)
|
|
124
138
|
if (open) setTooltipOpen(false)
|
|
@@ -139,7 +153,7 @@ export function AccountMenu(props: AccountMenuProps) {
|
|
|
139
153
|
</TooltipTrigger>
|
|
140
154
|
<TooltipContent placement="top">{local.label ?? 'Account and settings'}</TooltipContent>
|
|
141
155
|
<DropdownMenuContent
|
|
142
|
-
|
|
156
|
+
hideArrow={local.hideArrow}
|
|
143
157
|
class="min-w-56 max-h-(--kb-popper-available-height) overflow-x-hidden overflow-y-auto"
|
|
144
158
|
onCloseAutoFocus={(event) => {
|
|
145
159
|
const selection = pendingAfterClose
|
|
@@ -174,24 +188,73 @@ export function AccountMenu(props: AccountMenuProps) {
|
|
|
174
188
|
)}
|
|
175
189
|
</For>
|
|
176
190
|
</DropdownMenuGroup>
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
191
|
+
<Show when={local.showSession !== false}>
|
|
192
|
+
<DropdownMenuSeparator />
|
|
193
|
+
<DropdownMenuGroup>
|
|
194
|
+
<DropdownMenuItem
|
|
195
|
+
disabled={local.busy}
|
|
196
|
+
onSelect={() => (local.authenticated ? local.onSignOut?.() : local.onSignIn?.())}
|
|
197
|
+
>
|
|
198
|
+
<Show when={local.authenticated} fallback={<LogIn aria-hidden="true" />}>
|
|
199
|
+
<LogOut aria-hidden="true" />
|
|
200
|
+
</Show>
|
|
201
|
+
<span>{local.authenticated ? 'Sign out' : 'Sign in'}</span>
|
|
202
|
+
</DropdownMenuItem>
|
|
203
|
+
</DropdownMenuGroup>
|
|
204
|
+
</Show>
|
|
189
205
|
</DropdownMenuContent>
|
|
190
206
|
</DropdownMenu>
|
|
191
207
|
</Tooltip>
|
|
192
208
|
)
|
|
193
209
|
}
|
|
194
210
|
|
|
211
|
+
/** The common support menu, with one app-specific entry before the shared items. */
|
|
212
|
+
export function createAppMenuItems(options: {
|
|
213
|
+
primaryItem?: AccountMenuItem
|
|
214
|
+
settingsShortcut?: string
|
|
215
|
+
onAbout?: AccountMenuItem['onSelectAfterClose']
|
|
216
|
+
onHelp?: AccountMenuItem['onSelectAfterClose']
|
|
217
|
+
onFeedback?: AccountMenuItem['onSelectAfterClose']
|
|
218
|
+
onUpdates?: AccountMenuItem['onSelectAfterClose']
|
|
219
|
+
onSettings?: AccountMenuItem['onSelectAfterClose']
|
|
220
|
+
}): AccountMenuItem[] {
|
|
221
|
+
return [
|
|
222
|
+
...(options.primaryItem ? [options.primaryItem] : []),
|
|
223
|
+
{
|
|
224
|
+
id: 'about',
|
|
225
|
+
label: 'About',
|
|
226
|
+
disabled: !options.onAbout,
|
|
227
|
+
onSelectAfterClose: options.onAbout,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: 'help',
|
|
231
|
+
label: 'Help Center',
|
|
232
|
+
disabled: !options.onHelp,
|
|
233
|
+
onSelectAfterClose: options.onHelp,
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: 'feedback',
|
|
237
|
+
label: 'Send Feedback',
|
|
238
|
+
disabled: !options.onFeedback,
|
|
239
|
+
onSelectAfterClose: options.onFeedback,
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: 'updates',
|
|
243
|
+
label: 'Updates',
|
|
244
|
+
platform: 'desktop',
|
|
245
|
+
disabled: !options.onUpdates,
|
|
246
|
+
onSelectAfterClose: options.onUpdates,
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: 'settings',
|
|
250
|
+
label: 'Settings',
|
|
251
|
+
shortcut: options.settingsShortcut,
|
|
252
|
+
disabled: !options.onSettings,
|
|
253
|
+
onSelectAfterClose: options.onSettings,
|
|
254
|
+
},
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
|
|
195
258
|
function AccountMenuButton(props: ComponentProps<typeof Button>) {
|
|
196
259
|
return <DropdownMenuTrigger as={Button} {...props} />
|
|
197
260
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createSignal, For, onCleanup, Show } from 'solid-js'
|
|
2
|
+
import { ExternalLink } from 'lucide-solid'
|
|
3
|
+
import { cn } from '#lib/utils'
|
|
4
|
+
import { ActionButton } from '../action-button'
|
|
5
|
+
import { Alert, AlertDescription } from '../../ui/alert'
|
|
6
|
+
import { Card, CardContent, CardHeader } from '../../ui/card'
|
|
7
|
+
import { Kbd, KbdGroup } from '../../ui/kbd'
|
|
8
|
+
|
|
9
|
+
export type HelpShortcut = { label: string; keys: readonly string[] }
|
|
10
|
+
export type HelpLink = { label: string; description?: string; url: string }
|
|
11
|
+
export type HelpCenterProps = {
|
|
12
|
+
appName: string
|
|
13
|
+
shortcuts: readonly HelpShortcut[]
|
|
14
|
+
links: readonly HelpLink[]
|
|
15
|
+
openExternal?: (url: string) => Promise<void>
|
|
16
|
+
class?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A shared help page: real keyboard shortcuts and the application's project resources. */
|
|
20
|
+
export function HelpCenter(props: HelpCenterProps) {
|
|
21
|
+
const [error, setError] = createSignal('')
|
|
22
|
+
let disposed = false
|
|
23
|
+
onCleanup(() => {
|
|
24
|
+
disposed = true
|
|
25
|
+
})
|
|
26
|
+
const openLink = async (url: string) => {
|
|
27
|
+
setError('')
|
|
28
|
+
try {
|
|
29
|
+
await props.openExternal?.(url)
|
|
30
|
+
} catch {
|
|
31
|
+
if (!disposed) setError('Could not open this help resource. Please try again.')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return (
|
|
35
|
+
<div class={cn('flex min-w-0 flex-col gap-5', props.class)}>
|
|
36
|
+
<div class="flex flex-col gap-2">
|
|
37
|
+
<h1 class="text-xl font-semibold tracking-tight">Help Center</h1>
|
|
38
|
+
<p class="text-sm text-muted-foreground">
|
|
39
|
+
Keyboard shortcuts and resources for {props.appName}.
|
|
40
|
+
</p>
|
|
41
|
+
</div>
|
|
42
|
+
<Card>
|
|
43
|
+
<CardHeader>
|
|
44
|
+
<h2 class="text-base font-semibold text-primary">Keyboard shortcuts</h2>
|
|
45
|
+
</CardHeader>
|
|
46
|
+
<CardContent>
|
|
47
|
+
<ul class="flex flex-col gap-3">
|
|
48
|
+
<For each={props.shortcuts}>
|
|
49
|
+
{(shortcut) => (
|
|
50
|
+
<li class="flex flex-wrap items-center justify-between gap-2">
|
|
51
|
+
<span class="text-sm">{shortcut.label}</span>
|
|
52
|
+
<KbdGroup>
|
|
53
|
+
<For each={shortcut.keys}>{(key) => <Kbd>{key}</Kbd>}</For>
|
|
54
|
+
</KbdGroup>
|
|
55
|
+
</li>
|
|
56
|
+
)}
|
|
57
|
+
</For>
|
|
58
|
+
</ul>
|
|
59
|
+
</CardContent>
|
|
60
|
+
</Card>
|
|
61
|
+
<Card>
|
|
62
|
+
<CardHeader>
|
|
63
|
+
<h2 class="text-base font-semibold text-primary">Project links</h2>
|
|
64
|
+
</CardHeader>
|
|
65
|
+
<CardContent>
|
|
66
|
+
<ul class="flex flex-col gap-4">
|
|
67
|
+
<For each={props.links}>
|
|
68
|
+
{(link) => (
|
|
69
|
+
<li class="flex min-w-0 flex-col items-start gap-1">
|
|
70
|
+
<ActionButton
|
|
71
|
+
as="a"
|
|
72
|
+
variant="link"
|
|
73
|
+
size="sm"
|
|
74
|
+
href={link.url}
|
|
75
|
+
target="_blank"
|
|
76
|
+
rel="noopener noreferrer"
|
|
77
|
+
tooltip={`Open ${link.label} in your browser.`}
|
|
78
|
+
onClick={(event) => {
|
|
79
|
+
if (props.openExternal) {
|
|
80
|
+
event.preventDefault()
|
|
81
|
+
void openLink(link.url)
|
|
82
|
+
}
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
{link.label}
|
|
86
|
+
<ExternalLink aria-hidden="true" />
|
|
87
|
+
</ActionButton>
|
|
88
|
+
<Show when={link.description}>
|
|
89
|
+
<p class="text-sm text-muted-foreground">{link.description}</p>
|
|
90
|
+
</Show>
|
|
91
|
+
</li>
|
|
92
|
+
)}
|
|
93
|
+
</For>
|
|
94
|
+
</ul>
|
|
95
|
+
</CardContent>
|
|
96
|
+
</Card>
|
|
97
|
+
<Show when={error()}>
|
|
98
|
+
<Alert variant="destructive" role="alert">
|
|
99
|
+
<AlertDescription>{error()}</AlertDescription>
|
|
100
|
+
</Alert>
|
|
101
|
+
</Show>
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|