uniweb 0.8.9 → 0.8.10
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/package.json +2 -2
- package/partials/agents.md +118 -43
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.10",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
|
-
"@uniweb/build": "0.8.
|
|
44
|
+
"@uniweb/build": "0.8.9",
|
|
45
45
|
"@uniweb/content-reader": "1.1.4",
|
|
46
46
|
"@uniweb/kit": "0.7.6",
|
|
47
47
|
"@uniweb/core": "0.5.6",
|
package/partials/agents.md
CHANGED
|
@@ -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
|
-
|
|
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
|
-
-
|
|
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
|
|
@@ -360,7 +359,9 @@ submitLabel: Send
|
|
|
360
359
|
|
|
361
360
|
Access: `content.data?.form` → `{ fields: [...], submitLabel: "Send" }`
|
|
362
361
|
|
|
363
|
-
**
|
|
362
|
+
> **FIXME:** The claim below is wrong. I think tagged blocks an only be YAML/JSON. We could add regular code blocks to the parsed content as an array of code blocks or "pre" elements.
|
|
363
|
+
|
|
364
|
+
**Untagged code blocks** 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`).
|
|
364
365
|
|
|
365
366
|
### Composition: Nesting and Embedding
|
|
366
367
|
|
|
@@ -461,6 +462,8 @@ export default function Grid({ block, params }) {
|
|
|
461
462
|
|
|
462
463
|
Set `background` in frontmatter — the runtime renders it automatically:
|
|
463
464
|
|
|
465
|
+
> **FIXME:** var(--primary-900) should be primary-900, right?
|
|
466
|
+
|
|
464
467
|
```yaml
|
|
465
468
|
background: /images/hero.jpg # Image
|
|
466
469
|
background: /videos/hero.mp4 # Video
|
|
@@ -916,8 +919,20 @@ export default {
|
|
|
916
919
|
compact: { label: 'Compact', params: { columns: 4 } },
|
|
917
920
|
},
|
|
918
921
|
|
|
922
|
+
// context and initialState: keys are developer-defined, not framework fields.
|
|
923
|
+
// Design your own names for your foundation's cross-block communication.
|
|
924
|
+
|
|
925
|
+
// Static — neighbors read via getNextBlockInfo().context
|
|
919
926
|
context: {
|
|
920
|
-
|
|
927
|
+
// Example: a Hero might declare this so a Header knows it can float.
|
|
928
|
+
// allowTranslucentTop: true,
|
|
929
|
+
},
|
|
930
|
+
|
|
931
|
+
// Dynamic — neighbors read via getNextBlockInfo().state
|
|
932
|
+
// Component can update with useBlockState()
|
|
933
|
+
initialState: {
|
|
934
|
+
// Example: Hero starts translucent-ready, but component logic may disable it.
|
|
935
|
+
// allowTranslucentTop: true,
|
|
921
936
|
},
|
|
922
937
|
}
|
|
923
938
|
```
|
|
@@ -928,46 +943,52 @@ All defaults belong in `meta.js`, not inline in component code.
|
|
|
928
943
|
|
|
929
944
|
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
945
|
|
|
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
|
|
946
|
+
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.
|
|
947
|
+
|
|
948
|
+
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:
|
|
949
|
+
|
|
950
|
+
```js
|
|
951
|
+
// meta.js — the union of all variants' needs
|
|
952
|
+
export default {
|
|
953
|
+
params: {
|
|
954
|
+
variant: { type: 'select', options: ['slider', 'contact'], default: 'slider' },
|
|
955
|
+
slideInterval: { type: 'number', default: 5 },
|
|
956
|
+
density: { type: 'select', options: ['default', 'compact'], default: 'default' },
|
|
957
|
+
style: { type: 'select', options: ['default', 'dramatic'], default: 'default' },
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
```
|
|
932
961
|
|
|
933
962
|
```jsx
|
|
934
|
-
// sections/
|
|
935
|
-
import {
|
|
936
|
-
import {
|
|
937
|
-
import { ComparisonTable } from '../../components/ComparisonTable'
|
|
963
|
+
// sections/Hero/index.jsx — the front desk
|
|
964
|
+
import { SliderHero } from '../../components/SliderHero'
|
|
965
|
+
import { ContactHero } from '../../components/ContactHero'
|
|
938
966
|
|
|
939
|
-
const variants = {
|
|
967
|
+
const variants = { slider: SliderHero, contact: ContactHero }
|
|
940
968
|
|
|
941
|
-
export default function
|
|
942
|
-
const
|
|
969
|
+
export default function Hero({ content, block, params }) {
|
|
970
|
+
const Variant = variants[params.variant] || SliderHero
|
|
943
971
|
|
|
944
972
|
return (
|
|
945
|
-
<
|
|
973
|
+
<Variant
|
|
974
|
+
// Shared — every variant gets these
|
|
946
975
|
title={content.title}
|
|
947
976
|
subtitle={content.paragraphs[0]}
|
|
948
|
-
|
|
977
|
+
links={content.links}
|
|
949
978
|
block={block}
|
|
950
|
-
|
|
951
|
-
|
|
979
|
+
// Content that only some variants use
|
|
980
|
+
images={content.imgs}
|
|
981
|
+
formData={content.data?.quote}
|
|
982
|
+
// Translated params — author vocabulary → developer props
|
|
983
|
+
interval={params.slideInterval}
|
|
952
984
|
compact={params.density === 'compact'}
|
|
985
|
+
transition={params.style === 'dramatic' ? 'zoom' : 'fade'}
|
|
953
986
|
/>
|
|
954
987
|
)
|
|
955
988
|
}
|
|
956
989
|
```
|
|
957
990
|
|
|
958
|
-
|
|
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.
|
|
991
|
+
`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
992
|
|
|
972
993
|
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
994
|
|
|
@@ -1027,11 +1048,65 @@ page.hasChildren(), page.children
|
|
|
1027
1048
|
|
|
1028
1049
|
### Cross-Block Communication
|
|
1029
1050
|
|
|
1051
|
+
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**.
|
|
1052
|
+
|
|
1053
|
+
`getBlockInfo()` exposes two channels:
|
|
1054
|
+
|
|
1055
|
+
- **`context`** — Static capabilities from `meta.js`. Never changes. The declaring section type always has this capability.
|
|
1056
|
+
- **`state`** — Dynamic runtime state via `useBlockState()`. Can change based on component logic. Initial value comes from `initialState` in `meta.js`.
|
|
1057
|
+
|
|
1058
|
+
```jsx
|
|
1059
|
+
// Header reads the next section's info to decide how to render
|
|
1060
|
+
const nextBlockInfo = block.getNextBlockInfo()
|
|
1061
|
+
// nextBlockInfo.context → static (meta.js)
|
|
1062
|
+
// nextBlockInfo.state → dynamic (useBlockState)
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
**Static context** — Hero declares a permanent capability, Header reads it:
|
|
1066
|
+
|
|
1067
|
+
```js
|
|
1068
|
+
// Hero/meta.js — "I always support a translucent header over me"
|
|
1069
|
+
export default {
|
|
1070
|
+
context: { allowTranslucentTop: true },
|
|
1071
|
+
}
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
```jsx
|
|
1075
|
+
// Header/index.jsx — adapts based on what's below
|
|
1076
|
+
const nextBlockInfo = block.getNextBlockInfo()
|
|
1077
|
+
const isFloating = nextBlockInfo?.context?.allowTranslucentTop || false
|
|
1078
|
+
```
|
|
1079
|
+
|
|
1080
|
+
**Dynamic state** — Hero declares an initial value but can change it at runtime:
|
|
1081
|
+
|
|
1082
|
+
```js
|
|
1083
|
+
// Hero/meta.js — starts as true, but component logic may change it
|
|
1084
|
+
export default {
|
|
1085
|
+
initialState: { allowTranslucentTop: true },
|
|
1086
|
+
}
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1030
1089
|
```jsx
|
|
1031
|
-
|
|
1032
|
-
|
|
1090
|
+
// Hero/index.jsx — conditionally updates
|
|
1091
|
+
function Hero({ content, block }) {
|
|
1092
|
+
const [state, setState] = block.useBlockState(useState)
|
|
1093
|
+
// state.allowTranslucentTop is true initially (from meta.js)
|
|
1094
|
+
// Component logic can change it: setState({ allowTranslucentTop: false })
|
|
1095
|
+
}
|
|
1033
1096
|
```
|
|
1034
1097
|
|
|
1098
|
+
```jsx
|
|
1099
|
+
// Header/index.jsx — reads dynamic state, falls back to static context
|
|
1100
|
+
const nextBlockInfo = block.getNextBlockInfo()
|
|
1101
|
+
const isFloating = nextBlockInfo?.state?.allowTranslucentTop
|
|
1102
|
+
?? nextBlockInfo?.context?.allowTranslucentTop
|
|
1103
|
+
?? false
|
|
1104
|
+
```
|
|
1105
|
+
|
|
1106
|
+
The key names (`allowTranslucentTop`, `expanded`, etc.) are yours to design — they're not framework fields. Define whatever protocol your foundation's sections need.
|
|
1107
|
+
|
|
1108
|
+
Other navigation methods: `block.getPrevBlockInfo()`, `block.page.getFirstBodyBlockInfo()`.
|
|
1109
|
+
|
|
1035
1110
|
### Custom Layouts
|
|
1036
1111
|
|
|
1037
1112
|
Layouts live in `foundation/src/layouts/` and are auto-discovered:
|
|
@@ -1154,9 +1229,9 @@ Semantic tokens come from `theme-tokens.css` (populated from `theme.yml`). Use `
|
|
|
1154
1229
|
|
|
1155
1230
|
**Content not appearing as expected?**
|
|
1156
1231
|
```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
|
|
1232
|
+
pnpm uniweb inspect pages/home/hero.md # Single section
|
|
1233
|
+
pnpm uniweb inspect pages/home/ # Whole page
|
|
1234
|
+
pnpm uniweb inspect pages/home/hero.md --raw # ProseMirror AST
|
|
1160
1235
|
```
|
|
1161
1236
|
|
|
1162
1237
|
## Learning from Official Templates
|
|
@@ -1164,7 +1239,7 @@ uniweb inspect pages/home/hero.md --raw # ProseMirror AST
|
|
|
1164
1239
|
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
1240
|
|
|
1166
1241
|
```bash
|
|
1167
|
-
uniweb add project marketing --from marketing
|
|
1242
|
+
pnpm uniweb add project marketing --from marketing
|
|
1168
1243
|
pnpm install
|
|
1169
1244
|
```
|
|
1170
1245
|
|