uniweb 0.8.12 → 0.8.14

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.8.12",
3
+ "version": "0.8.14",
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.11",
45
- "@uniweb/core": "0.5.8",
44
+ "@uniweb/build": "0.8.13",
46
45
  "@uniweb/content-reader": "1.1.4",
47
- "@uniweb/kit": "0.7.8",
48
- "@uniweb/runtime": "0.6.8",
49
- "@uniweb/semantic-parser": "1.1.5"
46
+ "@uniweb/kit": "0.7.10",
47
+ "@uniweb/runtime": "0.6.10",
48
+ "@uniweb/core": "0.5.10",
49
+ "@uniweb/semantic-parser": "1.1.6"
50
50
  }
51
51
  }
@@ -172,7 +172,6 @@ content = {
172
172
  title: '', // Main heading (string or string[] for multi-line)
173
173
  pretitle: '', // Heading before main title (auto-detected)
174
174
  subtitle: '', // Heading after title (string or string[] for multi-line)
175
- subtitle2: '', // Third-level heading
176
175
  paragraphs: [], // Text blocks
177
176
  links: [], // { href, label, role } — standalone links become buttons
178
177
  images: [], // { src, alt, role, href }
@@ -183,7 +182,7 @@ content = {
183
182
  quotes: [], // Blockquotes
184
183
  snippets: [], // Fenced code — [{ language, code }]
185
184
  data: {}, // From tagged data blocks (```yaml:tagname, ```json:tagname)
186
- headings: [], // Overflow headings after subtitle2
185
+ headings: [], // Headings after subtitle, in document order
187
186
  items: [], // Each has the same flat structure — from headings after body content
188
187
  sequence: [], // All elements in document order
189
188
  }
@@ -431,6 +430,13 @@ Does the content author write content *inside* the nested element? **Yes** → c
431
430
 
432
431
  Inset components declare `inset: true` in meta.js. Don't use `hidden: true` on insets — `hidden` means "don't export this component at all" (for internal helpers), while `inset: true` means "available for `@Component` references in markdown."
433
432
 
433
+ **What inset components receive:** Insets are full section types — they get `{ content, params, block }` like any other section. The alt text becomes `content.title`, and attributes become `params`:
434
+
435
+ ```markdown
436
+ ![npm create uniweb](@CommandBlock){note="Ready to go"}
437
+ ```
438
+ → CommandBlock receives `content.title = "npm create uniweb"` and `params.note = "Ready to go"`.
439
+
434
440
  **SSG:** Insets, `<ChildBlocks>`, and `<Visual>` all render correctly during prerender. Inset components that use React hooks internally (useState, useEffect) will trigger prerender warnings — this is expected and harmless; the page renders correctly client-side.
435
441
 
436
442
  ### Section Nesting Details
@@ -601,21 +607,27 @@ Components use **semantic CSS tokens** instead of hardcoded colors. The runtime
601
607
  <h2 className="text-heading">...</h2>
602
608
  ```
603
609
 
604
- **Core tokens** (available as Tailwind classes):
610
+ **Semantic tokens** (available as Tailwind classes — `text-*`, `bg-*`, `border-*`):
605
611
 
606
612
  | Token | Purpose |
607
613
  |-------|---------|
608
- | `text-heading` | Headings |
609
- | `text-body` | Body text |
610
- | `text-subtle` | Secondary/de-emphasized text |
611
- | `bg-section` | Section background |
612
- | `bg-card` | Card/panel background |
613
- | `bg-muted` | Hover states, zebra rows |
614
- | `border-border` | Borders |
615
- | `text-link` | Link color |
616
- | `bg-primary` / `text-primary-foreground` / `hover:bg-primary-hover` | Primary actions |
617
- | `bg-secondary` / `text-secondary-foreground` / `hover:bg-secondary-hover` | Secondary actions |
618
- | `text-success` / `text-error` / `text-warning` / `text-info` | Status colors |
614
+ | `heading` | Heading text |
615
+ | `body` | Body text |
616
+ | `subtle` | Secondary/de-emphasized text |
617
+ | `section` | Section background |
618
+ | `card` | Card/panel/well background |
619
+ | `muted` | Hover states, zebra rows |
620
+ | `border` | Lines, dividers |
621
+ | `ring` | Focus indicators |
622
+ | `link` / `link-hover` | Link colors |
623
+ | `primary` / `primary-foreground` / `primary-hover` / `primary-border` | Primary actions |
624
+ | `secondary` / `secondary-foreground` / `secondary-hover` / `secondary-border` | Secondary actions |
625
+ | `success` / `warning` / `error` / `info` | Status colors |
626
+ | `success-subtle` / `warning-subtle` / `error-subtle` / `info-subtle` | Status backgrounds (alerts) |
627
+
628
+ Use with any Tailwind prefix: `text-heading`, `bg-section`, `border-border`, `bg-primary`, `text-primary-foreground`, `hover:bg-primary-hover`, `bg-error-subtle`, etc.
629
+
630
+ **Palette shades** are also available: `text-primary-600`, `bg-neutral-100`, `border-accent-300` — 11 shades (50–950) for each palette color (primary, secondary, accent, neutral). See `theme-tokens.css` for the complete mapping.
619
631
 
620
632
  **Content authors control context** in frontmatter:
621
633
 
@@ -787,14 +799,16 @@ Content fields are **HTML strings** — they contain `<strong>`, `<em>`, `<a>` f
787
799
  **Extracted fields** (most common — custom layout with content from markdown):
788
800
 
789
801
  ```jsx
790
- import { H1, H2, P, Span } from '@uniweb/kit'
802
+ import { H1, H2, H3, P, Span } from '@uniweb/kit'
791
803
 
792
804
  <H1 text={content.title} className="text-heading text-5xl font-bold" />
805
+ <H2 text={content.subtitle} className="text-heading text-2xl" />
806
+ <H3 text={item.title} className="text-heading text-lg font-semibold" />
793
807
  <P text={content.paragraphs} className="text-body" />
794
808
  <Span text={listItem.paragraphs[0]} className="text-subtle" />
795
809
  ```
796
810
 
797
- These render their own HTML tag — don't wrap: `<H2 text={...} />` not `<h2><H2 text={...} /></h2>`.
811
+ Kit provides `H1` through `H6` — use the appropriate level for semantic hierarchy. These render their own HTML tag — don't wrap: `<H2 text={...} />` not `<h2><H2 text={...} /></h2>`.
798
812
 
799
813
  **Full content rendering** (article/docs sections where the author controls the flow):
800
814
 
@@ -823,18 +837,46 @@ import { Visual } from '@uniweb/kit'
823
837
 
824
838
  **Navigation and routing:** `Link` (`to`/`href`, `to="page:about"` for page ID resolution, auto `target="_blank"` for external, `reload` for full page reload), `useActiveRoute()`, `useWebsite()`, `useRouting()`
825
839
 
826
- **Header and layout:** `useScrolled(threshold)`, `useMobileMenu()`, `useAppearance()` (light/dark mode)
840
+ **Header and layout:** `useScrolled(threshold)`, `useMobileMenu()`, `useAppearance()`
827
841
 
828
842
  **Layout helpers:** `useGridLayout(columns, { gap })`, `useAccordion({ multiple, defaultOpen })`, `useTheme(name)`
829
843
 
830
844
  **Data and theming:** `useThemeData()` (programmatic color access), `useColorContext(block)`
831
845
 
832
- **Utilities:** `cn()` (Tailwind class merge), `Link`, `Image`, `Asset`, `SafeHtml`, `SocialIcon`, `filterSocialLinks(links)`, `getSocialPlatform(url)`
846
+ **Utilities:** `cn()` (Tailwind class merge — `cn('px-4', condition && 'bg-primary')` resolves conflicts), `Link`, `Image`, `Asset`, `SafeHtml`, `SocialIcon`, `filterSocialLinks(links)`, `getSocialPlatform(url)`
833
847
 
834
848
  **Other styled:** `SidebarLayout`, `Prose`, `Article`, `Code`, `Alert`, `Table`, `Details`, `Divider`, `Disclaimer`
835
849
 
850
+ ### Hook Signatures
851
+
852
+ ```js
853
+ useActiveRoute() → { route, rootSegment, isActive(pageOrRoute), isActiveOrAncestor(pageOrRoute) }
854
+ useMobileMenu() → { isOpen, open, close, toggle } // auto-closes on route change
855
+ useScrolled(threshold?) → boolean // true when scrolled past threshold (px)
856
+ useAppearance() → { scheme, setScheme, toggle, canToggle, schemes }
857
+ useWebsite() → { website } // the Website object
858
+ useThemeData() → Theme // programmatic color access
859
+ useColorContext(block) → 'light' | 'medium' | 'dark' // current section context
860
+ ```
861
+
862
+ `isActive` and `isActiveOrAncestor` accept a Page object or a route string. `useAppearance` reads `appearance:` from `theme.yml` — `scheme` is `'light'`|`'dark'`, `canToggle` reflects `allowToggle` config. Stores preference in localStorage, respects system preference.
863
+
864
+ ### Icon Component
865
+
866
+ The `<Icon>` renders icons from content or explicit props. The simplest usage — spread an icon object from content:
867
+
868
+ ```jsx
869
+ {content.icons.map((icon, i) => <Icon key={i} {...icon} />)}
870
+ ```
871
+
872
+ Props: `library` + `name` (from content), `svg` (direct SVG string), `url` (fetch from URL), `size` (default `'24'`), `className`. The legacy `icon` prop accepts shorthand strings (`"lu-house"`) or objects.
873
+
874
+ Built-in icons (no library needed): `check`, `close`, `menu`, `chevronDown`, `chevronRight`, `externalLink`, `download`, `play`, and a few others.
875
+
836
876
  ### Content Patterns for Header and Footer
837
877
 
878
+ Layout sections (`header.md`, `footer.md`) are regular section types — they support the full content shape including tagged data blocks, lists, links, icons, and items. The only difference is they render on every page instead of one.
879
+
838
880
  Header and Footer combine several content categories. Use different parts of the content shape for each role:
839
881
 
840
882
  **Header** — title for logo, list for nav, standalone link for CTA:
@@ -225,7 +225,7 @@ async function loadI18nConfig(projectDir, siteConfig = null) {
225
225
 
226
226
  // Resolve locales (undefined/'*' → all available, array → specific)
227
227
  const { resolveLocales } = await import('@uniweb/build/i18n')
228
- const locales = await resolveLocales(config.i18n?.locales, localesPath)
228
+ const locales = await resolveLocales(config.languages, localesPath)
229
229
 
230
230
  if (locales.length === 0) return null
231
231
 
@@ -80,7 +80,7 @@ ${colors.bright}Page Ordering:${colors.reset}
80
80
  Rest are auto-discovered
81
81
 
82
82
  ${colors.bright}Internationalization:${colors.reset}
83
- ${colors.cyan}i18n.locales${colors.reset} Locales to build (array or "*" for all)
83
+ ${colors.cyan}languages${colors.reset} Languages to build (array or "*" for all)
84
84
  Example: [es, fr, de]
85
85
  ${colors.cyan}i18n.localesDir${colors.reset} Translation files directory (default: "locales")
86
86
 
@@ -213,7 +213,7 @@ async function loadSiteConfig(siteRoot) {
213
213
 
214
214
  // Resolve locales (undefined/'*' → all available, array → specific)
215
215
  const { resolveLocales } = await import('@uniweb/build/i18n')
216
- const locales = await resolveLocales(config.i18n?.locales, localesPath)
216
+ const locales = await resolveLocales(config.languages, localesPath)
217
217
 
218
218
  return {
219
219
  defaultLocale: config.defaultLanguage || 'en',
@@ -376,7 +376,7 @@ async function runInit(siteRoot, config, args) {
376
376
  if (!targetLocales || targetLocales.length === 0) {
377
377
  error('No target locales specified.')
378
378
  log(`${colors.dim}Specify locales as arguments (e.g., "uniweb i18n generate es fr")`)
379
- log(`or configure them in site.yml under i18n.locales.${colors.reset}`)
379
+ log(`or configure them in site.yml under languages.${colors.reset}`)
380
380
  process.exit(1)
381
381
  }
382
382
 
@@ -1454,10 +1454,10 @@ ${colors.bright}Options:${colors.reset}
1454
1454
  ${colors.bright}Configuration:${colors.reset}
1455
1455
  Optional site.yml settings:
1456
1456
 
1457
+ languages: [es, fr] # Languages to build (default: all available)
1458
+
1457
1459
  i18n:
1458
- locales: [es, fr] # Specific locales only (default: all available)
1459
- locales: '*' # Explicitly all available locales
1460
- localesDir: locales # Directory for translation files (default: locales)
1460
+ localesDir: locales # Directory for translation files (default: locales)
1461
1461
 
1462
1462
  By default, all *.json files in locales/ are treated as translation targets.
1463
1463
 
@@ -174,7 +174,6 @@ function guaranteeContentStructure(parsedContent) {
174
174
  title: content.title || '',
175
175
  pretitle: content.pretitle || '',
176
176
  subtitle: content.subtitle || '',
177
- subtitle2: content.subtitle2 || '',
178
177
  alignment: content.alignment || null,
179
178
  paragraphs: content.paragraphs || [],
180
179
  links: content.links || [],
package/src/index.js CHANGED
@@ -411,7 +411,7 @@ async function main() {
411
411
  type: projectName ? null : 'text',
412
412
  name: 'projectName',
413
413
  message: 'Project name:',
414
- initial: 'my-uniweb-project',
414
+ initial: 'website',
415
415
  validate: (value) => {
416
416
  if (!value) return 'Project name is required'
417
417
  if (!/^[a-z0-9-]+$/.test(value)) {
@@ -177,7 +177,7 @@ async function processFile(sourcePath, targetPath, data, options = {}) {
177
177
  * @param {Function} options.onProgress - Progress callback
178
178
  */
179
179
  export async function copyTemplateDirectory(sourcePath, targetPath, data, options = {}) {
180
- const { onWarning, onProgress } = options
180
+ const { onWarning, onProgress, skip } = options
181
181
 
182
182
  await fs.mkdir(targetPath, { recursive: true })
183
183
  const entries = await fs.readdir(sourcePath, { withFileTypes: true })
@@ -194,13 +194,19 @@ export async function copyTemplateDirectory(sourcePath, targetPath, data, option
194
194
  : sourceName
195
195
  const targetFullPath = path.join(targetPath, targetName)
196
196
 
197
- await copyTemplateDirectory(sourceFullPath, targetFullPath, data, { onWarning, onProgress })
197
+ await copyTemplateDirectory(sourceFullPath, targetFullPath, data, { onWarning, onProgress, skip })
198
198
  } else {
199
199
  // Skip template.json as it's metadata for the template, not for the output
200
200
  if (sourceName === 'template.json') {
201
201
  continue
202
202
  }
203
203
 
204
+ // Determine the output filename (strip .hbs extension) for skip check
205
+ const outputName = sourceName.endsWith('.hbs') ? sourceName.slice(0, -4) : sourceName
206
+ if (skip?.includes(outputName)) {
207
+ continue
208
+ }
209
+
204
210
  // Remove .hbs extension for target filename
205
211
  const targetName = sourceName.endsWith('.hbs')
206
212
  ? sourceName.slice(0, -4)
@@ -30,10 +30,17 @@ const STARTER_DIR = join(__dirname, '..', '..', 'starter')
30
30
  export async function scaffoldWorkspace(targetDir, context, options = {}) {
31
31
  registerVersions(getVersionsForTemplates())
32
32
 
33
+ // Skip pnpm-workspace.yaml for blank workspaces (no packages yet).
34
+ // The `add` command creates it on demand via addWorkspaceGlob().
35
+ // Without this, pnpm fails with ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND
36
+ // because an empty packages: [] makes pnpm search parent directories.
37
+ const skip = context.workspaceGlobs?.length ? [] : ['pnpm-workspace.yaml']
38
+
33
39
  const templatePath = join(TEMPLATES_DIR, 'workspace')
34
40
  await copyTemplateDirectory(templatePath, targetDir, context, {
35
41
  onProgress: options.onProgress,
36
42
  onWarning: options.onWarning,
43
+ skip,
37
44
  })
38
45
  }
39
46