uniweb 0.8.9 → 0.8.11

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/README.md CHANGED
@@ -115,7 +115,7 @@ Frontmatter specifies the component type and configuration. The body contains th
115
115
 
116
116
  ### Beyond Markdown
117
117
 
118
- For content that doesn't fit markdown patterns—products, team members, events—use tagged code blocks:
118
+ For content that doesn't fit markdown patterns—products, team members, events—use tagged data blocks:
119
119
 
120
120
  ````markdown
121
121
  ```yaml:team-member
@@ -202,7 +202,7 @@ Open `foundation/src/sections/Hero.jsx`. The component receives parsed content:
202
202
 
203
203
  ```jsx
204
204
  export default function Hero({ content }) {
205
- const { title, paragraphs, links, imgs, items } = content
205
+ const { title, paragraphs, links, images, items } = content
206
206
  // Edit the JSX below...
207
207
  }
208
208
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.8.9",
3
+ "version": "0.8.11",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,11 +41,11 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
- "@uniweb/build": "0.8.8",
44
+ "@uniweb/build": "0.8.10",
45
45
  "@uniweb/content-reader": "1.1.4",
46
- "@uniweb/kit": "0.7.6",
47
- "@uniweb/core": "0.5.6",
48
- "@uniweb/runtime": "0.6.6",
49
- "@uniweb/semantic-parser": "1.1.4"
46
+ "@uniweb/core": "0.5.7",
47
+ "@uniweb/runtime": "0.6.7",
48
+ "@uniweb/kit": "0.7.7",
49
+ "@uniweb/semantic-parser": "1.1.5"
50
50
  }
51
51
  }
@@ -50,10 +50,10 @@ This project was created with [Uniweb CLI](https://github.com/uniweb/cli). Full
50
50
  | Component metadata (meta.js) | `reference/component-metadata.md` |
51
51
  | Migrating existing designs | `development/converting-existing.md` |
52
52
 
53
- > **npm registry:** Use `https://registry.npmjs.org/uniweb` for package metadata — the npmjs.com website blocks automated requests.
54
-
55
53
  ## Project Structure
56
54
 
55
+ Most projects start as a workspace with two packages:
56
+
57
57
  ```
58
58
  project/
59
59
  ├── foundation/ # Component developer's domain
@@ -61,23 +61,24 @@ project/
61
61
  └── pnpm-workspace.yaml
62
62
  ```
63
63
 
64
- Multi-site variant uses `foundations/` and `sites/` (plural) folders.
64
+ - **Foundation** (developer): React components. Those in `src/sections` and `src/layouts` are *section types* — selectable by content authors via `type:` in frontmatter, or used for site-level layout areas (header, footer, panel). Most have an a `meta.js` with metadata in them. Everything in `src/components` (or elsewhere) is ordinary React.
65
+ - **Site** (content author): Markdown content + configuration. Each section file references a section type. Authors work here without touching foundation code. It may also contain collections of structured content and/or references to external data sources.
65
66
 
66
- - **Foundation** (developer): React components. Those with `meta.js` are *section types* selectable by content authors via `type:` in frontmatter. Everything else is ordinary React.
67
- - **Site** (content author): Markdown content + configuration. Each section file references a section type. Authors work here without touching foundation code.
67
+ > Multi-site projects use sub-folders with site/foundation pairs in them, or segregate foundations and sites into separate folders (`foundations/`, `sites/`).
68
68
 
69
69
  ## Project Setup
70
70
 
71
71
  Always use the CLI to scaffold projects — never write `package.json`, `vite.config.js`, `main.js`, or `index.html` manually. The CLI resolves correct versions and structure.
72
72
 
73
+ **npm or pnpm.** Projects include both `pnpm-workspace.yaml` and npm workspaces. Replace `pnpm` with `npm` in any command below.
74
+
73
75
  ### New workspace
74
76
 
75
77
  ```bash
76
- pnpm create uniweb my-project
78
+ pnpm create uniweb my-project --template <n>
77
79
  cd my-project && pnpm install
78
80
  ```
79
-
80
- This creates a workspace with foundation + site + starter content — two commands to a dev server. Use `--template <n>` for an official template (`marketing`, `docs`, `academic`, etc.), `--template none` for foundation + site with no content, or `--blank` for an empty workspace.
81
+ Use `--template <n>` for an official template (`none`, `starter`, `marketing`, `docs`, `academic`, etc.), `--template none` for foundation + site with no content, or `--blank` for an empty workspace.
81
82
 
82
83
  ### Adding a co-located project
83
84
 
@@ -129,8 +130,6 @@ pnpm build # Build for production
129
130
  pnpm preview # Preview production build (SSG + SPA)
130
131
  ```
131
132
 
132
- > **npm works too.** Projects include both `pnpm-workspace.yaml` and npm workspaces. Replace `pnpm` with `npm` in any command above.
133
-
134
133
  ---
135
134
 
136
135
  ## Content Authoring
@@ -176,13 +175,14 @@ content = {
176
175
  subtitle2: '', // Third-level heading
177
176
  paragraphs: [], // Text blocks
178
177
  links: [], // { href, label, role } — standalone links become buttons
179
- imgs: [], // { src, alt, role }
178
+ images: [], // { src, alt, role, href }
180
179
  icons: [], // { library, name, role }
181
- videos: [], // Video embeds
180
+ videos: [], // { src, alt, role, poster, href }
182
181
  insets: [], // Inline @Component references — { refId }
183
182
  lists: [], // [[{ paragraphs, links, lists, ... }]] — each list item is an object, not a string
184
183
  quotes: [], // Blockquotes
185
- data: {}, // From tagged code blocks (```yaml:tagname) and (```js:tagname)
184
+ snippets: [], // Fenced code [{ language, code }]
185
+ data: {}, // From tagged data blocks (```yaml:tagname, ```json:tagname)
186
186
  headings: [], // Overflow headings after subtitle2
187
187
  items: [], // Each has the same flat structure — from headings after body content
188
188
  sequence: [], // All elements in document order
@@ -205,7 +205,7 @@ Lightning quick. ← items[0].paragraphs[0]
205
205
  Enterprise-grade. ← items[1].paragraphs[0]
206
206
  ```
207
207
 
208
- **Items have the full content shape** — this is the most commonly overlooked feature. Each item has `title`, `pretitle`, `subtitle`, `paragraphs`, `links`, `icons`, `lists`, and even `data` (tagged blocks). You don't need workarounds for structured content within items:
208
+ **Items have the full content shape** — this is the most commonly overlooked feature. Each item has `title`, `pretitle`, `subtitle`, `paragraphs`, `links`, `icons`, `lists`, `snippets`, and even `data` (tagged data blocks). You don't need workarounds for structured content within items:
209
209
 
210
210
  ```markdown
211
211
  ### The Problem ← items[0].pretitle
@@ -258,7 +258,7 @@ You have three layers. Most of the design skill is choosing between them:
258
258
 
259
259
  **Frontmatter params** — `columns: 3`, `variant: centered`, `theme: dark`. Configuration that an author might change but that isn't *content*. Would changing this value change the section's *meaning*, or just its *presentation*? Presentation → param. Meaning → content.
260
260
 
261
- **Tagged data blocks** — for content that doesn't fit markdown patterns. Products with SKUs, team members with roles, event schedules, pricing metadata, form definitions. When the information is genuinely structured data that a content author still owns, a well-named tagged block (`yaml:pricing`, `yaml:speakers`, `yaml:config`) is clearer than contorting markdown into a data format.
261
+ **Tagged data blocks** — for content that doesn't fit markdown patterns. Products with SKUs, team members with roles, event schedules, pricing metadata, form definitions. When the information is genuinely structured data that a content author still owns, a well-named tagged block (`yaml:pricing`, `yaml:speakers`, `yaml:config`) is clearer than contorting markdown into a data format. Supported formats: `yaml` and `json`. The format is a serialization format (how to parse the data), not a language for display. Tagged blocks are parsed at build time into JS objects and delivered as `content.data.tagName`.
262
262
 
263
263
  Read the markdown out loud. If a content author would understand what every line does and how to edit it, you've chosen the right layer. The moment markdown feels like it's encoding data rather than expressing content, step up to a tagged block — that's fine. A well-documented `yaml:pricing` block is better than a markdown structure that puzzles the author.
264
264
 
@@ -321,7 +321,7 @@ Custom SVGs: `![Logo](./logo.svg){role=icon}`
321
321
  ```markdown
322
322
  [text](url){target=_blank} <!-- Open in new tab -->
323
323
  [text](./file.pdf){download} <!-- Download -->
324
- ![alt](./img.jpg){role=banner} <!-- Role determines array: imgs, icons, or videos -->
324
+ ![alt](./img.jpg){role=banner} <!-- Role determines array: images, icons, or videos -->
325
325
  ```
326
326
 
327
327
  **Quote values that contain spaces:** `{note="Ready to go"}` not `{note=Ready to go}`. Unquoted values end at the first space.
@@ -345,9 +345,11 @@ This is [less important]{muted} context.
345
345
 
346
346
  Sites can define additional named styles in `theme.yml`'s `inline:` section.
347
347
 
348
- ### Structured Data
348
+ ### Fenced Code in Content
349
+
350
+ Fenced code in markdown serves two distinct purposes depending on whether it has a tag:
349
351
 
350
- Tagged code blocks pass structured data via `content.data`:
352
+ **Tagged data blocks** structured data parsed into JS objects. The format (`yaml`/`json`) is a serialization format, not a display language. The tag is the key in `content.data`:
351
353
 
352
354
  ````markdown
353
355
  ```yaml:form
@@ -358,9 +360,21 @@ submitLabel: Send
358
360
  ```
359
361
  ````
360
362
 
361
- Access: `content.data?.form` → `{ fields: [...], submitLabel: "Send" }`
363
+ Access: `content.data?.form` → `{ fields: [...], submitLabel: "Send" }`. Supported formats: `yaml` (or `yml`) and `json`.
364
+
365
+ **Code snippets** — display content with a language for syntax highlighting. Available in `content.snippets` as `[{ language, code }]`:
366
+
367
+ ````markdown
368
+ ```jsx
369
+ function Hello() {
370
+ return <h1>Hello world</h1>
371
+ }
372
+ ```
373
+ ````
374
+
375
+ Access: `content.snippets[0]` → `{ language: 'jsx', code: 'function Hello() {...}' }`. The `language` attribute is a display hint for syntax highlighting, not a parsing format. Filter by language: `content.snippets.filter(s => s.language === 'css')`.
362
376
 
363
- **Untagged code blocks** (plain ``` js) are only visible to sequential-rendering components. If a component needs to access code blocks by name, tag them (`jsx:before`, `jsx:after` → `content.data?.before`, `content.data?.after`).
377
+ Both appear in `content.sequence` for document-order rendering. The difference: tagged data blocks are parsed and extracted to `content.data`; code snippets are preserved and collected in `content.snippets`.
364
378
 
365
379
  ### Composition: Nesting and Embedding
366
380
 
@@ -377,7 +391,7 @@ In other frameworks, this is where you'd reach for MDX, or prop-drill a componen
377
391
  Standard markdown image syntax — `![alt](@Component){attributes}`. The content author placed a full React component with content and params, and it looks like an image reference. The developer builds `NetworkDiagram` as an ordinary React component with `inset: true` in its `meta.js`. The kit's `<Visual>` component renders the first non-empty candidate — so the same section type works whether the author provides a static image, a video, or an interactive component:
378
392
 
379
393
  ```jsx
380
- <Visual inset={block.insets[0]} video={content.videos[0]} image={content.imgs[0]} className="rounded-2xl" />
394
+ <Visual inset={block.insets[0]} video={content.videos[0]} image={content.images[0]} className="rounded-2xl" />
381
395
  ```
382
396
 
383
397
  The content author controls what goes in the visual slot. The developer's component doesn't need to know or care whether it's rendering an image or a ThreeJS scene.
@@ -461,6 +475,8 @@ export default function Grid({ block, params }) {
461
475
 
462
476
  Set `background` in frontmatter — the runtime renders it automatically:
463
477
 
478
+ > **FIXME:** var(--primary-900) should be primary-900, right?
479
+
464
480
  ```yaml
465
481
  background: /images/hero.jpg # Image
466
482
  background: /videos/hero.mp4 # Video
@@ -796,7 +812,7 @@ import { Section, Render } from '@uniweb/kit'
796
812
  ```jsx
797
813
  import { Visual } from '@uniweb/kit'
798
814
 
799
- <Visual inset={block.insets[0]} video={content.videos[0]} image={content.imgs[0]} className="rounded-2xl" />
815
+ <Visual inset={block.insets[0]} video={content.videos[0]} image={content.images[0]} className="rounded-2xl" />
800
816
  ```
801
817
 
802
818
  ### Kit API by Use Case
@@ -916,8 +932,20 @@ export default {
916
932
  compact: { label: 'Compact', params: { columns: 4 } },
917
933
  },
918
934
 
935
+ // context and initialState: keys are developer-defined, not framework fields.
936
+ // Design your own names for your foundation's cross-block communication.
937
+
938
+ // Static — neighbors read via getNextBlockInfo().context
919
939
  context: {
920
- allowTranslucentTop: true,
940
+ // Example: a Hero might declare this so a Header knows it can float.
941
+ // allowTranslucentTop: true,
942
+ },
943
+
944
+ // Dynamic — neighbors read via getNextBlockInfo().state
945
+ // Component can update with useBlockState()
946
+ initialState: {
947
+ // Example: Hero starts translucent-ready, but component logic may disable it.
948
+ // allowTranslucentTop: true,
921
949
  },
922
950
  }
923
951
  ```
@@ -928,46 +956,52 @@ All defaults belong in `meta.js`, not inline in component code.
928
956
 
929
957
  Section types naturally use params to adjust their own rendering — `variant: flipped` reverses a flex direction, `columns: 3` sets a grid. That's not a pattern, that's the baseline.
930
958
 
931
- The **Front Desk pattern** is when a section type does virtually no rendering itself. It reads the author's params, picks the right helper component from `src/components/`, and translates author-friendly vocabulary into developer-oriented props. The section type is a front desk — it greets the request and routes it to the right specialist:
959
+ The **Front Desk pattern** is when a section type does virtually no rendering itself. It reads the author's params, picks the right helper component, and translates author-friendly vocabulary into developer-oriented props. The section type is a front desk — it greets the request and routes it to the right specialist.
960
+
961
+ The workers behind the front desk don't need to share the same interface. A `Hero` might delegate to a `SliderHero` that renders an image carousel and a `ContactHero` that renders a quote request form. They expect different content and different props — that's fine. The front desk declares the **union** of all content its workers might need. Some content won't be used for a given variant, and that's perfectly normal in CCA — params change behavior, and that includes not rendering some content:
962
+
963
+ ```js
964
+ // meta.js — the union of all variants' needs
965
+ export default {
966
+ params: {
967
+ variant: { type: 'select', options: ['slider', 'contact'], default: 'slider' },
968
+ slideInterval: { type: 'number', default: 5 },
969
+ density: { type: 'select', options: ['default', 'compact'], default: 'default' },
970
+ style: { type: 'select', options: ['default', 'dramatic'], default: 'default' },
971
+ }
972
+ }
973
+ ```
932
974
 
933
975
  ```jsx
934
- // sections/Features/index.jsx — the front desk
935
- import { CardGrid } from '../../components/CardGrid'
936
- import { CardList } from '../../components/CardList'
937
- import { ComparisonTable } from '../../components/ComparisonTable'
976
+ // sections/Hero/index.jsx — the front desk
977
+ import { SliderHero } from '../../components/SliderHero'
978
+ import { ContactHero } from '../../components/ContactHero'
938
979
 
939
- const variants = { grid: CardGrid, list: CardList, comparison: ComparisonTable }
980
+ const variants = { slider: SliderHero, contact: ContactHero }
940
981
 
941
- export default function Features({ content, block, params }) {
942
- const Layout = variants[params.variant] || CardGrid
982
+ export default function Hero({ content, block, params }) {
983
+ const Variant = variants[params.variant] || SliderHero
943
984
 
944
985
  return (
945
- <Layout
986
+ <Variant
987
+ // Shared — every variant gets these
946
988
  title={content.title}
947
989
  subtitle={content.paragraphs[0]}
948
- items={content.items}
990
+ links={content.links}
949
991
  block={block}
950
- columns={params.columns}
951
- showIcons={params.showIcon !== false}
992
+ // Content that only some variants use
993
+ images={content.images}
994
+ formData={content.data?.quote}
995
+ // Translated params — author vocabulary → developer props
996
+ interval={params.slideInterval}
952
997
  compact={params.density === 'compact'}
998
+ transition={params.style === 'dramatic' ? 'zoom' : 'fade'}
953
999
  />
954
1000
  )
955
1001
  }
956
1002
  ```
957
1003
 
958
- ```js
959
- // meta.js — author-friendly language
960
- export default {
961
- params: {
962
- variant: { type: 'select', options: ['grid', 'list', 'comparison'], default: 'grid' },
963
- columns: { type: 'number', default: 3 },
964
- showIcon: { type: 'boolean', default: true },
965
- density: { type: 'select', options: ['default', 'compact'], default: 'default' },
966
- }
967
- }
968
- ```
969
-
970
- The content author writes `variant: comparison` — they don't know or care about `ComparisonTable`. The section type translates `density: compact` into a `compact={true}` prop. `CardGrid`, `CardList`, `ComparisonTable` live in `src/components/` — ordinary React, reusable across multiple section types, testable independently.
1004
+ `SliderHero` uses `images`, `interval`, and `transition`; it ignores `formData` and `compact`. `ContactHero` uses `formData` and `compact`; it ignores `images` and `interval`. Each worker takes what it needs. Some params only matter for certain variants (`slideInterval` for slider, `density` for contact). Some are high-level names that the front desk translates into developer-oriented values (`style: dramatic` → `transition="zoom"`). The content author writes `variant: contact` — they don't know or care about `ContactHero`.
971
1005
 
972
1006
  This is the system-building pattern at its clearest: **section types are the public interface** to your content system (author-friendly names, documented in `meta.js`). **Helper components are the implementation** (developer-friendly APIs, ordinary React props). The section type is the thin translation layer that connects the two worlds.
973
1007
 
@@ -1027,11 +1061,65 @@ page.hasChildren(), page.children
1027
1061
 
1028
1062
  ### Cross-Block Communication
1029
1063
 
1064
+ Section types sometimes need to coordinate. The typical case: a Header needs to know whether the section below it supports a floating translucent overlay — a Hero with a full-bleed background does, a plain text section doesn't. The section that **owns the capability declares it**; the section that **needs to adapt reads it**.
1065
+
1066
+ `getBlockInfo()` exposes two channels:
1067
+
1068
+ - **`context`** — Static capabilities from `meta.js`. Never changes. The declaring section type always has this capability.
1069
+ - **`state`** — Dynamic runtime state via `useBlockState()`. Can change based on component logic. Initial value comes from `initialState` in `meta.js`.
1070
+
1071
+ ```jsx
1072
+ // Header reads the next section's info to decide how to render
1073
+ const nextBlockInfo = block.getNextBlockInfo()
1074
+ // nextBlockInfo.context → static (meta.js)
1075
+ // nextBlockInfo.state → dynamic (useBlockState)
1076
+ ```
1077
+
1078
+ **Static context** — Hero declares a permanent capability, Header reads it:
1079
+
1080
+ ```js
1081
+ // Hero/meta.js — "I always support a translucent header over me"
1082
+ export default {
1083
+ context: { allowTranslucentTop: true },
1084
+ }
1085
+ ```
1086
+
1087
+ ```jsx
1088
+ // Header/index.jsx — adapts based on what's below
1089
+ const nextBlockInfo = block.getNextBlockInfo()
1090
+ const isFloating = nextBlockInfo?.context?.allowTranslucentTop || false
1091
+ ```
1092
+
1093
+ **Dynamic state** — Hero declares an initial value but can change it at runtime:
1094
+
1095
+ ```js
1096
+ // Hero/meta.js — starts as true, but component logic may change it
1097
+ export default {
1098
+ initialState: { allowTranslucentTop: true },
1099
+ }
1100
+ ```
1101
+
1102
+ ```jsx
1103
+ // Hero/index.jsx — conditionally updates
1104
+ function Hero({ content, block }) {
1105
+ const [state, setState] = block.useBlockState(useState)
1106
+ // state.allowTranslucentTop is true initially (from meta.js)
1107
+ // Component logic can change it: setState({ allowTranslucentTop: false })
1108
+ }
1109
+ ```
1110
+
1030
1111
  ```jsx
1031
- const firstBody = block.page.getFirstBodyBlockInfo()
1032
- // { type, theme, context: { allowTranslucentTop }, state }
1112
+ // Header/index.jsx reads dynamic state, falls back to static context
1113
+ const nextBlockInfo = block.getNextBlockInfo()
1114
+ const isFloating = nextBlockInfo?.state?.allowTranslucentTop
1115
+ ?? nextBlockInfo?.context?.allowTranslucentTop
1116
+ ?? false
1033
1117
  ```
1034
1118
 
1119
+ The key names (`allowTranslucentTop`, `expanded`, etc.) are yours to design — they're not framework fields. Define whatever protocol your foundation's sections need.
1120
+
1121
+ Other navigation methods: `block.getPrevBlockInfo()`, `block.page.getFirstBodyBlockInfo()`.
1122
+
1035
1123
  ### Custom Layouts
1036
1124
 
1037
1125
  Layouts live in `foundation/src/layouts/` and are auto-discovered:
@@ -1154,9 +1242,9 @@ Semantic tokens come from `theme-tokens.css` (populated from `theme.yml`). Use `
1154
1242
 
1155
1243
  **Content not appearing as expected?**
1156
1244
  ```bash
1157
- uniweb inspect pages/home/hero.md # Single section
1158
- uniweb inspect pages/home/ # Whole page
1159
- uniweb inspect pages/home/hero.md --raw # ProseMirror AST
1245
+ pnpm uniweb inspect pages/home/hero.md # Single section
1246
+ pnpm uniweb inspect pages/home/ # Whole page
1247
+ pnpm uniweb inspect pages/home/hero.md --raw # ProseMirror AST
1160
1248
  ```
1161
1249
 
1162
1250
  ## Learning from Official Templates
@@ -1164,7 +1252,7 @@ uniweb inspect pages/home/hero.md --raw # ProseMirror AST
1164
1252
  When you're unsure how to implement a pattern — data fetching, i18n, layouts, insets, theming — install an official template as a reference project in your workspace:
1165
1253
 
1166
1254
  ```bash
1167
- uniweb add project marketing --from marketing
1255
+ pnpm uniweb add project marketing --from marketing
1168
1256
  pnpm install
1169
1257
  ```
1170
1258
 
@@ -149,7 +149,7 @@ function guaranteeItemStructure(item) {
149
149
  subtitle: item.subtitle || '',
150
150
  paragraphs: item.paragraphs || [],
151
151
  links: item.links || [],
152
- imgs: item.imgs || [],
152
+ images: item.images || [],
153
153
  lists: item.lists || [],
154
154
  icons: item.icons || [],
155
155
  videos: item.videos || [],
@@ -178,7 +178,7 @@ function guaranteeContentStructure(parsedContent) {
178
178
  alignment: content.alignment || null,
179
179
  paragraphs: content.paragraphs || [],
180
180
  links: content.links || [],
181
- imgs: content.imgs || [],
181
+ images: content.images || [],
182
182
  lists: content.lists || [],
183
183
  icons: content.icons || [],
184
184
  videos: content.videos || [],
@@ -7,7 +7,7 @@ import { H1, H2, P, Link, cn } from '@uniweb/kit'
7
7
  * Uses semantic tokens so it adapts to any theme context automatically.
8
8
  */
9
9
  export default function Section({ content, params }) {
10
- const { title, pretitle, subtitle, paragraphs = [], links = [], imgs = [] } = content || {}
10
+ const { title, pretitle, subtitle, paragraphs = [], links = [], images = [] } = content || {}
11
11
 
12
12
  const {
13
13
  align = 'center',
@@ -77,9 +77,9 @@ export default function Section({ content, params }) {
77
77
  </div>
78
78
  )}
79
79
 
80
- {imgs.length > 0 && (
80
+ {images.length > 0 && (
81
81
  <div className="mt-8">
82
- {imgs.map((img, index) => (
82
+ {images.map((img, index) => (
83
83
  <img
84
84
  key={index}
85
85
  src={img.url || img.src}
@@ -10,7 +10,7 @@ export default {
10
10
  subtitle: 'Secondary heading',
11
11
  paragraphs: 'Body text',
12
12
  links: 'Call-to-action buttons',
13
- imgs: 'Section images',
13
+ images: 'Section images',
14
14
  },
15
15
 
16
16
  params: {