@adea-ai/ui 0.96.0 → 0.97.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.
- 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 +12 -0
- package/dist/components/composites/account-menu/account-menu.d.ts.map +1 -1
- package/dist/components/composites/account-menu/account-menu.js +91 -49
- 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 +64 -12
- 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 +64 -12
- 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/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
|
+
}
|
|
@@ -64,6 +64,8 @@ export type AccountMenuProps = {
|
|
|
64
64
|
platform?: 'desktop' | 'web'
|
|
65
65
|
/** Whether a session exists. Decides the sign-in/sign-out row. */
|
|
66
66
|
authenticated: boolean
|
|
67
|
+
/** Local-only applications can omit the session actions altogether. */
|
|
68
|
+
showSession?: boolean
|
|
67
69
|
/** Disable the session row while a sign-in or sign-out is in flight. */
|
|
68
70
|
busy?: boolean
|
|
69
71
|
/** The name shown on the trigger's accessible label. */
|
|
@@ -90,6 +92,7 @@ export function AccountMenu(props: AccountMenuProps) {
|
|
|
90
92
|
'items',
|
|
91
93
|
'platform',
|
|
92
94
|
'authenticated',
|
|
95
|
+
'showSession',
|
|
93
96
|
'busy',
|
|
94
97
|
'label',
|
|
95
98
|
'onSignIn',
|
|
@@ -174,24 +177,73 @@ export function AccountMenu(props: AccountMenuProps) {
|
|
|
174
177
|
)}
|
|
175
178
|
</For>
|
|
176
179
|
</DropdownMenuGroup>
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
<
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
180
|
+
<Show when={local.showSession !== false}>
|
|
181
|
+
<DropdownMenuSeparator />
|
|
182
|
+
<DropdownMenuGroup>
|
|
183
|
+
<DropdownMenuItem
|
|
184
|
+
disabled={local.busy}
|
|
185
|
+
onSelect={() => (local.authenticated ? local.onSignOut?.() : local.onSignIn?.())}
|
|
186
|
+
>
|
|
187
|
+
<Show when={local.authenticated} fallback={<LogIn aria-hidden="true" />}>
|
|
188
|
+
<LogOut aria-hidden="true" />
|
|
189
|
+
</Show>
|
|
190
|
+
<span>{local.authenticated ? 'Sign out' : 'Sign in'}</span>
|
|
191
|
+
</DropdownMenuItem>
|
|
192
|
+
</DropdownMenuGroup>
|
|
193
|
+
</Show>
|
|
189
194
|
</DropdownMenuContent>
|
|
190
195
|
</DropdownMenu>
|
|
191
196
|
</Tooltip>
|
|
192
197
|
)
|
|
193
198
|
}
|
|
194
199
|
|
|
200
|
+
/** The common support menu, with one app-specific entry before the shared items. */
|
|
201
|
+
export function createAppMenuItems(options: {
|
|
202
|
+
primaryItem?: AccountMenuItem
|
|
203
|
+
settingsShortcut?: string
|
|
204
|
+
onAbout?: AccountMenuItem['onSelectAfterClose']
|
|
205
|
+
onHelp?: AccountMenuItem['onSelectAfterClose']
|
|
206
|
+
onFeedback?: AccountMenuItem['onSelectAfterClose']
|
|
207
|
+
onUpdates?: AccountMenuItem['onSelectAfterClose']
|
|
208
|
+
onSettings?: AccountMenuItem['onSelectAfterClose']
|
|
209
|
+
}): AccountMenuItem[] {
|
|
210
|
+
return [
|
|
211
|
+
...(options.primaryItem ? [options.primaryItem] : []),
|
|
212
|
+
{
|
|
213
|
+
id: 'about',
|
|
214
|
+
label: 'About',
|
|
215
|
+
disabled: !options.onAbout,
|
|
216
|
+
onSelectAfterClose: options.onAbout,
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: 'help',
|
|
220
|
+
label: 'Help Center',
|
|
221
|
+
disabled: !options.onHelp,
|
|
222
|
+
onSelectAfterClose: options.onHelp,
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: 'feedback',
|
|
226
|
+
label: 'Send Feedback',
|
|
227
|
+
disabled: !options.onFeedback,
|
|
228
|
+
onSelectAfterClose: options.onFeedback,
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
id: 'updates',
|
|
232
|
+
label: 'Updates',
|
|
233
|
+
platform: 'desktop',
|
|
234
|
+
disabled: !options.onUpdates,
|
|
235
|
+
onSelectAfterClose: options.onUpdates,
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: 'settings',
|
|
239
|
+
label: 'Settings',
|
|
240
|
+
shortcut: options.settingsShortcut,
|
|
241
|
+
disabled: !options.onSettings,
|
|
242
|
+
onSelectAfterClose: options.onSettings,
|
|
243
|
+
},
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
|
|
195
247
|
function AccountMenuButton(props: ComponentProps<typeof Button>) {
|
|
196
248
|
return <DropdownMenuTrigger as={Button} {...props} />
|
|
197
249
|
}
|
|
@@ -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
|
+
}
|