@byline/richtext-lexical 2.6.0 → 2.7.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.
@@ -8,7 +8,9 @@ const EditorComponent = /*#__PURE__*/ lazy(()=>import("./editor-component.js").t
8
8
  function EditorField(props) {
9
9
  return /*#__PURE__*/ jsx(Suspense, {
10
10
  fallback: /*#__PURE__*/ jsx(Shimmer, {
11
- height: "35vh"
11
+ variant: "text",
12
+ lines: 20,
13
+ lineHeight: "1.15rem"
12
14
  }),
13
15
  children: /*#__PURE__*/ jsx(EditorComponent, {
14
16
  ...props
@@ -1,25 +1,51 @@
1
- .inline-image-plugin--modal-image {
2
- margin-bottom: 2rem;
1
+ .inline-image-modal-picker {
2
+ align-items: center;
3
+ gap: .75rem;
4
+ display: flex;
5
+ }
6
+
7
+ .inline-image-modal-thumb, .inline-image-modal-thumb-placeholder {
8
+ border: 1px solid var(--border-color);
9
+ border-radius: .25rem;
10
+ flex-shrink: 0;
11
+ width: 8rem;
12
+ height: 8rem;
3
13
  }
4
14
 
5
- .inline-image-plugin--modal-media-display {
6
- margin-bottom: 2em;
15
+ .inline-image-modal-thumb {
16
+ object-fit: cover;
7
17
  }
8
18
 
9
- .inline-image-plugin--modal-alt-text {
10
- margin-bottom: 1em;
19
+ .inline-image-modal-thumb-placeholder {
20
+ background-color: var(--surface-subtle);
21
+ color: var(--text-subtle);
22
+ justify-content: center;
23
+ align-items: center;
24
+ font-size: .75rem;
25
+ display: flex;
11
26
  }
12
27
 
13
- .inline-image-plugin--modal-position {
14
- margin-bottom: 2em;
28
+ .inline-image-modal-picker-details {
29
+ flex-direction: column;
30
+ flex: 1;
31
+ align-items: flex-start;
32
+ gap: .5rem;
33
+ min-width: 0;
34
+ display: flex;
15
35
  }
16
36
 
17
- .inline-image-plugin--modal-show-caption {
18
- margin-bottom: 1em;
37
+ .inline-image-modal-change-btn {
38
+ flex-shrink: 0;
39
+ min-width: 70px;
19
40
  }
20
41
 
21
- .inline-image-plugin--modal-actions {
22
- gap: 12px;
23
- display: flex;
42
+ .inline-image-modal-title {
43
+ white-space: nowrap;
44
+ text-overflow: ellipsis;
45
+ min-width: 0;
46
+ color: var(--text-subtle);
47
+ align-self: stretch;
48
+ font-size: .875rem;
49
+ overflow: hidden;
24
50
  }
25
51
 
@@ -7,4 +7,5 @@
7
7
  */
8
8
  import type * as React from 'react';
9
9
  import type { InlineImageModalProps } from './types';
10
+ import './inline-image-modal.css';
10
11
  export declare const InlineImageModal: React.FC<InlineImageModalProps>;
@@ -7,6 +7,7 @@ import { Button, Checkbox, CloseIcon, ErrorText, IconButton, Input, Label, Modal
7
7
  import { useModalFormState } from "../../shared/useModalFormState.js";
8
8
  import { isAltTextValid, positionOptions } from "./fields.js";
9
9
  import { deriveImageSizes, getPreferredSize } from "./utils.js";
10
+ import "./inline-image-modal.css";
10
11
  function emptyState() {
11
12
  return {
12
13
  documentRelation: null,
@@ -24,6 +25,22 @@ function fromInlineImageData(data) {
24
25
  showCaption: data.showCaption ?? false
25
26
  };
26
27
  }
28
+ const LABEL_FIELD_KEYS = [
29
+ 'title',
30
+ 'name',
31
+ 'subject',
32
+ 'label'
33
+ ];
34
+ function deriveLabel(fields) {
35
+ for (const key of LABEL_FIELD_KEYS){
36
+ const value = fields[key];
37
+ if ('string' == typeof value && value.trim().length > 0) return value;
38
+ }
39
+ }
40
+ const PICKER_EXTRA_FIELDS = [
41
+ 'altText',
42
+ ...LABEL_FIELD_KEYS
43
+ ];
27
44
  const InlineImageModal = ({ isOpen, collection, data: dataFromProps, onSubmit, onClose })=>{
28
45
  const [pickerOpen, setPickerOpen] = useState(false);
29
46
  const [altError, setAltError] = useState(null);
@@ -58,7 +75,8 @@ const InlineImageModal = ({ isOpen, collection, data: dataFromProps, onSubmit, o
58
75
  const fields = selection.record?.fields ?? {};
59
76
  const image = fields.image;
60
77
  const title = 'string' == typeof fields.title ? fields.title : void 0;
61
- const altTextFromMedia = 'string' == typeof fields.altText ? fields.altText : void 0;
78
+ const altTextFromMedia = 'string' == typeof fields.altText && fields.altText.trim().length > 0 ? fields.altText : void 0;
79
+ const derivedLabel = deriveLabel(fields);
62
80
  const sizes = image ? deriveImageSizes(image) : [];
63
81
  setState((s)=>{
64
82
  const document = {};
@@ -74,7 +92,7 @@ const InlineImageModal = ({ isOpen, collection, data: dataFromProps, onSubmit, o
74
92
  targetCollectionPath: collection,
75
93
  document: Object.keys(document).length > 0 ? document : void 0
76
94
  },
77
- altText: s.altText.length > 0 ? s.altText : altTextFromMedia ?? ''
95
+ altText: s.altText.length > 0 ? s.altText : altTextFromMedia ?? derivedLabel ?? ''
78
96
  };
79
97
  });
80
98
  setImageError(null);
@@ -139,28 +157,33 @@ const InlineImageModal = ({ isOpen, collection, data: dataFromProps, onSubmit, o
139
157
  children: "Image"
140
158
  }),
141
159
  /*#__PURE__*/ jsxs("div", {
142
- className: "flex items-center gap-3",
160
+ className: "inline-image-modal-picker",
143
161
  children: [
144
162
  pickedThumbUrl ? /*#__PURE__*/ jsx("img", {
145
163
  src: pickedThumbUrl,
146
164
  alt: pickedTitle ?? '',
147
- className: "w-18 h-18 object-cover rounded border border-gray-700"
165
+ className: "inline-image-modal-thumb"
148
166
  }) : /*#__PURE__*/ jsx("div", {
149
- className: "w-18 h-18 flex items-center justify-center bg-gray-800 rounded border border-gray-700 text-xs text-gray-500",
167
+ className: "inline-image-modal-thumb-placeholder",
150
168
  children: "—"
151
169
  }),
152
- /*#__PURE__*/ jsx(Button, {
153
- size: "sm",
154
- className: "min-w-[70px]",
155
- variant: "outlined",
156
- intent: "noeffect",
157
- type: "button",
158
- onClick: ()=>setPickerOpen(true),
159
- children: state.documentRelation ? 'Change image…' : `Pick ${targetDef?.labels.singular ?? 'image'}…`
160
- }),
161
- pickedTitle && /*#__PURE__*/ jsx("span", {
162
- className: "text-sm text-gray-200 truncate",
163
- children: pickedTitle
170
+ /*#__PURE__*/ jsxs("div", {
171
+ className: "inline-image-modal-picker-details",
172
+ children: [
173
+ pickedTitle && /*#__PURE__*/ jsx("span", {
174
+ className: "inline-image-modal-title",
175
+ children: pickedTitle
176
+ }),
177
+ /*#__PURE__*/ jsx(Button, {
178
+ size: "sm",
179
+ className: "inline-image-modal-change-btn",
180
+ variant: "outlined",
181
+ intent: "noeffect",
182
+ type: "button",
183
+ onClick: ()=>setPickerOpen(true),
184
+ children: state.documentRelation ? 'Change image…' : `Pick ${targetDef?.labels.singular ?? 'image'}…`
185
+ })
186
+ ]
164
187
  })
165
188
  ]
166
189
  }),
@@ -254,6 +277,7 @@ const InlineImageModal = ({ isOpen, collection, data: dataFromProps, onSubmit, o
254
277
  /*#__PURE__*/ jsx(RelationPicker, {
255
278
  targetCollectionPath: collection,
256
279
  targetDefinition: targetDef,
280
+ extraSelectFields: PICKER_EXTRA_FIELDS,
257
281
  isOpen: pickerOpen,
258
282
  onSelect: handlePickerSelect,
259
283
  onDismiss: ()=>setPickerOpen(false)
@@ -52,7 +52,9 @@ function EditorPlaceholder() {
52
52
  children: /*#__PURE__*/ jsx("div", {
53
53
  className: "byline-field-richtext-body",
54
54
  children: /*#__PURE__*/ jsx(Shimmer, {
55
- height: "35vh"
55
+ variant: "text",
56
+ lines: 20,
57
+ lineHeight: "1.15rem"
56
58
  })
57
59
  })
58
60
  });
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "type": "module",
5
5
  "license": "MPL-2.0",
6
- "version": "2.6.0",
6
+ "version": "2.7.0",
7
7
  "engines": {
8
8
  "node": ">=20.9.0"
9
9
  },
@@ -72,10 +72,10 @@
72
72
  "npm-run-all": "^4.1.5",
73
73
  "prism-react-renderer": "^2.4.1",
74
74
  "react-error-boundary": "^6.1.1",
75
- "@byline/admin": "2.6.0",
76
- "@byline/client": "2.6.0",
77
- "@byline/core": "2.6.0",
78
- "@byline/ui": "2.6.0"
75
+ "@byline/client": "2.7.0",
76
+ "@byline/ui": "2.7.0",
77
+ "@byline/core": "2.7.0",
78
+ "@byline/admin": "2.7.0"
79
79
  },
80
80
  "peerDependencies": {
81
81
  "react": "^19.0.0",
@@ -44,7 +44,7 @@ const EditorComponent = lazy(() =>
44
44
 
45
45
  export function EditorField(props: EditorFieldProps): React.JSX.Element {
46
46
  return (
47
- <Suspense fallback={<Shimmer height="35vh" />}>
47
+ <Suspense fallback={<Shimmer variant="text" lines={20} lineHeight="1.15rem" />}>
48
48
  <EditorComponent {...props} />
49
49
  </Suspense>
50
50
  )
@@ -1,29 +1,59 @@
1
- .inline-image-plugin--modal-image {
2
- margin-bottom: 2rem;
3
- }
1
+ /**
2
+ * Inline image modal — image picker row.
3
+ *
4
+ * Only the picker row (thumbnail + change button + title) needs bespoke
5
+ * layout; the rest of the modal is composed from shared @byline/ui
6
+ * components. Colours use the design tokens so the row is theme-aware.
7
+ */
4
8
 
5
- .inline-image-plugin--modal-actions {
9
+ .inline-image-modal-picker {
6
10
  display: flex;
7
- gap: 12px;
11
+ align-items: center;
12
+ gap: 0.75rem;
13
+ }
14
+
15
+ .inline-image-modal-thumb,
16
+ .inline-image-modal-thumb-placeholder {
17
+ flex-shrink: 0;
18
+ width: 8rem;
19
+ height: 8rem;
20
+ border-radius: 0.25rem;
21
+ border: 1px solid var(--border-color);
8
22
  }
9
23
 
10
- .inline-image-plugin--modal-media-display {
11
- margin-bottom: 2em;
24
+ .inline-image-modal-thumb {
25
+ object-fit: cover;
12
26
  }
13
27
 
14
- .inline-image-plugin--modal-alt-text {
15
- margin-bottom: 1em;
28
+ .inline-image-modal-thumb-placeholder {
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ background-color: var(--surface-subtle);
33
+ color: var(--text-subtle);
34
+ font-size: 0.75rem;
16
35
  }
17
36
 
18
- .inline-image-plugin--modal-position {
19
- margin-bottom: 2em;
37
+ .inline-image-modal-picker-details {
38
+ display: flex;
39
+ flex: 1;
40
+ min-width: 0;
41
+ flex-direction: column;
42
+ align-items: flex-start;
43
+ gap: 0.5rem;
20
44
  }
21
45
 
22
- .inline-image-plugin--modal-show-caption {
23
- margin-bottom: 1em;
46
+ .inline-image-modal-change-btn {
47
+ flex-shrink: 0;
48
+ min-width: 70px;
24
49
  }
25
50
 
26
- .inline-image-plugin--modal-actions {
27
- display: flex;
28
- gap: 12px;
51
+ .inline-image-modal-title {
52
+ align-self: stretch;
53
+ min-width: 0;
54
+ overflow: hidden;
55
+ white-space: nowrap;
56
+ text-overflow: ellipsis;
57
+ font-size: 0.875rem;
58
+ color: var(--text-subtle);
29
59
  }
@@ -34,6 +34,8 @@ import type { DocumentRelation } from '../../nodes/document-relation'
34
34
  import type { Position } from './node-types'
35
35
  import type { InlineImageData, InlineImageModalProps } from './types'
36
36
 
37
+ import './inline-image-modal.css'
38
+
37
39
  interface FormState {
38
40
  documentRelation: DocumentRelation | null
39
41
  altText: string
@@ -60,6 +62,30 @@ function fromInlineImageData(data: InlineImageData | undefined): FormState {
60
62
  }
61
63
  }
62
64
 
65
+ /**
66
+ * Human-readable label fields a media/document collection might use, in
67
+ * priority order. Used only to *seed* the alt-text field when the picked
68
+ * record has no explicit `altText`. Intentionally excludes filenames — a
69
+ * label like `IMG_2024.jpg` makes poor alt text. The user can always edit.
70
+ */
71
+ const LABEL_FIELD_KEYS = ['title', 'name', 'subject', 'label'] as const
72
+
73
+ function deriveLabel(fields: Record<string, any>): string | undefined {
74
+ for (const key of LABEL_FIELD_KEYS) {
75
+ const value = fields[key]
76
+ if (typeof value === 'string' && value.trim().length > 0) return value
77
+ }
78
+ return undefined
79
+ }
80
+
81
+ /**
82
+ * Extra fields the relation picker must load (beyond its display columns)
83
+ * so `handlePickerSelect` can seed the alt-text field: the media's own
84
+ * `altText`, then the human-readable label fallbacks. Module-level so the
85
+ * reference is stable across renders (it feeds the picker's fetch effect).
86
+ */
87
+ const PICKER_EXTRA_FIELDS: string[] = ['altText', ...LABEL_FIELD_KEYS]
88
+
63
89
  export const InlineImageModal: React.FC<InlineImageModalProps> = ({
64
90
  isOpen,
65
91
  collection,
@@ -110,7 +136,13 @@ export const InlineImageModal: React.FC<InlineImageModalProps> = ({
110
136
  const fields = selection.record?.fields ?? {}
111
137
  const image = fields.image as StoredFileValue | undefined
112
138
  const title = typeof fields.title === 'string' ? fields.title : undefined
113
- const altTextFromMedia = typeof fields.altText === 'string' ? fields.altText : undefined
139
+ // Treat a present-but-blank `altText` as absent so the title fallback
140
+ // below still applies.
141
+ const altTextFromMedia =
142
+ typeof fields.altText === 'string' && fields.altText.trim().length > 0
143
+ ? fields.altText
144
+ : undefined
145
+ const derivedLabel = deriveLabel(fields)
114
146
  const sizes = image ? deriveImageSizes(image) : []
115
147
 
116
148
  setState((s) => {
@@ -128,10 +160,11 @@ export const InlineImageModal: React.FC<InlineImageModalProps> = ({
128
160
  targetCollectionPath: collection,
129
161
  document: Object.keys(document).length > 0 ? document : undefined,
130
162
  },
131
- // Pre-fill alt-text from the media's `altText` field on first pick if
132
- // the form's alt-text is still empty. Editorial wins over the source
133
- // record once the user starts typing.
134
- altText: s.altText.length > 0 ? s.altText : (altTextFromMedia ?? ''),
163
+ // Pre-fill alt-text on first pick when the form's alt-text is still
164
+ // empty: prefer the media's own `altText`, then fall back to a
165
+ // human-readable label (title / name / subject / label). Editorial
166
+ // wins over the source record once the user starts typing.
167
+ altText: s.altText.length > 0 ? s.altText : (altTextFromMedia ?? derivedLabel ?? ''),
135
168
  }
136
169
  })
137
170
  setImageError(null)
@@ -182,33 +215,31 @@ export const InlineImageModal: React.FC<InlineImageModalProps> = ({
182
215
  <div className="flex flex-col gap-4">
183
216
  <div className="flex flex-col gap-2">
184
217
  <span className="text-sm font-medium">Image</span>
185
- <div className="flex items-center gap-3">
218
+ <div className="inline-image-modal-picker">
186
219
  {pickedThumbUrl ? (
187
220
  <img
188
221
  src={pickedThumbUrl}
189
222
  alt={pickedTitle ?? ''}
190
- className="w-18 h-18 object-cover rounded border border-gray-700"
223
+ className="inline-image-modal-thumb"
191
224
  />
192
225
  ) : (
193
- <div className="w-18 h-18 flex items-center justify-center bg-gray-800 rounded border border-gray-700 text-xs text-gray-500">
194
-
195
- </div>
196
- )}
197
- <Button
198
- size="sm"
199
- className="min-w-[70px]"
200
- variant="outlined"
201
- intent="noeffect"
202
- type="button"
203
- onClick={() => setPickerOpen(true)}
204
- >
205
- {state.documentRelation
206
- ? 'Change image…'
207
- : `Pick ${targetDef?.labels.singular ?? 'image'}…`}
208
- </Button>
209
- {pickedTitle && (
210
- <span className="text-sm text-gray-200 truncate">{pickedTitle}</span>
226
+ <div className="inline-image-modal-thumb-placeholder">—</div>
211
227
  )}
228
+ <div className="inline-image-modal-picker-details">
229
+ {pickedTitle && <span className="inline-image-modal-title">{pickedTitle}</span>}
230
+ <Button
231
+ size="sm"
232
+ className="inline-image-modal-change-btn"
233
+ variant="outlined"
234
+ intent="noeffect"
235
+ type="button"
236
+ onClick={() => setPickerOpen(true)}
237
+ >
238
+ {state.documentRelation
239
+ ? 'Change image…'
240
+ : `Pick ${targetDef?.labels.singular ?? 'image'}…`}
241
+ </Button>
242
+ </div>
212
243
  </div>
213
244
  {imageError && <ErrorText id="image-error" text={imageError} />}
214
245
  </div>
@@ -293,6 +324,7 @@ export const InlineImageModal: React.FC<InlineImageModalProps> = ({
293
324
  <RelationPicker
294
325
  targetCollectionPath={collection}
295
326
  targetDefinition={targetDef}
327
+ extraSelectFields={PICKER_EXTRA_FIELDS}
296
328
  isOpen={pickerOpen}
297
329
  onSelect={handlePickerSelect}
298
330
  onDismiss={() => setPickerOpen(false)}
@@ -142,7 +142,7 @@ function EditorPlaceholder() {
142
142
  return (
143
143
  <div className="byline-field-richtext">
144
144
  <div className="byline-field-richtext-body">
145
- <Shimmer height="35vh" />
145
+ <Shimmer variant="text" lines={20} lineHeight="1.15rem" />
146
146
  </div>
147
147
  </div>
148
148
  )