@byline/cli 1.7.3 → 1.7.5

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.
@@ -0,0 +1,273 @@
1
+ /**
2
+ * MediaListView — card-grid list view for the Media collection.
3
+ */
4
+
5
+ .header {
6
+ display: flex;
7
+ align-items: center;
8
+ gap: 0.75rem;
9
+ padding-top: 0.125rem;
10
+ padding-bottom: 0.125rem;
11
+ }
12
+
13
+ .heading {
14
+ margin: 0 !important;
15
+ padding-bottom: 0.125rem;
16
+ }
17
+
18
+ .create-link {
19
+ margin-left: auto;
20
+ }
21
+
22
+ .stats {
23
+ display: inline-flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ height: 1.75rem;
27
+ min-width: 1.75rem;
28
+ padding: 0.3125rem 0.375rem;
29
+ margin-bottom: -0.25rem;
30
+ white-space: nowrap;
31
+ font-size: var(--font-size-sm);
32
+ line-height: 0;
33
+ background-color: var(--gray-25);
34
+ border: var(--border-width-thin) var(--border-style-solid) var(--gray-200);
35
+ border-radius: var(--border-radius-sm);
36
+ }
37
+
38
+ :is([data-theme="dark"], :global(.dark)) .stats {
39
+ background-color: var(--canvas-700);
40
+ }
41
+
42
+ .toolbar {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: 0.5rem;
46
+ align-items: flex-start;
47
+ margin-top: 0.75rem;
48
+ margin-bottom: 1rem;
49
+ }
50
+
51
+ @media (min-width: 40rem) {
52
+ .toolbar {
53
+ flex-direction: row;
54
+ flex-wrap: wrap;
55
+ align-items: center;
56
+ }
57
+ }
58
+
59
+ .search {
60
+ width: 100%;
61
+ max-width: 21.875rem;
62
+ }
63
+
64
+ .order-group {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 0.5rem;
68
+ }
69
+
70
+ @media (min-width: 40rem) {
71
+ .order-group {
72
+ margin-left: auto;
73
+ }
74
+ }
75
+
76
+ .order-label {
77
+ font-size: var(--font-size-sm);
78
+ white-space: nowrap;
79
+ color: var(--gray-500);
80
+ }
81
+
82
+ :is([data-theme="dark"], :global(.dark)) .order-label {
83
+ color: var(--gray-400);
84
+ }
85
+
86
+ .empty-state {
87
+ display: flex;
88
+ flex-direction: column;
89
+ align-items: center;
90
+ justify-content: center;
91
+ padding-top: 5rem;
92
+ padding-bottom: 5rem;
93
+ color: var(--gray-500);
94
+ }
95
+
96
+ :is([data-theme="dark"], :global(.dark)) .empty-state {
97
+ color: var(--gray-400);
98
+ }
99
+
100
+ .empty-loader {
101
+ margin-bottom: 1rem;
102
+ opacity: 0;
103
+ }
104
+
105
+ .empty-text {
106
+ font-size: var(--font-size-sm);
107
+ }
108
+
109
+ .grid {
110
+ display: grid;
111
+ grid-template-columns: repeat(2, minmax(0, 1fr));
112
+ gap: 1rem;
113
+ margin-top: 1rem;
114
+ margin-bottom: 1.5rem;
115
+ }
116
+
117
+ @media (min-width: 40rem) {
118
+ .grid {
119
+ grid-template-columns: repeat(3, minmax(0, 1fr));
120
+ }
121
+ }
122
+
123
+ @media (min-width: 48rem) {
124
+ .grid {
125
+ grid-template-columns: repeat(4, minmax(0, 1fr));
126
+ }
127
+ }
128
+
129
+ @media (min-width: 77rem) {
130
+ .grid {
131
+ grid-template-columns: repeat(5, minmax(0, 1fr));
132
+ }
133
+ }
134
+
135
+ .card {
136
+ display: flex;
137
+ flex-direction: column;
138
+ overflow: hidden;
139
+ border: var(--border-width-thin) var(--border-style-solid) var(--gray-200);
140
+ border-radius: var(--border-radius-sm);
141
+ background-color: white;
142
+ text-decoration: none;
143
+ transition: border-color 0.15s ease;
144
+ }
145
+
146
+ .card:hover {
147
+ border-color: var(--color-indigo-400);
148
+ }
149
+
150
+ :is([data-theme="dark"], :global(.dark)) .card {
151
+ border-color: var(--gray-700);
152
+ background-color: var(--canvas-800);
153
+ }
154
+
155
+ :is([data-theme="dark"], :global(.dark)) .card:hover {
156
+ border-color: var(--color-indigo-500);
157
+ }
158
+
159
+ .thumb-wrap {
160
+ position: relative;
161
+ aspect-ratio: 5 / 4;
162
+ overflow: hidden;
163
+ background-color: var(--gray-100);
164
+ }
165
+
166
+ :is([data-theme="dark"], :global(.dark)) .thumb-wrap {
167
+ background-color: var(--canvas-700);
168
+ }
169
+
170
+ .thumb-img {
171
+ width: 100%;
172
+ height: 100%;
173
+ object-fit: cover;
174
+ transition: transform 0.2s ease;
175
+ }
176
+
177
+ .card:hover .thumb-img {
178
+ transform: scale(1.05);
179
+ }
180
+
181
+ .thumb-placeholder {
182
+ display: flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ width: 100%;
186
+ height: 100%;
187
+ font-size: var(--font-size-xs);
188
+ color: var(--gray-400);
189
+ }
190
+
191
+ :is([data-theme="dark"], :global(.dark)) .thumb-placeholder {
192
+ color: var(--gray-600);
193
+ }
194
+
195
+ .card-meta {
196
+ display: flex;
197
+ flex-direction: column;
198
+ gap: 0.375rem;
199
+ padding: 0.5rem;
200
+ min-width: 0;
201
+ background-color: white;
202
+ }
203
+
204
+ :is([data-theme="dark"], :global(.dark)) .card-meta {
205
+ background-color: var(--canvas-800);
206
+ }
207
+
208
+ .card-title {
209
+ overflow: hidden;
210
+ text-overflow: ellipsis;
211
+ white-space: nowrap;
212
+ font-size: var(--font-size-sm);
213
+ font-weight: var(--font-weight-medium);
214
+ line-height: 1.375;
215
+ }
216
+
217
+ .card-meta-row {
218
+ display: flex;
219
+ flex-wrap: wrap;
220
+ align-items: center;
221
+ justify-content: space-between;
222
+ gap: 0.25rem;
223
+ }
224
+
225
+ .badges {
226
+ display: flex;
227
+ flex-wrap: wrap;
228
+ align-items: center;
229
+ gap: 0.25rem;
230
+ }
231
+
232
+ .updated-at {
233
+ margin-left: auto;
234
+ font-size: var(--font-size-xs);
235
+ color: var(--gray-500);
236
+ }
237
+
238
+ :is([data-theme="dark"], :global(.dark)) .updated-at {
239
+ color: var(--gray-400);
240
+ }
241
+
242
+ .status-badge {
243
+ display: inline-flex;
244
+ align-items: center;
245
+ border-radius: var(--border-radius-full);
246
+ padding: 0.125rem 0.5rem;
247
+ font-size: var(--font-size-xs);
248
+ font-weight: var(--font-weight-medium);
249
+ }
250
+
251
+ .status-published {
252
+ color: var(--color-emerald-400);
253
+ background-color: color-mix(in oklab, var(--color-emerald-500) 15%, transparent);
254
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-emerald-500) 30%, transparent);
255
+ }
256
+
257
+ .status-archived {
258
+ color: var(--color-gray-400);
259
+ background-color: color-mix(in oklab, var(--color-gray-500) 15%, transparent);
260
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-gray-500) 30%, transparent);
261
+ }
262
+
263
+ .status-default {
264
+ color: var(--color-amber-400);
265
+ background-color: color-mix(in oklab, var(--color-amber-500) 15%, transparent);
266
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-amber-500) 30%, transparent);
267
+ }
268
+
269
+ .bottom-pager {
270
+ display: flex;
271
+ justify-content: flex-end;
272
+ margin-bottom: 1.25rem;
273
+ }
@@ -35,16 +35,20 @@ import {
35
35
  Select,
36
36
  } from '@byline/ui/react'
37
37
 
38
+ import styles from './media-list-view.module.css'
38
39
  import { FormatBadge } from './media-thumbnail'
39
40
 
40
- function formatNumber(n: number, decimalPlaces: number): string {
41
- if (typeof n !== 'number' || Number.isNaN(n)) {
41
+ export function formatNumber(number: number, decimalPlaces: number) {
42
+ if (typeof number !== 'number' || Number.isNaN(number)) {
42
43
  throw new TypeError('Input must be a valid number')
43
44
  }
44
- return n.toLocaleString('en-US', {
45
- minimumFractionDigits: decimalPlaces,
46
- maximumFractionDigits: decimalPlaces,
47
- })
45
+
46
+ const options = {
47
+ minimumFractionDigits: decimalPlaces !== undefined ? decimalPlaces : 0,
48
+ maximumFractionDigits: decimalPlaces !== undefined ? decimalPlaces : 20,
49
+ }
50
+
51
+ return number.toLocaleString('en-US', options)
48
52
  }
49
53
 
50
54
  // ---------------------------------------------------------------------------
@@ -82,11 +86,13 @@ function splitOrderValue(value: OrderValue): { order: string; desc: boolean } {
82
86
  // ---------------------------------------------------------------------------
83
87
 
84
88
  function Stats({ total }: { total: number }) {
85
- return (
86
- <span className="flex items-center justify-center h-7 min-w-7 px-1.5 py-1.25 -mb-1 whitespace-nowrap text-sm leading-0 bg-gray-25 dark:bg-canvas-700 border rounded-md">
87
- {formatNumber(total, 0)}
88
- </span>
89
- )
89
+ return <span className={styles.stats}>{formatNumber(total, 0)}</span>
90
+ }
91
+
92
+ function statusClassName(status: string): string {
93
+ if (status === 'published') return styles['status-published']
94
+ if (status === 'archived') return styles['status-archived']
95
+ return styles['status-default']
90
96
  }
91
97
 
92
98
  function StatusBadge({
@@ -97,26 +103,14 @@ function StatusBadge({
97
103
  workflowStatuses: WorkflowStatus[]
98
104
  }) {
99
105
  const label = workflowStatuses.find((s) => s.name === status)?.label ?? status
100
- const colour =
101
- status === 'published'
102
- ? 'bg-emerald-500/15 text-emerald-400 ring-emerald-500/30'
103
- : status === 'archived'
104
- ? 'bg-gray-500/15 text-gray-400 ring-gray-500/30'
105
- : 'bg-amber-500/15 text-amber-400 ring-amber-500/30'
106
- return (
107
- <span
108
- className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ring-1 ring-inset ${colour}`}
109
- >
110
- {label}
111
- </span>
112
- )
106
+ return <span className={`${styles['status-badge']} ${statusClassName(status)}`}>{label}</span>
113
107
  }
114
108
 
115
109
  function EmptyState() {
116
110
  return (
117
- <div className="flex flex-col items-center justify-center py-20 text-gray-500 dark:text-gray-400">
118
- <LoaderRing className="mb-4 opacity-0" size={1} color="transparent" />
119
- <p className="text-sm">No media items found.</p>
111
+ <div className={styles['empty-state']}>
112
+ <LoaderRing className={styles['empty-loader']} size={1} color="transparent" />
113
+ <p className={styles['empty-text']}>No media items found.</p>
120
114
  </div>
121
115
  )
122
116
  }
@@ -184,14 +178,14 @@ export function MediaListView({
184
178
  <Section>
185
179
  <Container>
186
180
  {/* ---- Header ---- */}
187
- <div className="flex items-center gap-3 py-0.5">
188
- <h1 className="m-0! pb-0.5">{data.included.collection.labels.plural as string}</h1>
181
+ <div className={styles.header}>
182
+ <h1 className={styles.heading}>{data.included.collection.labels.plural as string}</h1>
189
183
  <Stats total={data.meta.total} />
190
184
  <IconButton
191
185
  aria-label="Upload New Media"
192
186
  render={
193
187
  <Link
194
- className="ml-auto"
188
+ className={styles['create-link']}
195
189
  to="/admin/collections/$collection/create"
196
190
  params={{ collection: collectionPath }}
197
191
  />
@@ -202,21 +196,18 @@ export function MediaListView({
202
196
  </div>
203
197
 
204
198
  {/* ---- Toolbar ---- */}
205
- <div className="flex flex-col gap-2 sm:flex-row sm:flex-wrap items-start sm:items-center mt-3 mb-4">
199
+ <div className={styles.toolbar}>
206
200
  <Search
207
201
  onSearch={handleOnSearch}
208
202
  onClear={handleOnClear}
209
203
  inputSize="sm"
210
204
  placeholder="Search media…"
211
- className="w-full max-w-87.5"
205
+ className={styles.search}
212
206
  />
213
207
 
214
208
  {/* Order-by */}
215
- <div className="flex items-center gap-2 sm:ml-auto">
216
- <label
217
- htmlFor="media_order"
218
- className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap"
219
- >
209
+ <div className={styles['order-group']}>
210
+ <label htmlFor="media_order" className={styles['order-label']}>
220
211
  Order by
221
212
  </label>
222
213
  <Select
@@ -244,7 +235,7 @@ export function MediaListView({
244
235
  {data.docs.length === 0 ? (
245
236
  <EmptyState />
246
237
  ) : (
247
- <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-5 gap-4 mb-6 mt-4">
238
+ <div className={styles.grid}>
248
239
  {(data.docs as any[]).map((doc) => {
249
240
  const fields = doc.fields ?? {}
250
241
  const img = fields.image as StoredFileValue | null | undefined
@@ -258,41 +249,36 @@ export function MediaListView({
258
249
  key={doc.id}
259
250
  to="/admin/collections/$collection/$id"
260
251
  params={{ collection: collectionPath, id: doc.id }}
261
- className="group flex flex-col overflow-hidden rounded border border-gray-200 dark:border-gray-700 bg-white dark:bg-canvas-800 hover:border-indigo-400 dark:hover:border-indigo-500 transition-colors no-underline"
252
+ className={styles.card}
262
253
  >
263
254
  {/* Thumbnail */}
264
- <div className="relative aspect-5/4 overflow-hidden bg-gray-100 dark:bg-canvas-700">
255
+ <div className={styles['thumb-wrap']}>
265
256
  {thumbUrl ? (
266
257
  <img
267
258
  src={thumbUrl}
268
259
  alt={fields.altText ?? img?.originalFilename ?? ''}
269
- className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-200"
260
+ className={styles['thumb-img']}
270
261
  loading="lazy"
271
262
  />
272
263
  ) : (
273
- <span className="flex h-full w-full items-center justify-center text-xs text-gray-400 dark:text-gray-600">
274
- No image
275
- </span>
264
+ <span className={styles['thumb-placeholder']}>No image</span>
276
265
  )}
277
266
  </div>
278
267
 
279
268
  {/* Card meta */}
280
- <div className="flex flex-col gap-1.5 p-2 min-w-0">
281
- <span
282
- className="truncate text-sm font-medium leading-snug"
283
- title={fields.title ?? ''}
284
- >
269
+ <div className={styles['card-meta']}>
270
+ <span className={styles['card-title']} title={fields.title ?? ''}>
285
271
  {fields.title ?? '—'}
286
272
  </span>
287
- <div className="flex flex-wrap items-center justify-between gap-1">
288
- <div className="flex flex-wrap items-center gap-1">
273
+ <div className={styles['card-meta-row']}>
274
+ <div className={styles.badges}>
289
275
  {doc.status && (
290
276
  <StatusBadge status={doc.status} workflowStatuses={workflowStatuses} />
291
277
  )}
292
278
  {img?.imageFormat && <FormatBadge format={img.imageFormat} />}
293
279
  </div>
294
280
  {updatedAt && (
295
- <span className="ml-auto text-xs text-gray-500 dark:text-gray-400">
281
+ <span className={styles['updated-at']}>
296
282
  <LocalDateTime value={updatedAt} mode="date" />
297
283
  </span>
298
284
  )}
@@ -305,7 +291,7 @@ export function MediaListView({
305
291
  )}
306
292
 
307
293
  {/* ---- Bottom pagination ---- */}
308
- <div className="flex justify-end mb-5">
294
+ <div className={styles['bottom-pager']}>
309
295
  <RouterPager
310
296
  smoothScrollToTop={true}
311
297
  page={data.meta.page}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * MediaThumbnail / FormatBadge — list-view thumbnail and image-format pill.
3
+ */
4
+
5
+ .format-badge {
6
+ display: inline-flex;
7
+ align-items: center;
8
+ border-radius: var(--border-radius-full);
9
+ padding: 0.125rem 0.5rem;
10
+ font-size: var(--font-size-xs);
11
+ font-weight: var(--font-weight-medium);
12
+ color: var(--color-gray-400);
13
+ background-color: color-mix(in oklab, var(--color-gray-500) 10%, transparent);
14
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-gray-500) 20%, transparent);
15
+ }
16
+
17
+ .placeholder {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ width: 4.5rem;
22
+ height: 4.5rem;
23
+ border-radius: var(--border-radius-sm);
24
+ background-color: var(--gray-800);
25
+ color: var(--gray-600);
26
+ font-size: 0.6rem;
27
+ }
28
+
29
+ .thumbnail {
30
+ width: 4.5rem;
31
+ height: 4.5rem;
32
+ object-fit: cover;
33
+ border-radius: var(--border-radius-sm);
34
+ border: var(--border-width-thin) var(--border-style-solid) var(--gray-700);
35
+ }
@@ -8,16 +8,14 @@
8
8
 
9
9
  import type { FormatterProps, StoredFileValue } from '@byline/core'
10
10
 
11
+ import styles from './media-thumbnail.module.css'
12
+
11
13
  /**
12
14
  * FormatBadge renders a muted pill showing the image format (e.g. JPEG, PNG, SVG).
13
15
  * Intended for use alongside the status badge in list-view card meta.
14
16
  */
15
17
  export function FormatBadge({ format }: { format: string }) {
16
- return (
17
- <span className="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ring-1 ring-inset bg-gray-500/10 text-gray-400 ring-gray-500/20">
18
- {format.toUpperCase()}
19
- </span>
20
- )
18
+ return <span className={styles['format-badge']}>{format.toUpperCase()}</span>
21
19
  }
22
20
 
23
21
  /**
@@ -31,11 +29,7 @@ export function MediaThumbnail({ record }: FormatterProps) {
31
29
  const img = fields.image as StoredFileValue | null | undefined
32
30
 
33
31
  if (!img?.storageUrl) {
34
- return (
35
- <span className="inline-flex items-center justify-center w-18 h-18 bg-gray-800 rounded text-gray-600 text-[0.6rem]">
36
-
37
- </span>
38
- )
32
+ return <span className={styles.placeholder}>—</span>
39
33
  }
40
34
 
41
35
  const thumbVariant = img.variants?.find((v) => v.name === 'thumbnail')
@@ -45,7 +39,7 @@ export function MediaThumbnail({ record }: FormatterProps) {
45
39
  <img
46
40
  src={thumbUrl}
47
41
  alt={img.originalFilename ?? img.filename}
48
- className="w-18 h-18 object-cover rounded border border-gray-700"
42
+ className={styles.thumbnail}
49
43
  loading="lazy"
50
44
  />
51
45
  )
@@ -0,0 +1,21 @@
1
+ /**
2
+ * SummaryLength — HelpText slot wrapping LengthIndicator + helpText description.
3
+ */
4
+
5
+ .wrap {
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: 0.25rem;
9
+ margin-top: 0.5rem;
10
+ }
11
+
12
+ .help-text {
13
+ margin-bottom: 0.25rem;
14
+ font-size: var(--font-size-sm);
15
+ line-height: 1.25;
16
+ color: var(--gray-500);
17
+ }
18
+
19
+ :is([data-theme="dark"], :global(.dark)) .help-text {
20
+ color: var(--gray-400);
21
+ }
@@ -10,6 +10,7 @@ import type { FieldHelpTextSlotProps, SlotComponent } from '@byline/core'
10
10
  import { useFieldValue } from '@byline/ui/react'
11
11
 
12
12
  import { LengthIndicator } from './length-indicator'
13
+ import styles from './summary-length.module.css'
13
14
 
14
15
  /**
15
16
  * Custom HelpText slot component for summary-style textArea fields.
@@ -29,11 +30,9 @@ export const SummaryLength: SlotComponent<FieldHelpTextSlotProps> = ({ path, hel
29
30
  const value = useFieldValue<string>(path)
30
31
 
31
32
  return (
32
- <div className="mt-2 flex flex-col gap-1">
33
+ <div className={styles.wrap}>
33
34
  <LengthIndicator minLength={100} maxLength={300} text={value} />
34
- {helpText && (
35
- <p className="text-sm text-gray-500 dark:text-gray-400 leading-tight mb-1">{helpText}</p>
36
- )}
35
+ {helpText && <p className={styles['help-text']}>{helpText}</p>}
37
36
  </div>
38
37
  )
39
38
  }
@@ -6,21 +6,38 @@ import { RichTextBlock as RichTextBlockDef } from '~/blocks/richtext-block'
6
6
 
7
7
  import { PhotoBlock } from '@/ui/byline/blocks/photo-block'
8
8
  import { RichTextBlock } from '@/ui/byline/blocks/richtext-block'
9
- import { toKebabCase } from '@/ui/byline/utils/to-kebab-case'
10
- import type { Locale } from '@/ui/byline/types/i18n'
9
+ import { toKebabCase } from '@/ui/utils/to-kebab-case'
10
+ import type { Locale } from '@/i18n/i18n-config'
11
11
 
12
- /**
13
- * Registered block schemas. Add a new block here (and a matching `case`
14
- * in the switch below) to wire it into the front-end renderer.
15
- */
16
12
  const Blocks = [PhotoBlockDef, RichTextBlockDef] as const
17
13
 
18
- /**
19
- * Discriminated union of every registered block's instance shape. Use
20
- * this as the `blocks` prop type when calling `RenderBlocks`.
21
- */
22
14
  export type AnyBlock = BlocksUnion<typeof Blocks>
23
15
 
16
+ // Mapped type ensures every AnyBlock['_type'] has a registered component.
17
+ // TypeScript errors here if Blocks gains a new type without a matching entry.
18
+ type BlockRegistry = {
19
+ [K in AnyBlock['_type']]: React.ComponentType<{
20
+ id: string
21
+ block: Extract<AnyBlock, { _type: K }>
22
+ lng: Locale
23
+ constrainedLayout?: boolean
24
+ }>
25
+ }
26
+
27
+ const blockComponents: BlockRegistry = {
28
+ photoBlock: PhotoBlock,
29
+ richTextBlock: RichTextBlock,
30
+ }
31
+
32
+ // Loose alias for the call site — BlockRegistry enforces correctness at
33
+ // definition time; TypeScript can't infer the correlation during the map loop.
34
+ type AnyBlockComponent = React.ComponentType<{
35
+ id: string
36
+ block: AnyBlock
37
+ lng: Locale
38
+ constrainedLayout?: boolean
39
+ }>
40
+
24
41
  interface Props {
25
42
  blocks: AnyBlock[] | undefined | null
26
43
  lng: Locale
@@ -37,34 +54,12 @@ export function RenderBlocks({
37
54
  return (
38
55
  <>
39
56
  {blocks.map((block) => {
40
- switch (block._type) {
41
- case 'photoBlock':
42
- return (
43
- <Section className={toKebabCase(block._type)} key={block._id}>
44
- <PhotoBlock
45
- id={block._id}
46
- block={block}
47
- lng={lng}
48
- constrainedLayout={constrainedLayout}
49
- />
50
- </Section>
51
- )
52
- case 'richTextBlock':
53
- return (
54
- <Section className={toKebabCase(block._type)} key={block._id}>
55
- <RichTextBlock
56
- id={block._id}
57
- block={block}
58
- lng={lng}
59
- constrainedLayout={constrainedLayout}
60
- />
61
- </Section>
62
- )
63
- default: {
64
- const _exhaustive: never = block
65
- return null
66
- }
67
- }
57
+ const Block = blockComponents[block._type] as AnyBlockComponent
58
+ return (
59
+ <Section className={toKebabCase(block._type)} key={block._id}>
60
+ <Block id={block._id} block={block} lng={lng} constrainedLayout={constrainedLayout} />
61
+ </Section>
62
+ )
68
63
  })}
69
64
  </>
70
65
  )
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@byline/cli",
3
3
  "private": false,
4
4
  "license": "MPL-2.0",
5
- "version": "1.7.3",
5
+ "version": "1.7.5",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },