Model.Unit.tsx files are foundational server components that provide reusable presentation elements for domain models in Akan.js applications. They create consistent visual representations that can be used across pages, zones, templates, and utilities.
Model Unit components serve several critical purposes in the Akan.js architecture:
Variation | Description | Example |
---|---|---|
Abstract | Minimal representation with essential information only |
|
Card | Medium-detail representation in a card format |
|
Full | Complete representation with all relevant details |
|
Row | Table row representation for list views |
|
Minimal representation with essential information only
<Product.Unit.Abstract product={product} />
Medium-detail representation in a card format
<Product.Unit.Card product={product} />
Complete representation with all relevant details
<Product.Unit.Full product={product} />
Table row representation for list views
<Product.Unit.Row product={product} />
// Product.Unit.tsx
export const Abstract = ({ product, className }) => (
<div className={"flex items-center gap-3"}>
{product.thumbnail && (
<Image file={product.thumbnail} className="h-12 w-12 rounded" />
)}
<div>
<h3 className="font-medium">{product.name}</h3>
<p className="text-sm">{product.price.toLocaleString()} KRW</p>
</div>
</div>
);
export const Card = ({ product, className }) => (
<div className={"card bg-base-100 shadow-sm"}>
{product.image && (
<Image file={product.image} className="h-48 w-full object-cover" />
)}
<div className="card-body">
<h2 className="card-title">{product.name}</h2>
<p className="line-clamp-2">{product.shortDescription}</p>
<div className="card-actions justify-end">
<button className="btn btn-primary btn-sm">View</button>
</div>
</div>
</div>
);
Best Practice | Description | Example |
---|---|---|
server-components | Keep as server components without client-side hooks |
|
lightmodel | Only access properties defined in LightModel types |
|
conditional | Use optional chaining for nested properties |
|
className | Always include className prop for styling |
|
Keep as server components without client-side hooks
// No useState, useEffect, etc.
Only access properties defined in LightModel types
product.name // Not product.description
Use optional chaining for nested properties
product.avatar?.url
Always include className prop for styling
className={'base-classes'}
import { Image } from "@akanjs/ui";
export const Card = ({ product }) => (
<div className="card">
{product.image ? (
<Image
file={product.image}
alt={product.name}
className="h-40 w-full object-cover"
sizes="(max-width: 768px) 100vw, 50vw"
/>
) : (
<div className="bg-base-200 flex h-40 items-center justify-center">
<span className="text-base-content/50">No image</span>
</div>
)}
</div>
);
// Product page
import { Product } from "@shared/client";
export default async function ProductsPage() {
const products = await getProducts();
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{products.map(product => (
<Product.Unit.Card
key={product.id}
product={product}
href={`/products/${product.id}`}
/>
))}
</div>
);
}
// Product selection template
"use client";
import { useState } from "react";
import { Product } from "@shared/client";
export const ProductSelector = ({ products }) => {
const [selectedId, setSelectedId] = useState(null);
return (
<div className="space-y-2">
{products.map(product => (
<div
key={product.id}
className={`cursor-pointer transition-all ${selectedId === product.id ? "ring-2 ring-primary" : ""}`}
onClick={() => setSelectedId(product.id)}
>
<Product.Unit.Abstract product={product} />
</div>
))}
</div>
);
};
For optimal performance with Unit components:
// Memoization in client components
"use client";
import { memo } from "react";
import { Product } from "@shared/client";
const MemoizedCard = memo(Product.Unit.Card);
export const ProductGrid = ({ products }) => (
<div className="grid grid-cols-3 gap-4">
{products.map(product => (
<MemoizedCard key={product.id} product={product} />
))}
</div>
);
// Virtualization for long lists
"use client";
import { useVirtualizer } from "@tanstack/react-virtual";
import { useRef } from "react";
import { User } from "@shared/client";
export const VirtualUserList = ({ users }) => {
const parentRef = useRef();
const rowVirtualizer = useVirtualizer({
count: users.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 64,
});
return (
<div ref={parentRef} className="h-[600px] overflow-auto">
<div style={{ height: `${rowVirtualizer.getTotalSize()}px`}}>
{rowVirtualizer.getVirtualItems().map(virtualItem => (
<div
key={virtualItem.key}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
<User.Unit.Row user={users[virtualItem.index]} />
</div>
))}
</div>
</div>
);
};
Troubleshooting | Description | Example |
---|---|---|
server-hooks | Server component using client hooks |
|
missing-props | Accessing undefined properties |
|
image-errors | Missing alt text for images |
|
Server component using client hooks
Remove useState/useEffect from Unit files
Accessing undefined properties
product.description → product.shortDescription
Missing alt text for images
<Image alt="Product image" ... />
Model.Unit.tsx files are essential building blocks in Akan.js that enable consistent, reusable presentation components for domain models. By following these implementation guidelines, you'll create performant, maintainable UI components that work seamlessly across your application.