@barefootjs/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/core/README.md +84 -0
- package/dist/docs/core/adapters/adapter-architecture.md +262 -0
- package/dist/docs/core/adapters/csr.md +78 -0
- package/dist/docs/core/adapters/custom-adapter.md +357 -0
- package/dist/docs/core/adapters/go-template-adapter.md +269 -0
- package/dist/docs/core/adapters/hono-adapter.md +199 -0
- package/dist/docs/core/adapters.md +37 -0
- package/dist/docs/core/advanced/code-splitting.md +142 -0
- package/dist/docs/core/advanced/compiler-internals.md +331 -0
- package/dist/docs/core/advanced/error-codes.md +261 -0
- package/dist/docs/core/advanced/ir-schema.md +65 -0
- package/dist/docs/core/advanced/performance.md +115 -0
- package/dist/docs/core/advanced/xyflow-browser-bundle.md +69 -0
- package/dist/docs/core/advanced.md +11 -0
- package/dist/docs/core/components/children-slots.md +150 -0
- package/dist/docs/core/components/component-authoring.md +207 -0
- package/dist/docs/core/components/context-api.md +236 -0
- package/dist/docs/core/components/portals.md +192 -0
- package/dist/docs/core/components/props-type-safety.md +97 -0
- package/dist/docs/core/components/styling.md +37 -0
- package/dist/docs/core/components.md +19 -0
- package/dist/docs/core/core-concepts/ai-native.md +83 -0
- package/dist/docs/core/core-concepts/backend-freedom.md +31 -0
- package/dist/docs/core/core-concepts/how-it-works.md +147 -0
- package/dist/docs/core/core-concepts/mpa-style.md +36 -0
- package/dist/docs/core/core-concepts/reactivity.md +35 -0
- package/dist/docs/core/core-concepts.md +28 -0
- package/dist/docs/core/introduction.md +87 -0
- package/dist/docs/core/llms.txt +53 -0
- package/dist/docs/core/quick-start.mdx +160 -0
- package/dist/docs/core/reactivity/create-effect.md +125 -0
- package/dist/docs/core/reactivity/create-memo.md +91 -0
- package/dist/docs/core/reactivity/create-signal.md +128 -0
- package/dist/docs/core/reactivity/on-cleanup.md +67 -0
- package/dist/docs/core/reactivity/on-mount.md +70 -0
- package/dist/docs/core/reactivity/props-reactivity.md +91 -0
- package/dist/docs/core/reactivity/untrack.md +69 -0
- package/dist/docs/core/reactivity.md +28 -0
- package/dist/docs/core/rendering/client-directive.md +82 -0
- package/dist/docs/core/rendering/fragment.md +43 -0
- package/dist/docs/core/rendering/jsx-compatibility.md +163 -0
- package/dist/docs/core/rendering.md +14 -0
- package/dist/index.js +25490 -0
- package/dist/tokens.json +74 -0
- package/package.json +37 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Context API
|
|
3
|
+
description: Share state with deeply nested children without prop drilling using createContext and useContext.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Context API
|
|
7
|
+
|
|
8
|
+
Context shares state with deeply nested children without prop drilling. It is the foundation of compound components (Dialog, Accordion, Tabs).
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
"use client"
|
|
12
|
+
import { createContext, useContext } from '@barefootjs/client'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## `createContext`
|
|
17
|
+
|
|
18
|
+
Creates a new context with an optional default value.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
const MyContext = createContext<T>(defaultValue?: T)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Type:**
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
type Context<T> = {
|
|
28
|
+
readonly id: symbol
|
|
29
|
+
readonly defaultValue: T | undefined
|
|
30
|
+
readonly Provider: (props: { value: T; children?: unknown }) => unknown
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## `Context.Provider`
|
|
36
|
+
|
|
37
|
+
Provides a value to all descendants. Components inside the provider tree read it with `useContext`.
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<MyContext.Provider value={someValue}>
|
|
41
|
+
{props.children}
|
|
42
|
+
</MyContext.Provider>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The compiler transforms this into a `provideContext()` call. The value is set synchronously before children initialize.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## `useContext`
|
|
49
|
+
|
|
50
|
+
Reads the current value from a context.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
const value = useContext(MyContext)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Behavior:**
|
|
57
|
+
|
|
58
|
+
- If a `Provider` ancestor exists, returns the provided value
|
|
59
|
+
- If no `Provider` exists and a default value was passed to `createContext`, returns the default
|
|
60
|
+
- Otherwise returns `undefined` — guard with optional chaining (`store?.value`)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## Basic Example
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
"use client"
|
|
67
|
+
import { createContext, useContext } from '@barefootjs/client'
|
|
68
|
+
|
|
69
|
+
// 1. Create the context
|
|
70
|
+
const ThemeContext = createContext<'light' | 'dark'>('light')
|
|
71
|
+
|
|
72
|
+
// 2. Provider component
|
|
73
|
+
export function ThemeProvider(props: { theme: 'light' | 'dark'; children?: Child }) {
|
|
74
|
+
return (
|
|
75
|
+
<ThemeContext.Provider value={props.theme}>
|
|
76
|
+
{props.children}
|
|
77
|
+
</ThemeContext.Provider>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 3. Consumer component
|
|
82
|
+
export function ThemedButton(props: { children?: Child }) {
|
|
83
|
+
const handleMount = (el: HTMLButtonElement) => {
|
|
84
|
+
const theme = useContext(ThemeContext)
|
|
85
|
+
el.className = theme === 'dark' ? 'btn-dark' : 'btn-light'
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return <button ref={handleMount}>{props.children}</button>
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// Usage
|
|
94
|
+
<ThemeProvider theme="dark">
|
|
95
|
+
<ThemedButton>Click me</ThemedButton> {/* Gets dark styling */}
|
|
96
|
+
</ThemeProvider>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
## Compound Components
|
|
101
|
+
|
|
102
|
+
A group of related components sharing internal state. The root provides state; sub-components consume it.
|
|
103
|
+
|
|
104
|
+
### Example: Accordion
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
"use client"
|
|
108
|
+
import { createSignal, createContext, useContext, createEffect } from '@barefootjs/client'
|
|
109
|
+
|
|
110
|
+
// Context type
|
|
111
|
+
interface AccordionContextValue {
|
|
112
|
+
activeItem: () => string | null
|
|
113
|
+
toggle: (id: string) => void
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Create context
|
|
117
|
+
const AccordionContext = createContext<AccordionContextValue>()
|
|
118
|
+
|
|
119
|
+
// Root component — provides state
|
|
120
|
+
function Accordion(props: { children?: Child }) {
|
|
121
|
+
const [activeItem, setActiveItem] = createSignal<string | null>(null)
|
|
122
|
+
|
|
123
|
+
const toggle = (id: string) => {
|
|
124
|
+
setActiveItem(prev => prev === id ? null : id)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<AccordionContext.Provider value={{ activeItem, toggle }}>
|
|
129
|
+
<div data-slot="accordion">{props.children}</div>
|
|
130
|
+
</AccordionContext.Provider>
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Trigger — toggles the active item
|
|
135
|
+
function AccordionTrigger(props: { itemId: string; children?: Child }) {
|
|
136
|
+
const handleMount = (el: HTMLButtonElement) => {
|
|
137
|
+
const ctx = useContext(AccordionContext)
|
|
138
|
+
|
|
139
|
+
el.addEventListener('click', () => {
|
|
140
|
+
ctx.toggle(props.itemId)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
createEffect(() => {
|
|
144
|
+
const isOpen = ctx.activeItem() === props.itemId
|
|
145
|
+
el.setAttribute('aria-expanded', String(isOpen))
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return <button ref={handleMount}>{props.children}</button>
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Content — shows/hides based on active item
|
|
153
|
+
function AccordionContent(props: { itemId: string; children?: Child }) {
|
|
154
|
+
const handleMount = (el: HTMLElement) => {
|
|
155
|
+
const ctx = useContext(AccordionContext)
|
|
156
|
+
|
|
157
|
+
createEffect(() => {
|
|
158
|
+
const isOpen = ctx.activeItem() === props.itemId
|
|
159
|
+
el.hidden = !isOpen
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return <div ref={handleMount}>{props.children}</div>
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Usage:**
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
<Accordion>
|
|
171
|
+
<AccordionTrigger itemId="faq-1">What is BarefootJS?</AccordionTrigger>
|
|
172
|
+
<AccordionContent itemId="faq-1">
|
|
173
|
+
<p>A JSX-to-template compiler with signal-based reactivity.</p>
|
|
174
|
+
</AccordionContent>
|
|
175
|
+
|
|
176
|
+
<AccordionTrigger itemId="faq-2">How does hydration work?</AccordionTrigger>
|
|
177
|
+
<AccordionContent itemId="faq-2">
|
|
178
|
+
<p>Marker-driven: bf-* attributes tell the client JS where to attach.</p>
|
|
179
|
+
</AccordionContent>
|
|
180
|
+
</Accordion>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
## Reactive Context Values
|
|
185
|
+
|
|
186
|
+
Context values can contain signal getters. Effects that read them re-run when the signal changes:
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
// Provider passes signal getter
|
|
190
|
+
<AccordionContext.Provider value={{ activeItem, toggle }}>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
// Consumer reads inside createEffect — reactive
|
|
195
|
+
const ctx = useContext(AccordionContext)
|
|
196
|
+
createEffect(() => {
|
|
197
|
+
const isOpen = ctx.activeItem() === props.itemId // Tracks activeItem signal
|
|
198
|
+
el.hidden = !isOpen
|
|
199
|
+
})
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`ctx.activeItem()` inside the effect subscribes to `activeItem`. When it changes, only affected effects re-run.
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
## Context Without a Default
|
|
206
|
+
|
|
207
|
+
Without a default value, `useContext` throws if no `Provider` ancestor exists. Recommended for compound components:
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
const DialogContext = createContext<DialogContextValue>()
|
|
211
|
+
|
|
212
|
+
// If DialogTrigger is used outside a Dialog, useContext throws
|
|
213
|
+
function DialogTrigger(props: { children?: Child }) {
|
|
214
|
+
const handleMount = (el: HTMLElement) => {
|
|
215
|
+
const ctx = useContext(DialogContext) // Throws if no Dialog ancestor
|
|
216
|
+
// ...
|
|
217
|
+
}
|
|
218
|
+
return <button ref={handleMount}>{props.children}</button>
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
This catches composition errors early — the error identifies the missing provider.
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
## Context With a Default
|
|
226
|
+
|
|
227
|
+
With a default, `useContext` always succeeds:
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
const ThemeContext = createContext<'light' | 'dark'>('light')
|
|
231
|
+
|
|
232
|
+
// Works even without a ThemeProvider ancestor — returns 'light'
|
|
233
|
+
const theme = useContext(ThemeContext)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Use for optional contexts with a sensible fallback.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Portals
|
|
3
|
+
description: Render elements outside their parent DOM hierarchy for overlays, modals, and tooltips.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Portals
|
|
7
|
+
|
|
8
|
+
Portals render elements outside their parent DOM hierarchy — useful for overlays, modals, and tooltips that need to escape `overflow: hidden` or `z-index` stacking contexts.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
"use client"
|
|
12
|
+
import { createPortal } from '@barefootjs/client'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## `createPortal`
|
|
17
|
+
|
|
18
|
+
Moves an element to a different DOM container.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
createPortal(children, container?, options?)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Type:**
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
type Portal = {
|
|
28
|
+
element: HTMLElement
|
|
29
|
+
unmount: () => void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface PortalOptions {
|
|
33
|
+
ownerScope?: Element // Component scope for scoped queries
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function createPortal(
|
|
37
|
+
children: HTMLElement | string,
|
|
38
|
+
container?: HTMLElement, // Default: document.body
|
|
39
|
+
options?: PortalOptions
|
|
40
|
+
): Portal
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Returns** a `Portal` object with:
|
|
44
|
+
- `element` — the mounted DOM element
|
|
45
|
+
- `unmount()` — removes the element from the container
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## Basic Usage
|
|
49
|
+
|
|
50
|
+
Create portals inside `ref` callbacks:
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
"use client"
|
|
54
|
+
import { createSignal, createEffect, createPortal, isSSRPortal } from '@barefootjs/client'
|
|
55
|
+
|
|
56
|
+
export function Tooltip(props: { text: string; children?: Child }) {
|
|
57
|
+
const [visible, setVisible] = createSignal(false)
|
|
58
|
+
|
|
59
|
+
const handleMount = (el: HTMLElement) => {
|
|
60
|
+
// Move to document.body to avoid overflow/z-index issues
|
|
61
|
+
if (el.parentNode !== document.body && !isSSRPortal(el)) {
|
|
62
|
+
const ownerScope = el.closest('[bf-s]') ?? undefined
|
|
63
|
+
createPortal(el, document.body, { ownerScope })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
createEffect(() => {
|
|
67
|
+
el.hidden = !visible()
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div>
|
|
73
|
+
<span
|
|
74
|
+
onMouseEnter={() => setVisible(true)}
|
|
75
|
+
onMouseLeave={() => setVisible(false)}
|
|
76
|
+
>
|
|
77
|
+
{props.children}
|
|
78
|
+
</span>
|
|
79
|
+
<div className="tooltip" ref={handleMount}>
|
|
80
|
+
{props.text}
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## SSR Portal Detection
|
|
89
|
+
|
|
90
|
+
`isSSRPortal` checks whether an element was already portaled during SSR to prevent double-portaling:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
"use client"
|
|
94
|
+
import { isSSRPortal, createPortal } from '@barefootjs/client'
|
|
95
|
+
|
|
96
|
+
const handleMount = (el: HTMLElement) => {
|
|
97
|
+
// Skip if already portaled during SSR
|
|
98
|
+
if (el.parentNode !== document.body && !isSSRPortal(el)) {
|
|
99
|
+
createPortal(el, document.body)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
After hydration, remove SSR placeholders:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
"use client"
|
|
108
|
+
import { cleanupPortalPlaceholder } from '@barefootjs/client'
|
|
109
|
+
|
|
110
|
+
declare const portalId: string
|
|
111
|
+
cleanupPortalPlaceholder(portalId)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
## Owner Scope
|
|
116
|
+
|
|
117
|
+
A portaled element is outside its original component's scope, so `find()` cannot locate it. The `ownerScope` option links it back:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
const handleMount = (el: HTMLElement) => {
|
|
121
|
+
const ownerScope = el.closest('[bf-s]') ?? undefined
|
|
122
|
+
createPortal(el, document.body, { ownerScope })
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`bf-po` on the moved element lets scoped queries from the owner still find it.
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
## Dialog Example
|
|
130
|
+
|
|
131
|
+
Dialog overlays are a common portal use case:
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
"use client"
|
|
135
|
+
import { createPortal, isSSRPortal, useContext, createEffect } from '@barefootjs/client'
|
|
136
|
+
|
|
137
|
+
function DialogOverlay() {
|
|
138
|
+
const handleMount = (el: HTMLElement) => {
|
|
139
|
+
// Portal to body
|
|
140
|
+
if (el.parentNode !== document.body && !isSSRPortal(el)) {
|
|
141
|
+
const ownerScope = el.closest('[bf-s]') ?? undefined
|
|
142
|
+
createPortal(el, document.body, { ownerScope })
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const ctx = useContext(DialogContext)
|
|
146
|
+
|
|
147
|
+
// Reactive visibility
|
|
148
|
+
createEffect(() => {
|
|
149
|
+
const isOpen = ctx.open()
|
|
150
|
+
el.dataset.state = isOpen ? 'open' : 'closed'
|
|
151
|
+
el.className = isOpen ? 'overlay overlay-visible' : 'overlay overlay-hidden'
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Click overlay to close
|
|
155
|
+
el.addEventListener('click', () => {
|
|
156
|
+
ctx.onOpenChange(false)
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return <div data-slot="dialog-overlay" ref={handleMount} />
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The overlay accesses `DialogContext` from the component tree but is moved to `document.body` to escape CSS containment.
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
## Cleanup
|
|
168
|
+
|
|
169
|
+
Combine `portal.unmount()` with `onCleanup`:
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
"use client"
|
|
173
|
+
import { createPortal, onCleanup } from '@barefootjs/client'
|
|
174
|
+
|
|
175
|
+
const handleMount = (el: HTMLElement) => {
|
|
176
|
+
const portal = createPortal(el, document.body)
|
|
177
|
+
|
|
178
|
+
onCleanup(() => {
|
|
179
|
+
portal.unmount()
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
## Custom Container
|
|
186
|
+
|
|
187
|
+
Specify a different container instead of the default `document.body`:
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
const container = document.getElementById('modal-root')!
|
|
191
|
+
createPortal(el, container)
|
|
192
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Props & Type Safety
|
|
3
|
+
description: Type component props with TypeScript interfaces and preserve type information through compilation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Props & Type Safety
|
|
7
|
+
|
|
8
|
+
The compiler preserves TypeScript type information through compilation. Adapters use it to generate type-safe templates.
|
|
9
|
+
|
|
10
|
+
Props in client components are reactive — see [Props Reactivity](../reactivity/props-reactivity.md). This page covers typing patterns.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Defining Props
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
interface GreetingProps {
|
|
17
|
+
name: string
|
|
18
|
+
greeting?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function Greeting(props: GreetingProps) {
|
|
22
|
+
return <h1>{props.greeting ?? 'Hello'}, {props.name}</h1>
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Default Values
|
|
28
|
+
|
|
29
|
+
Use nullish coalescing (`??`) on the props object:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
function Button(props: { variant?: 'default' | 'primary'; children?: Child }) {
|
|
33
|
+
const variant = props.variant ?? 'default'
|
|
34
|
+
return <button className={variant}>{props.children}</button>
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For initial-value-only props, default parameter syntax works. Add `@bf-ignore` to suppress the `BF043` destructuring warning:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// @bf-ignore props-destructuring
|
|
42
|
+
function Counter({ initial = 0 }: { initial?: number }) {
|
|
43
|
+
const [count, setCount] = createSignal(initial)
|
|
44
|
+
return <button onClick={() => setCount(n => n + 1)}>{count()}</button>
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Extending HTML Attributes
|
|
50
|
+
|
|
51
|
+
Extend HTML attribute types for components wrapping native elements:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import type { ButtonHTMLAttributes } from '@barefootjs/jsx'
|
|
55
|
+
|
|
56
|
+
interface ButtonProps extends ButtonHTMLAttributes {
|
|
57
|
+
variant?: 'default' | 'primary' | 'destructive'
|
|
58
|
+
size?: 'sm' | 'md' | 'lg'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function Button(props: ButtonProps) {
|
|
62
|
+
const variant = props.variant ?? 'default'
|
|
63
|
+
const size = props.size ?? 'md'
|
|
64
|
+
const classes = `btn btn-${variant} btn-${size} ${props.className ?? ''}`
|
|
65
|
+
|
|
66
|
+
return <button className={classes} {...props}>{props.children}</button>
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Callers can pass standard button attributes (`type`, `disabled`, `aria-label`, etc.) alongside custom props.
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## Rest Spreading
|
|
74
|
+
|
|
75
|
+
Rest spreading captures values once, so use it for server components or non-reactive attributes:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
function Card(props: { title: string; children?: Child } & HTMLAttributes) {
|
|
79
|
+
return (
|
|
80
|
+
<div className={props.className}>
|
|
81
|
+
<h2>{props.title}</h2>
|
|
82
|
+
{props.children}
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
<Card title="Dashboard" className="shadow-lg" data-testid="dashboard-card">
|
|
90
|
+
<p>Content</p>
|
|
91
|
+
</Card>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
## Type Preservation
|
|
96
|
+
|
|
97
|
+
The compiler carries TypeScript type information through the IR. Each adapter uses it for type-safe server output. See [Adapters](../adapters.md) for backend-specific type mapping.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Style Overrides
|
|
3
|
+
description: How user-supplied classes override component base classes via CSS Cascade Layers
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Style Overrides
|
|
7
|
+
|
|
8
|
+
User-supplied classes always override component base classes — guaranteed by CSS Cascade Layers. No runtime JS, no merge functions, no class-order concerns.
|
|
9
|
+
|
|
10
|
+
Styles in a named `@layer` lose to un-layered styles regardless of specificity. BarefootJS puts component base classes into `@layer components`:
|
|
11
|
+
|
|
12
|
+
```css
|
|
13
|
+
@layer preflights, base, shortcuts, components, default;
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The compiler's `cssLayerPrefix` option prefixes base classes at compile time:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
// Source
|
|
20
|
+
const baseClasses = 'inline-flex items-center bg-primary text-primary-foreground'
|
|
21
|
+
|
|
22
|
+
// Compiled (cssLayerPrefix: 'components')
|
|
23
|
+
const baseClasses = 'layer-components:inline-flex layer-components:items-center layer-components:bg-primary layer-components:text-primary-foreground'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
User classes remain un-layered and always win:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
<Button className="bg-red-500">
|
|
30
|
+
|
|
31
|
+
layer-components:bg-primary → @layer components (lower priority)
|
|
32
|
+
bg-red-500 → un-layered (higher priority)
|
|
33
|
+
|
|
34
|
+
Result: bg-red-500 wins.
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Zero runtime cost. Prefixing applies to the IR, so all adapters benefit.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Components
|
|
3
|
+
description: How to author, compose, and type components in BarefootJS, including props, children, context, and portals.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Components
|
|
7
|
+
|
|
8
|
+
For the `"use client"` directive and the server/client boundary, see [Backend Freedom](./core-concepts/backend-freedom.md#the-use-client-directive).
|
|
9
|
+
|
|
10
|
+
## Pages
|
|
11
|
+
|
|
12
|
+
| Topic | Description |
|
|
13
|
+
|-------|-------------|
|
|
14
|
+
| [Component Authoring](./components/component-authoring.md) | Server components, client components, and the compilation model |
|
|
15
|
+
| [Props & Type Safety](./components/props-type-safety.md) | Typing props, defaults, and rest spreading |
|
|
16
|
+
| [Children & Slots](./components/children-slots.md) | Children prop, the `Slot` component, and the `asChild` pattern |
|
|
17
|
+
| [Context API](./components/context-api.md) | Sharing state across compound components with `createContext` / `useContext` |
|
|
18
|
+
| [Portals](./components/portals.md) | Rendering elements outside their parent DOM hierarchy |
|
|
19
|
+
| [Style Overrides](./components/styling.md) | How user-supplied classes override component base classes via CSS Cascade Layers |
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: AI-native Development
|
|
3
|
+
description: Millisecond IR tests and the bf CLI for human + agent workflows
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# AI-native Development
|
|
7
|
+
|
|
8
|
+
BarefootJS is designed so both humans and AI agents can build components without reading source files. Two pillars carry that:
|
|
9
|
+
|
|
10
|
+
1. **IR tests** verify component structure in milliseconds, no browser required.
|
|
11
|
+
2. **The `bf` CLI** drives discovery, scaffolding, testing, and debugging — every command supports `--json` for machine consumption.
|
|
12
|
+
|
|
13
|
+
## IR Tests
|
|
14
|
+
|
|
15
|
+
`renderToTest()` verifies component structure, signals, events, and accessibility against the compiler's IR — in milliseconds, without a browser. Real interactions and visual behavior still need E2E tests, but structural issues are caught before you get there:
|
|
16
|
+
|
|
17
|
+
> **Test runner**: the snippet below uses `bun:test`. `bf init` picks the runner that matches your package manager — bun users get `bun:test`, everyone else gets [Vitest](https://vitest.dev) (`from 'vitest'`). The surfaces are API-compatible, so swap the import line if you're following along under npm / pnpm / yarn. `bf gen test` and `bf gen component` emit the right line for you.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { describe, test, expect } from 'bun:test'
|
|
21
|
+
import { readFileSync } from 'fs'
|
|
22
|
+
import { resolve } from 'path'
|
|
23
|
+
import { renderToTest } from '@barefootjs/test'
|
|
24
|
+
|
|
25
|
+
const source = readFileSync(resolve(__dirname, 'Counter.tsx'), 'utf-8')
|
|
26
|
+
|
|
27
|
+
describe('Counter', () => {
|
|
28
|
+
const ir = renderToTest(source, 'Counter.tsx')
|
|
29
|
+
|
|
30
|
+
test('compiles cleanly with a `count` signal', () => {
|
|
31
|
+
expect(ir.errors).toEqual([])
|
|
32
|
+
expect(ir.signals).toContain('count')
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('has a button wired to a click handler', () => {
|
|
36
|
+
const button = ir.find({ tag: 'button' })
|
|
37
|
+
expect(button).not.toBeNull()
|
|
38
|
+
expect(button!.events).toContain('click')
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`renderToTest(source, filePath)` takes the component source as a string and returns a queryable `TestResult` (`root`, `signals`, `memos`, `errors`, plus `find`/`findAll`/`findByText` for traversal). The same call shape is what `bf gen component` and `bf gen test` write — see the auto-generated `index.test.tsx` next to any component for a richer worked example.
|
|
44
|
+
|
|
45
|
+
See [IR Schema Reference](../advanced/ir-schema.md) for the full specification.
|
|
46
|
+
|
|
47
|
+
## CLI Workflow
|
|
48
|
+
|
|
49
|
+
Install via `npm create barefootjs@latest`. Run `bf --help` for the full command surface — this section shows the daily loop, not the manual page.
|
|
50
|
+
|
|
51
|
+
A typical component task is one straight line:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
search → docs → add → <pm> test → debug
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bf search dialog # find a component in the registry + docs
|
|
59
|
+
bf docs dialog # read its API (props, examples, a11y)
|
|
60
|
+
bf add dialog # copy it into your project
|
|
61
|
+
<pm> test # verify the IR (bun test / npm test / pnpm test / yarn test)
|
|
62
|
+
bf debug graph dialog # inspect reactivity
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
When nothing in the registry fits, `bf gen component <name> <comps...>` scaffolds a new component composed from existing ones, with an IR test stub. When a signal doesn't update what you expect, `bf debug trace <comp> <signal>` walks the propagation path.
|
|
66
|
+
|
|
67
|
+
**Visual preview**: hosted previews live at [ui.barefootjs.dev/components/<name>](https://ui.barefootjs.dev). Standalone `bf preview` for npm-installed projects is tracked in [#885](https://github.com/piconic-ai/barefootjs/issues/885).
|
|
68
|
+
|
|
69
|
+
## Agent Loop
|
|
70
|
+
|
|
71
|
+
The same workflow driven by an AI agent — say, "add a settings form":
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bf search settings-form --json
|
|
75
|
+
bf docs field --json
|
|
76
|
+
bf docs switch --json
|
|
77
|
+
bf gen component settings-form field switch label
|
|
78
|
+
# edit components/ui/settings-form/index.tsx
|
|
79
|
+
bun test components/ui/settings-form/index.test.tsx # or `npm test -- <path>`, `pnpm test <path>`, etc.
|
|
80
|
+
bf debug graph settings-form
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
No source files read — the CLI is the API.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Backend Freedom
|
|
3
|
+
description: How adapters let the same JSX run on any server — Hono, Go, and beyond
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Backend Freedom
|
|
7
|
+
|
|
8
|
+
JSX gives you components with props, composition, and type checking. But server rendering with JSX usually means running Node.js on the server.
|
|
9
|
+
|
|
10
|
+
BarefootJS compiles JSX at build time into your backend's native template format. Go renders `.tmpl` with `html/template`; Perl renders `.html.ep` with Mojolicious; Hono renders the generated `.tsx`. For non-Node backends, no Node.js runs at serving time. The compiler produces a backend-agnostic IR, then an adapter converts it:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
JSX → IR (backend-agnostic) → Adapter → Template
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
| Language | Adapter | Notes |
|
|
17
|
+
|----------|---------|-------|
|
|
18
|
+
| TypeScript | [HonoAdapter](../adapters/hono-adapter.md) | Hono / JSX-based TS servers |
|
|
19
|
+
| TypeScript | [TestAdapter](https://github.com/piconic-ai/barefootjs/tree/main/packages/test) | IR-based component testing |
|
|
20
|
+
| Go | [GoTemplateAdapter](../adapters/go-template-adapter.md) | `html/template` |
|
|
21
|
+
| Perl | [MojoliciousAdapter](https://github.com/piconic-ai/barefootjs/tree/main/packages/adapter-mojolicious) | Mojolicious EP templates |
|
|
22
|
+
|
|
23
|
+
### Planned
|
|
24
|
+
|
|
25
|
+
| Language | Adapter |
|
|
26
|
+
|----------|---------|
|
|
27
|
+
| Rust | (TBD) |
|
|
28
|
+
| Python | Jinja2Adapter |
|
|
29
|
+
| Ruby | ERBAdapter |
|
|
30
|
+
|
|
31
|
+
The IR contract is stable. You can [write a custom adapter](../adapters/custom-adapter.md) for any backend.
|