Initial commit

This commit is contained in:
2026-03-07 11:07:45 -03:00
commit 9d523f8b6a
65 changed files with 17311 additions and 0 deletions

View File

@@ -0,0 +1,436 @@
# Displaying Entities - Patterns and Components
## Contents
- [When to Use Each Pattern](#when-to-use-each-pattern)
- [DataTable Pattern](#datatable-pattern)
- [Complete DataTable Implementation](#complete-datatable-implementation)
- [DataTable Troubleshooting](#datatable-troubleshooting)
- [Simple List Patterns](#simple-list-patterns)
- [Product/Variant List Item](#productvariant-list-item)
- [Simple Text List (No Thumbnails)](#simple-text-list-no-thumbnails)
- [Compact List (No Cards)](#compact-list-no-cards)
- [Grid Display](#grid-display)
- [Key Design Elements](#key-design-elements)
- [Empty States](#empty-states)
- [Loading States](#loading-states)
- [Conditional Rendering Based on Count](#conditional-rendering-based-on-count)
- [Common Class Patterns](#common-class-patterns)
## When to Use Each Pattern
**Use DataTable when:**
- Displaying potentially many entries (>5-10 items)
- Users need to search, filter, or paginate
- Bulk actions are needed (select multiple, delete, etc.)
- Displaying in a main list view
**Use simple list components when:**
- Displaying a few entries (<5-10 items)
- In a widget or sidebar context
- As a preview or summary
- Space is limited
## DataTable Pattern
**⚠️ pnpm Users**: DataTable examples may use `react-router-dom` for navigation. Install it BEFORE implementing if needed.
### Complete DataTable Implementation
```tsx
import {
DataTable,
DataTableRowSelectionState,
DataTablePaginationState,
createDataTableColumnHelper,
useDataTable,
} from "@medusajs/ui"
import { useState, useMemo } from "react"
import { useQuery } from "@tanstack/react-query"
import { sdk } from "../lib/client"
const columnHelper = createDataTableColumnHelper<HttpTypes.AdminProduct>()
const columns = [
columnHelper.select(), // For row selection
columnHelper.accessor("title", {
header: "Title",
}),
columnHelper.accessor("status", {
header: "Status",
}),
columnHelper.accessor("created_at", {
header: "Created",
cell: ({ getValue }) => new Date(getValue()).toLocaleDateString(),
}),
]
export function ProductTable() {
const [rowSelection, setRowSelection] = useState<DataTableRowSelectionState>(
{}
)
const [searchValue, setSearchValue] = useState("")
const [pagination, setPagination] = useState<DataTablePaginationState>({
pageIndex: 0,
pageSize: 15,
})
const limit = pagination.pageSize
const offset = pagination.pageIndex * limit
// Fetch products with search and pagination
const { data, isLoading } = useQuery({
queryFn: () =>
sdk.admin.product.list({
limit,
offset,
q: searchValue || undefined, // Search query
}),
queryKey: ["products", limit, offset, searchValue],
keepPreviousData: true, // Smooth pagination
})
const table = useDataTable({
data: data?.products || [],
columns,
getRowId: (product) => product.id,
rowCount: data?.count || 0,
isLoading,
rowSelection: {
state: rowSelection,
onRowSelectionChange: setRowSelection,
},
search: {
state: searchValue,
onSearchChange: setSearchValue,
},
pagination: {
state: pagination,
onPaginationChange: setPagination,
},
})
return (
<DataTable instance={table}>
<DataTable.Toolbar>
<div className="flex gap-2">
<DataTable.Search placeholder="Search products..." />
</div>
</DataTable.Toolbar>
<DataTable.Table />
<DataTable.Pagination />
</DataTable>
)
}
```
### DataTable Troubleshooting
**"DataTable.Search was rendered but search is not enabled"**
You must pass search state configuration to useDataTable:
```tsx
search: {
state: searchValue,
onSearchChange: setSearchValue,
}
```
**"Cannot destructure property 'pageIndex' of pagination as it is undefined"**
Always initialize pagination state with both properties:
```tsx
const [pagination, setPagination] = useState({
pageIndex: 0,
pageSize: 15,
})
```
## Simple List Patterns
### Product/Variant List Item
For displaying a small list of products or variants with thumbnails:
```tsx
import { Thumbnail, Text } from "@medusajs/ui"
import { TriangleRightMini } from "@medusajs/icons"
import { Link } from "react-router-dom"
// Component for displaying a product variant
const ProductVariantItem = ({ variant, link }) => {
const Inner = (
<div className="shadow-elevation-card-rest bg-ui-bg-component rounded-md px-4 py-2 transition-colors">
<div className="flex items-center gap-3">
<div className="shadow-elevation-card-rest rounded-md">
<Thumbnail src={variant.product?.thumbnail} />
</div>
<div className="flex flex-1 flex-col">
<Text size="small" leading="compact" weight="plus">
{variant.title}
</Text>
<Text size="small" leading="compact" className="text-ui-fg-subtle">
{variant.options.map((o) => o.value).join(" ⋅ ")}
</Text>
</div>
<div className="size-7 flex items-center justify-center">
<TriangleRightMini className="text-ui-fg-muted rtl:rotate-180" />
</div>
</div>
</div>
)
if (!link) {
return <div key={variant.id}>{Inner}</div>
}
return (
<Link
to={link}
key={variant.id}
className="outline-none focus-within:shadow-borders-interactive-with-focus rounded-md [&:hover>div]:bg-ui-bg-component-hover"
>
{Inner}
</Link>
)
}
// Usage in a widget
const RelatedProductsDisplay = ({ products }) => {
if (products.length > 10) {
// Use DataTable for many items
return <ProductDataTable products={products} />
}
// Use simple list for few items
return (
<div className="flex flex-col gap-2">
{products.map((product) => (
<ProductVariantItem
key={product.id}
variant={product}
link={`/products/${product.id}`}
/>
))}
</div>
)
}
```
### Simple Text List (No Thumbnails)
For entities without images (categories, regions, etc.):
```tsx
import { Text } from "@medusajs/ui"
import { TriangleRightMini } from "@medusajs/icons"
import { Link } from "react-router-dom"
const SimpleListItem = ({ title, description, link }) => {
const Inner = (
<div className="shadow-elevation-card-rest bg-ui-bg-component rounded-md px-4 py-3 transition-colors">
<div className="flex items-center gap-3">
<div className="flex flex-1 flex-col gap-y-1">
<Text size="small" leading="compact" weight="plus">
{title}
</Text>
{description && (
<Text size="small" leading="compact" className="text-ui-fg-subtle">
{description}
</Text>
)}
</div>
<div className="size-7 flex items-center justify-center">
<TriangleRightMini className="text-ui-fg-muted rtl:rotate-180" />
</div>
</div>
</div>
)
if (!link) {
return <div>{Inner}</div>
}
return (
<Link
to={link}
className="outline-none focus-within:shadow-borders-interactive-with-focus rounded-md [&:hover>div]:bg-ui-bg-component-hover"
>
{Inner}
</Link>
)
}
// Usage
<div className="flex flex-col gap-2">
{categories.map((cat) => (
<SimpleListItem
key={cat.id}
title={cat.name}
description={cat.description}
link={`/categories/${cat.id}`}
/>
))}
</div>
```
### Compact List (No Cards)
For very compact displays:
```tsx
import { Text } from "@medusajs/ui"
<div className="flex flex-col gap-y-2">
{items.map((item) => (
<div key={item.id} className="flex items-center justify-between">
<Text size="small" leading="compact" weight="plus">
{item.title}
</Text>
<Text size="small" leading="compact" className="text-ui-fg-subtle">
{item.metadata}
</Text>
</div>
))}
</div>
```
### Grid Display
For displaying items in a grid:
```tsx
<div className="grid grid-cols-2 gap-4">
{items.map((item) => (
<div
key={item.id}
className="shadow-elevation-card-rest bg-ui-bg-component rounded-md p-4"
>
<div className="flex flex-col gap-y-2">
<Thumbnail src={item.thumbnail} />
<Text size="small" leading="compact" weight="plus">
{item.title}
</Text>
<Text size="small" leading="compact" className="text-ui-fg-subtle">
{item.description}
</Text>
</div>
</div>
))}
</div>
```
## Key Design Elements
### For Product/Variant displays:
- Always show the thumbnail using `<Thumbnail />` component
- Display title with `<Text size="small" leading="compact" weight="plus">`
- Show secondary info with `<Text size="small" leading="compact" className="text-ui-fg-subtle">`
- Use `shadow-elevation-card-rest` for card elevation
- Include hover states with `bg-ui-bg-component-hover`
- Add navigation indicators (arrows) when items are clickable
### For other entities:
- Use similar card patterns but adapt the content
- Keep consistent spacing (`gap-3` for items, `gap-2` for lists)
- Always use the Text component with correct typography patterns
- Maintain visual hierarchy with `weight="plus"` for primary and `text-ui-fg-subtle` for secondary text
## Empty States
Always handle empty states gracefully:
```tsx
{items.length === 0 ? (
<Text size="small" leading="compact" className="text-ui-fg-subtle">
No items to display
</Text>
) : (
<div className="flex flex-col gap-2">
{items.map((item) => (
<ItemDisplay key={item.id} item={item} />
))}
</div>
)}
```
## Loading States
Show loading states while data is being fetched:
```tsx
import { Spinner } from "@medusajs/ui"
{isLoading ? (
<div className="flex items-center justify-center p-8">
<Spinner />
</div>
) : (
<div className="flex flex-col gap-2">
{items.map((item) => (
<ItemDisplay key={item.id} item={item} />
))}
</div>
)}
```
## Conditional Rendering Based on Count
```tsx
const DisplayComponent = ({ items }) => {
// Use DataTable for many items
if (items.length > 10) {
return <ItemsDataTable items={items} />
}
// Use simple list for few items
if (items.length > 0) {
return (
<div className="flex flex-col gap-2">
{items.map((item) => (
<SimpleListItem key={item.id} item={item} />
))}
</div>
)
}
// Empty state
return (
<Text size="small" leading="compact" className="text-ui-fg-subtle">
No items to display
</Text>
)
}
```
## Common Class Patterns
### Card with elevation and hover
```tsx
className="shadow-elevation-card-rest bg-ui-bg-component rounded-md transition-colors hover:bg-ui-bg-component-hover"
```
### Flex container with consistent spacing
```tsx
className="flex flex-col gap-2" // For vertical lists
className="flex items-center gap-3" // For horizontal items
```
### Focus states for interactive elements
```tsx
className="outline-none focus-within:shadow-borders-interactive-with-focus rounded-md"
```
### RTL support for directional icons
```tsx
className="text-ui-fg-muted rtl:rotate-180"
```