@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.
Files changed (49) hide show
  1. package/dist/components/composites/about-dialog/about-dialog.d.ts +17 -0
  2. package/dist/components/composites/about-dialog/about-dialog.d.ts.map +1 -0
  3. package/dist/components/composites/about-dialog/about-dialog.js +167 -0
  4. package/dist/components/composites/about-dialog/about-dialog.js.map +1 -0
  5. package/dist/components/composites/about-dialog/index.d.ts +3 -0
  6. package/dist/components/composites/about-dialog/index.d.ts.map +1 -0
  7. package/dist/components/composites/about-dialog/index.js +2 -0
  8. package/dist/components/composites/account-menu/account-menu.d.ts +12 -0
  9. package/dist/components/composites/account-menu/account-menu.d.ts.map +1 -1
  10. package/dist/components/composites/account-menu/account-menu.js +91 -49
  11. package/dist/components/composites/account-menu/account-menu.js.map +1 -1
  12. package/dist/components/composites/account-menu/index.d.ts +1 -1
  13. package/dist/components/composites/account-menu/index.d.ts.map +1 -1
  14. package/dist/components/composites/account-menu/index.js +2 -2
  15. package/dist/components/composites/help-center/help-center.d.ts +19 -0
  16. package/dist/components/composites/help-center/help-center.d.ts.map +1 -0
  17. package/dist/components/composites/help-center/help-center.js +135 -0
  18. package/dist/components/composites/help-center/help-center.js.map +1 -0
  19. package/dist/components/composites/help-center/index.d.ts +3 -0
  20. package/dist/components/composites/help-center/index.d.ts.map +1 -0
  21. package/dist/components/composites/help-center/index.js +2 -0
  22. package/dist/components/composites/update-dialog/update-dialog.d.ts +11 -1
  23. package/dist/components/composites/update-dialog/update-dialog.d.ts.map +1 -1
  24. package/dist/components/composites/update-dialog/update-dialog.js +209 -78
  25. package/dist/components/composites/update-dialog/update-dialog.js.map +1 -1
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +4 -2
  29. package/dist/r/about-dialog.json +26 -0
  30. package/dist/r/help-center.json +28 -0
  31. package/dist/r/registry.json +55 -0
  32. package/dist/r/src/components/composites/about-dialog/about-dialog.tsx +161 -0
  33. package/dist/r/src/components/composites/about-dialog/index.ts +2 -0
  34. package/dist/r/src/components/composites/account-menu/account-menu.tsx +64 -12
  35. package/dist/r/src/components/composites/account-menu/index.ts +6 -1
  36. package/dist/r/src/components/composites/help-center/help-center.tsx +104 -0
  37. package/dist/r/src/components/composites/help-center/index.ts +2 -0
  38. package/dist/r/src/components/composites/update-dialog/update-dialog.tsx +200 -27
  39. package/dist/r/update-dialog.json +1 -0
  40. package/package.json +1 -1
  41. package/registry.json +55 -0
  42. package/src/components/composites/about-dialog/about-dialog.tsx +161 -0
  43. package/src/components/composites/about-dialog/index.ts +2 -0
  44. package/src/components/composites/account-menu/account-menu.tsx +64 -12
  45. package/src/components/composites/account-menu/index.ts +6 -1
  46. package/src/components/composites/help-center/help-center.tsx +104 -0
  47. package/src/components/composites/help-center/index.ts +2 -0
  48. package/src/components/composites/update-dialog/update-dialog.tsx +200 -27
  49. 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
+ }
@@ -0,0 +1,2 @@
1
+ /** Shared app identity and version support dialog. */
2
+ export { AboutDialog, type AboutDialogProps } from './about-dialog'
@@ -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
- <DropdownMenuSeparator />
178
- <DropdownMenuGroup>
179
- <DropdownMenuItem
180
- disabled={local.busy}
181
- onSelect={() => (local.authenticated ? local.onSignOut?.() : local.onSignIn?.())}
182
- >
183
- <Show when={local.authenticated} fallback={<LogIn aria-hidden="true" />}>
184
- <LogOut aria-hidden="true" />
185
- </Show>
186
- <span>{local.authenticated ? 'Sign out' : 'Sign in'}</span>
187
- </DropdownMenuItem>
188
- </DropdownMenuGroup>
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
  }
@@ -1 +1,6 @@
1
- export { AccountMenu, type AccountMenuItem, type AccountMenuProps } from './account-menu'
1
+ export {
2
+ AccountMenu,
3
+ createAppMenuItems,
4
+ type AccountMenuItem,
5
+ type AccountMenuProps,
6
+ } from './account-menu'
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ /** Shared keyboard shortcuts and project resource page. */
2
+ export { HelpCenter, type HelpCenterProps, type HelpShortcut, type HelpLink } from './help-center'