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
1<Product.Unit.Abstract product={product} />
Medium-detail representation in a card format
1<Product.Unit.Card product={product} />
Complete representation with all relevant details
1<Product.Unit.Full product={product} />
Table row representation for list views
1<Product.Unit.Row product={product} />
1// Product.Unit.tsx
2export const Abstract = ({ product, className }) => (
3 <div className={"flex items-center gap-3"}>
4 {product.thumbnail && (
5 <Image file={product.thumbnail} className="h-12 w-12 rounded" />
6 )}
7 <div>
8 <h3 className="font-medium">{product.name}</h3>
9 <p className="text-sm">{product.price.toLocaleString()} KRW</p>
10 </div>
11 </div>
12);
13
14export const Card = ({ product, className }) => (
15 <div className={"card bg-base-100 shadow-sm"}>
16 {product.image && (
17 <Image file={product.image} className="h-48 w-full object-cover" />
18 )}
19 <div className="card-body">
20 <h2 className="card-title">{product.name}</h2>
21 <p className="line-clamp-2">{product.shortDescription}</p>
22 <div className="card-actions justify-end">
23 <button className="btn btn-primary btn-sm">View</button>
24 </div>
25 </div>
26 </div>
27);
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
1// No useState, useEffect, etc.
Only access properties defined in LightModel types
1product.name // Not product.description
Use optional chaining for nested properties
1product.avatar?.url
Always include className prop for styling
1className={'base-classes'}
1import { Image } from "@akanjs/ui";
2
3export const Card = ({ product }) => (
4 <div className="card">
5 {product.image ? (
6 <Image
7 file={product.image}
8 alt={product.name}
9 className="h-40 w-full object-cover"
10 sizes="(max-width: 768px) 100vw, 50vw"
11 />
12 ) : (
13 <div className="bg-base-200 flex h-40 items-center justify-center">
14 <span className="text-base-content/50">No image</span>
15 </div>
16 )}
17 </div>
18);
1// Product page
2import { Product } from "@shared/client";
3
4export default async function ProductsPage() {
5 const products = await getProducts();
6
7 return (
8 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
9 {products.map(product => (
10 <Product.Unit.Card
11 key={product.id}
12 product={product}
13 href={`/products/${product.id}`}
14 />
15 ))}
16 </div>
17 );
18}
1// Product selection template
2"use client";
3import { useState } from "react";
4import { Product } from "@shared/client";
5
6export const ProductSelector = ({ products }) => {
7 const [selectedId, setSelectedId] = useState(null);
8
9 return (
10 <div className="space-y-2">
11 {products.map(product => (
12 <div
13 key={product.id}
14 className={`cursor-pointer transition-all ${selectedId === product.id ? "ring-2 ring-primary" : ""}`}
15 onClick={() => setSelectedId(product.id)}
16 >
17 <Product.Unit.Abstract product={product} />
18 </div>
19 ))}
20 </div>
21 );
22};
For optimal performance with Unit components:
1// Memoization in client components
2"use client";
3import { memo } from "react";
4import { Product } from "@shared/client";
5
6const MemoizedCard = memo(Product.Unit.Card);
7
8export const ProductGrid = ({ products }) => (
9 <div className="grid grid-cols-3 gap-4">
10 {products.map(product => (
11 <MemoizedCard key={product.id} product={product} />
12 ))}
13 </div>
14);
15
16// Virtualization for long lists
17"use client";
18import { useVirtualizer } from "@tanstack/react-virtual";
19import { useRef } from "react";
20import { User } from "@shared/client";
21
22export const VirtualUserList = ({ users }) => {
23 const parentRef = useRef();
24 const rowVirtualizer = useVirtualizer({
25 count: users.length,
26 getScrollElement: () => parentRef.current,
27 estimateSize: () => 64,
28 });
29
30 return (
31 <div ref={parentRef} className="h-[600px] overflow-auto">
32 <div style={{ height: `${rowVirtualizer.getTotalSize()}px`}}>
33 {rowVirtualizer.getVirtualItems().map(virtualItem => (
34 <div
35 key={virtualItem.key}
36 style={{
37 position: "absolute",
38 top: 0,
39 left: 0,
40 width: "100%",
41 height: `${virtualItem.size}px`,
42 transform: `translateY(${virtualItem.start}px)`,
43 }}
44 >
45 <User.Unit.Row user={users[virtualItem.index]} />
46 </div>
47 ))}
48 </div>
49 </div>
50 );
51};
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
1Remove useState/useEffect from Unit files
Accessing undefined properties
1product.description → product.shortDescription
Missing alt text for images
1<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.