mirror of
https://github.com/kbenestad/mdcms.git
synced 2026-06-18 15:24:32 +00:00
253 lines
5.9 KiB
Markdown
253 lines
5.9 KiB
Markdown
---
|
|
title: Components
|
|
sort: 110
|
|
section-id: core-concepts
|
|
keywords: components, props, slots, lifecycle, tsx, velox components, islands
|
|
description: The Velox component model, including props, slots, lifecycle hooks, and the islands architecture
|
|
language: en
|
|
---
|
|
|
|
# Components
|
|
|
|
Velox components are TypeScript/TSX files that describe a piece of UI. They are the fundamental building blocks of a Velox application — reusable, composable, and by default rendered entirely on the server.
|
|
|
|
## Defining a Component
|
|
|
|
A basic Velox component is a TypeScript function that returns JSX:
|
|
|
|
```tsx
|
|
// components/Greeting.tsx
|
|
interface GreetingProps {
|
|
name: string;
|
|
formal?: boolean;
|
|
}
|
|
|
|
export default function Greeting({ name, formal = false }: GreetingProps) {
|
|
const salutation = formal ? 'Good day' : 'Hello';
|
|
return <p>{salutation}, {name}!</p>;
|
|
}
|
|
```
|
|
|
|
Use it in a route or another component:
|
|
|
|
```tsx
|
|
---
|
|
import Greeting from '../components/Greeting.tsx';
|
|
---
|
|
|
|
<main>
|
|
<Greeting name="Alice" />
|
|
<Greeting name="Bob" formal />
|
|
</main>
|
|
```
|
|
|
|
## Props
|
|
|
|
Props are typed using TypeScript interfaces or types. All props are validated at compile time — no runtime prop checking overhead.
|
|
|
|
```tsx
|
|
interface CardProps {
|
|
title: string;
|
|
description: string;
|
|
href?: string;
|
|
variant?: 'default' | 'featured' | 'compact';
|
|
children?: VeloxNode;
|
|
}
|
|
|
|
export default function Card({
|
|
title,
|
|
description,
|
|
href,
|
|
variant = 'default',
|
|
children,
|
|
}: CardProps) {
|
|
return (
|
|
<div class={`card card--${variant}`}>
|
|
<h3>{href ? <a href={href}>{title}</a> : title}</h3>
|
|
<p>{description}</p>
|
|
{children && <div class="card__body">{children}</div>}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Default Props
|
|
|
|
Set default values directly in the destructuring parameter, as shown above. Velox does not use a separate `defaultProps` mechanism.
|
|
|
|
### Required vs Optional Props
|
|
|
|
By TypeScript convention, optional props are marked with `?`. Omitting a required prop is a compile-time error.
|
|
|
|
## Slots
|
|
|
|
The `children` prop is the default slot — content placed between the opening and closing tags of a component:
|
|
|
|
```tsx
|
|
<Card title="Welcome" description="Get started">
|
|
<p>This is the card body.</p>
|
|
</Card>
|
|
```
|
|
|
|
### Named Slots
|
|
|
|
For more complex component APIs, use named slot props:
|
|
|
|
```tsx
|
|
interface ModalProps {
|
|
title: VeloxNode;
|
|
footer?: VeloxNode;
|
|
children: VeloxNode;
|
|
}
|
|
|
|
export default function Modal({ title, footer, children }: ModalProps) {
|
|
return (
|
|
<div class="modal">
|
|
<div class="modal__header">{title}</div>
|
|
<div class="modal__body">{children}</div>
|
|
{footer && <div class="modal__footer">{footer}</div>}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
Usage:
|
|
|
|
```tsx
|
|
<Modal
|
|
title={<h2>Confirm Delete</h2>}
|
|
footer={
|
|
<>
|
|
<button onClick={cancel}>Cancel</button>
|
|
<button onClick={confirm}>Delete</button>
|
|
</>
|
|
}
|
|
>
|
|
<p>Are you sure you want to delete this item?</p>
|
|
</Modal>
|
|
```
|
|
|
|
## Server vs Client Components
|
|
|
|
By default, all Velox components run on the server. They have no JavaScript bundle size on the client and cannot use browser APIs or client-side reactivity.
|
|
|
|
To make a component interactive on the client, use the `client:*` hydration directive when you include it in a route:
|
|
|
|
```tsx
|
|
---
|
|
import Counter from '../components/Counter.tsx';
|
|
import LazyChart from '../components/LazyChart.tsx';
|
|
---
|
|
|
|
<!-- Hydrate immediately when page loads -->
|
|
<Counter client:load />
|
|
|
|
<!-- Hydrate when browser is idle -->
|
|
<Counter client:idle />
|
|
|
|
<!-- Hydrate when the component enters the viewport -->
|
|
<LazyChart client:visible />
|
|
|
|
<!-- Hydrate only when a media query matches -->
|
|
<Sidebar client:media="(max-width: 768px)" />
|
|
```
|
|
|
|
### Writing Client Components
|
|
|
|
Client components can use signals, effects, and browser APIs:
|
|
|
|
```tsx
|
|
import { signal, effect, onMount, onCleanup } from 'velox/client';
|
|
|
|
export default function LiveClock() {
|
|
const time = signal(new Date().toLocaleTimeString());
|
|
|
|
onMount(() => {
|
|
const interval = setInterval(() => {
|
|
time.value = new Date().toLocaleTimeString();
|
|
}, 1000);
|
|
|
|
onCleanup(() => clearInterval(interval));
|
|
});
|
|
|
|
return <p>Current time: {time}</p>;
|
|
}
|
|
```
|
|
|
|
## Lifecycle Hooks
|
|
|
|
Client-side components can use the following lifecycle hooks:
|
|
|
|
| Hook | When it runs |
|
|
|------|-------------|
|
|
| `onMount(fn)` | After the component is first rendered and inserted into the DOM |
|
|
| `onUpdate(fn)` | After every re-render (reactive signal change) |
|
|
| `onCleanup(fn)` | Before the component unmounts or before the next `onUpdate` call |
|
|
| `onDestroy(fn)` | When the component is permanently unmounted |
|
|
|
|
```tsx
|
|
import { signal, onMount, onUpdate, onCleanup } from 'velox/client';
|
|
|
|
export default function DataComponent() {
|
|
const data = signal<any[]>([]);
|
|
const loading = signal(true);
|
|
|
|
onMount(async () => {
|
|
const response = await fetch('/api/data');
|
|
data.value = await response.json();
|
|
loading.value = false;
|
|
});
|
|
|
|
onCleanup(() => {
|
|
// abort pending requests, clear timers, etc.
|
|
});
|
|
|
|
return (
|
|
<div>
|
|
{loading.value ? <p>Loading...</p> : <ul>{data.value.map(d => <li>{d.name}</li>)}</ul>}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Component Composition Patterns
|
|
|
|
### Higher-Order Components
|
|
|
|
```tsx
|
|
function withAuth<T extends {}>(Component: VeloxComponent<T>) {
|
|
return function AuthGated(props: T) {
|
|
const user = useUser();
|
|
if (!user) return <Redirect to="/login" />;
|
|
return <Component {...props} />;
|
|
};
|
|
}
|
|
```
|
|
|
|
### Render Props / Function-as-Children
|
|
|
|
```tsx
|
|
interface FetcherProps<T> {
|
|
url: string;
|
|
render: (data: T) => VeloxNode;
|
|
}
|
|
|
|
export function Fetcher<T>({ url, render }: FetcherProps<T>) {
|
|
// ... fetch logic
|
|
return render(data);
|
|
}
|
|
```
|
|
|
|
## Component Library
|
|
|
|
Velox ships an optional official component library `@velox/ui` with accessible, unstyled base components. Install it separately:
|
|
|
|
```bash
|
|
npm install @velox/ui
|
|
```
|
|
|
|
```tsx
|
|
import { Button, Input, Dialog } from '@velox/ui';
|
|
```
|
|
|
|
See the [Component API](api-components.md) reference for the full `defineComponent` API and advanced component patterns.
|