@alfakit/react 0.1.11 → 0.1.13
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/CHANGELOG.md +12 -0
- package/dist/components/entries/other-entries.d.ts.map +1 -1
- package/dist/components/entries/other-entries.js +1 -1
- package/dist/components/entries/other-entries.js.map +1 -1
- package/dist/components/fields/builder.d.ts.map +1 -1
- package/dist/components/fields/builder.js +3 -3
- package/dist/components/fields/builder.js.map +1 -1
- package/dist/components/fields/file-upload.d.ts.map +1 -1
- package/dist/components/fields/file-upload.js +2 -5
- package/dist/components/fields/file-upload.js.map +1 -1
- package/dist/components/fields/repeater.d.ts.map +1 -1
- package/dist/components/fields/repeater.js +3 -3
- package/dist/components/fields/repeater.js.map +1 -1
- package/dist/components/fields/rich-editor-extensions.js +5 -2
- package/dist/components/fields/rich-editor-extensions.js.map +1 -1
- package/dist/components/layout/wizard.d.ts.map +1 -1
- package/dist/components/layout/wizard.js +4 -1
- package/dist/components/layout/wizard.js.map +1 -1
- package/dist/components/notifications/flash-toasts.d.ts.map +1 -1
- package/dist/components/notifications/flash-toasts.js +11 -1
- package/dist/components/notifications/flash-toasts.js.map +1 -1
- package/dist/components/notifications/notifications-menu.js +3 -3
- package/dist/components/notifications/notifications-menu.js.map +1 -1
- package/dist/components/panel/pwa.d.ts.map +1 -1
- package/dist/components/panel/pwa.js +8 -7
- package/dist/components/panel/pwa.js.map +1 -1
- package/dist/components/panel/shortcuts-overlay.js +1 -1
- package/dist/components/panel/shortcuts-overlay.js.map +1 -1
- package/dist/components/table/cells.js +1 -1
- package/dist/components/table/cells.js.map +1 -1
- package/dist/components.css +19 -0
- package/dist/create-alfakit.d.ts.map +1 -1
- package/dist/create-alfakit.js +3 -1
- package/dist/create-alfakit.js.map +1 -1
- package/dist/eject-manifest.json +2 -2
- package/dist/pages/page.js +1 -1
- package/dist/pages/page.js.map +1 -1
- package/dist/standalone/alfakit-component.js +2 -1
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone/alfakit-toaster.js +2 -1
- package/dist/standalone/alfakit-toaster.js.map +1 -1
- package/dist/standalone/runtime.js +1 -11
- package/dist/standalone/runtime.js.map +1 -1
- package/dist/standalone/toaster-claims.js +16 -0
- package/dist/standalone/toaster-claims.js.map +1 -0
- package/dist/starter-kit/widgets-loaded.js +63 -6
- package/dist/starter-kit/widgets-loaded.js.map +1 -1
- package/package.json +2 -2
- package/src/components/entries/other-entries.tsx +2 -0
- package/src/components/fields/builder.tsx +7 -2
- package/src/components/fields/file-upload.tsx +2 -7
- package/src/components/fields/repeater.tsx +6 -3
- package/src/components/fields/rich-editor-extensions.tsx +9 -5
- package/src/components/layout/wizard.tsx +5 -1
- package/src/components/notifications/flash-toasts.tsx +8 -0
- package/src/components/notifications/notifications-menu.tsx +3 -3
- package/src/components/panel/pwa.tsx +23 -9
- package/src/components/panel/shortcuts-overlay.tsx +1 -1
- package/src/components/table/cells.tsx +3 -1
- package/src/create-alfakit.tsx +6 -0
- package/src/pages/page.tsx +1 -1
- package/src/standalone/runtime.ts +2 -14
- package/src/standalone/toaster-claims.ts +16 -0
- package/src/starter-kit/widgets-loaded.tsx +84 -1
|
@@ -14,6 +14,7 @@ import { toast } from 'sonner'
|
|
|
14
14
|
import { usePanel, useT } from '../../context'
|
|
15
15
|
import { useStore } from '../../hooks/use-store'
|
|
16
16
|
import { Icon } from '../../lib/icons'
|
|
17
|
+
import { toastOptions } from '../notifications/flash-toasts'
|
|
17
18
|
import { DropdownMenuItem } from '../ui/dropdown-menu'
|
|
18
19
|
|
|
19
20
|
const UPDATE_TOAST_ID = 'alfakit-pwa-update'
|
|
@@ -27,7 +28,7 @@ export async function installPwa(t: Translator['t']): Promise<void> {
|
|
|
27
28
|
{},
|
|
28
29
|
'To install this app, tap the Share button and then “Add to Home Screen”.',
|
|
29
30
|
),
|
|
30
|
-
{ duration: 10000 },
|
|
31
|
+
toastOptions({ duration: 10000 }),
|
|
31
32
|
)
|
|
32
33
|
}
|
|
33
34
|
}
|
|
@@ -49,11 +50,14 @@ export function PwaManager() {
|
|
|
49
50
|
useEffect(() => {
|
|
50
51
|
if (!updateAvailable) return
|
|
51
52
|
|
|
52
|
-
toast(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
toast(
|
|
54
|
+
t('pwa.update_available', {}, 'A new version is available'),
|
|
55
|
+
toastOptions({
|
|
56
|
+
id: UPDATE_TOAST_ID,
|
|
57
|
+
duration: Infinity,
|
|
58
|
+
action: { label: t('pwa.reload', {}, 'Reload'), onClick: () => applyPwaUpdate() },
|
|
59
|
+
}),
|
|
60
|
+
)
|
|
57
61
|
}, [updateAvailable, t])
|
|
58
62
|
|
|
59
63
|
return null
|
|
@@ -74,15 +78,25 @@ export function PwaMenuItems() {
|
|
|
74
78
|
state.push === 'subscribed' ? await unsubscribeFromPush() : await subscribeToPush()
|
|
75
79
|
|
|
76
80
|
if (outcome === 'subscribed') {
|
|
77
|
-
toast.success(
|
|
81
|
+
toast.success(
|
|
82
|
+
t('pwa.notifications.enabled', {}, 'Notifications are on for this device'),
|
|
83
|
+
toastOptions(),
|
|
84
|
+
)
|
|
78
85
|
} else if (outcome === 'unsubscribed') {
|
|
79
|
-
toast.success(
|
|
86
|
+
toast.success(
|
|
87
|
+
t('pwa.notifications.disabled', {}, 'Notifications are off for this device'),
|
|
88
|
+
toastOptions(),
|
|
89
|
+
)
|
|
80
90
|
} else if (outcome === 'denied') {
|
|
81
91
|
toast.error(
|
|
82
92
|
t('pwa.notifications.blocked', {}, 'Notifications are blocked in your browser settings'),
|
|
93
|
+
toastOptions(),
|
|
83
94
|
)
|
|
84
95
|
} else {
|
|
85
|
-
toast.error(
|
|
96
|
+
toast.error(
|
|
97
|
+
t('pwa.notifications.failed', {}, 'Couldn’t change notifications. Try again.'),
|
|
98
|
+
toastOptions(),
|
|
99
|
+
)
|
|
86
100
|
}
|
|
87
101
|
}
|
|
88
102
|
|
|
@@ -221,7 +221,7 @@ export function ShortcutsOverlay({
|
|
|
221
221
|
aria-label={t(
|
|
222
222
|
'shortcuts.reset_label',
|
|
223
223
|
{ label: item.label },
|
|
224
|
-
`
|
|
224
|
+
`Reset the shortcut for ${item.label}`,
|
|
225
225
|
)}
|
|
226
226
|
onClick={() => void save(item.id, null)}
|
|
227
227
|
>
|
|
@@ -242,7 +242,9 @@ function ImageCell({ column, cell }: CellProps) {
|
|
|
242
242
|
<span
|
|
243
243
|
data-slot="image-remaining"
|
|
244
244
|
className={cn(
|
|
245
|
-
'text-muted-foreground
|
|
245
|
+
'text-muted-foreground',
|
|
246
|
+
// The last image keeps the stack's negative end margin (W-S3-03).
|
|
247
|
+
schema.stacked && usesDefaultImageStack(schema) ? 'ms-3' : 'ms-1',
|
|
246
248
|
remainingTextSizeClass(schema.limitedRemainingTextSize),
|
|
247
249
|
)}
|
|
248
250
|
>
|
package/src/create-alfakit.tsx
CHANGED
|
@@ -32,6 +32,7 @@ import { Direction } from 'radix-ui'
|
|
|
32
32
|
import {
|
|
33
33
|
StrictMode,
|
|
34
34
|
createElement,
|
|
35
|
+
useEffect,
|
|
35
36
|
useLayoutEffect,
|
|
36
37
|
type ReactElement,
|
|
37
38
|
type ReactNode,
|
|
@@ -51,6 +52,7 @@ import { discoverComponents } from './lib/discovery'
|
|
|
51
52
|
import { preloadIcons } from './lib/icons'
|
|
52
53
|
import { AlfakitPage } from './pages/page'
|
|
53
54
|
import { builtInComponents, createDefaultRegistry } from './registry'
|
|
55
|
+
import { claimToaster } from './standalone/toaster-claims'
|
|
54
56
|
|
|
55
57
|
/**
|
|
56
58
|
* Rendered inside Inertia's page tree, so it follows the current page: the reading
|
|
@@ -91,6 +93,10 @@ export function AlfakitRoot({
|
|
|
91
93
|
applyPanelTheme(panel)
|
|
92
94
|
}, [panel])
|
|
93
95
|
|
|
96
|
+
// This root renders the toaster: starter kit header components and standalone components
|
|
97
|
+
// on the same page don't mount a second one.
|
|
98
|
+
useEffect(() => claimToaster(), [])
|
|
99
|
+
|
|
94
100
|
return (
|
|
95
101
|
<Direction.Provider dir={direction}>
|
|
96
102
|
<TooltipProvider delayDuration={0}>
|
package/src/pages/page.tsx
CHANGED
|
@@ -281,7 +281,7 @@ export function ContentBlockRenderer({ block }: { block: ContentBlock }) {
|
|
|
281
281
|
<div data-slot="infolist" className="contents">
|
|
282
282
|
<SchemaRenderer
|
|
283
283
|
components={(block as InfolistBlock).components}
|
|
284
|
-
columns={{ default: 1, lg: 2 }}
|
|
284
|
+
columns={(block as InfolistBlock).columns ?? { default: 1, lg: 2 }}
|
|
285
285
|
/>
|
|
286
286
|
</div>
|
|
287
287
|
)
|
|
@@ -95,20 +95,8 @@ export function createStandaloneContext(
|
|
|
95
95
|
|
|
96
96
|
// ---------------------------------------------------------------- Toaster ownership
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
/** An `<AlfakitToaster>` (or the component that took the role) is mounted; returns a release. */
|
|
101
|
-
export function claimToaster(): () => void {
|
|
102
|
-
toasters++
|
|
103
|
-
|
|
104
|
-
return () => {
|
|
105
|
-
toasters--
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function hasToaster(): boolean {
|
|
110
|
-
return toasters > 0
|
|
111
|
-
}
|
|
98
|
+
// In a module of its own: the starter kit header components check it without loading this one.
|
|
99
|
+
export { claimToaster, hasToaster } from './toaster-claims'
|
|
112
100
|
|
|
113
101
|
// ---------------------------------------------------------------- Flashed notifications
|
|
114
102
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Who renders AlfaKit's toaster on the page: `<AlfakitToaster>`, `AlfakitRoot`, the first
|
|
2
|
+
// standalone component or the starter kit header components on the host's own pages.
|
|
3
|
+
let toasters = 0
|
|
4
|
+
|
|
5
|
+
/** An `<AlfakitToaster>` (or the component that took the role) is mounted; returns a release. */
|
|
6
|
+
export function claimToaster(): () => void {
|
|
7
|
+
toasters++
|
|
8
|
+
|
|
9
|
+
return () => {
|
|
10
|
+
toasters--
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function hasToaster(): boolean {
|
|
15
|
+
return toasters > 0
|
|
16
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ActionRunner,
|
|
3
3
|
ComponentRegistry,
|
|
4
|
+
STARTER_KIT_TOASTER_ID,
|
|
4
5
|
Translator,
|
|
5
6
|
initials,
|
|
6
7
|
isAlfakitPage,
|
|
7
8
|
loadStarterKitPanel,
|
|
8
9
|
loadedStarterKitPanel,
|
|
9
10
|
openRequestedStarterKitSearch,
|
|
11
|
+
overlayPlacementDirection,
|
|
12
|
+
resolveToastPosition,
|
|
10
13
|
showsLanguageSwitcher,
|
|
11
14
|
starterKitCopilot,
|
|
12
15
|
starterKitPanelLocale,
|
|
@@ -17,12 +20,15 @@ import {
|
|
|
17
20
|
tenantNameText,
|
|
18
21
|
type PanelSchema,
|
|
19
22
|
type StarterKitWidgets,
|
|
23
|
+
type ToastPosition,
|
|
20
24
|
} from '@alfakit/core'
|
|
21
25
|
import { router, usePage } from '@inertiajs/react'
|
|
22
26
|
import { ChevronsUpDownIcon, LanguagesIcon, PlusIcon } from 'lucide-react'
|
|
23
27
|
import { Direction } from 'radix-ui'
|
|
24
28
|
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
|
|
29
|
+
import { createPortal } from 'react-dom'
|
|
25
30
|
import { AiCopilot } from '../components/ai/copilot'
|
|
31
|
+
import { setToasterId } from '../components/notifications/flash-toasts'
|
|
26
32
|
import { NotificationsMenu } from '../components/notifications/notifications-menu'
|
|
27
33
|
import { RealtimeNotifications } from '../components/notifications/realtime-notifications'
|
|
28
34
|
import { GlobalSearch } from '../components/panel/global-search'
|
|
@@ -38,16 +44,20 @@ import {
|
|
|
38
44
|
DropdownMenuSeparator,
|
|
39
45
|
DropdownMenuTrigger,
|
|
40
46
|
} from '../components/ui/dropdown-menu'
|
|
47
|
+
import { Toaster } from '../components/ui/sonner'
|
|
41
48
|
import { TooltipProvider } from '../components/ui/tooltip'
|
|
42
49
|
import {
|
|
43
50
|
AlfakitProvider,
|
|
44
51
|
StandaloneProvider,
|
|
45
52
|
useLocalization,
|
|
53
|
+
useRegistry,
|
|
46
54
|
useT,
|
|
47
55
|
type AlfakitContextValue,
|
|
48
56
|
type AnyComponent,
|
|
49
57
|
} from '../context'
|
|
50
58
|
import { Icon } from '../lib/icons'
|
|
59
|
+
import { ensureFlashToasts } from '../standalone/runtime'
|
|
60
|
+
import { claimToaster, hasToaster } from '../standalone/toaster-claims'
|
|
51
61
|
import {
|
|
52
62
|
CopilotTrigger,
|
|
53
63
|
LanguageSwitcherTrigger,
|
|
@@ -117,16 +127,89 @@ function useWidgetsPanel(source: StarterKitWidgets): PanelSchema | null {
|
|
|
117
127
|
)
|
|
118
128
|
}
|
|
119
129
|
|
|
130
|
+
/**
|
|
131
|
+
* AlfaKit's toaster on a host page (as `<AlfakitToaster>`, without its standalone context):
|
|
132
|
+
* Alfakit's toasts go to it, and so do flashed notifications.
|
|
133
|
+
*/
|
|
134
|
+
function HostToaster({ position, dir }: { position?: ToastPosition; dir: 'ltr' | 'rtl' }) {
|
|
135
|
+
const registry = useRegistry()
|
|
136
|
+
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
ensureFlashToasts(registry)
|
|
139
|
+
}, [registry])
|
|
140
|
+
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
setToasterId(STARTER_KIT_TOASTER_ID)
|
|
143
|
+
|
|
144
|
+
return () => setToasterId(undefined)
|
|
145
|
+
}, [])
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<Toaster
|
|
149
|
+
id={STARTER_KIT_TOASTER_ID}
|
|
150
|
+
position={resolveToastPosition(position, dir)}
|
|
151
|
+
dir={dir}
|
|
152
|
+
richColors
|
|
153
|
+
closeButton
|
|
154
|
+
/>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* On the host's own pages no AlfaKit toaster is mounted, so the first loaded header
|
|
160
|
+
* component renders one: the push toggle's and the PWA's messages show there too (W-S1-03).
|
|
161
|
+
* AlfaKit pages (`AlfakitRoot`) and standalone components claim it themselves; the check
|
|
162
|
+
* runs after every component's effects, and again after each visit.
|
|
163
|
+
*/
|
|
164
|
+
function useOwnsHostToaster(): boolean {
|
|
165
|
+
const alfakitPage = isAlfakitPage(usePage().props)
|
|
166
|
+
const [owns, setOwns] = useState(false)
|
|
167
|
+
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
if (alfakitPage) return undefined
|
|
170
|
+
|
|
171
|
+
let release: (() => void) | null = null
|
|
172
|
+
let cancelled = false
|
|
173
|
+
|
|
174
|
+
queueMicrotask(() => {
|
|
175
|
+
if (cancelled || hasToaster()) return
|
|
176
|
+
|
|
177
|
+
release = claimToaster()
|
|
178
|
+
setOwns(true)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
return () => {
|
|
182
|
+
cancelled = true
|
|
183
|
+
release?.()
|
|
184
|
+
setOwns(false)
|
|
185
|
+
}
|
|
186
|
+
}, [alfakitPage])
|
|
187
|
+
|
|
188
|
+
return owns && !alfakitPage
|
|
189
|
+
}
|
|
190
|
+
|
|
120
191
|
function WidgetShell({ panel, children }: { panel: PanelSchema; children: ReactNode }) {
|
|
121
192
|
const context = useWidgetsContext(panel)
|
|
122
193
|
const page = useMemo(() => starterKitWidgetsPage(), [])
|
|
194
|
+
const ownsToaster = useOwnsHostToaster()
|
|
195
|
+
const direction = panel.localization?.direction ?? 'ltr'
|
|
123
196
|
|
|
124
197
|
return (
|
|
125
198
|
<AlfakitProvider value={context}>
|
|
126
199
|
<StandaloneProvider value={{ panel, page }}>
|
|
127
|
-
<Direction.Provider dir={
|
|
200
|
+
<Direction.Provider dir={direction}>
|
|
128
201
|
<TooltipProvider delayDuration={0}>{children}</TooltipProvider>
|
|
129
202
|
</Direction.Provider>
|
|
203
|
+
{/* In the body, not in the header (a header with a backdrop filter would hold its
|
|
204
|
+
fixed position), placed in the document's direction as on AlfaKit pages. */}
|
|
205
|
+
{ownsToaster &&
|
|
206
|
+
createPortal(
|
|
207
|
+
<HostToaster
|
|
208
|
+
position={panel.toastPosition}
|
|
209
|
+
dir={overlayPlacementDirection(direction, true)}
|
|
210
|
+
/>,
|
|
211
|
+
document.body,
|
|
212
|
+
)}
|
|
130
213
|
</StandaloneProvider>
|
|
131
214
|
</AlfakitProvider>
|
|
132
215
|
)
|