@payload-onpage/react 0.2.0 → 0.3.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/OnPageClient.d.ts.map +1 -1
- package/dist/OnPageClient.js +9 -5
- package/dist/OnPageClient.js.map +1 -1
- package/dist/annotations.d.ts +1 -1
- package/dist/annotations.d.ts.map +1 -1
- package/dist/context.d.ts +19 -5
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +31 -7
- package/dist/context.js.map +1 -1
- package/dist/dom.d.ts +16 -12
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +16 -16
- package/dist/dom.js.map +1 -1
- package/dist/editor/FieldHost.d.ts.map +1 -1
- package/dist/editor/FieldHost.js +18 -11
- package/dist/editor/FieldHost.js.map +1 -1
- package/dist/editor/OnPageEditor.d.ts +15 -8
- package/dist/editor/OnPageEditor.d.ts.map +1 -1
- package/dist/editor/OnPageEditor.js +73 -103
- package/dist/editor/OnPageEditor.js.map +1 -1
- package/dist/editor/editTarget.d.ts +28 -0
- package/dist/editor/editTarget.d.ts.map +1 -0
- package/dist/editor/editTarget.js +20 -0
- package/dist/editor/editTarget.js.map +1 -0
- package/dist/editor/useClientDocs.d.ts +4 -0
- package/dist/editor/useClientDocs.d.ts.map +1 -0
- package/dist/editor/useClientDocs.js +16 -0
- package/dist/editor/useClientDocs.js.map +1 -0
- package/dist/editor/useEditorSession.d.ts +13 -0
- package/dist/editor/useEditorSession.d.ts.map +1 -0
- package/dist/editor/useEditorSession.js +30 -0
- package/dist/editor/useEditorSession.js.map +1 -0
- package/dist/editor/useFlash.d.ts +4 -0
- package/dist/editor/useFlash.d.ts.map +1 -0
- package/dist/editor/useFlash.js +16 -0
- package/dist/editor/useFlash.js.map +1 -0
- package/dist/editor/useGhosts.d.ts +5 -7
- package/dist/editor/useGhosts.d.ts.map +1 -1
- package/dist/editor/useGhosts.js.map +1 -1
- package/dist/editor/usePageEvents.d.ts.map +1 -1
- package/dist/editor/usePageEvents.js +16 -16
- package/dist/editor/usePageEvents.js.map +1 -1
- package/dist/editor-kit.d.ts +10 -6
- package/dist/editor-kit.d.ts.map +1 -1
- package/dist/editor-kit.js +8 -5
- package/dist/editor-kit.js.map +1 -1
- package/dist/editors/CheckboxEditor.d.ts +6 -1
- package/dist/editors/CheckboxEditor.d.ts.map +1 -1
- package/dist/editors/CheckboxEditor.js +6 -3
- package/dist/editors/CheckboxEditor.js.map +1 -1
- package/dist/editors/ColorEditor.d.ts +3 -1
- package/dist/editors/ColorEditor.d.ts.map +1 -1
- package/dist/editors/ColorEditor.js +9 -15
- package/dist/editors/ColorEditor.js.map +1 -1
- package/dist/editors/DateEditor.d.ts +3 -1
- package/dist/editors/DateEditor.d.ts.map +1 -1
- package/dist/editors/DateEditor.js +10 -12
- package/dist/editors/DateEditor.js.map +1 -1
- package/dist/editors/InlineText.d.ts +2 -2
- package/dist/editors/InlineText.d.ts.map +1 -1
- package/dist/editors/InlineText.js +2 -3
- package/dist/editors/InlineText.js.map +1 -1
- package/dist/editors/NumberEditor.d.ts +3 -1
- package/dist/editors/NumberEditor.d.ts.map +1 -1
- package/dist/editors/NumberEditor.js +9 -17
- package/dist/editors/NumberEditor.js.map +1 -1
- package/dist/editors/RelationshipEditor.d.ts +7 -1
- package/dist/editors/RelationshipEditor.d.ts.map +1 -1
- package/dist/editors/RelationshipEditor.js +10 -18
- package/dist/editors/RelationshipEditor.js.map +1 -1
- package/dist/editors/SelectEditor.d.ts +4 -1
- package/dist/editors/SelectEditor.d.ts.map +1 -1
- package/dist/editors/SelectEditor.js +8 -13
- package/dist/editors/SelectEditor.js.map +1 -1
- package/dist/editors/UploadEditor.d.ts +7 -1
- package/dist/editors/UploadEditor.d.ts.map +1 -1
- package/dist/editors/UploadEditor.js +5 -5
- package/dist/editors/UploadEditor.js.map +1 -1
- package/dist/editors/index.d.ts +0 -1
- package/dist/editors/index.d.ts.map +1 -1
- package/dist/editors/index.js +56 -16
- package/dist/editors/index.js.map +1 -1
- package/dist/editors/kit.d.ts +39 -0
- package/dist/editors/kit.d.ts.map +1 -0
- package/dist/editors/kit.js +44 -0
- package/dist/editors/kit.js.map +1 -0
- package/dist/editors/useDebouncedSearch.d.ts +10 -6
- package/dist/editors/useDebouncedSearch.d.ts.map +1 -1
- package/dist/editors/useDebouncedSearch.js +11 -7
- package/dist/editors/useDebouncedSearch.js.map +1 -1
- package/dist/geometry.d.ts +49 -0
- package/dist/geometry.d.ts.map +1 -0
- package/dist/geometry.js +48 -0
- package/dist/geometry.js.map +1 -0
- package/dist/hub.d.ts +4 -4
- package/dist/hub.d.ts.map +1 -1
- package/dist/hub.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +4 -2
- package/dist/layout.js.map +1 -1
- package/dist/locators.d.ts +0 -2
- package/dist/locators.d.ts.map +1 -1
- package/dist/locators.js +10 -16
- package/dist/locators.js.map +1 -1
- package/dist/messages.d.ts +15 -2
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +5 -0
- package/dist/messages.js.map +1 -1
- package/dist/registry.d.ts +21 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +31 -6
- package/dist/registry.js.map +1 -1
- package/dist/rowOps.d.ts +20 -0
- package/dist/rowOps.d.ts.map +1 -0
- package/dist/rowOps.js +48 -0
- package/dist/rowOps.js.map +1 -0
- package/dist/sanitize.d.ts +2 -0
- package/dist/sanitize.d.ts.map +1 -1
- package/dist/sanitize.js +3 -0
- package/dist/sanitize.js.map +1 -1
- package/dist/settings.d.ts +10 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +30 -0
- package/dist/settings.js.map +1 -0
- package/dist/styles.d.ts +0 -4
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +2 -1
- package/dist/styles.js.map +1 -1
- package/dist/targets.d.ts +1 -1
- package/dist/targets.d.ts.map +1 -1
- package/dist/types.d.ts +71 -46
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/BlockPicker.d.ts.map +1 -1
- package/dist/ui/BlockPicker.js +6 -5
- package/dist/ui/BlockPicker.js.map +1 -1
- package/dist/ui/Dialog.d.ts +0 -5
- package/dist/ui/Dialog.d.ts.map +1 -1
- package/dist/ui/Dialog.js +10 -6
- package/dist/ui/Dialog.js.map +1 -1
- package/dist/ui/Dock.d.ts +1 -6
- package/dist/ui/Dock.d.ts.map +1 -1
- package/dist/ui/Dock.js +46 -28
- package/dist/ui/Dock.js.map +1 -1
- package/dist/ui/Overlay.d.ts.map +1 -1
- package/dist/ui/Overlay.js +39 -37
- package/dist/ui/Overlay.js.map +1 -1
- package/dist/ui/PreviewToolbar.d.ts +3 -3
- package/dist/ui/PreviewToolbar.d.ts.map +1 -1
- package/dist/ui/PreviewToolbar.js +4 -6
- package/dist/ui/PreviewToolbar.js.map +1 -1
- package/dist/ui/SettingsPanel.d.ts +0 -9
- package/dist/ui/SettingsPanel.d.ts.map +1 -1
- package/dist/ui/SettingsPanel.js +36 -41
- package/dist/ui/SettingsPanel.js.map +1 -1
- package/dist/ui/ToolbarButton.d.ts +12 -0
- package/dist/ui/ToolbarButton.d.ts.map +1 -0
- package/dist/ui/ToolbarButton.js +7 -0
- package/dist/ui/ToolbarButton.js.map +1 -0
- package/dist/useEvent.d.ts +1 -1
- package/dist/useEvent.d.ts.map +1 -1
- package/dist/useEvent.js +3 -1
- package/dist/useEvent.js.map +1 -1
- package/dist/useIsClient.d.ts +5 -0
- package/dist/useIsClient.d.ts.map +1 -0
- package/dist/useIsClient.js +8 -0
- package/dist/useIsClient.js.map +1 -0
- package/package.json +2 -2
- package/src/OnPageClient.tsx +9 -5
- package/src/annotations.ts +1 -1
- package/src/context.tsx +53 -10
- package/src/dom.ts +17 -18
- package/src/editor/FieldHost.tsx +26 -11
- package/src/editor/OnPageEditor.tsx +103 -160
- package/src/editor/editTarget.ts +41 -0
- package/src/editor/useClientDocs.ts +19 -0
- package/src/editor/useEditorSession.ts +36 -0
- package/src/editor/useFlash.ts +18 -0
- package/src/editor/useGhosts.ts +5 -7
- package/src/editor/usePageEvents.ts +13 -14
- package/src/editor-kit.ts +10 -6
- package/src/editors/CheckboxEditor.tsx +11 -12
- package/src/editors/ColorEditor.tsx +15 -35
- package/src/editors/DateEditor.tsx +18 -37
- package/src/editors/InlineText.tsx +4 -5
- package/src/editors/NumberEditor.tsx +15 -36
- package/src/editors/RelationshipEditor.tsx +18 -36
- package/src/editors/SelectEditor.tsx +14 -22
- package/src/editors/UploadEditor.tsx +6 -6
- package/src/editors/index.ts +56 -16
- package/src/editors/kit.tsx +97 -0
- package/src/editors/useDebouncedSearch.ts +18 -10
- package/src/geometry.ts +62 -0
- package/src/hub.ts +5 -5
- package/src/index.ts +5 -2
- package/src/layout.ts +5 -2
- package/src/locators.ts +11 -14
- package/src/messages.ts +26 -5
- package/src/registry.ts +42 -8
- package/src/rowOps.ts +64 -0
- package/src/sanitize.ts +6 -0
- package/src/settings.ts +49 -0
- package/src/styles.ts +2 -1
- package/src/targets.ts +1 -1
- package/src/types.ts +78 -45
- package/src/ui/BlockPicker.tsx +6 -7
- package/src/ui/Dialog.tsx +9 -5
- package/src/ui/Dock.tsx +67 -50
- package/src/ui/Overlay.tsx +75 -102
- package/src/ui/PreviewToolbar.tsx +7 -15
- package/src/ui/SettingsPanel.tsx +84 -81
- package/src/ui/ToolbarButton.tsx +36 -0
- package/src/useEvent.ts +4 -2
- package/src/useIsClient.ts +15 -0
package/src/ui/SettingsPanel.tsx
CHANGED
|
@@ -6,40 +6,29 @@
|
|
|
6
6
|
* spacing…), checkboxes, numbers, dates, relationships, URLs and other plain
|
|
7
7
|
* text, images that aren't on the page. Changes apply immediately.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
fieldAtPath,
|
|
11
|
+
getAt,
|
|
12
|
+
relationTitle,
|
|
13
|
+
rowFieldsOf,
|
|
14
|
+
toText,
|
|
15
|
+
isListField,
|
|
16
|
+
type ChoiceField,
|
|
17
|
+
type NumberField,
|
|
18
|
+
type ScalarField,
|
|
19
|
+
type SchemaField,
|
|
20
|
+
isReady,
|
|
21
|
+
type RefKey,
|
|
22
|
+
} from '@payload-onpage/core'
|
|
10
23
|
import { useId, useRef, useState, type KeyboardEvent, type RefObject } from 'react'
|
|
11
24
|
import { useOnPage } from '../context.tsx'
|
|
12
25
|
import type { Translate } from '../messages.ts'
|
|
13
|
-
import
|
|
26
|
+
import { checkedOf } from '../editors/CheckboxEditor.tsx'
|
|
27
|
+
import { selectedValues } from '../editors/SelectEditor.tsx'
|
|
28
|
+
import { canEdit } from '../registry.ts'
|
|
29
|
+
import { settable } from '../settings.ts'
|
|
14
30
|
import { Dialog } from './Dialog.tsx'
|
|
15
31
|
|
|
16
|
-
const SKIP = new Set(['id', 'blockType', 'blockName'])
|
|
17
|
-
const TEXTUAL = new Set(['text', 'textarea', 'email', 'number', 'date', 'relationship', 'upload'])
|
|
18
|
-
const INPUT_TYPES = new Set(['text', 'textarea', 'email', 'number'])
|
|
19
|
-
|
|
20
|
-
function settable(field: SchemaField, path: string, key: string, targets: TargetIndex): boolean {
|
|
21
|
-
if (SKIP.has(field.name)) return false
|
|
22
|
-
const full = `${path}.${field.name}`
|
|
23
|
-
switch (field.type) {
|
|
24
|
-
case 'select':
|
|
25
|
-
case 'radio':
|
|
26
|
-
case 'checkbox':
|
|
27
|
-
return true
|
|
28
|
-
case 'group':
|
|
29
|
-
return (field.fields ?? []).some((f) => settable(f, full, key, targets))
|
|
30
|
-
default:
|
|
31
|
-
// Shown on the page? Edit it there.
|
|
32
|
-
return TEXTUAL.has(field.type) && !targets.find(key, full)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export const hasSettings = (
|
|
37
|
-
fields: SchemaField[],
|
|
38
|
-
path: string,
|
|
39
|
-
key: string,
|
|
40
|
-
targets: TargetIndex,
|
|
41
|
-
): boolean => fields.some((f) => settable(f, path, key, targets))
|
|
42
|
-
|
|
43
32
|
const display = (v: unknown, t: Translate): string => {
|
|
44
33
|
if (v == null || v === '') return t('common.none')
|
|
45
34
|
if (Array.isArray(v))
|
|
@@ -53,9 +42,9 @@ function TextInput({
|
|
|
53
42
|
field,
|
|
54
43
|
id,
|
|
55
44
|
}: {
|
|
56
|
-
docKey:
|
|
45
|
+
docKey: RefKey
|
|
57
46
|
path: string
|
|
58
|
-
field:
|
|
47
|
+
field: ScalarField | NumberField
|
|
59
48
|
id: string
|
|
60
49
|
}) {
|
|
61
50
|
const { session, snap } = useOnPage()
|
|
@@ -92,14 +81,15 @@ function Segmented({
|
|
|
92
81
|
field,
|
|
93
82
|
labelId,
|
|
94
83
|
}: {
|
|
95
|
-
docKey:
|
|
84
|
+
docKey: RefKey
|
|
96
85
|
path: string
|
|
97
|
-
field:
|
|
86
|
+
field: ChoiceField
|
|
98
87
|
labelId: string
|
|
99
88
|
}) {
|
|
100
89
|
const { session, snap } = useOnPage()
|
|
101
|
-
const options = field.options
|
|
102
|
-
const current =
|
|
90
|
+
const options = field.options
|
|
91
|
+
const current =
|
|
92
|
+
selectedValues(getAt(snap.docs[docKey]?.doc, path) ?? field.defaultValue, field)[0] ?? ''
|
|
103
93
|
const index = Math.max(
|
|
104
94
|
0,
|
|
105
95
|
options.findIndex((o) => o.value === current),
|
|
@@ -148,54 +138,17 @@ function Control({
|
|
|
148
138
|
id,
|
|
149
139
|
labelId,
|
|
150
140
|
}: {
|
|
151
|
-
docKey:
|
|
141
|
+
docKey: RefKey
|
|
152
142
|
path: string
|
|
153
143
|
field: SchemaField
|
|
154
144
|
anchor: RefObject<HTMLDivElement | null>
|
|
155
145
|
id: string
|
|
156
146
|
labelId: string
|
|
157
147
|
}) {
|
|
158
|
-
const { session, snap, edit, t } = useOnPage()
|
|
148
|
+
const { session, snap, edit, editors, t } = useOnPage()
|
|
159
149
|
const value = getAt(snap.docs[docKey]?.doc, path)
|
|
160
150
|
const set = (v: unknown) => session.setValue(docKey, path, v)
|
|
161
|
-
|
|
162
|
-
const options = field.options ?? []
|
|
163
|
-
if (options.length <= 5)
|
|
164
|
-
return <Segmented docKey={docKey} path={path} field={field} labelId={labelId} />
|
|
165
|
-
return (
|
|
166
|
-
<select
|
|
167
|
-
id={id}
|
|
168
|
-
className="po-select"
|
|
169
|
-
value={toText(value ?? field.defaultValue)}
|
|
170
|
-
onChange={(e) => set(e.target.value || null)}
|
|
171
|
-
data-testid={`onpage-setting-${path}`}
|
|
172
|
-
>
|
|
173
|
-
{!field.required && <option value="">{t('common.none')}</option>}
|
|
174
|
-
{options.map((o) => (
|
|
175
|
-
<option key={o.value} value={o.value}>
|
|
176
|
-
{o.label}
|
|
177
|
-
</option>
|
|
178
|
-
))}
|
|
179
|
-
</select>
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
if (field.type === 'checkbox') {
|
|
183
|
-
return (
|
|
184
|
-
<label className="po-check">
|
|
185
|
-
<input
|
|
186
|
-
type="checkbox"
|
|
187
|
-
checked={Boolean(value ?? field.defaultValue)}
|
|
188
|
-
onChange={(e) => set(e.target.checked)}
|
|
189
|
-
data-testid={`onpage-setting-${path}`}
|
|
190
|
-
/>
|
|
191
|
-
{field.label}
|
|
192
|
-
</label>
|
|
193
|
-
)
|
|
194
|
-
}
|
|
195
|
-
if (INPUT_TYPES.has(field.type))
|
|
196
|
-
return <TextInput docKey={docKey} path={path} field={field} id={id} />
|
|
197
|
-
// Everything else opens its field editor, anchored to the panel row.
|
|
198
|
-
return (
|
|
151
|
+
const openEditor = (
|
|
199
152
|
<button
|
|
200
153
|
id={id}
|
|
201
154
|
type="button"
|
|
@@ -208,9 +161,59 @@ function Control({
|
|
|
208
161
|
: display(value, t)}
|
|
209
162
|
</button>
|
|
210
163
|
)
|
|
164
|
+
// A field with its own editor (e.g. `editor: 'color'`) opens it, whatever its type.
|
|
165
|
+
if (field.editor && canEdit(editors[field.editor], field)) return openEditor
|
|
166
|
+
switch (field.type) {
|
|
167
|
+
case 'select':
|
|
168
|
+
case 'radio': {
|
|
169
|
+
if (field.hasMany) return openEditor
|
|
170
|
+
if (field.options.length <= 5)
|
|
171
|
+
return <Segmented docKey={docKey} path={path} field={field} labelId={labelId} />
|
|
172
|
+
return (
|
|
173
|
+
<select
|
|
174
|
+
id={id}
|
|
175
|
+
className="po-select"
|
|
176
|
+
value={selectedValues(value ?? field.defaultValue, field)[0] ?? ''}
|
|
177
|
+
onChange={(e) => set(e.target.value || null)}
|
|
178
|
+
data-testid={`onpage-setting-${path}`}
|
|
179
|
+
>
|
|
180
|
+
{!field.required && <option value="">{t('common.none')}</option>}
|
|
181
|
+
{field.options.map((o) => (
|
|
182
|
+
<option key={o.value} value={o.value}>
|
|
183
|
+
{o.label}
|
|
184
|
+
</option>
|
|
185
|
+
))}
|
|
186
|
+
</select>
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
case 'checkbox':
|
|
190
|
+
return (
|
|
191
|
+
<label className="po-check">
|
|
192
|
+
<input
|
|
193
|
+
type="checkbox"
|
|
194
|
+
checked={checkedOf(value, field)}
|
|
195
|
+
onChange={(e) => set(e.target.checked)}
|
|
196
|
+
data-testid={`onpage-setting-${path}`}
|
|
197
|
+
/>
|
|
198
|
+
{field.label}
|
|
199
|
+
</label>
|
|
200
|
+
)
|
|
201
|
+
case 'text':
|
|
202
|
+
case 'textarea':
|
|
203
|
+
case 'email':
|
|
204
|
+
case 'number':
|
|
205
|
+
return 'hasMany' in field && field.hasMany ? (
|
|
206
|
+
openEditor
|
|
207
|
+
) : (
|
|
208
|
+
<TextInput docKey={docKey} path={path} field={field} id={id} />
|
|
209
|
+
)
|
|
210
|
+
default:
|
|
211
|
+
// Dates, relationships, images…: open the field editor, anchored to the panel row.
|
|
212
|
+
return openEditor
|
|
213
|
+
}
|
|
211
214
|
}
|
|
212
215
|
|
|
213
|
-
function Row({ docKey, path, field }: { docKey:
|
|
216
|
+
function Row({ docKey, path, field }: { docKey: RefKey; path: string; field: SchemaField }) {
|
|
214
217
|
const anchor = useRef<HTMLDivElement>(null)
|
|
215
218
|
const id = useId()
|
|
216
219
|
const labelId = `${id}-label`
|
|
@@ -234,7 +237,7 @@ function Row({ docKey, path, field }: { docKey: string; path: string; field: Sch
|
|
|
234
237
|
)
|
|
235
238
|
}
|
|
236
239
|
|
|
237
|
-
function Fields({ docKey, fields, path }: { docKey:
|
|
240
|
+
function Fields({ docKey, fields, path }: { docKey: RefKey; fields: SchemaField[]; path: string }) {
|
|
238
241
|
const { targets } = useOnPage()
|
|
239
242
|
return (
|
|
240
243
|
<>
|
|
@@ -246,7 +249,7 @@ function Fields({ docKey, fields, path }: { docKey: string; fields: SchemaField[
|
|
|
246
249
|
return (
|
|
247
250
|
<fieldset key={f.name} className="po-group">
|
|
248
251
|
<legend className="po-h4">{f.label}</legend>
|
|
249
|
-
<Fields docKey={docKey} fields={f.fields
|
|
252
|
+
<Fields docKey={docKey} fields={f.fields} path={full} />
|
|
250
253
|
</fieldset>
|
|
251
254
|
)
|
|
252
255
|
}
|
|
@@ -260,11 +263,11 @@ export function SettingsPanel() {
|
|
|
260
263
|
const { panel, setPanel, snap, t } = useOnPage()
|
|
261
264
|
if (panel?.kind !== 'settings') return null
|
|
262
265
|
const state = snap.docs[panel.key]
|
|
263
|
-
if (!state
|
|
264
|
-
const owner = fieldAtPath(state.schema.fields, state.doc, panel.path)
|
|
266
|
+
if (!isReady(state)) return null
|
|
267
|
+
const owner = fieldAtPath(state.schema.fields, state.doc, panel.path)?.field
|
|
265
268
|
// Row fields: the block's fields for a blocks row, the item fields for an array row.
|
|
266
269
|
const fields =
|
|
267
|
-
owner
|
|
270
|
+
owner && isListField(owner) ? (rowFieldsOf(owner, getAt(state.doc, panel.path)) ?? []) : []
|
|
268
271
|
return (
|
|
269
272
|
<Dialog
|
|
270
273
|
variant="panel"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
|
|
5
|
+
/** An icon button in a toolbar: labelled for assistive tech, with the label (or a hint) as its tooltip. */
|
|
6
|
+
export function ToolbarButton({
|
|
7
|
+
label,
|
|
8
|
+
hint,
|
|
9
|
+
icon,
|
|
10
|
+
onClick,
|
|
11
|
+
disabled,
|
|
12
|
+
danger,
|
|
13
|
+
testId,
|
|
14
|
+
}: {
|
|
15
|
+
label: string
|
|
16
|
+
hint?: string
|
|
17
|
+
icon: ReactNode
|
|
18
|
+
onClick: () => void
|
|
19
|
+
disabled?: boolean
|
|
20
|
+
danger?: boolean
|
|
21
|
+
testId: string
|
|
22
|
+
}) {
|
|
23
|
+
return (
|
|
24
|
+
<button
|
|
25
|
+
type="button"
|
|
26
|
+
className={`po-btn${danger ? ' is-danger' : ''}`}
|
|
27
|
+
disabled={disabled}
|
|
28
|
+
onClick={onClick}
|
|
29
|
+
aria-label={label}
|
|
30
|
+
title={hint ?? label}
|
|
31
|
+
data-testid={testId}
|
|
32
|
+
>
|
|
33
|
+
{icon}
|
|
34
|
+
</button>
|
|
35
|
+
)
|
|
36
|
+
}
|
package/src/useEvent.ts
CHANGED
|
@@ -7,10 +7,12 @@ import { useCallback, useInsertionEffect, useRef } from 'react'
|
|
|
7
7
|
* pattern). Our own, because the React bundled with Next.js 15 doesn't export
|
|
8
8
|
* `useEffectEvent`. Call it from effects and event handlers, not during render.
|
|
9
9
|
*/
|
|
10
|
-
export function useEvent<
|
|
10
|
+
export function useEvent<F extends (...args: never[]) => unknown>(fn: F): F {
|
|
11
11
|
const latest = useRef(fn)
|
|
12
12
|
useInsertionEffect(() => {
|
|
13
13
|
latest.current = fn
|
|
14
14
|
})
|
|
15
|
-
|
|
15
|
+
const stable = useCallback((...args: Parameters<F>) => latest.current(...args), [])
|
|
16
|
+
// The wrapper has F's exact signature: it forwards its arguments and result unchanged.
|
|
17
|
+
return stable as F
|
|
16
18
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useEffect, useSyncExternalStore } from 'react'
|
|
2
|
+
import { injectStyles } from './styles.ts'
|
|
3
|
+
|
|
4
|
+
const noopSubscribe = () => () => {}
|
|
5
|
+
|
|
6
|
+
/** False during SSR and hydration, true after: for UI that only exists in the browser. */
|
|
7
|
+
export const useIsClient = (): boolean =>
|
|
8
|
+
useSyncExternalStore(
|
|
9
|
+
noopSubscribe,
|
|
10
|
+
() => true,
|
|
11
|
+
() => false,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
/** Adds the editor's stylesheet to the page while mounted. */
|
|
15
|
+
export const useEditorStyles = (): void => useEffect(() => injectStyles(), [])
|