diff --git a/docs/CONTRIBUTING.md b/CONTRIBUTING.md similarity index 73% rename from docs/CONTRIBUTING.md rename to CONTRIBUTING.md index b3f2638..f7fc3f6 100644 --- a/docs/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,8 +25,12 @@ Stay **pre-rendered, declarative, and brutally fast.** packages/ ├── core -> @react-zero-ui/core (library logic + postcss) └── cli -> create-zero-ui (npx installer) + docs/ -> Next.js + Fumadocs docs site (apps/docs replacement) + examples/demo/ -> Live performance + sprite showcase app ``` +For the variant extractor / PostCSS pipeline internals (AST parsing, literal resolution, token scanning), see [packages/core/ARCHITECTURE.md](packages/core/ARCHITECTURE.md). + --- ## Local Setup @@ -85,6 +89,26 @@ pnpm test # Runs all of the above --- +## Bundle Size + +This repo measures bundle size from the built `@react-zero-ui/core` entry. + +```bash +pnpm build +pnpm size # prints the gzipped byte count +pnpm size:badge # refreshes the README badge JSON +``` + +The `size` script runs: + +```bash +npx esbuild ./packages/core/dist/index.js --bundle --minify --format=esm --external:react --define:process.env.NODE_ENV='"production"' | gzip -c | wc -c +``` + +`size:badge` writes [`.github/badges/core-size.json`](.github/badges/core-size.json), which the README badge reads through a Shields endpoint. Run it after a build when you want to refresh the number. + +--- + ## Code of Conduct Keep it respectful and accessible. Push ideas hard, not people. diff --git a/docs/api-reference.md b/docs/api-reference.md deleted file mode 100644 index cf0c6a1..0000000 --- a/docs/api-reference.md +++ /dev/null @@ -1,421 +0,0 @@ -
- -# API Reference - -**Complete API documentation for React Zero-UI** - -Detailed reference for all hooks, utilities, and configuration options. - -
- ---- - -## Table of Contents - -- [Core Hooks](#core-hooks) -- [Utilities](#utilities) -- [Experimental APIs](#experimental-apis) -- [TypeScript Types](#typescript-types) -- [Limitations & Constraints](#limitations--constraints) -- [Generated Files](#generated-files) -- [Debugging](#debugging) - ---- - -## Core Hooks - -### `useUI(key, initial, flag?)` - -Global UI state hook that updates `data-*` attributes on ``. - -```tsx -const [staleValue, setter] = useUI(key, initial, flag?); -``` - -#### Parameters - -| Parameter | Type | Description | -| --------- | --------------- | ------------------------------------------------------ | -| `key` | `string` | The state key (becomes `data-{key}` attribute) | -| `initial` | `T` | Initial/default value for SSR | -| `flag?` | `typeof CssVar` | Optional: Use CSS variables instead of data attributes | - -#### Returns - -| Return | Type | Description | -| ------------ | ------------------- | ------------------------------------------------ | -| `staleValue` | `T` | Initial value (doesn't update, use for SSR only) | -| `setter` | `GlobalSetterFn` | Function to update the global state | - -#### Examples - -```tsx -// Basic usage -const [theme, setTheme] = useUI("theme", "light"); -setTheme("dark"); // Sets data-theme="dark" on - -// With TypeScript generics -const [status, setStatus] = useUI<"loading" | "success" | "error">("status", "loading"); - -// With CSS variables -const [color, setColor] = useUI("primary", "#blue", CssVar); -setColor("#red"); // Sets --primary: #red on - -// Functional updates -setTheme((prev) => (prev === "light" ? "dark" : "light")); -``` - ---- - -### `useScopedUI(key, initial, flag?)` - -Scoped UI state hook that updates `data-*` attributes on a specific element. - -```tsx -const [staleValue, setter] = useScopedUI(key, initial, flag?); -``` - -#### Parameters - -Same as `useUI`, but affects only the element assigned to `setter.ref`. - -#### Returns - -| Return | Type | Description | -| ------------ | ------------------- | ------------------------------------------------ | -| `staleValue` | `T` | Initial value (doesn't update, use for SSR only) | -| `setter` | `ScopedSetterFn` | Function with attached `ref` property | - -#### Examples - -```tsx -// Basic scoped usage -const [modal, setModal] = useScopedUI("modal", "closed"); - -
- Modal content -
; - -// With CSS variables -const [blur, setBlur] = useScopedUI("blur", "0px", CssVar); - -
- Blurred content -
; -``` - ---- - -## Utilities - -### `CssVar` - -Flag to enable CSS variable mode instead of data attributes. - -```tsx -import { CssVar } from "@react-zero-ui/core"; - -// Global CSS variable -const [color, setColor] = useUI("primary", "#blue", CssVar); -// Result: - -// Scoped CSS variable -const [size, setSize] = useScopedUI("font-size", "16px", CssVar); -// Result:
-``` - ---- - -## Experimental APIs - -### `zeroSSR.onClick(key, values)` - -Creates click handlers for server components (experimental). - -```tsx -import { zeroSSR } from "@react-zero-ui/core/experimental"; - -const clickHandler = zeroSSR.onClick(key, values); -``` - -#### Parameters - -| Parameter | Type | Description | -| --------- | ---------- | -------------------------------- | -| `key` | `string` | State key (kebab-case required) | -| `values` | `string[]` | Array of values to cycle through | - -#### Returns - -Object with `data-ui` attribute for JSX spread. - -#### Examples - -```tsx -// Global state toggle - - -// Multi-value cycling - -``` - ---- - -### `scopedZeroSSR.onClick(key, values)` - -Creates scoped click handlers for server components. - -```tsx -import { scopedZeroSSR } from "@react-zero-ui/core/experimental"; - -const clickHandler = scopedZeroSSR.onClick(key, values); -``` - -#### Usage - -Same as `zeroSSR.onClick`, but affects the closest ancestor with `data-{key}` attribute. - -```tsx -
- - -
Modal content
-
-``` - ---- - -### `activateZeroUiRuntime(variantMap)` - -Activates the SSR runtime for click handling (experimental). - -```tsx -import { activateZeroUiRuntime } from "@react-zero-ui/core/experimental/runtime"; -import { variantKeyMap } from "./.zero-ui/attributes"; - -activateZeroUiRuntime(variantKeyMap); -``` - -#### Parameters - -| Parameter | Type | Description | -| ------------ | -------------------------- | -------------------------------------------- | -| `variantMap` | `Record` | Generated variant mapping from build process | - -#### Setup - -```tsx -// src/components/InitZeroUI.tsx -"use client"; - -import { variantKeyMap } from "../.zero-ui/attributes"; -import { activateZeroUiRuntime } from "@react-zero-ui/core/experimental/runtime"; - -activateZeroUiRuntime(variantKeyMap); - -export const InitZeroUI = () => null; -``` - -```tsx -// app/layout.tsx -import { InitZeroUI } from "../components/InitZeroUI"; - -export default function RootLayout({ children }) { - return ( - - - - {children} - - - ); -} -``` - ---- - -## TypeScript Types - -### `UIAction` - -Union type for state update actions. - -```tsx -type UIAction = T | ((prev: T) => T); -``` - -### `GlobalSetterFn` - -Function type for global state setters. - -```tsx -type GlobalSetterFn = (action: UIAction) => void; -``` - -### `ScopedSetterFn` - -Function type for scoped state setters with attached ref. - -```tsx -interface ScopedSetterFn { - (action: UIAction): void; - ref?: RefObject | ((node: HTMLElement | null) => void); - cssVar?: typeof cssVar; -} -``` - ---- - -## Limitations & Constraints - -### State Key Requirements - -- DO NOT USE IMPORTED VARIABLES IN THE STATE KEY -- Must be valid HTML attribute names -- Kebab-case required: `'sidebar-state'` not `'sidebarState'` -- Avoid conflicts with existing data attributes -- Must resolve to a non-spaced string: `'sidebar-state'` not `'sidebar State'` -- Must be a local constant that resolves to a string: - -### Scoped UI Constraints - -- Each `useScopedUI` hook supports only one ref attachment -- Multiple refs will throw development-time errors -- Use separate hooks or components for multiple scoped elements - -### CSS Variable Naming - -- Automatically prefixed with `--` -- Must be valid CSS custom property names -- Example: `'primary-color'` becomes `'--primary-color'` - -### SSR Considerations - -- Initial values must be deterministic for SSR -- Avoid dynamic initial values that differ between server/client -- Use `useEffect` for client-only state initialization - ---- - -## Generated Files - -### `.zero-ui/attributes.ts` - -Generated variant mapping for runtime activation. - -```ts -/* AUTO-GENERATED - DO NOT EDIT */ -export const bodyAttributes = { - "data-theme": "light", - "data-accent": "violet", - "data-scrolled": "up", - // ... -}; -``` - -### `.zero-ui/styles.css` (Vite) - -Generated CSS variants for Vite projects. - -```css -/* Auto-generated Tailwind variants */ -[data-theme="dark"] .theme-dark\:bg-gray-900 { - background-color: rgb(17 24 39); -} -/* ... */ -``` - ---- - -## Debugging - -### Enable Debug Mode - -```js -// postcss.config.js -module.exports = { - plugins: { - "@react-zero-ui/core/postcss": { - debug: true, // Enables verbose logging - }, - tailwindcss: {}, - }, -}; -``` - -### Check Generated Output - -```bash -# View generated variants -ls -la .zero-ui/ -cat .zero-ui/attributes.ts -``` - -### Browser DevTools - -1. **Elements tab:** Check for `data-*` attributes on target elements -2. **Computed styles:** Verify CSS rules are applying correctly -3. **Console:** Look for Zero-UI runtime messages (in debug mode) - ---- - -## Best Practices - -### State Key Naming - -```tsx -// Good: Descriptive and clear -useUI("theme", "light"); -useUI("sidebar-state", "collapsed"); -useUI("modal-visibility", "hidden"); - -// Avoid: Generic or unclear -useUI("state", "on"); -useUI("x", "y"); -useUI("toggle", "true"); -``` - -### TypeScript Usage - -```tsx -// Use specific union types -type Theme = "light" | "dark" | "auto"; -const [, setTheme] = useUI("theme", "light"); - -// For complex states, define types -type ModalState = "closed" | "opening" | "open" | "closing"; -const [, setModal] = useUI("modal", "closed"); -``` - -### Performance Optimization - -```tsx -// Use CSS for conditional rendering -
Modal content
; - -// Avoid reading stale values for logic -const [modal, setModal] = useUI("modal", "closed"); -{ - modal === "open" && ; -} // Won't work as expected -``` - ---- - -
- -### Need More Help? - -[**Usage Examples**](./usage-examples.md) | [**Troubleshooting**](./troubleshooting.md) | [**Migration Guide**](./migration-guide.md) - -
diff --git a/docs/demo.md b/docs/demo.md deleted file mode 100644 index bdd0ffd..0000000 --- a/docs/demo.md +++ /dev/null @@ -1,65 +0,0 @@ -# Demo + Benchmarks - -
- -**See Zero-UI in action** with interactive demos and performance comparisons. - -Experience the difference between React re-renders and Zero-UI's instant updates. - -[**Live Demo**](https://zero-ui.dev/) | [**React Version**](https://zero-ui.dev/react) | [**Zero-UI Version**](https://zero-ui.dev/zero-ui) - -
- ---- - -## Interactive Examples - -| Demo | Description | Live Link | Source Code | -| --------------------- | ------------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------- | -| **Interactive Menu** | Side-by-side comparison with render tracker | [Main Demo](https://zero-ui.dev/) | [GitHub](https://zero-ui.dev/react) | -| **React Benchmark** | Traditional React render path (10k nodes) | [React 10k](https://zero-ui.dev/react) | [GitHub](https://github.com/react-zero-ui/core/tree/main/examples/demo/src/app/react) | -| **Zero-UI Benchmark** | Identical DOM with `data-*` switching (10k nodes) | [Zero-UI 10k](https://zero-ui.dev/zero-ui) | [GitHub](https://github.com/react-zero-ui/core/tree/main/examples/demo/src/app/zero-ui) | - -> **Full Demo Source:** [Zero Rerender Demo](/examples/demo/) - ---- - -## Why Zero-UI? - -Every `setState` in React triggers the full **VDOM -> Diff -> Reconciliation -> Paint** pipeline. For _pure UI state_ (themes, menus, toggles) **that work is wasted**. - -### Zero-UI's "PRE-rendering" Approach: - -1. **Build-time:** Tailwind variants generated for every state -2. **Pre-render:** App renders once with all possible states -3. **Runtime:** State changes only flip a `data-*` attribute - -**Result:** **5-10× faster visual updates** with **ZERO additional bundle cost**. - ---- - -## Performance Benchmarks - -
- -_Tested on Apple M1 - Chrome DevTools Performance Tab_ - -
- -| **Nodes Updated** | **React State** | **Zero-UI** | **Speed Improvement** | -| :---------------: | :-------------: | :---------: | :-------------------: | -| 10,000 | ~50 ms | ~5 ms | **10× faster** | -| 25,000 | ~180 ms | ~15 ms | **12× faster** | -| 50,000 | ~300 ms | ~20 ms | **15× faster** | - -> **Try it yourself:** Re-run these benchmarks using the demo links above with Chrome DevTools. - ---- - -
- -### Ready to get started? - -[**Get Started**](https://github.com/react-zero-ui/core/#quick-start) and never re-render again. - -
diff --git a/docs/experimental.md b/docs/experimental.md deleted file mode 100644 index e720537..0000000 --- a/docs/experimental.md +++ /dev/null @@ -1,197 +0,0 @@ -
-

Experimental Runtime (Zero-UI)

- -**SSR-safe runtime logic** for handling interactivity in React server components without using - -```diff -- use client -``` - -
-
-Designed to be tiny(~300 bytes), deterministic, and fully compatible with - -React Zero-UI's pre-rendered data-attribute model. - -
- ---- - -### Why This Approach? - -**The Problem:** A single `onClick` event forces your entire component tree to become client-rendered. In Next.js, this means shipping extra JavaScript, losing SSR benefits, and adding hydration overhead, all for basic interactivity. - -**The Solution:** This design creates the perfect bridge between **static HTML** and **interactive UX**, while maintaining: - -- Server-rendered performance -- Zero JavaScript bundle overhead -- Instant visual feedback - -_Why sacrifice server-side rendering for a simple click handler when 300 bytes of runtime can handle all the clicks in your app?_ - ---- - -## Core Functionality - -### `activateZeroUiRuntime()` - -The core runtime entrypoint that enables client-side interactivity in server components: - -**How it works:** - -1. **Single Global Listener** - Registers one click event listener on `document` -2. **Smart Detection** - Listens for clicks on elements with `data-ui` attributes -3. **Directive Parsing** - Interprets `data-ui` directives in this format. - -```diff -+ data-ui="global:key(val1,val2,...)" -> flips data-key on document.body -+ data-ui="scoped:key(val1,val2,...)" -> flips data-key on closest ancestor/self -``` - -4. **Round-Robin Cycling** - Cycles through values in sequence -5. **Instant DOM Updates** - Updates DOM immediately for Tailwind responsiveness - -> **Note:** Guards against duplicate initialization using `window.__zero` flag. - ---- - -## Helper Functions - -### `zeroSSR.onClick()` & `scopedZeroSSR.onClick()` - -Utility functions that generate valid `data-ui` attributes for JSX/TSX: - -**Global Example:** - -```tsx -zeroSSR.onClick("theme", ["dark", "light"]); -// Returns: { 'data-ui': 'global:theme(dark,light)' } -``` - -**Scoped Example:** - -```tsx -scopedZeroSSR.onClick("modal", ["open", "closed"]); -// Returns: { 'data-ui': 'scoped:modal(open,closed)' } -``` - -**Development Validation:** - -- Ensures keys are kebab-case -- Validates at least one value is provided - ---- - -## Installation & Setup - -### Step 1: Install Package - -```bash -npm install @react-zero-ui/core@0.3.1-beta.2 -``` - -### Step 2: Generate Variants - -Run your development server to generate the required variant map: - -```bash -npm run dev -``` - -This creates `.zero-ui/attributes.ts` containing the variant map needed for runtime activation. - -### Step 3: Create `` Component - -```tsx -"use client"; - -import { variantKeyMap } from "path/to/.zero-ui/attributes"; -import { activateZeroUiRuntime } from "@react-zero-ui/core/experimental/runtime"; - -activateZeroUiRuntime(variantKeyMap); - -export const InitZeroUI = () => null; -``` - -### Step 4: Add to Root Layout - -```tsx -import { InitZeroUI } from "path/to/InitZeroUI"; - -export default function RootLayout({ children }) { - return ( - - - - {children} - - - ); -} -``` - ---- - -## Usage Examples - -### Global Theme Toggle - -```tsx -import { zeroSSR } from "@react-zero-ui/core/experimental"; - -
Click me to cycle themes!
; -``` - -**Pair with Tailwind variants:** - -```html -
Interactive Server Component!
-``` - -### Scoped Modal Toggle - -```tsx -import { scopedZeroSSR } from "@react-zero-ui/core/experimental"; - -// Scopes based on matching data-* attribute (e.g. data-modal) -
- -
; -``` - ---- - -## Design Philosophy - -### Core Principles - -- **No React State** - Zero re-renders involved -- **Pure DOM Mutations** - Works entirely via `data-*` attribute changes -- **Server Component Compatible** - Full compatibility with all server components -- **Tailwind-First** - Designed for conditional CSS classes - ---- - -## Summary - -| Feature | Description | -| ------------------------------- | --------------------------------------------------------- | -| **`activateZeroUiRuntime()`** | Enables click handling on static components via `data-ui` | -| **`zeroSSR` / `scopedZeroSSR`** | Generate valid click handlers as JSX props | -| **Runtime Overhead** | ~300 bytes total | -| **React Re-renders** | Zero | -| **Server Component Support** | Full compatibility | - -> **Source Code:** See [experimental](/packages/core/src/experimental) for implementation details. - ---- - -
- -**The bridge between static HTML and interactive UX** - -_No state. No runtime overhead. Works in server components. ZERO re-renders._ - -[**Get Started in less than 5 minutes**](/#quick-start) - -
diff --git a/docs/faq.md b/docs/faq.md deleted file mode 100644 index a87c703..0000000 --- a/docs/faq.md +++ /dev/null @@ -1,374 +0,0 @@ -
- -# Frequently Asked Questions - -**Common questions and answers about React Zero-UI** - -Everything you need to know to get the most out of Zero-UI. - -
- ---- - -## General Questions - -### What exactly is React Zero-UI? - -React Zero-UI is a state management library that eliminates React re-renders for UI state by using CSS and data attributes instead of component state. It "pre-renders" all possible UI states at **build time** and switches between them by flipping data attributes. - -### How is this different from regular React state? - -**Traditional React:** - -```tsx -const [theme, setTheme] = useState("light"); -// Every setState() triggers re-render of component tree -``` - -**React Zero-UI:** - -```tsx -const [, setTheme] = useUI("theme", "light"); -// No re-renders, just flips data-theme="dark" on -``` - -### Do I need to learn anything new? - -Not really! If you know React hooks and Tailwind CSS, you already know 95% of what you need: - -1. Replace `useState` with `useUI` -2. Replace conditional classes with Tailwind variants -3. Everything else works the same - ---- - -## Performance Questions - -### How much faster is it really? - -**Benchmarks** (10,000 DOM nodes): - -- React state changes: ~50ms -- Zero-UI state changes: ~5ms -- **Result: 10× faster updates** - -The more complex your UI, the bigger the performance gain. - -### Does it increase my bundle size? - -**Zero-UI core: ~350 bytes** in production (10x smaller than a single svg icon) - -Compare that to: - -- Redux: ~5KB -- SVG Icon: ~4KB - So about 10x smaller than a single SVG icon - -### What about CSS file size? - -CSS variants are only generated for the states you actually use. If you use 3 theme values, you get CSS for 3 variants—not hundreds. - ---- - -Here’s a tighter, sharper version with your key points, a nod to Prepack’s failure, and a forward-looking note about Turbopack: - ---- - -## Technical Questions - -### Why can't I use imported variables in the state key? - -Zero-UI uses a custom Babel-based resolver that only analyzes **top-level `const` values in the same file**. Imported variables are **not supported**, even if re-assigned to a local `const`. - -Why? Because resolving cross-file imports requires a **full module graph**, accounting for: - -- ESM vs CJS interop -- TypeScript vs JavaScript -- Re-exports, namespace imports, aliased paths, dynamic values -- Recursive constant folding across files - -This problem is **deceptively deep** — even Facebook's [Prepack](https://prepack.io/) abandoned the attempt after years of effort. - -Until a future plugin system for **Turbopack** emerges, we've chosen the simpler, safer route: -**Only local `const` string literals are supported.** - -We may ship a resolver plugin for Turbopack once it's open to third-party hooks. - ---- - -Want it even shorter or more opinionated? - -### Can I use it with existing state management? - -Absolutely! Zero-UI is designed for **UI state only**. Use it alongside: - -- Redux/Zustand for business logic -- React Query for server state -- Regular `useState` for component-specific data - -```tsx -// Great combination -const [data, setData] = useState(null); // Component state -const { user } = useQuery("user"); // Server state -const [, setTheme] = useUI("theme", "light"); // UI state -``` - -### Does it work with SSR/Next.js? - -Yes! Zero-UI is designed with SSR in mind: - -- No hydration mismatches -- Perfect FOUC prevention -- Works with Next.js App Router -- Experimental server component support see [experimental](./experimental.md) - -### Can I persist state across page reloads? - -Zero-UI state is DOM-based, so it doesn't persist automatically. For persistence you handle it the same as you would with regular React state: - -```tsx -// Save to localStorage -const [, setTheme] = useUI("theme", "light"); - -const persistentSetTheme = (value) => { - setTheme(value); - localStorage.setItem("theme", value); -}; - -// Load on mount -useEffect(() => { - const saved = localStorage.getItem("theme"); - if (saved) setTheme(saved); -}, []); -``` - ---- - -## Styling Questions - -### Do I have to use Tailwind? - -**For the best experience, yes.** Zero-UI generates Tailwind variants automatically. - -and tailwind variants are generated by tailwindCSS. - -```css -[data-theme="dark"] { - background: black; -} -``` - -### Can I use CSS variables? - -Yes! Pass the `CssVar` flag: - -```tsx -import { useUI, CssVar } from "@react-zero-ui/core"; - -const [, setColor] = useUI("primary", "#blue", CssVar); -// Result: -``` - ---- - -## Migration Questions - -### How hard is it to migrate from useState? - -**Very easy** for UI state: - -```tsx -// Before -const [theme, setTheme] = useState("light"); - -// After -const [, setTheme] = useUI("theme", "light"); -// Note: Don't use the first return value for logic -``` - -### What about Context API? - -If context is used for UI state, it's even easier! Just remove the provider: - -```tsx -// Before: Need provider, useContext, prop drilling - - -; - -// After: State works everywhere automatically -function App() { - const [, setTheme] = useUI("theme", "light"); - // Theme accessible anywhere via CSS classes -} -``` - -### Should I migrate everything at once? - -**No!** Migrate incrementally: - -1. Start with global UI state (theme, modals) -2. Move component-specific UI state -3. Leave business logic in existing solutions - ---- - -## Experimental Features - -### What's the experimental SSR runtime? - -It allows interactivity in **server components** without `'use client'` see [experimental](./experimental.md): - -```tsx -// This is a SERVER COMPONENT! -import { zeroSSR } from "@react-zero-ui/core/experimental"; - -function ServerThemeToggle() { - return ; -} -``` - -Only ~300 bytes of runtime for unlimited server component interactivity. - -### Is the experimental API stable? - -It's **experimental** but used in production by early adopters. The API may change in minor versions, but we'll provide migration guides. - -### Should I use it in production? - -I would not recommend using it in production until the API is stable. Because the API is still in development and the API may change in minor versions, but we'll provide migration guides. - ---- - -## Troubleshooting - -### My Tailwind variants aren't working - -**Check these in order:** - -1. **PostCSS plugin configured? (Next.js)** - - ```js - // postcss.config.js - module.exports = { - plugins: { - "@react-zero-ui/core/postcss": {}, // Before Tailwind! - tailwindcss: {}, - }, - }; - ``` - -2. **Tailwind V4 Configured and imported?** - ```css - @import "tailwindcss"; - ``` - -### I get "Multiple ref attachments" error - -Each `useScopedUI` can only attach to one element: - -```tsx -// Wrong -const [, setState] = useScopedUI('state', 'default'); -
-
// Error! - -// Right -const [, setState1] = useScopedUI('state-1', 'default'); -const [, setState2] = useScopedUI('state-2', 'default'); -
-
-``` - ---- - -## Best Practices - -### When should I use Zero-UI? - -**Perfect for:** - -- Theme switching -- Modal/drawer states -- Navigation states -- UI toggles and animations -- Any visual state that doesn't affect business logic - -**Not ideal for:** - -- Form data -- API responses -- Complex business logic -- State that needs to trigger side effects - -### Should I use global or scoped state? - -**Global (`useUI`)** for: - -- App-wide state (theme, language) -- State that affects multiple components -- State you want accessible everywhere - -**Scoped (`useScopedUI`)** for: - -- Component-specific state -- State that doesn't affect other components -- Better performance for isolated changes - ---- - -## Future & Roadmap - -### What's coming next? - -- Enhanced TypeScript support -- More framework integrations (Vue, Svelte) -- Better DevTools integration -- Performance monitoring tools - -### How can I influence the roadmap? - -- Vote on feature requests in GitHub Discussions -- Report bugs and use cases -- Share your usage patterns -- Contribute code or documentation - ---- - -## Getting Help - -### Where can I ask questions? - -1. **Documentation:** Check the guides first -2. **Discussions:** [GitHub Discussions](https://github.com/react-zero-ui/core/discussions) for questions -3. **Issues:** [GitHub Issues](https://github.com/react-zero-ui/core/issues) for bugs - -### How do I report a bug? - -1. Check existing issues first -2. Create a minimal reproduction -3. Include your configuration (postcss.config.js, etc.) -4. Include browser and framework versions - -### Can I contribute? - -**Absolutely!** We welcome: - -- Documentation improvements -- Bug fixes -- Feature implementations -- Examples and demos - -See our [Contributing Guide](./CONTRIBUTING.md) to get started. - ---- - -
- -### Still have questions? - -[**Ask in Discussions**](https://github.com/react-zero-ui/core/discussions) | [**Read the Docs**](../README.md) | [**Try the Demo**](https://zero-ui.dev) - -The community is here to help! - -
diff --git a/docs/installation-next.md b/docs/installation-next.md deleted file mode 100644 index 197a9b2..0000000 --- a/docs/installation-next.md +++ /dev/null @@ -1,76 +0,0 @@ -### Next.js (App Router) Setup - -1. **Install the dependencies** - -```bash -npm install @react-zero-ui/core -``` - -```bash -npm install @tailwindcss/postcss -``` - ---- - -2. **Add the PostCSS plugin (must come _before_ Tailwind).** - -```js -// postcss.config.* ESM Syntax -const config = { - // Zero-UI must come before Tailwind - plugins: ["@react-zero-ui/core/postcss", "@tailwindcss/postcss"], -}; -export default config; -``` - -```js -// postcss.config.* Common Module Syntax -module.exports = { - // Zero-UI must come before Tailwind - plugins: { "@react-zero-ui/core/postcss": {}, tailwindcss: {} }, -}; -``` - -3. **Import Tailwind CSS** - -```css -// global.css -@import "tailwindcss"; -``` - ---- - -4. **Start the App** - -```bash -npm run dev -``` - -> Zero-UI will generate a .zero-ui folder in your project root. and generate the attributes.ts and type definitions for it. - ---- - -5. **Preventing FOUC (Flash Of Unstyled Content)** - -Spread `bodyAttributes` on `` in your root layout. - -```tsx -// app/layout.tsx -import { bodyAttributes } from "./.zero-ui/attributes"; - -export default function RootLayout({ children }) { - return ( - - // Spread the bodyAttributes on the body tag - {children} - - ); -} -``` - -**Thats it.** -Zero-UI will now add used data-\* attributes to the body tag and the CSS will be injected and transformed by tailwind. - -**Checkout our Experimental SSR Safe OnClick Handler** - -[**Zero UI OnClick**](/docs/experimental.md) diff --git a/docs/installation-vite.md b/docs/installation-vite.md deleted file mode 100644 index dffedf6..0000000 --- a/docs/installation-vite.md +++ /dev/null @@ -1,45 +0,0 @@ -### Vite Setup - -1. **Install the dependencies** - -```bash -npm install @react-zero-ui/core -``` - -```bash -npm install @tailwindcss/postcss -``` - ---- - -## Setup - -### Vite - -2. **Add the plugin to your vite.config.ts** - -```js -// vite.config.* -import zeroUI from "@react-zero-ui/core/vite"; -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; -import tailwindCss from "@tailwindcss/postcss"; - -export default defineConfig({ - // Remove the default `tailwindcss()` plugin - and pass it into the `zeroUI` plugin - plugins: [zeroUI({ tailwind: tailwindCss }), react()], -}); -``` - -3. **Import Tailwind CSS** - -```css -// global.css -@import "tailwindcss"; -``` - -**Thats it.** - -The plugin will add the data-\* attributes to the body tag (no FOUC) and the CSS will be injected and transformed by tailwind. - - diff --git a/docs/migration-guide.md b/docs/migration-guide.md deleted file mode 100644 index 5003459..0000000 --- a/docs/migration-guide.md +++ /dev/null @@ -1,443 +0,0 @@ -# Migration Guide - -
- -**Migrate to React Zero-UI from existing state management solutions** - -Step-by-step guides for common migration scenarios. - -
- ---- - -## From React useState - -### Basic State Migration - -**Before (React useState):** - -```tsx -import { useState } from "react"; - -function ThemeToggle() { - const [theme, setTheme] = useState("light"); - - return ( -
- -
- ); -} -``` - -**After (React Zero-UI):** - -```tsx -import { useUI } from "@react-zero-ui/core"; - -function ThemeToggle() { - const [, setTheme] = useUI("theme", "light"); - - return ( -
- -
- ); -} -``` - -**Key Changes:** - -1. Replace `useState` with `useUI` -2. Replace conditional classNames with Tailwind variants -3. State key becomes a data attribute (`data-theme`) -4. Use functional updates for state transitions - -### Modal State Migration - -**Before:** - -```tsx -function App() { - const [isModalOpen, setIsModalOpen] = useState(false); - - return ( - <> - - - {isModalOpen && ( -
-
- -
-
- )} - - ); -} -``` - -**After:** - -```tsx -function App() { - const [, setModal] = useUI("modal", "closed"); - - return ( - <> - - -
-
- -
-
- - ); -} -``` - ---- - -## From Context API - -### Global Theme Context - -**Before (Context API):** - -```tsx -const ThemeContext = createContext(); - -function ThemeProvider({ children }) { - const [theme, setTheme] = useState("light"); - - return ( - -
{children}
-
- ); -} - -function ThemeToggle() { - const { theme, setTheme } = useContext(ThemeContext); - - return ; -} -``` - -**After (React Zero-UI):** - -```tsx -// No provider needed! - -function App({ children }) { - return
{children}
; -} - -function ThemeToggle() { - const [, setTheme] = useUI("theme", "light"); - - return ( - - ); -} -``` - -**Benefits:** - -- No more context providers -- No more prop drilling -- No re-renders when state changes -- State accessible anywhere via Tailwind classes - ---- - -## From Redux/Zustand - -### Redux Theme Slice - -**Before (Redux):** - -```tsx -// store/themeSlice.ts -const themeSlice = createSlice({ - name: "theme", - initialState: { value: "light" }, - reducers: { - toggleTheme: (state) => { - state.value = state.value === "light" ? "dark" : "light"; - }, - }, -}); - -// Component -function ThemeToggle() { - const theme = useSelector((state) => state.theme.value); - const dispatch = useDispatch(); - - return ( -
- -
- ); -} -``` - -**After (React Zero-UI):** - -```tsx -// No store setup needed! - -function ThemeToggle() { - const [, setTheme] = useUI("theme", "light"); - - return ( -
- -
- ); -} -``` - -### Zustand Store - -**Before (Zustand):** - -```tsx -const useThemeStore = create((set) => ({ theme: "light", toggleTheme: () => set((state) => ({ theme: state.theme === "light" ? "dark" : "light" })) })); - -function ThemeToggle() { - const { theme, toggleTheme } = useThemeStore(); - - return ( -
- -
- ); -} -``` - -**After (React Zero-UI):** - -```tsx -function ThemeToggle() { - const [, setTheme] = useUI("theme", "light"); - - return ( -
- -
- ); -} -``` - ---- - -## From CSS-in-JS Solutions - -### Styled Components with Theme - -**Before (Styled Components):** - -```tsx -const ThemeProvider = styled.div` - background: ${(props) => props.theme.bg}; - color: ${(props) => props.theme.text}; -`; - -const theme = { light: { bg: "white", text: "black" }, dark: { bg: "black", text: "white" } }; - -function App() { - const [currentTheme, setCurrentTheme] = useState("light"); - - return ( - - - - ); -} -``` - -**After (React Zero-UI + Tailwind):** - -```tsx -function App() { - const [, setTheme] = useUI("theme", "light"); - - return ( -
- -
- ); -} -``` - ---- - -## From Component State to Global State - -### Converting Local State to Global - -**Before (Local component state):** - -```tsx -function Sidebar() { - const [isOpen, setIsOpen] = useState(false); - - return ( -
- -
- ); -} - -function Header() { - // Can't access sidebar state! - return
Header content
; -} -``` - -**After (Global state, accessible everywhere):** - -```tsx -function Sidebar() { - const [, setSidebar] = useUI("sidebar", "closed"); - - return ( -
- -
- ); -} - -function Header() { - // Can respond to sidebar state! - return
Header content
; -} -``` - ---- - -## Migration Checklist - -### Step 1: Identify UI State - -- [ ] List all `useState` hooks that control UI appearance -- [ ] Identify global state (Context, Redux, Zustand) -- [ ] Find conditional className logic -- [ ] Note prop drilling for UI state - -### Step 2: Install and Configure - -```bash -npx create-zero-ui -``` - -Or manual setup: - -- [ ] Install `@react-zero-ui/core` -- [ ] Configure PostCSS plugin -- [ ] Update Tailwind config - -### Step 3: Convert State by State - -- [ ] Replace `useState` with `useUI` -- [ ] Convert conditional classes to Tailwind variants -- [ ] Remove context providers for UI state -- [ ] Update component dependencies - -### Step 4: Test and Verify - -- [ ] Verify all state changes work -- [ ] Check for any missing CSS variants -- [ ] Test SSR/hydration (no FOUC) -- [ ] Validate performance improvements - ---- - -## Migration Gotchas - -### 1. State Key Naming - -```tsx -// Avoid conflicts with existing data attributes -const [, setState] = useUI("id", "default"); // conflicts with data-id - -// Use descriptive, unique keys -const [, setState] = useUI("modal-state", "closed"); -``` - -### 2. Initial Value Consistency - -```tsx -// Ensure initial values match what CSS expects -const [, setTheme] = useUI("theme", "lite"); // typo! - -// Match your Tailwind variants exactly -const [, setTheme] = useUI("theme", "light"); // matches theme-light: -``` - ---- - -### 3. **No Imported State Keys (Yet)** - -Imported variables can't be resolved statically — even if reassigned locally. - -```ts -// This will fail at build time -import { THEME_KEY } from "./constants"; -const localKey = THEME_KEY; -const [, setTheme] = useUI(localKey, "dark"); -``` - -```ts -// Inline the string directly or re-declare as a top-level const -const THEME_KEY = "theme"; -const [, setTheme] = useUI(THEME_KEY, "dark"); -``` - -> We're working on support for imported bindings once Next.js exposes a plugin API for Turbopack. Until then, stick with top-level `const` literals. - -### 4. Don't Read Stale Values - -```tsx -// Don't use returned value for logic -const [theme, setTheme] = useUI("theme", "light"); -if (theme === "dark") { - /* Won't work! */ -} - -// Use CSS classes for visual state -
Only visible in light mode
; -``` - ---- - -## Before/After Comparison - -| Aspect | Before (Traditional) | After (React Zero-UI) | -| ----------------- | ----------------------------- | -------------------------- | -| **Bundle Size** | +5KB (Redux) / +2KB (Context) | +350 bytes | -| **Re-renders** | Every state change | Zero | -| **Performance** | Slower with scale | Constant fast | -| **Setup** | Complex (store, providers) | Simple (one hook) | -| **Global Access** | Prop drilling / Context | Tailwind variants anywhere | -| **SSR** | Hydration mismatches | Perfect SSR | - ---- - -
- -### Migration Complete! - -Your app should now be faster, simpler, and more maintainable. - -[**Next: Usage Examples**](./usage-examples.md) | [**API Reference**](../README.md#api-reference) - -
diff --git a/docs/rules.md b/docs/rules.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/size.md b/docs/size.md deleted file mode 100644 index ce11131..0000000 --- a/docs/size.md +++ /dev/null @@ -1,29 +0,0 @@ -# Bundle Size Proof - -This repo measures bundle size from the built `@react-zero-ui/core` entry. - -## Command - -```bash -pnpm build -pnpm size -pnpm size:badge -``` - -The `size` script in [`package.json`](../package.json) runs: - -```bash -npx esbuild ./packages/core/dist/index.js --bundle --minify --format=esm --external:react --define:process.env.NODE_ENV='"production"' | gzip -c | wc -c -``` - -That produces the gzipped byte count used for the README badge. - -## Badge File - -Run `pnpm size:badge` after a build when you want to refresh the badge JSON. - -That writes: - -- [`core-size.json`](../.github/badges/core-size.json) - -The README badge reads from that committed file through a Shields endpoint. diff --git a/docs/usage-examples.md b/docs/usage-examples.md deleted file mode 100644 index 82b5341..0000000 --- a/docs/usage-examples.md +++ /dev/null @@ -1,477 +0,0 @@ -# Usage Examples & Patterns - -
- -**Comprehensive examples and patterns for React Zero-UI** - -Learn through practical, real-world use cases and best practices. - -
- ---- - -## Basic Usage Patterns - -### 1. Theme Toggle (Global State) - -The most common pattern - global theme switching: - -```tsx -import { useUI } from "@react-zero-ui/core"; - -function ThemeToggle() { - const [theme, setTheme] = useUI("theme", "light"); - - return ( - - ); -} -``` - -**Tailwind usage anywhere in your app:** - -```html -
Content that responds to theme
-``` - -### 2. Modal State Management - -```tsx -import { useUI } from "@react-zero-ui/core"; - -function App() { - const [, setModal] = useUI("modal", "closed"); - - return ( - <> - - - {/* Modal backdrop */} -
-
-

Modal Content

- -
-
- - ); -} -``` - -### 3. Multi-State Navigation - -```tsx -import { useUI } from "@react-zero-ui/core"; - -type TabState = "home" | "about" | "contact"; - -function Navigation() { - const [activeTab, setActiveTab] = useUI("nav-tab", "home"); - - const tabs = [ - { id: "home", label: "Home" }, - { id: "about", label: "About" }, - { id: "contact", label: "Contact" }, - ] as const; - - return ( - - ); -} -``` - ---- - -## Scoped UI Patterns - -### 1. Component-Level State - -```tsx -import { useScopedUI } from "@react-zero-ui/core"; - -function Card() { - const [state, setState] = useScopedUI("card-state", "collapsed"); - - return ( -
- - -
-

This content only shows when expanded!

-
-
- ); -} -``` - -### 2. Form Field States - -```tsx -import { useScopedUI } from "@react-zero-ui/core"; - -function FormField({ label, ...props }) { - const [state, setState] = useScopedUI("field-state", "default"); - - return ( -
- - setState("focused")} - onBlur={() => setState("default")} - onChange={(e) => { - // Validate and set state accordingly - const isValid = e.target.value.length > 0; - setState(isValid ? "success" : "error"); - }} - /> -
This field is required
-
- ); -} -``` - ---- - -## CSS Variables Patterns - -### 1. Dynamic Styling with CSS Variables - -```tsx -import { useUI, CssVar } from "@react-zero-ui/core"; - -function DynamicTheme() { - const [, setPrimaryColor] = useUI("primary-color", "#3b82f6", CssVar); - const [, setBlur] = useUI("blur-amount", "0px", CssVar); - - const colors = ["#3b82f6", "#ef4444", "#10b981", "#f59e0b"]; - - return ( -
-
- {colors.map((color) => ( -
- - - - {/* Uses CSS variables */} -
- Dynamic styled content -
-
- ); -} -``` - -### 2. Scoped CSS Variables - -```tsx -import { useScopedUI, CssVar } from "@react-zero-ui/core"; - -function CustomSlider() { - const [value, setValue] = useScopedUI("slider-value", "50", CssVar); - - return ( -
- setValue(e.target.value)} - className="w-full" - /> - - {/* Progress indicator using CSS variable */} -
-
- ); -} -``` - ---- - -## SSR-Safe Patterns (Experimental) - -### 1. Server Component Interactivity - -```tsx -// This is a SERVER COMPONENT! No 'use client' needed -import { zeroSSR } from "@react-zero-ui/core/experimental"; - -function ServerThemeToggle() { - return ( - - ); -} -``` - -### 2. Scoped Server Component State - -```tsx -import { scopedZeroSSR } from "@react-zero-ui/core/experimental"; - -function ServerModal() { - return ( - // This data key will set the scope -
- - -
-
-

Server-Rendered Modal

- -
-
-
- ); -} -``` - ---- - -## Advanced Patterns - -### 1. State Composition - -```tsx -import { useUI } from "@react-zero-ui/core"; - -function Dashboard() { - const [, setSidebar] = useUI("sidebar", "collapsed"); - const [, setTheme] = useUI("theme", "light"); - const [, setNotifications] = useUI("notifications", "hidden"); - - return ( -
- {/* Multiple states working together */} -
- ); -} -``` - -### 2. Conditional Logic with Functional Updates - -```tsx -import { useUI } from "@react-zero-ui/core"; - -function SmartToggle() { - const [, setMode] = useUI("app-mode", "normal"); - - const handleModeChange = (condition: boolean) => { - setMode((prev) => { - if (condition && prev === "normal") return "advanced"; - if (!condition && prev === "advanced") return "normal"; - return prev; // No change - }); - }; - - return ; -} -``` - -### 3. Animation Sequences - -```tsx -import { useUI } from "@react-zero-ui/core"; - -function AnimatedCard() { - const [, setAnimation] = useUI("card-anim", "idle"); - - const playAnimation = async () => { - setAnimation("preparing"); - await new Promise((resolve) => setTimeout(resolve, 100)); - - setAnimation("animating"); - await new Promise((resolve) => setTimeout(resolve, 500)); - - setAnimation("complete"); - await new Promise((resolve) => setTimeout(resolve, 200)); - - setAnimation("idle"); - }; - - return ( -
- -
- ); -} -``` - ---- - -## Styling Best Practices - -### 1. Semantic State Names - -```tsx -// Good: Semantic and clear -const [, setModal] = useUI("modal", "closed"); -const [, setTheme] = useUI("theme", "light"); -const [, setNavigation] = useUI("nav-state", "collapsed"); - -// Avoid: Generic or unclear -const [, setState] = useUI("state", "on"); -const [, setThing] = useUI("x", "y"); -``` - -### 2. Consistent Naming Conventions - -```tsx -// Use kebab-case for multi-word keys -const [, setSidebarState] = useUI("sidebar-state", "collapsed"); -const [, setUserProfile] = useUI("user-profile", "hidden"); - -// Use clear value names -const [, setModal] = useUI("modal", "closed"); // closed/open -const [, setTheme] = useUI("theme", "light"); // light/dark/auto -``` - -### 3. Organize Complex States - -```tsx -// For complex UIs, group related states -function App() { - // Layout states - const [, setSidebar] = useUI("sidebar", "collapsed"); - const [, setHeader] = useUI("header", "visible"); - - // Theme states - const [, setColorScheme] = useUI("color-scheme", "light"); - const [, setAccentColor] = useUI("accent-color", "blue"); - - // UI states - const [, setModal] = useUI("modal", "closed"); - const [, setToast] = useUI("toast", "hidden"); -} -``` - ---- - -## Common Pitfalls - -### 1. Don't use Imported Variables in the state key/initial value - -```tsx -// Wrong: Imported variables are not allowed -import { THEME_KEY } from "./constants"; -const [, setTheme] = useUI(THEME_KEY, "dark"); -``` - -### 1. Don't read state values for logic - -```tsx -// Wrong: staleValue doesn't update -const [theme, setTheme] = useUI("theme", "light"); -if (theme === "dark") { - /* This won't work as expected */ -} - -// Correct: Use CSS classes instead -
Light mode content
; -``` - -### 2. Avoid over-engineering simple toggles - -```tsx -// Overcomplicated for simple boolean -const [, setState] = useUI("feature", "disabled"); -setState((prev) => (prev === "disabled" ? "enabled" : "disabled")); - -// Better: Use descriptive boolean-like values -const [, setFeature] = useUI("feature", "off"); -setState((prev) => (prev === "off" ? "on" : "off")); -``` - -### 3. Don't attach multiple refs to scoped UI - -```tsx -// Wrong: Multiple refs not supported -const [, setState] = useScopedUI("state", "default"); -return ( - <> -
{/* First ref */} -
{/* This will throw an error! */} - -); - -// Correct: Create separate components/hooks -function ComponentA() { - const [, setState] = useScopedUI("state-a", "default"); - return
; -} - -function ComponentB() { - const [, setState] = useScopedUI("state-b", "default"); - return
; -} -``` - ---- - -
- -### Ready to build? - -These patterns cover 95% of real-world use cases. Mix and match them to create powerful, performant UIs. - -[**View Live Demo**](https://zero-ui.dev) | [**API Reference**](../README.md#api-reference) - -
diff --git a/examples/demo/.gitignore b/examples/demo/.gitignore new file mode 100644 index 0000000..55a12ae --- /dev/null +++ b/examples/demo/.gitignore @@ -0,0 +1,28 @@ +# deps +/node_modules + +# generated content +.contentlayer +.content-collections +.source + +# test & build +/coverage +/.next/ +/out/ +/build +*.tsbuildinfo + +# misc +.DS_Store +*.pem +/.pnp +.pnp.js +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# others +.env*.local +.vercel +next-env.d.ts \ No newline at end of file diff --git a/examples/demo/.zero-ui/attributes.d.ts b/examples/demo/.zero-ui/attributes.d.ts index a2af8b3..a1476b5 100644 --- a/examples/demo/.zero-ui/attributes.d.ts +++ b/examples/demo/.zero-ui/attributes.d.ts @@ -1,15 +1,12 @@ /* AUTO-GENERATED - DO NOT EDIT */ export declare const bodyAttributes: { - "data-accent": "amber" | "emerald" | "violet"; - "data-active": "react" | "zero"; - "data-menu-open": "false" | "true"; - "data-mobile-menu": "closed" | "open"; - "data-scrolled": "down" | "up"; - "data-theme": "dark" | "light"; - "data-theme-test": "dark" | "light"; - "data-theme-test-ssr": "dark" | "light"; + "data-demo-search-status": "loading"; + "data-perf-accent": "amber" | "emerald" | "violet"; + "data-perf-active": "react" | "zero"; + "data-perf-menu-open": "false" | "true"; + "data-perf-theme": "dark" | "light"; }; export declare const variantKeyMap: { - [key: string]: true; + [key: string]: true | string[] | '*'; }; diff --git a/examples/demo/.zero-ui/attributes.js b/examples/demo/.zero-ui/attributes.js index cd87cdd..daa2e62 100644 --- a/examples/demo/.zero-ui/attributes.js +++ b/examples/demo/.zero-ui/attributes.js @@ -1,21 +1,15 @@ /* AUTO-GENERATED - DO NOT EDIT */ export const bodyAttributes = { - "data-accent": "violet", - "data-active": "zero", - "data-menu-open": "false", - "data-mobile-menu": "closed", - "data-scrolled": "up", - "data-theme": "light", - "data-theme-test": "light", - "data-theme-test-ssr": "dark" + "data-demo-search-status": "idle", + "data-perf-accent": "violet", + "data-perf-active": "zero", + "data-perf-menu-open": "false", + "data-perf-theme": "light" }; -export const variantKeyMap = { - "data-accent": true, - "data-active": true, - "data-menu-open": true, - "data-mobile-menu": true, - "data-scrolled": true, - "data-theme": true, - "data-theme-test": true, - "data-theme-test-ssr": true +export const variantKeyMap = { + "data-demo-search-status": true, + "data-perf-accent": true, + "data-perf-active": true, + "data-perf-menu-open": true, + "data-perf-theme": true }; diff --git a/examples/demo/README.md b/examples/demo/README.md new file mode 100644 index 0000000..9b7bba9 --- /dev/null +++ b/examples/demo/README.md @@ -0,0 +1,45 @@ +# docs + +This is a Next.js application generated with +[Create Fumadocs](https://github.com/fuma-nama/fumadocs). + +Run development server: + +```bash +npm run dev +# or +pnpm dev +# or +yarn dev +``` + +Open http://localhost:3000 with your browser to see the result. + +## Explore + +In the project, you can see: + +- `lib/source.ts`: Code for content source adapter, [`loader()`](https://fumadocs.dev/docs/headless/source-api) provides the interface to access your content. +- `lib/layout.shared.tsx`: Shared options for layouts, optional but preferred to keep. + +| Route | Description | +| ------------------------- | ------------------------------------------------------ | +| `app/(home)` | The route group for your landing page and other pages. | +| `app/docs` | The documentation layout and pages. | +| `app/api/search/route.ts` | The Route Handler for search. | + +### Fumadocs MDX + +A `source.config.ts` config file has been included, you can customise different options like frontmatter schema. + +Read the [Introduction](https://fumadocs.dev/docs/mdx) for further details. + +## Learn More + +To learn more about Next.js and Fumadocs, take a look at the following +resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js + features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +- [Fumadocs](https://fumadocs.dev) - learn about Fumadocs diff --git a/examples/demo/app/(home)/_components/Comparison.tsx b/examples/demo/app/(home)/_components/Comparison.tsx new file mode 100644 index 0000000..9cc7af6 --- /dev/null +++ b/examples/demo/app/(home)/_components/Comparison.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { Atom, Zap } from 'lucide-react'; +import { useUI } from '@react-zero-ui/core'; +import { ReactState } from './ReactState'; +import { ZeroState } from './ZeroState'; + +export function Comparison() { + const [, setActive] = useUI<'zero' | 'react'>('perf-active', 'zero'); + + return ( +
+
+
+ + +
+ +
+
+ + +
+
+
+ ); +} diff --git a/examples/demo/app/(home)/_components/ReactState.tsx b/examples/demo/app/(home)/_components/ReactState.tsx new file mode 100644 index 0000000..34d91df --- /dev/null +++ b/examples/demo/app/(home)/_components/ReactState.tsx @@ -0,0 +1,123 @@ +'use client'; + +import { useRef, useState } from 'react'; + +type Theme = 'light' | 'dark'; +type Accent = 'violet' | 'emerald' | 'amber'; + +export function ReactState() { + const [accent, setAccent] = useState('violet'); + const [theme, setTheme] = useState('light'); + const [menuOpen, setMenuOpen] = useState(false); + const renderCount = useRef(0); + renderCount.current += 1; + + return ( +
+
+ + + + +
+ ); +} + +function Header({ theme, renderCount }: { theme: Theme; renderCount: number }) { + return ( +
+
+ renders: {renderCount} +
+

+ React State Management +

+

+ Reactive state management with React
+ Re-renders O(n) +

+
+ ); +} + +function ThemeSwitcher({ theme, setTheme }: { theme: Theme; setTheme: (t: Theme) => void }) { + return ( +
+ + +
+ ); +} + +function AccentPicker({ accent, setAccent, theme }: { accent: Accent; setAccent: (a: Accent) => void; theme: Theme }) { + return ( +
+

Choose Accent

+
+
+
+ ); +} + +function InteractiveCard({ theme, menuOpen, setMenuOpen, accent }: { theme: Theme; menuOpen: boolean; setMenuOpen: (o: boolean) => void; accent: Accent }) { + return ( +
+
+

Open Menu Demo

+ +
+
+
+

✨ This panel slides open and has to re-render!

+
+
+
+ ); +} + +function StateDisplay({ theme, accent, menuOpen }: { theme: Theme; accent: Accent; menuOpen: boolean }) { + return ( +
+
+
theme: {theme}
+
accent: {accent}
+
menu: {menuOpen ? 'Open' : 'Closed'}
+
+
+ ); +} diff --git a/examples/demo/app/(home)/_components/ZeroState.tsx b/examples/demo/app/(home)/_components/ZeroState.tsx new file mode 100644 index 0000000..4a9a513 --- /dev/null +++ b/examples/demo/app/(home)/_components/ZeroState.tsx @@ -0,0 +1,134 @@ +'use client'; + +import { useRef } from 'react'; +import { useUI } from '@react-zero-ui/core'; + +export function ZeroState() { + const [, setTheme] = useUI<'light' | 'dark'>('perf-theme', 'light'); + const [, setAccent] = useUI<'violet' | 'emerald' | 'amber'>('perf-accent', 'violet'); + const [, setMenuOpen] = useUI<'true' | 'false'>('perf-menu-open', 'false'); + const renderCount = useRef(0); + renderCount.current += 1; + + return ( +
+
+ + + setMenuOpen((prev) => (prev === 'true' ? 'false' : 'true'))} /> + +
+ ); +} + +function Header({ renderCount }: { renderCount: number }) { + return ( +
+
+ renders: {renderCount} +
+

Zero UI

+

+ Reactive state without re-rendering. +
+ + Zero re-renders,{' '} + Reactive &{' '} + Global state. + +

+
+ ); +} + +function ThemeSwitcher({ setTheme }: { setTheme: (t: 'light' | 'dark') => void }) { + return ( +
+ + +
+ ); +} + +function AccentPicker({ setAccent }: { setAccent: (a: 'violet' | 'emerald' | 'amber') => void }) { + return ( +
+

Choose Accent

+
+
+
+ ); +} + +function InteractiveCard({ toggleMenu }: { toggleMenu: () => void }) { + return ( +
+
+

Open Menu Demo

+ +
+
+
+

✨ This panel slides open without re-rendering!

+
+
+
+ ); +} + +function StateDisplay() { + return ( +
+
+
+ theme: Light + Dark +
+
+ accent: + Violet + Emerald + Amber +
+
+ menu: + Open + Closed +
+
+
+ ); +} diff --git a/examples/demo/app/(home)/demo/real-world/_components.tsx b/examples/demo/app/(home)/demo/real-world/_components.tsx new file mode 100644 index 0000000..a4d6176 --- /dev/null +++ b/examples/demo/app/(home)/demo/real-world/_components.tsx @@ -0,0 +1,181 @@ +'use client'; + +import { useEffect, useRef, useState } from 'react'; +import { useUI } from '@react-zero-ui/core'; +import { categories, fetchWithDelay, type Category, type Product } from './_data'; + +type CategoryFilter = Category | 'all'; + +export function RealWorldDemo() { + const [query, setQuery] = useState(''); + const [category, setCategory] = useState('all'); + + return ( +
+ +
+ + +
+

+ Type in the search or change the filter. Watch the render counters — both panes update on keystroke (the search input drives both), + but only the React pane re-renders during the loading transition. +

+
+ ); +} + +function SearchControls({ + query, + setQuery, + category, + setCategory, +}: { + query: string; + setQuery: (v: string) => void; + category: CategoryFilter; + setCategory: (v: CategoryFilter) => void; +}) { + return ( +
+ setQuery(e.target.value)} + className="border-fd-border bg-fd-background focus:border-fd-primary w-full rounded-md border px-3 py-2 text-sm outline-none sm:max-w-xs" + /> +
+ {categories.map((c) => { + const active = c.value === category; + return ( + + ); + })} +
+
+ ); +} + +function ReactPane({ query, category }: { query: string; category: CategoryFilter }) { + const [data, setData] = useState(() => []); + const [loading, setLoading] = useState(true); + const renderCount = useRef(0); + renderCount.current += 1; + + useEffect(() => { + let cancelled = false; + setLoading(true); + fetchWithDelay(query, category).then((results) => { + if (cancelled) return; + setData(results); + setLoading(false); + }); + return () => { + cancelled = true; + }; + }, [query, category]); + + return ( + + {loading ? : } + + ); +} + +function ZeroUiPane({ query, category }: { query: string; category: CategoryFilter }) { + const [data, setData] = useState(() => []); + const [, setStatus] = useUI<'idle' | 'loading' | 'success'>('demo-search-status', 'idle'); + const renderCount = useRef(0); + renderCount.current += 1; + + useEffect(() => { + let cancelled = false; + setStatus('loading'); + fetchWithDelay(query, category).then((results) => { + if (cancelled) return; + setData(results); + setStatus('success'); + }); + return () => { + cancelled = true; + }; + }, [query, category, setStatus]); + + return ( + +
+ +
+
+ +
+
+ ); +} + +function Pane({ title, subtitle, renderCount, children }: { title: string; subtitle: string; renderCount: number; children: React.ReactNode }) { + return ( +
+
+
+

{title}

+

{subtitle}

+
+
+ renders: {renderCount} +
+
+
{children}
+
+ ); +} + +function ProductList({ products }: { products: Product[] }) { + if (products.length === 0) { + return

No products match.

; + } + return ( +
    + {products.map((p) => ( +
  • +
    +
    {p.name}
    +
    {p.category}
    +
    +
    ${p.price}
    +
  • + ))} +
+ ); +} + +function SkeletonList() { + return ( +
    + {Array.from({ length: 5 }).map((_, i) => ( +
  • +
    +
    +
    +
    +
    +
  • + ))} +
+ ); +} diff --git a/examples/demo/app/(home)/demo/real-world/_data.ts b/examples/demo/app/(home)/demo/real-world/_data.ts new file mode 100644 index 0000000..9eb6f3f --- /dev/null +++ b/examples/demo/app/(home)/demo/real-world/_data.ts @@ -0,0 +1,40 @@ +export type Category = 'electronics' | 'books' | 'clothing'; + +export type Product = { + id: number; + name: string; + category: Category; + price: number; +}; + +export const categories: { value: Category | 'all'; label: string }[] = [ + { value: 'all', label: 'All' }, + { value: 'electronics', label: 'Electronics' }, + { value: 'books', label: 'Books' }, + { value: 'clothing', label: 'Clothing' }, +]; + +export const products: Product[] = [ + { id: 1, name: 'Laptop Pro 14"', category: 'electronics', price: 1299 }, + { id: 2, name: 'Noise-cancelling Headphones', category: 'electronics', price: 299 }, + { id: 3, name: 'Wireless Mouse', category: 'electronics', price: 49 }, + { id: 4, name: '4K Monitor', category: 'electronics', price: 449 }, + { id: 5, name: 'Mechanical Keyboard', category: 'electronics', price: 129 }, + { id: 6, name: 'The Pragmatic Programmer', category: 'books', price: 32 }, + { id: 7, name: 'Designing Data-Intensive Apps', category: 'books', price: 45 }, + { id: 8, name: 'Refactoring', category: 'books', price: 38 }, + { id: 9, name: 'Clean Code', category: 'books', price: 29 }, + { id: 10, name: 'Merino Wool Sweater', category: 'clothing', price: 89 }, + { id: 11, name: 'Selvedge Denim Jeans', category: 'clothing', price: 145 }, + { id: 12, name: 'Leather Boots', category: 'clothing', price: 220 }, + { id: 13, name: 'Oxford Shirt', category: 'clothing', price: 68 }, + { id: 14, name: 'Wool Overcoat', category: 'clothing', price: 320 }, +]; + +export async function fetchWithDelay(query: string, category: Category | 'all'): Promise { + await new Promise((r) => setTimeout(r, 650)); + const q = query.trim().toLowerCase(); + return products.filter( + (p) => (category === 'all' || p.category === category) && (q === '' || p.name.toLowerCase().includes(q)) + ); +} diff --git a/examples/demo/app/(home)/demo/real-world/page.tsx b/examples/demo/app/(home)/demo/real-world/page.tsx new file mode 100644 index 0000000..d53efea --- /dev/null +++ b/examples/demo/app/(home)/demo/real-world/page.tsx @@ -0,0 +1,63 @@ +import Link from 'next/link'; +import { ArrowLeft } from 'lucide-react'; +import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock'; +import { RealWorldDemo } from './_components'; + +export const metadata = { + title: 'Real-world demo · React Zero-UI', + description: 'Searchable list with skeleton loading — React useState vs the Zero-UI hybrid pattern.', +}; + +export default function RealWorldDemoPage() { + return ( +
+
+ + + Home + +

The hybrid pattern.

+

+ Use useState for what changes (the product data) and useUI{' '} + for how it looks (the loading skeleton). Both panes below fetch the same mock data with a 650 ms delay — only the React + version re-renders when the skeleton appears and disappears. +

+
+ + + +
+
+

The code that matters

+ { + setStatus('loading'); // no re-render + fetchResults(query).then((r) => { + setData(r); // 1 re-render + setStatus('success'); // no re-render + }); +}, [query]);`} + /> +
+
+

When to reach for this

+
    +
  • — Presentation states (loading, expanded, focused) flip a data-attribute.
  • +
  • — Actual data (fetched items, form values) stays in React state.
  • +
  • — The expensive tree (the list) only re-renders when the data changes.
  • +
  • — The skeleton appears instantly via CSS, not via React reconciliation.
  • +
+
+
+
+ ); +} diff --git a/examples/demo/app/(home)/layout.tsx b/examples/demo/app/(home)/layout.tsx new file mode 100644 index 0000000..77379fa --- /dev/null +++ b/examples/demo/app/(home)/layout.tsx @@ -0,0 +1,6 @@ +import { HomeLayout } from 'fumadocs-ui/layouts/home'; +import { baseOptions } from '@/lib/layout.shared'; + +export default function Layout({ children }: LayoutProps<'/'>) { + return {children}; +} diff --git a/examples/demo/app/(home)/page.tsx b/examples/demo/app/(home)/page.tsx new file mode 100644 index 0000000..44a2174 --- /dev/null +++ b/examples/demo/app/(home)/page.tsx @@ -0,0 +1,217 @@ +import Link from 'next/link'; +import { ArrowRight, Zap, Layers, Feather, Github } from 'lucide-react'; +import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock'; +import { Comparison } from './_components/Comparison'; + +export default function HomePage() { + return ( +
+ + + + + +
+ ); +} + +function Hero() { + return ( +
+
+ + Zero runtime · Zero re-renders · ~350 bytes +
+

+ Ultra-fast React UI state,
+ powered by CSS. +

+

+ React Zero-UI pre-renders every UI state at build time and flips data-* attributes on the + fly — giving you global state without providers, re-renders, or hydration headaches. +

+ +
+ $ + npm install @react-zero-ui/core +
+ +
+ + Read the Docs + + + See it in action + +
+
+ ); +} + +function MentalModel() { + return ( +
+
+

Presentation state is not data state.

+

+ Themes, modals, sidebars, accents — none of that needs to live in React. Zero-UI moves presentation state into the DOM where it + belongs, while you keep React for the things React is good at. +

+
+ +
+ + +
+);`} + /> + . +return ( +
+ +
+);`} + /> +
+ + ); +} + +function CodeCard({ label, tone, code }: { label: string; tone: 'muted' | 'primary'; code: string }) { + return ( +
+
{label}
+ +
+ ); +} + +function Demo() { + return ( +
+
+

Try it right here.

+

+ Same UI, built twice. The Zero-UI tab flips data-* attributes on{' '} + <body>; the React tab holds the same state in{' '} + useState. Watch the render counters as you click around. +

+
+ + +
+ +
+
The hybrid pattern
+
Search + skeleton loading, useState for data, useUI for presentation.
+
+ + +
+
+ ); +} + +function WhyFast() { + const cards = [ + { + icon: , + title: 'Zero re-renders', + body: 'State changes flip DOM attributes. React stays completely out of the loop — no reconciliation, no render cycles.', + }, + { + icon: , + title: '~350 bytes', + body: 'Smaller than a single SVG icon. An order of magnitude leaner than Redux or Zustand for UI state.', + }, + { + icon: , + title: 'Build-time CSS', + body: 'Tailwind variants are generated for every possible state at build time. Switching states is just changing a selector match.', + }, + ]; + + return ( +
+
+

Why it's fast.

+
+
+ {cards.map((c) => ( +
+
{c.icon}
+

{c.title}

+

{c.body}

+
+ ))} +
+
+ ); +} + +function SocialProof() { + return ( +
+

Open source. Tiny. Tested.

+

+ MIT licensed. Production-ready core, experimental SSR runtime, and a growing demo suite. +

+ +
+ ); +} diff --git a/examples/demo/app/api/search/route.ts b/examples/demo/app/api/search/route.ts new file mode 100644 index 0000000..7ba7e82 --- /dev/null +++ b/examples/demo/app/api/search/route.ts @@ -0,0 +1,7 @@ +import { source } from '@/lib/source'; +import { createFromSource } from 'fumadocs-core/search/server'; + +export const { GET } = createFromSource(source, { + // https://docs.orama.com/docs/orama-js/supported-languages + language: 'english', +}); diff --git a/examples/demo/app/docs/[[...slug]]/page.tsx b/examples/demo/app/docs/[[...slug]]/page.tsx new file mode 100644 index 0000000..9b6d208 --- /dev/null +++ b/examples/demo/app/docs/[[...slug]]/page.tsx @@ -0,0 +1,54 @@ +import { getPageImage, source } from '@/lib/source'; +import { + DocsBody, + DocsDescription, + DocsPage, + DocsTitle, +} from 'fumadocs-ui/page'; +import { notFound } from 'next/navigation'; +import { getMDXComponents } from '@/mdx-components'; +import type { Metadata } from 'next'; +import { createRelativeLink } from 'fumadocs-ui/mdx'; + +export default async function Page(props: PageProps<'/docs/[[...slug]]'>) { + const params = await props.params; + const page = source.getPage(params.slug); + if (!page) notFound(); + + const MDX = page.data.body; + + return ( + + {page.data.title} + {page.data.description} + + + + + ); +} + +export async function generateStaticParams() { + return source.generateParams(); +} + +export async function generateMetadata( + props: PageProps<'/docs/[[...slug]]'>, +): Promise { + const params = await props.params; + const page = source.getPage(params.slug); + if (!page) notFound(); + + return { + title: page.data.title, + description: page.data.description, + openGraph: { + images: getPageImage(page).url, + }, + }; +} diff --git a/examples/demo/app/docs/layout.tsx b/examples/demo/app/docs/layout.tsx new file mode 100644 index 0000000..8e9ec72 --- /dev/null +++ b/examples/demo/app/docs/layout.tsx @@ -0,0 +1,11 @@ +import { DocsLayout } from 'fumadocs-ui/layouts/docs'; +import { baseOptions } from '@/lib/layout.shared'; +import { source } from '@/lib/source'; + +export default function Layout({ children }: LayoutProps<'/docs'>) { + return ( + + {children} + + ); +} diff --git a/examples/demo/app/global.css b/examples/demo/app/global.css new file mode 100644 index 0000000..50b3bc2 --- /dev/null +++ b/examples/demo/app/global.css @@ -0,0 +1,3 @@ +@import 'tailwindcss'; +@import 'fumadocs-ui/css/neutral.css'; +@import 'fumadocs-ui/css/preset.css'; diff --git a/examples/demo/app/layout.tsx b/examples/demo/app/layout.tsx new file mode 100644 index 0000000..2a0242f --- /dev/null +++ b/examples/demo/app/layout.tsx @@ -0,0 +1,30 @@ +import '@/app/global.css'; +import { RootProvider } from 'fumadocs-ui/provider/next'; +import { Inter } from 'next/font/google'; +import { bodyAttributes } from '@zero-ui/attributes'; + +const inter = Inter({ + subsets: ['latin'], +}); + +const siteUrl = + process.env.NEXT_PUBLIC_SITE_URL ?? (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'https://zero-ui.dev'); + +export const metadata = { + metadataBase: new URL(siteUrl), + title: { + default: 'React Zero-UI', + template: '%s · React Zero-UI', + }, + description: 'Ultra-fast React UI state with zero runtime and zero re-renders.', +}; + +export default function Layout({ children }: LayoutProps<'/'>) { + return ( + + + {children} + + + ); +} diff --git a/examples/demo/app/llms-full.txt/route.ts b/examples/demo/app/llms-full.txt/route.ts new file mode 100644 index 0000000..d494d2c --- /dev/null +++ b/examples/demo/app/llms-full.txt/route.ts @@ -0,0 +1,10 @@ +import { getLLMText, source } from '@/lib/source'; + +export const revalidate = false; + +export async function GET() { + const scan = source.getPages().map(getLLMText); + const scanned = await Promise.all(scan); + + return new Response(scanned.join('\n\n')); +} diff --git a/examples/demo/app/og/docs/[...slug]/route.tsx b/examples/demo/app/og/docs/[...slug]/route.tsx new file mode 100644 index 0000000..f5df96d --- /dev/null +++ b/examples/demo/app/og/docs/[...slug]/route.tsx @@ -0,0 +1,36 @@ +import { getPageImage, source } from '@/lib/source'; +import { notFound } from 'next/navigation'; +import { ImageResponse } from 'next/og'; +import { generate as DefaultImage } from 'fumadocs-ui/og'; + +export const revalidate = false; + +export async function GET( + _req: Request, + { params }: RouteContext<'/og/docs/[...slug]'>, +) { + const { slug } = await params; + const page = source.getPage(slug.slice(0, -1)); + if (!page) notFound(); + + return new ImageResponse( + ( + + ), + { + width: 1200, + height: 630, + }, + ); +} + +export function generateStaticParams() { + return source.getPages().map((page) => ({ + lang: page.locale, + slug: getPageImage(page).segments, + })); +} diff --git a/examples/demo/app/robots.ts b/examples/demo/app/robots.ts new file mode 100644 index 0000000..c91e0dc --- /dev/null +++ b/examples/demo/app/robots.ts @@ -0,0 +1,11 @@ +import type { MetadataRoute } from 'next'; + +const siteUrl = + process.env.NEXT_PUBLIC_SITE_URL ?? (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'https://zero-ui.dev'); + +export default function robots(): MetadataRoute.Robots { + return { + rules: [{ userAgent: '*', allow: '/' }], + sitemap: `${siteUrl}/sitemap.xml`, + }; +} diff --git a/examples/demo/app/sitemap.ts b/examples/demo/app/sitemap.ts new file mode 100644 index 0000000..2f6c5c6 --- /dev/null +++ b/examples/demo/app/sitemap.ts @@ -0,0 +1,27 @@ +import type { MetadataRoute } from 'next'; +import { source } from '@/lib/source'; + +const siteUrl = + process.env.NEXT_PUBLIC_SITE_URL ?? (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : 'https://zero-ui.dev'); + +const staticRoutes = ['/', '/demo/real-world']; + +export default function sitemap(): MetadataRoute.Sitemap { + const now = new Date().toISOString(); + + const staticEntries: MetadataRoute.Sitemap = staticRoutes.map((path) => ({ + url: `${siteUrl}${path}`, + lastModified: now, + changeFrequency: path === '/' ? 'weekly' : 'monthly', + priority: path === '/' ? 1 : 0.7, + })); + + const docEntries: MetadataRoute.Sitemap = source.getPages().map((page) => ({ + url: `${siteUrl}${page.url}`, + lastModified: now, + changeFrequency: 'weekly', + priority: 0.8, + })); + + return [...staticEntries, ...docEntries]; +} diff --git a/examples/demo/content/docs/api-reference.mdx b/examples/demo/content/docs/api-reference.mdx new file mode 100644 index 0000000..494362f --- /dev/null +++ b/examples/demo/content/docs/api-reference.mdx @@ -0,0 +1,12 @@ +--- +title: API Reference +description: Complete API documentation for React Zero-UI +--- + +import { Callout } from 'fumadocs-ui/components/callout'; + + + This page is being written. In the meantime, the [Introduction](/docs) and + [Getting Started](/docs/getting-started/next) guides cover the core API. + Source and examples live on the [GitHub repo](https://github.com/react-zero-ui/core). + diff --git a/examples/demo/content/docs/experimental.mdx b/examples/demo/content/docs/experimental.mdx new file mode 100644 index 0000000..0b2eec8 --- /dev/null +++ b/examples/demo/content/docs/experimental.mdx @@ -0,0 +1,12 @@ +--- +title: Experimental Runtime +description: SSR-safe click handling for React server components without "use client" +--- + +import { Callout } from 'fumadocs-ui/components/callout'; + + + This page is being written. In the meantime, the [Introduction](/docs) and + [Getting Started](/docs/getting-started/next) guides cover the core API. + Source and examples live on the [GitHub repo](https://github.com/react-zero-ui/core). + diff --git a/examples/demo/content/docs/faq.mdx b/examples/demo/content/docs/faq.mdx new file mode 100644 index 0000000..3524d70 --- /dev/null +++ b/examples/demo/content/docs/faq.mdx @@ -0,0 +1,12 @@ +--- +title: FAQ +description: Frequently asked questions about React Zero-UI +--- + +import { Callout } from 'fumadocs-ui/components/callout'; + + + This page is being written. In the meantime, the [Introduction](/docs) and + [Getting Started](/docs/getting-started/next) guides cover the core API. + Source and examples live on the [GitHub repo](https://github.com/react-zero-ui/core). + diff --git a/examples/demo/content/docs/getting-started/meta.json b/examples/demo/content/docs/getting-started/meta.json new file mode 100644 index 0000000..0022fb7 --- /dev/null +++ b/examples/demo/content/docs/getting-started/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Getting Started", + "pages": ["next", "vite"] +} diff --git a/examples/demo/content/docs/getting-started/next.mdx b/examples/demo/content/docs/getting-started/next.mdx new file mode 100644 index 0000000..4d69065 --- /dev/null +++ b/examples/demo/content/docs/getting-started/next.mdx @@ -0,0 +1,69 @@ +--- +title: Next.js +description: Set up React Zero-UI in a Next.js App Router project +--- + +## 1. Install dependencies + +```bash +npm install @react-zero-ui/core +``` + +```bash +npm install @tailwindcss/postcss +``` + +## 2. Add the PostCSS plugin + +Zero-UI must come **before** Tailwind. + +```js +// postcss.config.* (ESM) +const config = { + plugins: ["@react-zero-ui/core/postcss", "@tailwindcss/postcss"], +}; +export default config; +``` + +```js +// postcss.config.* (CJS) +module.exports = { + plugins: { "@react-zero-ui/core/postcss": {}, tailwindcss: {} }, +}; +``` + +## 3. Import Tailwind + +```css +/* global.css */ +@import "tailwindcss"; +``` + +## 4. Start the app + +```bash +npm run dev +``` + +Zero-UI generates a `.zero-ui/` folder in your project root containing `attributes.ts` and type definitions. + +## 5. Prevent FOUC + +Spread `bodyAttributes` on `` in your root layout. + +```tsx +// app/layout.tsx +import { bodyAttributes } from "./.zero-ui/attributes"; + +export default function RootLayout({ children }) { + return ( + + {children} + + ); +} +``` + +That's it. Zero-UI now adds the data-`*` attributes to the body tag and Tailwind generates the matching variant classes. + +Next up: check out the [experimental SSR-safe click handler](/docs/experimental). diff --git a/examples/demo/content/docs/getting-started/vite.mdx b/examples/demo/content/docs/getting-started/vite.mdx new file mode 100644 index 0000000..39355df --- /dev/null +++ b/examples/demo/content/docs/getting-started/vite.mdx @@ -0,0 +1,39 @@ +--- +title: Vite +description: Set up React Zero-UI in a Vite project +--- + +## 1. Install dependencies + +```bash +npm install @react-zero-ui/core +``` + +```bash +npm install @tailwindcss/postcss +``` + +## 2. Add the plugin to `vite.config.ts` + +```js +import zeroUI from "@react-zero-ui/core/vite"; +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import tailwindCss from "@tailwindcss/postcss"; + +export default defineConfig({ + // Remove the default `tailwindcss()` plugin — pass it into zeroUI instead + plugins: [zeroUI({ tailwind: tailwindCss }), react()], +}); +``` + +## 3. Import Tailwind + +```css +/* global.css */ +@import "tailwindcss"; +``` + +That's it. The plugin adds the data-`*` attributes to the body tag (no FOUC) and Tailwind generates the matching variant classes. + +See [Usage examples](/docs/usage-examples) for patterns you can drop into your app. diff --git a/examples/demo/content/docs/index.mdx b/examples/demo/content/docs/index.mdx new file mode 100644 index 0000000..b925500 --- /dev/null +++ b/examples/demo/content/docs/index.mdx @@ -0,0 +1,22 @@ +--- +title: Introduction +description: Ultra-fast React UI state with zero runtime and zero re-renders +--- + +React Zero-UI is a state management library that eliminates React re-renders for UI state by using CSS and data attributes instead of component state. It pre-renders all possible UI states at build time and switches between them by flipping `data-*` attributes on `` (or on scoped elements). + +## Why Zero-UI? + +- **Zero re-renders** — state changes flip DOM attributes, not component trees. +- **~350 bytes** — smaller than a single SVG icon. +- **SSR-safe** — no hydration mismatches, no FOUC. +- **Tailwind-native** — use variants like `theme-dark:bg-gray-900` anywhere. + +## Get started + + + + + + + diff --git a/examples/demo/content/docs/meta.json b/examples/demo/content/docs/meta.json new file mode 100644 index 0000000..71010e7 --- /dev/null +++ b/examples/demo/content/docs/meta.json @@ -0,0 +1,15 @@ +{ + "title": "Docs", + "pages": [ + "index", + "---Getting Started---", + "getting-started", + "---Core Concepts---", + "usage-examples", + "api-reference", + "migration-guide", + "---Advanced---", + "experimental", + "faq" + ] +} diff --git a/examples/demo/content/docs/migration-guide.mdx b/examples/demo/content/docs/migration-guide.mdx new file mode 100644 index 0000000..69b4d05 --- /dev/null +++ b/examples/demo/content/docs/migration-guide.mdx @@ -0,0 +1,12 @@ +--- +title: Migration Guide +description: Migrate to React Zero-UI from existing state management solutions +--- + +import { Callout } from 'fumadocs-ui/components/callout'; + + + This page is being written. In the meantime, the [Introduction](/docs) and + [Getting Started](/docs/getting-started/next) guides cover the core API. + Source and examples live on the [GitHub repo](https://github.com/react-zero-ui/core). + diff --git a/examples/demo/content/docs/usage-examples.mdx b/examples/demo/content/docs/usage-examples.mdx new file mode 100644 index 0000000..55f8533 --- /dev/null +++ b/examples/demo/content/docs/usage-examples.mdx @@ -0,0 +1,12 @@ +--- +title: Usage Examples +description: Comprehensive examples and patterns for React Zero-UI +--- + +import { Callout } from 'fumadocs-ui/components/callout'; + + + This page is being written. In the meantime, the [Introduction](/docs) and + [Getting Started](/docs/getting-started/next) guides cover the core API. + Source and examples live on the [GitHub repo](https://github.com/react-zero-ui/core). + diff --git a/examples/demo/lib/layout.shared.tsx b/examples/demo/lib/layout.shared.tsx new file mode 100644 index 0000000..909fc2e --- /dev/null +++ b/examples/demo/lib/layout.shared.tsx @@ -0,0 +1,28 @@ +import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; +import { BookOpen, Github } from 'lucide-react'; + +export function baseOptions(): BaseLayoutProps { + return { + nav: { + title: ( + + React Zero-UI + + ), + }, + links: [ + { + icon: , + text: 'Docs', + url: '/docs', + }, + { + type: 'icon', + icon: , + text: 'GitHub', + url: 'https://github.com/react-zero-ui/core', + external: true, + }, + ], + }; +} diff --git a/examples/demo/lib/source.ts b/examples/demo/lib/source.ts new file mode 100644 index 0000000..5895211 --- /dev/null +++ b/examples/demo/lib/source.ts @@ -0,0 +1,27 @@ +import { docs } from '@/.source'; +import { type InferPageType, loader } from 'fumadocs-core/source'; +import { lucideIconsPlugin } from 'fumadocs-core/source/lucide-icons'; + +// See https://fumadocs.dev/docs/headless/source-api for more info +export const source = loader({ + baseUrl: '/docs', + source: docs.toFumadocsSource(), + plugins: [lucideIconsPlugin()], +}); + +export function getPageImage(page: InferPageType) { + const segments = [...page.slugs, 'image.png']; + + return { + segments, + url: `/og/docs/${segments.join('/')}`, + }; +} + +export async function getLLMText(page: InferPageType) { + const processed = await page.data.getText('processed'); + + return `# ${page.data.title} (${page.url}) + +${processed}`; +} diff --git a/examples/demo/mdx-components.tsx b/examples/demo/mdx-components.tsx new file mode 100644 index 0000000..d3fbb13 --- /dev/null +++ b/examples/demo/mdx-components.tsx @@ -0,0 +1,10 @@ +import defaultMdxComponents from 'fumadocs-ui/mdx'; +import type { MDXComponents } from 'mdx/types'; + +// use this function to get MDX components, you will need it for rendering MDX +export function getMDXComponents(components?: MDXComponents): MDXComponents { + return { + ...defaultMdxComponents, + ...components, + }; +} diff --git a/examples/demo/next-env.d.ts b/examples/demo/next-env.d.ts index 1b3be08..830fb59 100644 --- a/examples/demo/next-env.d.ts +++ b/examples/demo/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/examples/demo/next.config.mjs b/examples/demo/next.config.mjs new file mode 100644 index 0000000..dcf88ae --- /dev/null +++ b/examples/demo/next.config.mjs @@ -0,0 +1,14 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { createMDX } from 'fumadocs-mdx/next'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const withMDX = createMDX(); + +/** @type {import('next').NextConfig} */ +const config = { + reactStrictMode: true, + outputFileTracingRoot: path.join(__dirname, '../..'), +}; + +export default withMDX(config); diff --git a/examples/demo/next.config.ts b/examples/demo/next.config.ts deleted file mode 100644 index 7921f35..0000000 --- a/examples/demo/next.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { NextConfig } from "next"; - -const nextConfig: NextConfig = { - /* config options here */ -}; - -export default nextConfig; diff --git a/examples/demo/package.json b/examples/demo/package.json index 11015bb..68582d4 100644 --- a/examples/demo/package.json +++ b/examples/demo/package.json @@ -1,39 +1,32 @@ { - "name": "react-zero", - "version": "0.1.0", - "private": true, - "type": "module", - "scripts": { - "dev": "next dev", - "prebuild": "zero-icons", - "build": "next build", - "start": "next start", - "lint": "next lint", - "lint:fix": "next lint --fix", - "format": "prettier --write .", - "format:check": "prettier --check .", - "type-check": "tsc --noEmit", - "clean": "rm -rf .next" - }, - "dependencies": { - "@codesandbox/sandpack-react": "^2.20.0", - "@react-zero-ui/core": "^0.4.0", - "@react-zero-ui/icon-sprite": "^0.1.4", - "@vercel/analytics": "^1.5.0", - "clsx": "^2.1.1", - "motion": "12.18.1", - "next": "15.3.8", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "react-scan": "^0.4.3" - }, - "devDependencies": { - "@tailwindcss/postcss": "^4.1.10", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "postcss": "^8.5.5", - "tailwindcss": "^4.1.10", - "typescript": "^5" - } -} + "name": "docs", + "version": "0.0.0", + "private": true, + "scripts": { + "build": "next build", + "dev": "next dev --turbo", + "start": "next start", + "postinstall": "fumadocs-mdx" + }, + "dependencies": { + "@react-zero-ui/core": "^0.4.0", + "fumadocs-core": "15.8.5", + "fumadocs-mdx": "12.0.3", + "fumadocs-ui": "15.8.5", + "lucide-react": "^0.544.0", + "next": "15.5.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "shiki": "3.23.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.1.10", + "@types/mdx": "^2.0.13", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "postcss": "^8.5.5", + "tailwindcss": "^4.1.10", + "typescript": "^5" + } +} \ No newline at end of file diff --git a/examples/demo/postcss.config.mjs b/examples/demo/postcss.config.mjs index 2e5af8c..5758b6d 100644 --- a/examples/demo/postcss.config.mjs +++ b/examples/demo/postcss.config.mjs @@ -1,5 +1,6 @@ -// postcss.config.mjs - -const config = { plugins: ["@react-zero-ui/core/postcss", "@tailwindcss/postcss"] }; +// Zero-UI must come before Tailwind +const config = { + plugins: ['@react-zero-ui/core/postcss', '@tailwindcss/postcss'], +}; export default config; diff --git a/docs/assets/useui-explained.webp b/examples/demo/public/assets/useui-explained.webp similarity index 100% rename from docs/assets/useui-explained.webp rename to examples/demo/public/assets/useui-explained.webp diff --git a/docs/assets/zero-ui-logo.png b/examples/demo/public/assets/zero-ui-logo.png similarity index 100% rename from docs/assets/zero-ui-logo.png rename to examples/demo/public/assets/zero-ui-logo.png diff --git a/examples/demo/public/icons-old.svg b/examples/demo/public/icons-old.svg deleted file mode 100644 index 35f9d92..0000000 --- a/examples/demo/public/icons-old.svg +++ /dev/null @@ -1,263 +0,0 @@ - \ No newline at end of file diff --git a/examples/demo/public/icons.svg b/examples/demo/public/icons.svg deleted file mode 100644 index 66ada77..0000000 --- a/examples/demo/public/icons.svg +++ /dev/null @@ -1,685 +0,0 @@ - \ No newline at end of file diff --git a/examples/demo/public/zero-ui-favicon.png b/examples/demo/public/zero-ui-favicon.png deleted file mode 100644 index bde7937..0000000 Binary files a/examples/demo/public/zero-ui-favicon.png and /dev/null differ diff --git a/examples/demo/public/zero-ui-variant-4.webp b/examples/demo/public/zero-ui-variant-4.webp deleted file mode 100644 index db489e1..0000000 Binary files a/examples/demo/public/zero-ui-variant-4.webp and /dev/null differ diff --git a/examples/demo/source.config.ts b/examples/demo/source.config.ts new file mode 100644 index 0000000..d2e968b --- /dev/null +++ b/examples/demo/source.config.ts @@ -0,0 +1,26 @@ +import { + defineConfig, + defineDocs, + frontmatterSchema, + metaSchema, +} from 'fumadocs-mdx/config'; + +// You can customise Zod schemas for frontmatter and `meta.json` here +// see https://fumadocs.dev/docs/mdx/collections +export const docs = defineDocs({ + docs: { + schema: frontmatterSchema, + postprocess: { + includeProcessedMarkdown: true, + }, + }, + meta: { + schema: metaSchema, + }, +}); + +export default defineConfig({ + mdxOptions: { + // MDX options + }, +}); diff --git a/examples/demo/src/app/(sprite-demo)/components/HeaderBar.tsx b/examples/demo/src/app/(sprite-demo)/components/HeaderBar.tsx deleted file mode 100644 index 68edabf..0000000 --- a/examples/demo/src/app/(sprite-demo)/components/HeaderBar.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from "react"; -import HeaderTabs from "./HeaderTabs"; - -type HeaderBarProps = { title: string; subtitle: React.ReactNode; activeTab: "lucide" | "sprite"; className?: string }; - -export default function HeaderBar({ title, subtitle, activeTab, className }: HeaderBarProps) { - return ( -
-
-

{title}

-

{subtitle}

-
- -
- ); -} diff --git a/examples/demo/src/app/(sprite-demo)/components/HeaderTabs.tsx b/examples/demo/src/app/(sprite-demo)/components/HeaderTabs.tsx deleted file mode 100644 index cccddc3..0000000 --- a/examples/demo/src/app/(sprite-demo)/components/HeaderTabs.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import Link from "next/link"; -import React from "react"; - -type HeaderTabsProps = { active: "lucide" | "sprite"; className?: string }; - -export default function HeaderTabs({ active, className }: HeaderTabsProps) { - const baseLink = "px-3 py-2 text-sm font-medium transition-colors duration-150"; - const inactive = "text-slate-700 hover:bg-slate-50 dark:text-slate-300 dark:hover:bg-slate-800"; - const activeClasses = "bg-slate-900 text-white dark:bg-white dark:text-slate-900"; - - return ( -
- - Zero UI Sprite - - - Lucide React - -
- ); -} diff --git a/examples/demo/src/app/(sprite-demo)/components/SectionGrid.tsx b/examples/demo/src/app/(sprite-demo)/components/SectionGrid.tsx deleted file mode 100644 index 3e9cfaf..0000000 --- a/examples/demo/src/app/(sprite-demo)/components/SectionGrid.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export const SectionGrid: React.FC<{ children: React.ReactNode }> = ({ children }) => { - return ( -
- {children} -
- ); -}; diff --git a/examples/demo/src/app/(sprite-demo)/components/StatsCard.tsx b/examples/demo/src/app/(sprite-demo)/components/StatsCard.tsx deleted file mode 100644 index 403dedd..0000000 --- a/examples/demo/src/app/(sprite-demo)/components/StatsCard.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; - -type StatsCardProps = { title: string; value: string; badgeText?: string; badgeTone?: "positive" | "negative" | "neutral"; className?: string }; - -export default function StatsCard({ title, value, badgeText, badgeTone = "neutral", className }: StatsCardProps) { - const badgeClasses = - badgeTone === "positive" ? "bg-green-100 text-green-700" : badgeTone === "negative" ? "bg-red-100 text-red-700" : "bg-slate-100 text-slate-700"; - - return ( -
-
- {title} HTML size: -
-
-
{value}
- {badgeText ? ( - {badgeText} - ) : null} -
-
- ); -} diff --git a/examples/demo/src/app/(sprite-demo)/icon-sprite/page.tsx b/examples/demo/src/app/(sprite-demo)/icon-sprite/page.tsx deleted file mode 100644 index 0fe11d3..0000000 --- a/examples/demo/src/app/(sprite-demo)/icon-sprite/page.tsx +++ /dev/null @@ -1,367 +0,0 @@ -import { - AlarmSmoke, - Album, - AArrowDown, - AArrowUp, - Accessibility, - Activity, - Airplay, - AirVent, - ALargeSmall, - AlarmClock, - AlarmClockCheck, - AlarmClockMinus, - AlarmClockOff, - AlarmClockPlus, - AlignCenter, - AlignCenterHorizontal, - AlignCenterVertical, - AlignEndHorizontal, - AlignEndVertical, - AlignHorizontalDistributeCenter, - AlignHorizontalDistributeEnd, - AlignHorizontalDistributeStart, - AlignHorizontalJustifyCenter, - AlignHorizontalJustifyEnd, - AlignHorizontalJustifyStart, - AlignHorizontalSpaceAround, - AlignHorizontalSpaceBetween, - AlignJustify, - AlignLeft, - AlignRight, - AlignStartHorizontal, - AlignStartVertical, - AlignVerticalDistributeCenter, - AlignVerticalDistributeEnd, - AlignVerticalDistributeStart, - AlignVerticalJustifyCenter, - AlignVerticalJustifyEnd, - AlignVerticalJustifyStart, - AlignVerticalSpaceAround, - AlignVerticalSpaceBetween, - Ambulance, - Ampersand, - Ampersands, - Amphora, - Anchor, - Angry, - Annoyed, - Antenna, - Anvil, - Aperture, - Apple, - AppWindow, - AppWindowMac, - Archive, - ArchiveRestore, - ArchiveX, - Armchair, - ArrowBigDown, - ArrowBigDownDash, - ArrowBigLeft, - ArrowBigLeftDash, - ArrowBigRight, - ArrowBigRightDash, - ArrowBigUp, - ArrowBigUpDash, - ArrowDown, - ArrowDown01, - ArrowDown10, - ArrowDownAZ, - ArrowDownFromLine, - ArrowDownLeft, - ArrowDownNarrowWide, - ArrowDownRight, - ArrowDownToDot, - ArrowDownToLine, - ArrowDownUp, - ArrowDownWideNarrow, - ArrowDownZA, - ArrowLeft, - ArrowLeftFromLine, - ArrowLeftRight, - ArrowLeftToLine, - ArrowRight, - ArrowRightFromLine, - ArrowRightLeft, - ArrowRightToLine, - ArrowsUpFromLine, - ArrowUp, - ArrowUp01, - ArrowUp10, - ArrowUpAZ, - ArrowUpDown, - ArrowUpFromDot, - ArrowUpFromLine, - ArrowUpLeft, - ArrowUpNarrowWide, - ArrowUpRight, - ArrowUpToLine, - ArrowUpWideNarrow, - ArrowUpZA, - Asterisk, - Atom, - AtSign, - AudioLines, - AudioWaveform, - Award, - Axe, - Axis3d, - Baby, - Backpack, - Badge, - BadgeAlert, - BadgeCent, - BadgeCheck, - BadgeDollarSign, - BadgeEuro, - BadgeIndianRupee, - BadgeInfo, - BadgeJapaneseYen, - BadgeMinus, - BadgePercent, - BadgePlus, - BadgePoundSterling, - BadgeRussianRuble, - BadgeSwissFranc, - BadgeX, - BaggageClaim, - Ban, - Banana, - Bandage, - Banknote, - Barcode, - Baseline, - Bath, - Battery, - BluetoothSearching, - Facebook, - Instagram, - Linkedin, - InspectionPanel, - GitMerge, - GitPullRequest, - GitBranch, - PencilRuler, - GitGraph, - PencilLine, - Pen, - Pencil, - Pin, - GitCommitVertical, -} from "@react-zero-ui/icon-sprite"; -import Link from "next/link"; -import HeaderBar from "../components/HeaderBar"; -import StatsCard from "../components/StatsCard"; -import { SectionGrid } from "../components/SectionGrid"; - -export const metadata = { - title: "Zero UI Icon Sprite - The fastest way to do icons in React", - description: "Lucide to SVG sprite for React. w/custom icon support.", - alternates: { canonical: "https://zero-ui.dev/icon-sprite" }, -}; - -const page = () => { - return ( -
-
- - Lucide to SVG sprite solution for React. w/custom icon support.{" "} - - See Github - - - } - activeTab="sprite" - /> - -
- - -
- -

Open DevTools → Elements to compare document size and structure.

- -
-
-

150 Icons - loaded with Zero Icon Sprite

-
Sprite-based rendering
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- ); -}; - -export default page; diff --git a/examples/demo/src/app/(sprite-demo)/layout.tsx b/examples/demo/src/app/(sprite-demo)/layout.tsx deleted file mode 100644 index 1292243..0000000 --- a/examples/demo/src/app/(sprite-demo)/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -const layout = ({ children }: { children: React.ReactNode }) => { - return ( -
- {children} -
- ); -}; - -export default layout; diff --git a/examples/demo/src/app/(sprite-demo)/lucide-react/page.tsx b/examples/demo/src/app/(sprite-demo)/lucide-react/page.tsx deleted file mode 100644 index 2dd3155..0000000 --- a/examples/demo/src/app/(sprite-demo)/lucide-react/page.tsx +++ /dev/null @@ -1,353 +0,0 @@ -import { - AlarmSmoke, - Album, - AArrowDown, - AArrowUp, - Accessibility, - Activity, - Airplay, - AirVent, - ALargeSmall, - AlarmClock, - AlarmClockCheck, - AlarmClockMinus, - AlarmClockOff, - AlarmClockPlus, - AlignCenter, - AlignCenterHorizontal, - AlignCenterVertical, - AlignEndHorizontal, - AlignEndVertical, - AlignHorizontalDistributeCenter, - AlignHorizontalDistributeEnd, - AlignHorizontalDistributeStart, - AlignHorizontalJustifyCenter, - AlignHorizontalJustifyEnd, - AlignHorizontalJustifyStart, - AlignHorizontalSpaceAround, - AlignHorizontalSpaceBetween, - AlignJustify, - AlignLeft, - AlignRight, - AlignStartHorizontal, - AlignStartVertical, - AlignVerticalDistributeCenter, - AlignVerticalDistributeEnd, - AlignVerticalDistributeStart, - AlignVerticalJustifyCenter, - AlignVerticalJustifyEnd, - AlignVerticalJustifyStart, - AlignVerticalSpaceAround, - AlignVerticalSpaceBetween, - Ambulance, - Ampersand, - Ampersands, - Amphora, - Anchor, - Angry, - Annoyed, - Antenna, - Anvil, - Aperture, - Apple, - AppWindow, - AppWindowMac, - Archive, - ArchiveRestore, - ArchiveX, - Armchair, - ArrowBigDown, - ArrowBigDownDash, - ArrowBigLeft, - ArrowBigLeftDash, - ArrowBigRight, - ArrowBigRightDash, - ArrowBigUp, - ArrowBigUpDash, - ArrowDown, - ArrowDown01, - ArrowDown10, - ArrowDownAZ, - ArrowDownFromLine, - ArrowDownLeft, - ArrowDownNarrowWide, - ArrowDownRight, - ArrowDownToDot, - ArrowDownToLine, - ArrowDownUp, - ArrowDownWideNarrow, - ArrowDownZA, - ArrowLeft, - ArrowLeftFromLine, - ArrowLeftRight, - ArrowLeftToLine, - ArrowRight, - ArrowRightFromLine, - ArrowRightLeft, - ArrowRightToLine, - ArrowsUpFromLine, - ArrowUp, - ArrowUp01, - ArrowUp10, - ArrowUpAZ, - ArrowUpDown, - ArrowUpFromDot, - ArrowUpFromLine, - ArrowUpLeft, - ArrowUpNarrowWide, - ArrowUpRight, - ArrowUpToLine, - ArrowUpWideNarrow, - ArrowUpZA, - Asterisk, - Atom, - AtSign, - AudioLines, - AudioWaveform, - Award, - Axe, - Axis3d, - Baby, - Backpack, - Badge, - BadgeAlert, - BadgeCent, - BadgeCheck, - BadgeDollarSign, - BadgeEuro, - BadgeIndianRupee, - BadgeInfo, - BadgeJapaneseYen, - BadgeMinus, - BadgePercent, - BadgePlus, - BadgePoundSterling, - BadgeRussianRuble, - BadgeSwissFranc, - BadgeX, - BaggageClaim, - Ban, - Banana, - Bandage, - Banknote, - Barcode, - Baseline, - Bath, - Battery, - BluetoothSearching, - Facebook, - Instagram, - Linkedin, - InspectionPanel, - Pin, - Pencil, - Pen, - PencilLine, - PencilRuler, - GitGraph, - GitBranch, - GitPullRequest, - GitMerge, - GitCommit, -} from "lucide-react"; -import HeaderBar from "../components/HeaderBar"; -import StatsCard from "../components/StatsCard"; -import { SectionGrid } from "../components/SectionGrid"; - -export const metadata = { - title: "Lucide React Icon Demo - Lucide to React solution", - description: "See the size difference between Lucide React and Zero UI Icon Sprite. Zero UI Icon Sprite is 300% smaller.", - alternates: { canonical: "https://zero-ui.dev/lucide-react" }, -}; - -const page = () => { - return ( -
-
- - -
- - -
- -

Open DevTools → Elements to compare document size and structure.

- -
-
-

150 Icons - loaded with Lucide React

-
Component-based rendering
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- ); -}; - -export default page; diff --git a/examples/demo/src/app/(test)/ReactState.tsx b/examples/demo/src/app/(test)/ReactState.tsx deleted file mode 100644 index bb6dac2..0000000 --- a/examples/demo/src/app/(test)/ReactState.tsx +++ /dev/null @@ -1,156 +0,0 @@ -"use client"; - -import { useState } from "react"; - -export function TestComponentWithState() { - const [accent, setAccent] = useState<"violet" | "emerald" | "amber">("violet"); - const [theme, setTheme] = useState<"light" | "dark">("light"); - const [menuOpen, setMenuOpen] = useState(false); - - return ( -
-
- - - - -
- ); -} - -// Header Component -function Header({ theme }: { theme: "light" | "dark" }) { - return ( -
-

- React State Management -

- -

- Reactive state management with React
- Re-renders O(n) -

-
- ); -} - -// Theme Switcher Component -function ThemeSwitcher({ theme, setTheme }: { theme: "light" | "dark"; setTheme: (t: "light" | "dark") => void }) { - return ( -
- - -
- ); -} - -// Accent Picker Component -function AccentPicker({ - accent, - setAccent, - theme, -}: { - accent: "violet" | "emerald" | "amber"; - setAccent: (a: "violet" | "emerald" | "amber") => void; - theme: "light" | "dark"; -}) { - return ( -
-

Choose Accent

-
-
-
- ); -} - -// Interactive Card Component -function InteractiveCard({ - theme, - menuOpen, - setMenuOpen, - accent, -}: { - theme: "light" | "dark"; - menuOpen: boolean; - setMenuOpen: (open: boolean) => void; - accent: "violet" | "emerald" | "amber"; -}) { - return ( -
-
-

Open Menu Demo

- - -
- - {/* Sliding Menu */} -
-
-

✨ This panel slides open and has to re-render!

-
-
-
- ); -} - -// State Display Component -function StateDisplay({ theme, accent, menuOpen }: { theme: "light" | "dark"; accent: "violet" | "emerald" | "amber"; menuOpen: boolean }) { - return ( -
-
-
theme: {theme}
-
accent: {accent}
-
menu: {menuOpen ? "Open" : "Closed"}
-
-
- ); -} diff --git a/examples/demo/src/app/(test)/ZeroState.tsx b/examples/demo/src/app/(test)/ZeroState.tsx deleted file mode 100644 index 1305557..0000000 --- a/examples/demo/src/app/(test)/ZeroState.tsx +++ /dev/null @@ -1,133 +0,0 @@ -"use client"; - -import { useUI } from "@react-zero-ui/core"; - -export function TestComponentZero() { - const [, setTheme] = useUI<"light" | "dark">("theme", "light"); - const [, setAccent] = useUI<"violet" | "emerald" | "amber">("accent", "violet"); - const [, setMenuOpen] = useUI<"true" | "false">("menu-open", "false"); - - return ( -
-
- - - setMenuOpen((prev) => (prev === "true" ? "false" : "true"))} /> - -
- ); -} - -// Header Component - Never re-renders! -function Header() { - return ( -
-

Zero UI

- -

- Reactive state without re-rendering .
- - Zero re-renders,{" "} - Reactive &{" "} - Global state. - -

-
- ); -} - -// Theme Switcher - Never re-renders! -function ThemeSwitcher({ setTheme }: { setTheme: (t: "light" | "dark") => void }) { - return ( -
- - -
- ); -} - -// Accent Picker - Never re-renders! -function AccentPicker({ setAccent }: { setAccent: (a: "violet" | "emerald" | "amber") => void }) { - return ( -
-

Choose Accent

-
-
-
- ); -} - -// Interactive Card - Never re-renders! -function InteractiveCard({ toggleMenu }: { toggleMenu: () => void }) { - return ( -
-
-

Open Menu Demo

- -
- - {/* Sliding Panel */} -
-
-

✨ This panel slides open without re-rendering!

-
-
-
- ); -} - -// State Display - Never re-renders! -function StateDisplay() { - return ( -
-
-
- theme: Light - Dark -
-
- accent: - Violet - Emerald - Amber -
-
- menu: - Open - Closed -
-
-
- ); -} diff --git a/examples/demo/src/app/(test)/layout.tsx b/examples/demo/src/app/(test)/layout.tsx deleted file mode 100644 index 9484d2f..0000000 --- a/examples/demo/src/app/(test)/layout.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { ReactScan } from "../components/ReactScan"; - -export const metadata = { - title: "React Zero UI Demo - Zero Re-Render State Management for React", - description: "Compare the difference in re-renders between React and Zero UI.", - alternates: { canonical: "https://zero-ui.dev/" }, -}; - -const layout: React.FC<{ children: React.ReactNode }> = ({ children }) => { - return ( -
-