Skip to content

Commit 84e30d7

Browse files
committed
Add sitemap to demo
1 parent 59befd8 commit 84e30d7

File tree

8 files changed

+91
-55
lines changed

8 files changed

+91
-55
lines changed

docs/api-reference.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,18 @@ const [staleValue, setter] = useUI(key, initial, flag?);
3434
3535
#### Parameters
3636
37-
| Parameter | Type | Description |
38-
| --------- | --------------- | ------------------------------------------------------ |
39-
| `key` | `string` | The state key (becomes `data-{key}` attribute) |
40-
| `initial` | `T` | Initial/default value for SSR |
37+
| Parameter | Type | Description |
38+
| --------- | ------ | ----------- |
39+
| `key` | `string` | The state key (becomes `data-{key}` attribute) |
40+
| `initial` | `T` | Initial/default value for SSR |
4141
| `flag?` | `typeof CssVar` | Optional: Use CSS variables instead of data attributes |
4242
4343
#### Returns
4444
45-
| Return | Type | Description |
46-
| ------------ | ------------------- | ------------------------------------------------ |
47-
| `staleValue` | `T` | Initial value (doesn't update, use for SSR only) |
48-
| `setter` | `GlobalSetterFn<T>` | Function to update the global state |
45+
| Return | Type | Description |
46+
| ------ | ---- | ----------- |
47+
| `staleValue` | `T` | Initial value (doesn't update, use for SSR only) |
48+
| `setter` | `GlobalSetterFn<T>` | Function to update the global state |
4949
5050
#### Examples
5151
@@ -81,10 +81,10 @@ Same as `useUI`, but affects only the element assigned to `setter.ref`.
8181
8282
#### Returns
8383
84-
| Return | Type | Description |
85-
| ------------ | ------------------- | ------------------------------------------------ |
86-
| `staleValue` | `T` | Initial value (doesn't update, use for SSR only) |
87-
| `setter` | `ScopedSetterFn<T>` | Function with attached `ref` property |
84+
| Return | Type | Description |
85+
| ------ | ---- | ----------- |
86+
| `staleValue` | `T` | Initial value (doesn't update, use for SSR only) |
87+
| `setter` | `ScopedSetterFn<T>` | Function with attached `ref` property |
8888
8989
#### Examples
9090
@@ -146,10 +146,10 @@ const clickHandler = zeroSSR.onClick(key, values);
146146
147147
#### Parameters
148148
149-
| Parameter | Type | Description |
150-
| --------- | ---------- | -------------------------------- |
151-
| `key` | `string` | State key (kebab-case required) |
152-
| `values` | `string[]` | Array of values to cycle through |
149+
| Parameter | Type | Description |
150+
| --------- | ---- | ----------- |
151+
| `key` | `string` | State key (kebab-case required) |
152+
| `values` | `string[]` | Array of values to cycle through |
153153
154154
#### Returns
155155
@@ -208,8 +208,8 @@ activateZeroUiRuntime(variantKeyMap);
208208
209209
#### Parameters
210210
211-
| Parameter | Type | Description |
212-
| ------------ | -------------------------- | -------------------------------------------- |
211+
| Parameter | Type | Description |
212+
| --------- | ---- | ----------- |
213213
| `variantMap` | `Record<string, string[]>` | Generated variant mapping from build process |
214214
215215
#### Setup

docs/experimental.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,13 @@ import { scopedZeroSSR } from '@react-zero-ui/core/experimental';
174174

175175
## 📋 Summary
176176

177-
| Feature | Description |
178-
| ------------------------------- | --------------------------------------------------------- |
177+
| Feature | Description |
178+
| --- | --- |
179179
| **`activateZeroUiRuntime()`** | Enables click handling on static components via `data-ui` |
180-
| **`zeroSSR` / `scopedZeroSSR`** | Generate valid click handlers as JSX props |
181-
| **Runtime Overhead** | ~300 bytes total |
182-
| **React Re-renders** | Zero |
183-
| **Server Component Support** | ✅ Full compatibility |
180+
| **`zeroSSR` / `scopedZeroSSR`** | Generate valid click handlers as JSX props |
181+
| **Runtime Overhead** | ~300 bytes total |
182+
| **React Re-renders** | Zero |
183+
| **Server Component Support** | ✅ Full compatibility |
184184

185185
> **Source Code:** See [experimental](/packages/core/src/experimental) for implementation details.
186186

docs/internal.md

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,23 @@ Below is a **"mental model"** of the Zero‑UI variant extractor-distilled so th
88

99
1. **Locate every hook call** `(ast-parsing.cts) ➡️ collectUseUIHooks`
1010

11-
```ts
12-
const [value, setterFn] = useUI('stateKey', 'initialValue');
13-
```
11+
```ts
12+
const [value, setterFn] = useUI('stateKey', 'initialValue');
13+
```
1414

1515
2. **Resolve** (using §3) the `stateKey` and `initialValue` arguments **at build‑time.**
16-
- `stateKey` must be a _local_, static string.
17-
- `initialValue` follows the same rule.
16+
- `stateKey` must be a _local_, static string.
17+
- `initialValue` follows the same rule.
1818

1919
3. **Globally scan all project files** for **variant tokens that match any discovered `stateKey`**-regardless of where hooks are declared. `(scanner.cts) ➡️ scanVariantTokens`
2020

21-
_Examples_
21+
_Examples_
2222

23-
```html
24-
<!-- stateKey‑true:bg-black ➡️ value = "true" -->
25-
<div class="stateKey-true:bg-black" />
26-
<!-- theme‑dark:text-white ➡️ value = "dark" -->
27-
```
23+
```html
24+
<!-- stateKey‑true:bg-black ➡️ value = "true" -->
25+
<div class="stateKey-true:bg-black" />
26+
<!-- theme‑dark:text-white ➡️ value = "dark" -->
27+
```
2828

2929
```bash
3030
┌────────────┐
@@ -71,10 +71,10 @@ function buildGlobalSelector(keySlug: string, valSlug: string): string {
7171

7272
## 2. Pipeline overview (AST + global token scan)
7373

74-
| Stage | Scope & algorithm | Output |
75-
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
76-
| **A - collectUseUIHooks** | Single AST traversal per file.<br>• Validate `useUI()` shapes.<br>• Resolve **stateKey** & **initialValue** with **`literalFromNode`** (§3).<br>• Builds global set of all state keys. | `HookMeta[]` = `{ stateKey, initialValue }[]`, global `Set<stateKey>` |
77-
| **B - global scanVariantTokens** | Single global regex scan pass over all files (same glob & delimiters Tailwind uses).<br>Matches tokens for **every stateKey discovered in Stage A**. | `Map<stateKey, Set<value>>` |
74+
| Stage | Scope & algorithm | Output |
75+
| --- | --- | --- |
76+
| **A - collectUseUIHooks** | Single AST traversal per file.<br>• Validate `useUI()` shapes.<br>• Resolve **stateKey** & **initialValue** with **`literalFromNode`** (§3).<br>• Builds global set of all state keys. | `HookMeta[]` = `{ stateKey, initialValue }[]`, global `Set<stateKey>` |
77+
| **B - global scanVariantTokens** | Single global regex scan pass over all files (same glob & delimiters Tailwind uses).<br>Matches tokens for **every stateKey discovered in Stage A**. | `Map<stateKey, Set<value>>` |
7878

7979
The pipeline now ensures tokens are captured globally-regardless of hook declarations in each file.
8080

@@ -88,12 +88,12 @@ Everything funnels through **`literalFromNode`**. Think of it as a deterministic
8888

8989
```bash
9090
┌──────────────────────────────┬──────────────────────────┐
91-
│ Expression │ Accepted? ➡️ Returns
91+
│ Expression │ Accepted? ➡️ Returns │
9292
├──────────────────────────────┼──────────────────────────┤
9393
"dark" │ ✅ string literal │
9494
`dark` │ ✅ template literal │
9595
`th-${COLOR}` │ ✅ if COLOR is const │
96-
"a" + "b" │ ✅ ➡️ "ab"
96+
"a" + "b" │ ✅ ➡️ "ab"
9797
│ a || b, a ?? b │ ✅ tries left, then right│
9898
│ const DARK = "dark" │ ✅ top-level const only │
9999
│ THEMES.dark │ ✅ const object access │
@@ -117,22 +117,22 @@ Everything funnels through **`literalFromNode`**. Think of it as a deterministic
117117
118118
### 3.2 Resolvers
119119
120-
| Helper | Purpose |
121-
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
122-
| **`resolveTemplateLiteral`** | Ensures every `${expr}` resolves via `literalFromNode`. |
120+
| Helper | Purpose |
121+
| --- | --- |
122+
| **`resolveTemplateLiteral`** | Ensures every `${expr}` resolves via `literalFromNode`. |
123123
| **`resolveLocalConstIdentifier`** | Maps an `Identifier` ➡️ its `const` initializer _iff_ initializer is a local static string/template. Imported bindings rejected explicitly. |
124-
| **`resolveMemberExpression`** | Static walk of `obj.prop`, `obj['prop']`, `obj?.prop`, arrays, numeric indexes, optional‑chaining… Throws if unresolved. |
125-
| **`literalFromNode`** | Router calling above; memoised (`WeakMap`) per AST node. |
124+
| **`resolveMemberExpression`** | Static walk of `obj.prop`, `obj['prop']`, `obj?.prop`, arrays, numeric indexes, optional‑chaining… Throws if unresolved. |
125+
| **`literalFromNode`** | Router calling above; memoised (`WeakMap`) per AST node. |
126126
127127
Resolvers throw contextual errors via **`throwCodeFrame`** (`@babel/code-frame`).
128128
129129
---
130130
131131
## 4. Validation rules
132132
133-
| Position in `useUI` | Allowed value | Example error |
134-
| ------------------------ | ------------------- | --------------------------------------------- |
135-
| **stateKey (arg 0)** | Local static string | `State key cannot be resolved at build‑time.` |
133+
| Position in `useUI` | Allowed value | Example error |
134+
| --- | --- | -- |
135+
| **stateKey (arg 0)** | Local static string | `State key cannot be resolved at build‑time.` |
136136
| **initialValue (arg 1)** | Same rule as above. | `Initial value cannot be resolved …` |
137137
138138
Imported bindings must be re‑cast locally.

docs/migration-guide.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,8 @@ if (theme === 'dark') {
421421

422422
## 📊 Before/After Comparison
423423

424-
| Aspect | Before (Traditional) | After (React Zero-UI) |
425-
| ----------------- | ----------------------------- | -------------------------- |
424+
| Aspect | Before (Traditional) | After (React Zero-UI) |
425+
| --- | --- | --- |
426426
| **Bundle Size** | +5KB (Redux) / +2KB (Context) | +350 bytes |
427427
| **Re-renders** | Every state change | Zero |
428428
| **Performance** | Slower with scale | Constant fast |
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const DOMAIN_URL = 'https://zero-ui.dev';
2+
3+
export const SITE_SLUGS = { home: '/', about: '/icon-sprite', contact: '/lucide-react', terms: '/react', privacy: '/ssr', quote: '/zero-ui' } as const;
4+
5+
const flattenSlugs = (obj: Record<string, string | Record<string, string>>): string[] => {
6+
return Object.values(obj).flatMap((value) => (typeof value === 'string' ? [value] : flattenSlugs(value)));
7+
};
8+
9+
export const ALL_PAGES: string[] = Object.values(SITE_SLUGS).flatMap((value) => (typeof value === 'string' ? [value] : flattenSlugs(value)));

examples/demo/src/app/robots.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { MetadataRoute } from 'next';
2+
import { DOMAIN_URL } from '../app/config/siteConfig';
3+
export default function robots(): MetadataRoute.Robots {
4+
return { rules: [{ userAgent: '*', allow: '/' }], sitemap: `${DOMAIN_URL}/sitemap.xml` };
5+
}

examples/demo/src/app/sitemap.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { SITE_SLUGS, DOMAIN_URL } from '../app/config/siteConfig';
2+
import type { MetadataRoute } from 'next';
3+
4+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
5+
const flatSlugs = Object.values(SITE_SLUGS).flatMap((val) => (typeof val === 'string' ? [val] : Object.values(val)));
6+
7+
// Filter out routes that contain hash symbols (scroll links)
8+
const allRoutes = flatSlugs.filter((route) => typeof route === 'string' && !route.includes('#'));
9+
10+
return allRoutes.map((url) => ({
11+
url: DOMAIN_URL + url,
12+
lastModified: new Date().toISOString(),
13+
priority: url === '/' ? 1.0 : 0.8,
14+
changeFrequency: url === '/' ? 'daily' : 'weekly',
15+
}));
16+
}

packages/core/src/index.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import { cssVar, makeSetter } from './internal.js';
44

55
type UIAction<T extends string> = T | ((prev: T) => T);
66

7+
type ScopedRef = RefObject<HTMLElement | null> | (((node: HTMLElement | null) => void) & { current: HTMLElement | null });
8+
79
interface ScopedSetterFn<T extends string = string> {
810
(action: UIAction<T>): void; // ← SINGLE source of truth
9-
ref?: RefObject<any> | ((node: HTMLElement | null) => void);
11+
ref?: ScopedRef;
1012
cssVar?: typeof cssVar;
1113
}
1214

@@ -28,23 +30,27 @@ function useScopedUI<T extends string = string>(key: string, initialValue: T, fl
2830
// Attach the ref to the setter function so users can write: <div ref={setterFn.ref} />
2931
const refAttachCount = useRef(0);
3032
// DEV: Wrap scopeRef to detect multiple attachments
31-
(setterFn as ScopedSetterFn<T>).ref = (node: HTMLElement | null) => {
33+
const attachRef = ((node: HTMLElement | null) => {
3234
if (node) {
3335
refAttachCount!.current++;
3436
if (refAttachCount!.current > 1) {
3537
// TODO add documentation link
3638
throw new Error(
3739
`[useUI] Multiple ref attachments detected for key "${key}". ` +
3840
`Each useScopedUI hook supports only one ref attachment per component. ` +
39-
`Solution: Create separate component. and reuse.\n`
41+
`Solution: Create separate component. and reuse.\n` +
42+
`React Strict Mode May Cause the Ref to be attached multiple times.`
4043
);
4144
}
4245
} else {
4346
// Handle cleanup when ref is detached
4447
refAttachCount!.current = Math.max(0, refAttachCount!.current - 1);
4548
}
4649
scopeRef.current = node;
47-
};
50+
attachRef.current = node;
51+
}) as ((node: HTMLElement | null) => void) & { current: HTMLElement | null };
52+
attachRef.current = null;
53+
(setterFn as ScopedSetterFn<T>).ref = attachRef;
4854
} else {
4955
// PROD: Direct ref assignment for zero overhead
5056
setterFn.ref = scopeRef;

0 commit comments

Comments
 (0)