Skip to content
Merged
38 changes: 38 additions & 0 deletions .changeset/badge-array-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
"@tailor-platform/app-shell": minor
---

Add array badge support to DataTable and DescriptionCard with shared `BadgeList` rendering and overflow popover.

```tsx
// DataTable — badge column with array accessor
column({
...infer("tags"),
type: "badge",
typeOptions: {
badgeVariantMap: { Premium: "warning", Office: "outline-info" },
maxVisible: 2,
},
})

// DescriptionCard — array badges with maxVisible
<DescriptionCard
data={{ tags: ["urgent", "fragile", "international"] }}
fields={[{
key: "tags",
label: "Tags",
type: "badge",
meta: {
badgeVariantMap: { urgent: "error", fragile: "warning", international: "outline-info" },
maxVisible: 2,
},
}]}
/>
```

Additional changes:

- Unify badge variant resolution into shared `resolveBadgeVariant()` utility with `"outline-neutral"` as the default variant (previously `"neutral"` in DataTable)
- Export `BadgeVariant` and `BadgeOptions` types from the public API
- `inferColumns()` no longer sets a default `render` function — columns without an explicit `type` or `render` now display `—` for null/empty values (aligns with typed-column behavior)
- Deprecate `BadgeVariantType` in favor of `BadgeVariant`
31 changes: 22 additions & 9 deletions examples/nextjs-app/src/modules/pages/data-table-demo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
defineResource,
Badge,
DataTable,
useDataTable,
useCollectionVariables,
Expand Down Expand Up @@ -44,6 +43,7 @@ const productMetadata = {
required: true,
enumValues: ["Active", "Draft", "Archived"],
},
{ name: "tags", type: "string", required: false },
],
} as const;

Expand Down Expand Up @@ -91,14 +91,27 @@ const productColumns = [
}),
column({
...infer("status"),
render: (row) => {
const variant =
row.status === "Active"
? ("success" as const)
: row.status === "Draft"
? ("outline-warning" as const)
: ("neutral" as const);
return <Badge variant={variant}>{row.status}</Badge>;
type: "badge",
typeOptions: {
badgeVariantMap: {
Active: "success",
Draft: "outline-warning",
Archived: "neutral",
},
},
}),
column({
...infer("tags"),
type: "badge",
typeOptions: {
badgeVariantMap: {
Premium: "warning",
Ergonomic: "success",
Office: "outline-info",
Wireless: "outline-neutral",
},
defaultBadgeVariant: "neutral",
maxVisible: 2,
},
}),
];
Expand Down
21 changes: 21 additions & 0 deletions examples/nextjs-app/src/modules/pages/mock-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type Product = {
price: number;
stock: number;
status: "Active" | "Draft" | "Archived";
tags: string[];
};

export const allProducts: Product[] = [
Expand All @@ -24,6 +25,7 @@ export const allProducts: Product[] = [
price: 499.99,
stock: 42,
status: "Active",
tags: ["Ergonomic", "Office", "Best Seller"],
},
{
id: "p-002",
Expand All @@ -37,6 +39,7 @@ export const allProducts: Product[] = [
price: 899.0,
stock: 15,
status: "Active",
tags: ["Motorized", "Office", "Adjustable", "Premium"],
},
{
id: "p-003",
Expand All @@ -49,6 +52,7 @@ export const allProducts: Product[] = [
price: 159.99,
stock: 230,
status: "Active",
tags: ["Mechanical", "RGB", "Hot-swap"],
},
{
id: "p-004",
Expand All @@ -62,6 +66,7 @@ export const allProducts: Product[] = [
price: 79.99,
stock: 0,
status: "Draft",
tags: ["USB-C", "Portable"],
},
{
id: "p-005",
Expand All @@ -75,6 +80,7 @@ export const allProducts: Product[] = [
price: 129.0,
stock: 57,
status: "Active",
tags: ["Ergonomic", "Adjustable"],
},
{
id: "p-006",
Expand All @@ -87,6 +93,7 @@ export const allProducts: Product[] = [
price: 89.99,
stock: 120,
status: "Archived",
tags: ["HD", "Autofocus"],
},
{
id: "p-007",
Expand All @@ -100,6 +107,7 @@ export const allProducts: Product[] = [
price: 45.0,
stock: 88,
status: "Active",
tags: ["LED", "USB Charging"],
},
{
id: "p-008",
Expand All @@ -113,6 +121,7 @@ export const allProducts: Product[] = [
price: 29.99,
stock: 200,
status: "Draft",
tags: ["Cable Management"],
},
{
id: "p-009",
Expand All @@ -126,6 +135,7 @@ export const allProducts: Product[] = [
price: 349.99,
stock: 64,
status: "Active",
tags: ["ANC", "Bluetooth", "Wireless", "Premium"],
},
{
id: "p-010",
Expand All @@ -138,6 +148,7 @@ export const allProducts: Product[] = [
price: 59.99,
stock: 110,
status: "Active",
tags: ["Portable", "Foldable"],
},
{
id: "p-011",
Expand All @@ -151,6 +162,7 @@ export const allProducts: Product[] = [
price: 69.99,
stock: 180,
status: "Active",
tags: ["Ergonomic", "Wireless", "Rechargeable"],
},
{
id: "p-012",
Expand All @@ -164,6 +176,7 @@ export const allProducts: Product[] = [
price: 34.99,
stock: 300,
status: "Active",
tags: ["Eco-friendly", "Non-slip"],
},
{
id: "p-013",
Expand All @@ -177,6 +190,7 @@ export const allProducts: Product[] = [
price: 249.0,
stock: 22,
status: "Draft",
tags: ["Adjustable", "Heavy-duty"],
},
{
id: "p-014",
Expand All @@ -189,6 +203,7 @@ export const allProducts: Product[] = [
price: 24.99,
stock: 500,
status: "Active",
tags: ["Surge Protection"],
},
{
id: "p-015",
Expand All @@ -202,6 +217,7 @@ export const allProducts: Product[] = [
price: 189.0,
stock: 18,
status: "Active",
tags: ["Magnetic", "Office"],
},
{
id: "p-016",
Expand All @@ -215,6 +231,7 @@ export const allProducts: Product[] = [
price: 129.99,
stock: 75,
status: "Active",
tags: ["USB", "Cardioid", "Streaming"],
},
{
id: "p-017",
Expand All @@ -228,6 +245,7 @@ export const allProducts: Product[] = [
price: 349.0,
stock: 8,
status: "Draft",
tags: ["Lockable", "Heavy-duty", "Office"],
},
{
id: "p-018",
Expand All @@ -240,6 +258,7 @@ export const allProducts: Product[] = [
price: 14.99,
stock: 600,
status: "Active",
tags: ["4K", "Braided"],
},
{
id: "p-019",
Expand All @@ -253,6 +272,7 @@ export const allProducts: Product[] = [
price: 79.0,
stock: 45,
status: "Archived",
tags: ["Ergonomic", "Adjustable"],
},
{
id: "p-020",
Expand All @@ -266,6 +286,7 @@ export const allProducts: Product[] = [
price: 199.99,
stock: 33,
status: "Active",
tags: ["Thunderbolt", "USB-C", "4K", "Ethernet", "Premium"],
},
];

Expand Down
17 changes: 17 additions & 0 deletions examples/vite-app/src/pages/dashboard/orders/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ const OrderDetailPage = () => {
const orderData = {
orderId: id,
status: "processing",
tags: ["wholesale", "priority", "fragile", "express", "insured", "tracked"],
customer: "Acme Corporation",
totalAmount: 3450.0,
currency: "USD",
Expand Down Expand Up @@ -109,6 +110,22 @@ const OrderDetailPage = () => {
},
},
},
{
key: "tags",
label: "Tags",
type: "badge",
meta: {
maxVisible: 3,
badgeVariantMap: {
wholesale: "outline-info",
priority: "error",
fragile: "warning",
express: "subtle-success",
insured: "outline-neutral",
tracked: "outline-success",
},
},
},
{ key: "customer", label: "Customer" },
{
key: "totalAmount",
Expand Down
Loading
Loading