@@ -119,6 +119,24 @@ packages/ui/src/
|
|||||||
packages/ui/src/RandomFolder/Text.tsx
|
packages/ui/src/RandomFolder/Text.tsx
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Primitives vs compound components
|
||||||
|
|
||||||
|
Components in `@probo/ui` fall into two categories: **primitives** and **compound** components.
|
||||||
|
|
||||||
|
### Primitives
|
||||||
|
|
||||||
|
**Primitives** (`Text`, `Image`, form inputs, layout helpers) are self-contained — they render a single semantic element with its own styling. A primitive **is its own shell**: it owns both its layout footprint and its visual output, so there is no separate shell wrapper. Each primitive has a paired skeleton (`TextSkeleton`, `ImageSkeleton`) that matches its dimensions.
|
||||||
|
|
||||||
|
### Compound components
|
||||||
|
|
||||||
|
**Compound components** (`ImageCard`, …) assemble multiple primitives into a larger UI region. When logic (state, effects, data fetching) lives inside the top-level component, a **shell** is required to separate layout from behavior:
|
||||||
|
|
||||||
|
- **Shell** — pure layout frame that accepts region props (`image`, `text`, …) as `ReactNode` and applies `tv` slot class names. No state, no effects, no data.
|
||||||
|
- **Root** — owns the logic and renders the shell, passing primitives into its region props.
|
||||||
|
- **Skeleton** — reuses the **same shell** with skeleton primitives, so the loading placeholder is structurally identical to the real component without pulling in the logic graph.
|
||||||
|
|
||||||
|
The shell exists so that **skeletons can share the exact same layout** as the real component without importing Root and its dependencies. If the compound component is **purely presentational** (no logic needed), there is no Root — expose only the Shell.
|
||||||
|
|
||||||
## Skeletons
|
## Skeletons
|
||||||
|
|
||||||
For each meaningful component, provide a paired loading UI:
|
For each meaningful component, provide a paired loading UI:
|
||||||
@@ -139,7 +157,7 @@ export function Text(props: TextProps) { /* … */ }
|
|||||||
export function LoadingText() { /* … */ } // use TextSkeleton instead
|
export function LoadingText() { /* … */ } // use TextSkeleton instead
|
||||||
```
|
```
|
||||||
|
|
||||||
## Compound components (e.g. `ImageCard`)
|
## Compound component structure (e.g. `ImageCard`)
|
||||||
|
|
||||||
Multi-region UI (card shell, media, text column, etc.) is exported as **individual named exports** — one per sub-component — all prefixed with the feature name (e.g. `ImageCardRoot`, `ImageCardShell`, `ImageCardSkeleton`). **Do not** group sub-components as static properties on a single namespace object (`ImageCard.Root`, `ImageCard.Shell`, …); flat named exports enable proper tree shaking and keep unwanted third-party dependencies out of loading-time bundles.
|
Multi-region UI (card shell, media, text column, etc.) is exported as **individual named exports** — one per sub-component — all prefixed with the feature name (e.g. `ImageCardRoot`, `ImageCardShell`, `ImageCardSkeleton`). **Do not** group sub-components as static properties on a single namespace object (`ImageCard.Root`, `ImageCard.Shell`, …); flat named exports enable proper tree shaking and keep unwanted third-party dependencies out of loading-time bundles.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user