uniweb 0.2.34 → 0.2.36
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 +5 -4
- package/partials/adding-pages.hbs +29 -0
- package/partials/ai-assistance.hbs +3 -0
- package/partials/claude-md.hbs +226 -0
- package/partials/components-docs.hbs +15 -0
- package/partials/deployment.hbs +13 -0
- package/partials/exports-js.hbs +14 -0
- package/partials/learn-more.hbs +5 -0
- package/partials/quick-start.hbs +8 -0
- package/partials/search-docs.hbs +110 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.36",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"src",
|
|
11
|
-
"templates"
|
|
11
|
+
"templates",
|
|
12
|
+
"partials"
|
|
12
13
|
],
|
|
13
14
|
"keywords": [
|
|
14
15
|
"uniweb",
|
|
@@ -37,8 +38,8 @@
|
|
|
37
38
|
"prompts": "^2.4.2",
|
|
38
39
|
"tar": "^7.0.0",
|
|
39
40
|
"@uniweb/build": "0.1.19",
|
|
41
|
+
"@uniweb/runtime": "0.2.10",
|
|
40
42
|
"@uniweb/core": "0.1.8",
|
|
41
|
-
"@uniweb/kit": "0.1.4"
|
|
42
|
-
"@uniweb/runtime": "0.2.10"
|
|
43
|
+
"@uniweb/kit": "0.1.4"
|
|
43
44
|
}
|
|
44
45
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
### Adding a New Page
|
|
2
|
+
|
|
3
|
+
1. Create a folder: `site/pages/my-page/`
|
|
4
|
+
2. Add `page.yml`:
|
|
5
|
+
```yaml
|
|
6
|
+
title: My Page
|
|
7
|
+
```
|
|
8
|
+
3. Add section files: `1-hero.md`, `2-content.md`, etc.
|
|
9
|
+
|
|
10
|
+
### Section Ordering
|
|
11
|
+
|
|
12
|
+
By default, sections are discovered from `.md` files and sorted by numeric prefix (`1-`, `2-`, `2.5-`).
|
|
13
|
+
|
|
14
|
+
For more control, use the `sections` property in `page.yml`:
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
title: My Page
|
|
18
|
+
sections:
|
|
19
|
+
- hero
|
|
20
|
+
- features
|
|
21
|
+
- pricing
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This approach makes reordering easy (just move lines) and allows temporarily removing sections by commenting them out.
|
|
25
|
+
|
|
26
|
+
**Options:**
|
|
27
|
+
- `sections: *` or omit — auto-discover and sort `.md` files (default)
|
|
28
|
+
- `sections: [hero, features]` — explicit ordering, no numeric prefixes needed
|
|
29
|
+
- `sections: []` or no `.md` files — pure route with no content sections
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance for AI assistants working with this Uniweb project.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
Uniweb projects have two structures. A single project can be converted to multi-site:
|
|
8
|
+
|
|
9
|
+
**Single (one foundation, one site):**
|
|
10
|
+
```
|
|
11
|
+
project/
|
|
12
|
+
├── foundation/ # Purpose-built component library (React)
|
|
13
|
+
├── site/ # Content (markdown pages)
|
|
14
|
+
└── pnpm-workspace.yaml
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Multi-site (multiple foundations and sites):**
|
|
18
|
+
```
|
|
19
|
+
project/
|
|
20
|
+
├── foundations/ # Purpose-built component libraries
|
|
21
|
+
│ └── marketing/
|
|
22
|
+
├── sites/ # Content sites
|
|
23
|
+
│ └── my-org/
|
|
24
|
+
└── pnpm-workspace.yaml
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- **Foundation**: React components. Components with `meta.js` are *exposed* to content authors.
|
|
28
|
+
- **Site**: Markdown content. Each section references a component via `type:` in frontmatter.
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm install # Install dependencies
|
|
34
|
+
pnpm dev # Start dev server (runs default/main site)
|
|
35
|
+
pnpm build # Build for production
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Multi-site also supports:
|
|
39
|
+
```bash
|
|
40
|
+
pnpm dev:all # Start all sites in parallel
|
|
41
|
+
pnpm build:all # Build all sites
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Discovering Components
|
|
45
|
+
|
|
46
|
+
Exposed components live in `foundation/src/components/` (or `foundations/*/src/components/`).
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# List exposed components (those with meta.js)
|
|
50
|
+
ls foundation/src/components/*/meta.js
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Understanding a component:**
|
|
54
|
+
|
|
55
|
+
1. **`meta.js`** - Defines the component's interface:
|
|
56
|
+
- `title`, `description` - What the component does
|
|
57
|
+
- `elements` - What content it expects (title, paragraphs, links, items, etc.)
|
|
58
|
+
- `properties` - Configurable parameters (theme, layout, etc.)
|
|
59
|
+
- `hidden: true` - Component exists but isn't selectable from frontmatter
|
|
60
|
+
|
|
61
|
+
2. **`index.jsx`** - The React implementation
|
|
62
|
+
|
|
63
|
+
3. **Existing content** - See how the component is used in `site/pages/`
|
|
64
|
+
|
|
65
|
+
**Note:** Components without `meta.js` are internal helpers used by other components.
|
|
66
|
+
|
|
67
|
+
## Content Authoring
|
|
68
|
+
|
|
69
|
+
### Section Format
|
|
70
|
+
|
|
71
|
+
Each section is a markdown file with YAML frontmatter:
|
|
72
|
+
|
|
73
|
+
```markdown
|
|
74
|
+
---
|
|
75
|
+
type: ComponentName # Must match an exposed component
|
|
76
|
+
theme: dark # Parameter from meta.js properties
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
### Eyebrow Text # H3 before H1 → pretitle
|
|
80
|
+
|
|
81
|
+
# Main Headline # H1 → title
|
|
82
|
+
|
|
83
|
+
## Subtitle # H2 after H1 → subtitle
|
|
84
|
+
|
|
85
|
+
Description paragraph.
|
|
86
|
+
|
|
87
|
+
[Call to Action](#link)
|
|
88
|
+
|
|
89
|
+

|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Content Structure
|
|
93
|
+
|
|
94
|
+
The semantic parser extracts markdown into:
|
|
95
|
+
|
|
96
|
+
- **`content.main.header`** - title, pretitle, subtitle
|
|
97
|
+
- **`content.main.body`** - paragraphs, links, imgs, lists
|
|
98
|
+
- **`content.items`** - Groups created by H3 headings (each with its own header/body)
|
|
99
|
+
|
|
100
|
+
### Page Organization
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
site/pages/ # or sites/*/pages/
|
|
104
|
+
├── @header/ # Rendered on all pages
|
|
105
|
+
├── @footer/ # Rendered on all pages
|
|
106
|
+
└── [page-name]/
|
|
107
|
+
├── page.yml # title, description, order
|
|
108
|
+
└── 1-section.md # Numbered for ordering
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**page.yml options:**
|
|
112
|
+
```yaml
|
|
113
|
+
title: About Us
|
|
114
|
+
description: Learn about our company
|
|
115
|
+
order: 2 # Sort order in navigation
|
|
116
|
+
|
|
117
|
+
# Section ordering (optional)
|
|
118
|
+
sections:
|
|
119
|
+
- hero
|
|
120
|
+
- features
|
|
121
|
+
- pricing
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Section ordering:**
|
|
125
|
+
- Default: `.md` files discovered and sorted by numeric prefix (`1-`, `2-`, `2.5-`)
|
|
126
|
+
- Explicit: Use `sections: [hero, features]` — no prefixes needed, easy reordering
|
|
127
|
+
- Empty: `sections: []` or no `.md` files — pure route with no content
|
|
128
|
+
- Wildcard: `sections: *` — explicitly use default behavior
|
|
129
|
+
|
|
130
|
+
## Component Development
|
|
131
|
+
|
|
132
|
+
### Props Interface
|
|
133
|
+
|
|
134
|
+
```jsx
|
|
135
|
+
function MyComponent({ content, params, block, website }) {
|
|
136
|
+
const { title, pretitle } = content.main?.header || {}
|
|
137
|
+
const { paragraphs = [], links = [] } = content.main?.body || {}
|
|
138
|
+
const items = content.items || []
|
|
139
|
+
// ...
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Using @uniweb/kit
|
|
144
|
+
|
|
145
|
+
```jsx
|
|
146
|
+
import { H1, P, Link, Image, Section, cn } from '@uniweb/kit'
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- `H1, H2, H3, P, Span` - Typography (handles arrays, filters empty)
|
|
150
|
+
- `Link` - Smart routing
|
|
151
|
+
- `Image` - Optimized images
|
|
152
|
+
- `Section` - Layout wrapper
|
|
153
|
+
- `cn()` - Tailwind class merging
|
|
154
|
+
|
|
155
|
+
### Creating a New Component
|
|
156
|
+
|
|
157
|
+
1. Create `foundation/src/components/NewComponent/index.jsx`
|
|
158
|
+
2. Create `foundation/src/components/NewComponent/meta.js`
|
|
159
|
+
3. Export from `foundation/src/index.js`
|
|
160
|
+
4. Rebuild: `cd foundation && pnpm build`
|
|
161
|
+
|
|
162
|
+
### meta.js Structure
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
export default {
|
|
166
|
+
title: 'Component Name',
|
|
167
|
+
description: 'What it does',
|
|
168
|
+
// hidden: true, // Uncomment to hide from content authors
|
|
169
|
+
elements: {
|
|
170
|
+
title: { label: 'Headline', required: true },
|
|
171
|
+
paragraphs: { label: 'Description' },
|
|
172
|
+
links: { label: 'Call to Action' },
|
|
173
|
+
},
|
|
174
|
+
properties: {
|
|
175
|
+
theme: {
|
|
176
|
+
type: 'select',
|
|
177
|
+
label: 'Theme',
|
|
178
|
+
options: [
|
|
179
|
+
{ value: 'light', label: 'Light' },
|
|
180
|
+
{ value: 'dark', label: 'Dark' },
|
|
181
|
+
],
|
|
182
|
+
default: 'light',
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Parameter Philosophy
|
|
189
|
+
|
|
190
|
+
Design parameters that describe **intent**, not implementation:
|
|
191
|
+
|
|
192
|
+
| Good | Bad |
|
|
193
|
+
|------|-----|
|
|
194
|
+
| `theme: "dark"` | `backgroundColor: "#1a1a1a"` |
|
|
195
|
+
| `layout: "split"` | `gridTemplateColumns: "1fr 1fr"` |
|
|
196
|
+
| `size: "large"` | `fontSize: "2rem"` |
|
|
197
|
+
|
|
198
|
+
## Tailwind CSS v4
|
|
199
|
+
|
|
200
|
+
Theme is defined in `foundation/src/styles.css`:
|
|
201
|
+
|
|
202
|
+
```css
|
|
203
|
+
@import "tailwindcss";
|
|
204
|
+
@source "./components/**/*.{js,jsx}";
|
|
205
|
+
|
|
206
|
+
@theme {
|
|
207
|
+
--color-primary: #3b82f6;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Use with: `bg-primary`, `text-primary`, `bg-primary/10` (10% opacity)
|
|
212
|
+
|
|
213
|
+
## Troubleshooting
|
|
214
|
+
|
|
215
|
+
**"Could not load foundation"**
|
|
216
|
+
- Single: Check `site/package.json` has `"foundation": "file:../foundation"`
|
|
217
|
+
- Multi: Check `sites/*/package.json` has `"default": "file:../../foundations/default"`
|
|
218
|
+
|
|
219
|
+
**Component not appearing**
|
|
220
|
+
1. Verify `meta.js` exists and doesn't have `hidden: true`
|
|
221
|
+
2. Check it's exported from `foundation/src/index.js`
|
|
222
|
+
3. Rebuild: `cd foundation && pnpm build`
|
|
223
|
+
|
|
224
|
+
**Styles not applying**
|
|
225
|
+
1. Verify `@source` in styles.css includes your component path
|
|
226
|
+
2. Check custom color names match `@theme` definitions
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## Component Documentation
|
|
2
|
+
|
|
3
|
+
Generate up-to-date documentation for all foundation components:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# From within the foundation directory
|
|
7
|
+
pnpm docs
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
This creates `COMPONENTS.md` with full details on each component's:
|
|
11
|
+
- Content elements (title, paragraphs, links, images, items)
|
|
12
|
+
- Parameters (theme, layout, columns, etc.)
|
|
13
|
+
- Presets (pre-configured parameter combinations)
|
|
14
|
+
|
|
15
|
+
The documentation is generated from component `meta.js` files, so it's always current.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## Deployment
|
|
2
|
+
|
|
3
|
+
Build and deploy the `site/dist` folder:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm build
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
**Supported platforms:**
|
|
10
|
+
- **Vercel**: `cd site && vercel`
|
|
11
|
+
- **Netlify**: Build command `pnpm build`, publish `site/dist`
|
|
12
|
+
- **GitHub Pages**: Use GitHub Actions
|
|
13
|
+
- **Cloudflare Pages**: Connect repo, set `pnpm build` and `site/dist`
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Foundation Exports
|
|
3
|
+
*
|
|
4
|
+
* Optional file to export foundation-level capabilities to the runtime:
|
|
5
|
+
* - Layout: Custom page layout component (receives header, body, footer, sidebars)
|
|
6
|
+
* - props: Foundation-wide props accessible via website.foundationProps
|
|
7
|
+
*
|
|
8
|
+
* The Layout component receives pre-rendered page areas as props:
|
|
9
|
+
* - page, website: Runtime context
|
|
10
|
+
* - header, body, footer: Core page regions (pre-rendered React elements)
|
|
11
|
+
* - left/leftPanel, right/rightPanel: Sidebar panels
|
|
12
|
+
*
|
|
13
|
+
* If this file is not provided, the runtime uses a default layout (header -> body -> footer).
|
|
14
|
+
*/
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
## Search Functionality
|
|
2
|
+
|
|
3
|
+
Uniweb sites support full-text search out of the box. Search indexes are generated automatically at build time.
|
|
4
|
+
|
|
5
|
+
### Enabling Search in Your Foundation
|
|
6
|
+
|
|
7
|
+
To add search to your site, your foundation needs the **Fuse.js** library:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
cd foundation
|
|
11
|
+
pnpm add fuse.js
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Then use the search helpers from `@uniweb/kit`:
|
|
15
|
+
|
|
16
|
+
```jsx
|
|
17
|
+
import { useSearch } from '@uniweb/kit/search'
|
|
18
|
+
|
|
19
|
+
function SearchComponent({ website }) {
|
|
20
|
+
const { query, results, isLoading, clear } = useSearch(website)
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
<input
|
|
25
|
+
type="search"
|
|
26
|
+
placeholder="Search..."
|
|
27
|
+
onChange={e => query(e.target.value)}
|
|
28
|
+
/>
|
|
29
|
+
{isLoading && <span>Searching...</span>}
|
|
30
|
+
<ul>
|
|
31
|
+
{results.map(result => (
|
|
32
|
+
<li key={result.id}>
|
|
33
|
+
<a href={result.href}>{result.title}</a>
|
|
34
|
+
<p dangerouslySetInnerHTML=\{{ __html: result.snippetHtml }} />
|
|
35
|
+
</li>
|
|
36
|
+
))}
|
|
37
|
+
</ul>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Search Configuration
|
|
44
|
+
|
|
45
|
+
Search is enabled by default. To customize, add to `site/site.yml`:
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
search:
|
|
49
|
+
enabled: true
|
|
50
|
+
exclude:
|
|
51
|
+
routes: [/admin, /private] # Don't index these routes
|
|
52
|
+
components: [Debug] # Don't index these component types
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### How It Works
|
|
56
|
+
|
|
57
|
+
1. **Build time**: Uniweb extracts text from all pages and sections
|
|
58
|
+
2. **Output**: A `search-index.json` file is generated in `dist/`
|
|
59
|
+
3. **Runtime**: Components fetch and search the index client-side
|
|
60
|
+
4. **Multi-locale**: Each locale gets its own search index
|
|
61
|
+
|
|
62
|
+
### Search API
|
|
63
|
+
|
|
64
|
+
The `website` object provides these methods:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// Check if search is available
|
|
68
|
+
website.isSearchEnabled() // Returns boolean
|
|
69
|
+
|
|
70
|
+
// Get the URL for the search index
|
|
71
|
+
website.getSearchIndexUrl() // "/search-index.json" or "/es/search-index.json"
|
|
72
|
+
|
|
73
|
+
// Get full search configuration
|
|
74
|
+
website.getSearchConfig() // { enabled, indexUrl, locale, include, exclude }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Search Result Format
|
|
78
|
+
|
|
79
|
+
Results from `useSearch()` include:
|
|
80
|
+
|
|
81
|
+
| Field | Description |
|
|
82
|
+
|-------|-------------|
|
|
83
|
+
| `id` | Unique result identifier |
|
|
84
|
+
| `type` | `"page"` or `"section"` |
|
|
85
|
+
| `route` | Page route |
|
|
86
|
+
| `href` | Full link including anchor |
|
|
87
|
+
| `title` | Result title |
|
|
88
|
+
| `pageTitle` | Parent page title (for sections) |
|
|
89
|
+
| `snippetHtml` | Excerpt with `<mark>` highlighted matches |
|
|
90
|
+
| `snippetText` | Plain text excerpt |
|
|
91
|
+
|
|
92
|
+
### Without @uniweb/kit
|
|
93
|
+
|
|
94
|
+
If you prefer not to use the kit helpers, you can fetch the index directly:
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
// Fetch the search index
|
|
98
|
+
const response = await fetch(website.getSearchIndexUrl())
|
|
99
|
+
const index = await response.json()
|
|
100
|
+
|
|
101
|
+
// Use with Fuse.js directly
|
|
102
|
+
import Fuse from 'fuse.js'
|
|
103
|
+
const fuse = new Fuse(index.entries, {
|
|
104
|
+
keys: ['title', 'content'],
|
|
105
|
+
threshold: 0.35,
|
|
106
|
+
includeMatches: true
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const results = fuse.search('your query')
|
|
110
|
+
```
|