@byline/cli 1.7.3 → 1.7.4

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,18 +35,10 @@ import {
35
35
  Select,
36
36
  } from '@byline/ui/react'
37
37
 
38
+ import { formatNumber } from '@/utils/utils.general'
39
+ import styles from './media-list-view.module.css'
38
40
  import { FormatBadge } from './media-thumbnail'
39
41
 
40
- function formatNumber(n: number, decimalPlaces: number): string {
41
- if (typeof n !== 'number' || Number.isNaN(n)) {
42
- throw new TypeError('Input must be a valid number')
43
- }
44
- return n.toLocaleString('en-US', {
45
- minimumFractionDigits: decimalPlaces,
46
- maximumFractionDigits: decimalPlaces,
47
- })
48
- }
49
-
50
42
  // ---------------------------------------------------------------------------
51
43
  // Order-by config
52
44
  // ---------------------------------------------------------------------------
@@ -82,11 +74,13 @@ function splitOrderValue(value: OrderValue): { order: string; desc: boolean } {
82
74
  // ---------------------------------------------------------------------------
83
75
 
84
76
  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
- )
77
+ return <span className={styles.stats}>{formatNumber(total, 0)}</span>
78
+ }
79
+
80
+ function statusClassName(status: string): string {
81
+ if (status === 'published') return styles['status-published']
82
+ if (status === 'archived') return styles['status-archived']
83
+ return styles['status-default']
90
84
  }
91
85
 
92
86
  function StatusBadge({
@@ -97,26 +91,14 @@ function StatusBadge({
97
91
  workflowStatuses: WorkflowStatus[]
98
92
  }) {
99
93
  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
- )
94
+ return <span className={`${styles['status-badge']} ${statusClassName(status)}`}>{label}</span>
113
95
  }
114
96
 
115
97
  function EmptyState() {
116
98
  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>
99
+ <div className={styles['empty-state']}>
100
+ <LoaderRing className={styles['empty-loader']} size={1} color="transparent" />
101
+ <p className={styles['empty-text']}>No media items found.</p>
120
102
  </div>
121
103
  )
122
104
  }
@@ -184,14 +166,14 @@ export function MediaListView({
184
166
  <Section>
185
167
  <Container>
186
168
  {/* ---- 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>
169
+ <div className={styles.header}>
170
+ <h1 className={styles.heading}>{data.included.collection.labels.plural as string}</h1>
189
171
  <Stats total={data.meta.total} />
190
172
  <IconButton
191
173
  aria-label="Upload New Media"
192
174
  render={
193
175
  <Link
194
- className="ml-auto"
176
+ className={styles['create-link']}
195
177
  to="/admin/collections/$collection/create"
196
178
  params={{ collection: collectionPath }}
197
179
  />
@@ -202,21 +184,18 @@ export function MediaListView({
202
184
  </div>
203
185
 
204
186
  {/* ---- Toolbar ---- */}
205
- <div className="flex flex-col gap-2 sm:flex-row sm:flex-wrap items-start sm:items-center mt-3 mb-4">
187
+ <div className={styles.toolbar}>
206
188
  <Search
207
189
  onSearch={handleOnSearch}
208
190
  onClear={handleOnClear}
209
191
  inputSize="sm"
210
192
  placeholder="Search media…"
211
- className="w-full max-w-87.5"
193
+ className={styles.search}
212
194
  />
213
195
 
214
196
  {/* 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
- >
197
+ <div className={styles['order-group']}>
198
+ <label htmlFor="media_order" className={styles['order-label']}>
220
199
  Order by
221
200
  </label>
222
201
  <Select
@@ -244,7 +223,7 @@ export function MediaListView({
244
223
  {data.docs.length === 0 ? (
245
224
  <EmptyState />
246
225
  ) : (
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">
226
+ <div className={styles.grid}>
248
227
  {(data.docs as any[]).map((doc) => {
249
228
  const fields = doc.fields ?? {}
250
229
  const img = fields.image as StoredFileValue | null | undefined
@@ -258,41 +237,36 @@ export function MediaListView({
258
237
  key={doc.id}
259
238
  to="/admin/collections/$collection/$id"
260
239
  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"
240
+ className={styles.card}
262
241
  >
263
242
  {/* Thumbnail */}
264
- <div className="relative aspect-5/4 overflow-hidden bg-gray-100 dark:bg-canvas-700">
243
+ <div className={styles['thumb-wrap']}>
265
244
  {thumbUrl ? (
266
245
  <img
267
246
  src={thumbUrl}
268
247
  alt={fields.altText ?? img?.originalFilename ?? ''}
269
- className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-200"
248
+ className={styles['thumb-img']}
270
249
  loading="lazy"
271
250
  />
272
251
  ) : (
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>
252
+ <span className={styles['thumb-placeholder']}>No image</span>
276
253
  )}
277
254
  </div>
278
255
 
279
256
  {/* 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
- >
257
+ <div className={styles['card-meta']}>
258
+ <span className={styles['card-title']} title={fields.title ?? ''}>
285
259
  {fields.title ?? '—'}
286
260
  </span>
287
- <div className="flex flex-wrap items-center justify-between gap-1">
288
- <div className="flex flex-wrap items-center gap-1">
261
+ <div className={styles['card-meta-row']}>
262
+ <div className={styles.badges}>
289
263
  {doc.status && (
290
264
  <StatusBadge status={doc.status} workflowStatuses={workflowStatuses} />
291
265
  )}
292
266
  {img?.imageFormat && <FormatBadge format={img.imageFormat} />}
293
267
  </div>
294
268
  {updatedAt && (
295
- <span className="ml-auto text-xs text-gray-500 dark:text-gray-400">
269
+ <span className={styles['updated-at']}>
296
270
  <LocalDateTime value={updatedAt} mode="date" />
297
271
  </span>
298
272
  )}
@@ -305,7 +279,7 @@ export function MediaListView({
305
279
  )}
306
280
 
307
281
  {/* ---- Bottom pagination ---- */}
308
- <div className="flex justify-end mb-5">
282
+ <div className={styles['bottom-pager']}>
309
283
  <RouterPager
310
284
  smoothScrollToTop={true}
311
285
  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.4",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },