@tailor-platform/app-shell 1.15.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-shell-core.css +1 -1
- package/dist/app-shell.d.ts +208 -114
- package/dist/app-shell.js +3449 -3446
- package/dist/{appshell-DLVBFo5J.js → appshell-B0wh5Lq0.js} +625 -512
- package/dist/style-entry.d.ts +116 -96
- package/dist/testing.d.ts +116 -96
- package/dist/testing.js +1 -1
- package/dist/vite-plugin.d.ts +116 -96
- package/package.json +8 -8
- package/skills/app-shell-patterns/SKILL.md +101 -14
- package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +115 -0
- package/skills/app-shell-patterns/references/api/create-typed-paths.md +117 -0
- package/skills/app-shell-patterns/references/api/define-i18n-labels.md +143 -0
- package/skills/app-shell-patterns/references/api/define-module.md +247 -0
- package/skills/app-shell-patterns/references/api/define-resource.md +307 -0
- package/skills/app-shell-patterns/references/api/guards/hidden.md +202 -0
- package/skills/app-shell-patterns/references/api/guards/overview.md +354 -0
- package/skills/app-shell-patterns/references/api/guards/pass.md +129 -0
- package/skills/app-shell-patterns/references/api/guards/redirect-to.md +307 -0
- package/skills/app-shell-patterns/references/api/router/use-location.md +240 -0
- package/skills/app-shell-patterns/references/api/router/use-navigate.md +138 -0
- package/skills/app-shell-patterns/references/api/router/use-params.md +218 -0
- package/skills/app-shell-patterns/references/api/use-ai-chat.md +110 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-config.md +126 -0
- package/skills/app-shell-patterns/references/api/use-app-shell-data.md +146 -0
- package/skills/app-shell-patterns/references/api/use-app-shell.md +143 -0
- package/skills/app-shell-patterns/references/api/use-auth.md +145 -0
- package/skills/app-shell-patterns/references/api/use-override-breadcrumb.md +74 -0
- package/skills/app-shell-patterns/references/api/use-page-meta.md +96 -0
- package/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md +90 -0
- package/skills/app-shell-patterns/references/api/use-resolved-locale.md +67 -0
- package/skills/app-shell-patterns/references/api/use-route-error.md +214 -0
- package/skills/app-shell-patterns/references/api/use-theme.md +108 -0
- package/skills/app-shell-patterns/references/api/use-time-zone.md +80 -0
- package/skills/app-shell-patterns/references/api/use-toast.md +107 -0
- package/skills/app-shell-patterns/references/components/action-panel.md +168 -0
- package/skills/app-shell-patterns/references/components/activity-card.md +142 -0
- package/skills/app-shell-patterns/references/components/ai-chat.md +315 -0
- package/skills/app-shell-patterns/references/components/alert.md +202 -0
- package/skills/app-shell-patterns/references/components/app-shell.md +530 -0
- package/skills/app-shell-patterns/references/components/appearance-switcher.md +87 -0
- package/skills/app-shell-patterns/references/components/attachment.md +138 -0
- package/skills/app-shell-patterns/references/components/autocomplete.md +278 -0
- package/skills/app-shell-patterns/references/components/avatar.md +80 -0
- package/skills/app-shell-patterns/references/components/badge.md +309 -0
- package/skills/app-shell-patterns/references/components/button.md +164 -0
- package/skills/app-shell-patterns/references/components/card.md +109 -0
- package/skills/app-shell-patterns/references/components/checkbox.md +169 -0
- package/skills/app-shell-patterns/references/components/combobox.md +318 -0
- package/skills/app-shell-patterns/references/components/command-palette.md +413 -0
- package/skills/app-shell-patterns/references/components/csv-importer.md +214 -0
- package/skills/app-shell-patterns/references/components/data-table.md +953 -0
- package/skills/app-shell-patterns/references/components/date-picker.md +248 -0
- package/skills/app-shell-patterns/references/components/default-header.md +75 -0
- package/skills/app-shell-patterns/references/components/default-sidebar.md +136 -0
- package/skills/app-shell-patterns/references/components/description-card.md +569 -0
- package/skills/app-shell-patterns/references/components/dialog.md +164 -0
- package/skills/app-shell-patterns/references/components/document-progress-card.md +154 -0
- package/skills/app-shell-patterns/references/components/form.md +378 -0
- package/skills/app-shell-patterns/references/components/global-header-layout.md +132 -0
- package/skills/app-shell-patterns/references/components/grid.md +184 -0
- package/skills/app-shell-patterns/references/components/input.md +83 -0
- package/skills/app-shell-patterns/references/components/layout.md +539 -0
- package/skills/app-shell-patterns/references/components/menu.md +218 -0
- package/skills/app-shell-patterns/references/components/metric-card.md +94 -0
- package/skills/app-shell-patterns/references/components/select.md +283 -0
- package/skills/app-shell-patterns/references/components/sheet.md +237 -0
- package/skills/app-shell-patterns/references/components/sidebar-group.md +466 -0
- package/skills/app-shell-patterns/references/components/sidebar-item.md +458 -0
- package/skills/app-shell-patterns/references/components/sidebar-layout.md +258 -0
- package/skills/app-shell-patterns/references/components/spinner.md +81 -0
- package/skills/app-shell-patterns/references/components/table.md +188 -0
- package/skills/app-shell-patterns/references/components/tabs.md +158 -0
- package/skills/app-shell-patterns/references/components/textarea.md +122 -0
- package/skills/app-shell-patterns/references/components/timeline.md +317 -0
- package/skills/app-shell-patterns/references/components/toolbar.md +138 -0
- package/skills/app-shell-patterns/references/components/tooltip.md +144 -0
- package/skills/app-shell-patterns/references/components/with-guard.md +445 -0
- package/skills/app-shell-patterns/references/concepts/authentication.md +301 -0
- package/skills/app-shell-patterns/references/concepts/custom-components.md +59 -0
- package/skills/app-shell-patterns/references/concepts/file-based-routing.md +350 -0
- package/skills/app-shell-patterns/references/{fundamental → concepts}/graphql.md +11 -4
- package/skills/app-shell-patterns/references/concepts/modules-and-resources.md +229 -0
- package/skills/app-shell-patterns/references/concepts/routing-navigation.md +387 -0
- package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +388 -0
- package/skills/app-shell-patterns/references/{fundamental/design-system.md → concepts/styling-theming.md} +258 -110
- package/skills/app-shell-patterns/references/migrations.md +34 -0
- package/skills/app-shell-patterns/references/pages/{detail.md → document-detail.md} +4 -27
- package/skills/app-shell-patterns/references/patterns/form-composer.md +43 -45
- package/skills/app-shell-patterns/references/patterns/form-modal.md +39 -100
- package/skills/app-shell-patterns/references/patterns/form-sectioned.md +12 -38
- package/skills/app-shell-patterns/references/patterns/form-single-page.md +11 -32
- package/skills/app-shell-patterns/references/patterns/form-wizard.md +12 -53
- package/skills/app-shell-patterns/references/patterns/interaction-confirm.md +11 -27
- package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +13 -27
- package/skills/app-shell-patterns/references/patterns/interaction-toast.md +10 -34
- package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +11 -86
- package/skills/app-shell-patterns/references/fundamental/components.md +0 -1009
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: DescriptionCard
|
|
3
|
+
description: Display structured key-value information in a responsive grid layout with automatic field rendering
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by docs-kit from docs-src/components/description-card.docs.outline.md — do not edit by hand. -->
|
|
7
|
+
|
|
8
|
+
# DescriptionCard
|
|
9
|
+
|
|
10
|
+
`DescriptionCard` is a powerful component for displaying structured document information in ERP applications. It automatically renders field-value pairs in a responsive grid with support for multiple field types, dividers, and smart empty value handling.
|
|
11
|
+
|
|
12
|
+
## Import
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { DescriptionCard } from "@tailor-platform/app-shell";
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Basic Usage
|
|
19
|
+
|
|
20
|
+
<example-preview name="basic-usage"></example-preview>
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
function BasicUsage() {
|
|
24
|
+
return (
|
|
25
|
+
<DescriptionCard
|
|
26
|
+
data={orderData}
|
|
27
|
+
title="Order details"
|
|
28
|
+
fields={[
|
|
29
|
+
{ key: "orderNumber", label: "Order Number" },
|
|
30
|
+
{ key: "customer", label: "Customer" },
|
|
31
|
+
{ key: "status", label: "Status", type: "badge" },
|
|
32
|
+
{ key: "totalAmount", label: "Total", type: "money", meta: { currencyKey: "currency" } },
|
|
33
|
+
{ key: "orderDate", label: "Order Date", type: "date" },
|
|
34
|
+
]}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Props
|
|
41
|
+
|
|
42
|
+
_Generated from the type surface — first-party props only._
|
|
43
|
+
|
|
44
|
+
### DescriptionCardProps
|
|
45
|
+
|
|
46
|
+
| Prop | Type | Default | Description |
|
|
47
|
+
| --------------- | ---------------------- | ------- | ------------------------------------------------------------------------ |
|
|
48
|
+
| `className?` | `string` | - | Additional CSS classes |
|
|
49
|
+
| `columns?` | `Columns` | - | Number of columns on desktop (3 or 4) |
|
|
50
|
+
| `data` | `TData` | - | Raw backend data object |
|
|
51
|
+
| `fields` | `FieldConfig<TData>[]` | - | Ordered list of field definitions (use { type: "divider" } for dividers) |
|
|
52
|
+
| `headerAction?` | `ReactNode` | - | Header action slot (e.g., edit button) |
|
|
53
|
+
| `style?` | `CSSProperties` | - | Inline styles |
|
|
54
|
+
| `title` | `string` | - | Card title |
|
|
55
|
+
|
|
56
|
+
## Exports
|
|
57
|
+
|
|
58
|
+
- `DescriptionCard` — function
|
|
59
|
+
- `DescriptionCardProps` — interface
|
|
60
|
+
|
|
61
|
+
## Field Types
|
|
62
|
+
|
|
63
|
+
### text (default)
|
|
64
|
+
|
|
65
|
+
Plain text display with optional truncation and copy button.
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
{
|
|
69
|
+
key: "description",
|
|
70
|
+
label: "Description",
|
|
71
|
+
type: "text", // or omit - it's the default
|
|
72
|
+
meta: {
|
|
73
|
+
copyable: true,
|
|
74
|
+
truncateLines: 3, // Show "..." after 3 lines with tooltip
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### badge
|
|
80
|
+
|
|
81
|
+
Displays value as a Badge component with variant mapping. Accepts a single value or an array of values.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
{
|
|
85
|
+
key: "status",
|
|
86
|
+
label: "Status",
|
|
87
|
+
type: "badge",
|
|
88
|
+
meta: {
|
|
89
|
+
sentenceCaseBadges: false, // Optional: render the original value instead
|
|
90
|
+
badgeVariantMap: {
|
|
91
|
+
draft: "neutral",
|
|
92
|
+
pending: "subtle-warning",
|
|
93
|
+
approved: "subtle-success",
|
|
94
|
+
shipped: "success",
|
|
95
|
+
cancelled: "subtle-error",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Badge values are rendered in sentence case by default. Set `meta.sentenceCaseBadges` to `false` when you need to display the original value from your data.
|
|
102
|
+
|
|
103
|
+
#### Array badges with overflow
|
|
104
|
+
|
|
105
|
+
When the field value is an array, each item is rendered as a separate badge. Use `maxVisible` to cap the number shown — extra items appear in a hover popover:
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
<DescriptionCard
|
|
109
|
+
data={{ tags: ["urgent", "fragile", "international"] }}
|
|
110
|
+
fields={[
|
|
111
|
+
{
|
|
112
|
+
key: "tags",
|
|
113
|
+
label: "Tags",
|
|
114
|
+
type: "badge",
|
|
115
|
+
meta: {
|
|
116
|
+
badgeVariantMap: { urgent: "error", fragile: "warning", international: "outline-info" },
|
|
117
|
+
maxVisible: 2,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
]}
|
|
121
|
+
/>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### money
|
|
125
|
+
|
|
126
|
+
Formats currency values with proper locale formatting.
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
{
|
|
130
|
+
key: "amount",
|
|
131
|
+
label: "Amount",
|
|
132
|
+
type: "money",
|
|
133
|
+
meta: {
|
|
134
|
+
currencyKey: "currency", // Path to currency code in data
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Data: { amount: 1234.56, currency: "USD" }
|
|
139
|
+
// Renders: $1,234.56
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### date
|
|
143
|
+
|
|
144
|
+
Formats date/time values with multiple format options.
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
{
|
|
148
|
+
key: "createdAt",
|
|
149
|
+
label: "Created",
|
|
150
|
+
type: "date",
|
|
151
|
+
meta: {
|
|
152
|
+
dateFormat: "medium", // "short" | "medium" | "long" | "relative"
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Format examples:
|
|
158
|
+
|
|
159
|
+
- `short`: 3/6/2026
|
|
160
|
+
- `medium`: Mar 6, 2026
|
|
161
|
+
- `long`: March 6, 2026 at 2:30 PM
|
|
162
|
+
- `relative`: 2 hours ago / In 3 days
|
|
163
|
+
|
|
164
|
+
Relative dates support both past and future values and are automatically localized based on the AppShell locale (English and Japanese are built in).
|
|
165
|
+
|
|
166
|
+
### link
|
|
167
|
+
|
|
168
|
+
Renders a clickable link.
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
{
|
|
172
|
+
key: "customerName",
|
|
173
|
+
label: "Customer",
|
|
174
|
+
type: "link",
|
|
175
|
+
meta: {
|
|
176
|
+
hrefKey: "customerUrl", // Path to URL in data
|
|
177
|
+
external: true, // Opens in new tab
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Internal links (when `external` is not set or `false`) use react-router `<Link>` for client-side navigation. External links (when `external: true`) open in a new tab using a standard `<a>` tag.
|
|
183
|
+
|
|
184
|
+
### address
|
|
185
|
+
|
|
186
|
+
Formats multi-line address display.
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
{
|
|
190
|
+
key: "shippingAddress",
|
|
191
|
+
label: "Shipping Address",
|
|
192
|
+
type: "address",
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Data: {
|
|
196
|
+
// shippingAddress: {
|
|
197
|
+
// street: "123 Main St",
|
|
198
|
+
// city: "San Francisco",
|
|
199
|
+
// state: "CA",
|
|
200
|
+
// zip: "94105",
|
|
201
|
+
// }
|
|
202
|
+
// }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### reference
|
|
206
|
+
|
|
207
|
+
Links to related documents with auto-generated URLs using react-router `<Link>` for client-side navigation.
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
{
|
|
211
|
+
key: "invoiceNumber",
|
|
212
|
+
label: "Invoice",
|
|
213
|
+
type: "reference",
|
|
214
|
+
meta: {
|
|
215
|
+
referenceIdKey: "invoiceId",
|
|
216
|
+
referenceUrlPattern: "/invoices/{id}",
|
|
217
|
+
},
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Custom Rendering
|
|
222
|
+
|
|
223
|
+
When no built-in `type` fits, pass `render` to draw the field yourself. It receives the whole `data` object — reach into it for whatever the field needs:
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
<DescriptionCard
|
|
227
|
+
data={orderData}
|
|
228
|
+
title="Order"
|
|
229
|
+
fields={[
|
|
230
|
+
{ key: "orderNumber", label: "Order Number" }, // default text
|
|
231
|
+
{ key: "status", label: "Status", type: "badge" }, // preset
|
|
232
|
+
{
|
|
233
|
+
key: "deliveryBreakdown",
|
|
234
|
+
label: "Delivery",
|
|
235
|
+
render: (data) => <PieChart data={data.deliveryBreakdown} />, // fully custom
|
|
236
|
+
},
|
|
237
|
+
]}
|
|
238
|
+
/>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Because `render` takes the whole object, a field can be derived from several keys at once — destructure the ones you want:
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
{
|
|
245
|
+
key: "total",
|
|
246
|
+
label: "Balance Due",
|
|
247
|
+
render: ({ total, amountPaid, currency }) => (
|
|
248
|
+
<Money amount={total - amountPaid} currency={currency} />
|
|
249
|
+
),
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
This is the same convention as [DataTable's](./data-table.md) column `render`, which also receives the whole row, so the two data-display components behave alike. It also means everything you touch keeps its declared type — nothing is widened to `unknown`.
|
|
254
|
+
|
|
255
|
+
### Rules
|
|
256
|
+
|
|
257
|
+
- **`render` always wins over `type`.** Both may be set, but the built-in renderer is skipped entirely.
|
|
258
|
+
- **`meta` is not applied.** A custom renderer owns its own presentation — the copy button, truncation, badge maps, and the `–` empty placeholder are all built-in behaviour that `render` replaces. Render them yourself if you want them.
|
|
259
|
+
- **`key` is still required.** It identifies the field and is what `emptyBehavior` tests. It does _not_ pre-resolve a value for `render` — dot-notation paths are only used by the built-in `type` renderers.
|
|
260
|
+
- **`render` still runs when the value at `key` is empty,** so you decide how to display that. `emptyBehavior: "hide"` is checked first and removes the field before `render` is called.
|
|
261
|
+
|
|
262
|
+
## Dividers
|
|
263
|
+
|
|
264
|
+
Use dividers to visually separate field groups:
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
<DescriptionCard
|
|
268
|
+
data={data}
|
|
269
|
+
fields={[
|
|
270
|
+
{ key: "orderNumber", label: "Order Number" },
|
|
271
|
+
{ key: "customer", label: "Customer" },
|
|
272
|
+
{ type: "divider" }, // Creates a horizontal line
|
|
273
|
+
{ key: "status", label: "Status", type: "badge" },
|
|
274
|
+
{ key: "totalAmount", label: "Total", type: "money" },
|
|
275
|
+
]}
|
|
276
|
+
/>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Empty Value Behavior
|
|
280
|
+
|
|
281
|
+
Control what displays when a field value is empty:
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
{
|
|
285
|
+
key: "notes",
|
|
286
|
+
label: "Notes",
|
|
287
|
+
emptyBehavior: "dash", // Shows "-" when empty (default)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
{
|
|
291
|
+
key: "optionalField",
|
|
292
|
+
label: "Optional",
|
|
293
|
+
emptyBehavior: "hide", // Hides the entire field when empty
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Nested Data Access
|
|
298
|
+
|
|
299
|
+
Use dot notation to access nested properties:
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
const data = {
|
|
303
|
+
customer: {
|
|
304
|
+
name: "Acme Corp",
|
|
305
|
+
contact: {
|
|
306
|
+
email: "contact@acme.com",
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
<DescriptionCard
|
|
312
|
+
data={data}
|
|
313
|
+
fields={[
|
|
314
|
+
{ key: "customer.name", label: "Customer Name" },
|
|
315
|
+
{ key: "customer.contact.email", label: "Email" },
|
|
316
|
+
]}
|
|
317
|
+
/>;
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Column Layouts
|
|
321
|
+
|
|
322
|
+
Choose between 2, 3, or 4 column layouts:
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
// 2 columns (better for mobile)
|
|
326
|
+
<DescriptionCard columns={2} data={data} fields={fields} />
|
|
327
|
+
|
|
328
|
+
// 3 columns (default, balanced)
|
|
329
|
+
<DescriptionCard columns={3} data={data} fields={fields} />
|
|
330
|
+
|
|
331
|
+
// 4 columns (more compact)
|
|
332
|
+
<DescriptionCard columns={4} data={data} fields={fields} />
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
The component is fully responsive:
|
|
336
|
+
|
|
337
|
+
- **Mobile (< 400px)**: 1 column
|
|
338
|
+
- **Tablet (400-600px)**: 2 columns
|
|
339
|
+
- **Desktop (600-800px)**: 3 columns
|
|
340
|
+
- **Large (> 800px)**: 4 columns (if `columns={4}`)
|
|
341
|
+
|
|
342
|
+
## Complete Example
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
import { DescriptionCard } from "@tailor-platform/app-shell";
|
|
346
|
+
|
|
347
|
+
const orderData = {
|
|
348
|
+
// Order info
|
|
349
|
+
orderNumber: "ORD-12345",
|
|
350
|
+
orderDate: "2026-03-01T10:00:00Z",
|
|
351
|
+
status: "shipped",
|
|
352
|
+
|
|
353
|
+
// Customer
|
|
354
|
+
customer: {
|
|
355
|
+
name: "Acme Corporation",
|
|
356
|
+
id: "CUST-001",
|
|
357
|
+
email: "orders@acme.com",
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
// Financial
|
|
361
|
+
subtotal: 14000.0,
|
|
362
|
+
tax: 1750.0,
|
|
363
|
+
total: 15750.0,
|
|
364
|
+
currency: "USD",
|
|
365
|
+
|
|
366
|
+
// Shipping
|
|
367
|
+
shippingAddress: {
|
|
368
|
+
street: "123 Business Ave",
|
|
369
|
+
city: "San Francisco",
|
|
370
|
+
state: "CA",
|
|
371
|
+
zip: "94105",
|
|
372
|
+
},
|
|
373
|
+
trackingNumber: "1Z999AA10123456784",
|
|
374
|
+
|
|
375
|
+
// Notes
|
|
376
|
+
notes: "Rush delivery requested",
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
function OrderDetails() {
|
|
380
|
+
return (
|
|
381
|
+
<DescriptionCard
|
|
382
|
+
data={orderData}
|
|
383
|
+
columns={3}
|
|
384
|
+
fields={[
|
|
385
|
+
// Order Information
|
|
386
|
+
{
|
|
387
|
+
key: "orderNumber",
|
|
388
|
+
label: "Order Number",
|
|
389
|
+
meta: { copyable: true },
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
key: "orderDate",
|
|
393
|
+
label: "Order Date",
|
|
394
|
+
type: "date",
|
|
395
|
+
meta: { dateFormat: "medium" },
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
key: "status",
|
|
399
|
+
label: "Status",
|
|
400
|
+
type: "badge",
|
|
401
|
+
meta: {
|
|
402
|
+
badgeVariantMap: {
|
|
403
|
+
draft: "neutral",
|
|
404
|
+
pending: "subtle-warning",
|
|
405
|
+
shipped: "subtle-success",
|
|
406
|
+
delivered: "success",
|
|
407
|
+
cancelled: "subtle-error",
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
{ type: "divider" },
|
|
413
|
+
|
|
414
|
+
// Customer Information
|
|
415
|
+
{
|
|
416
|
+
key: "customer.name",
|
|
417
|
+
label: "Customer",
|
|
418
|
+
type: "reference",
|
|
419
|
+
meta: {
|
|
420
|
+
referenceIdKey: "customer.id",
|
|
421
|
+
referenceUrlPattern: "/customers/{id}",
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
key: "customer.email",
|
|
426
|
+
label: "Email",
|
|
427
|
+
type: "link",
|
|
428
|
+
meta: { hrefKey: "customer.email" },
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
{ type: "divider" },
|
|
432
|
+
|
|
433
|
+
// Financial
|
|
434
|
+
{
|
|
435
|
+
key: "subtotal",
|
|
436
|
+
label: "Subtotal",
|
|
437
|
+
type: "money",
|
|
438
|
+
meta: { currencyKey: "currency" },
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
key: "tax",
|
|
442
|
+
label: "Tax",
|
|
443
|
+
type: "money",
|
|
444
|
+
meta: { currencyKey: "currency" },
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
key: "total",
|
|
448
|
+
label: "Total",
|
|
449
|
+
type: "money",
|
|
450
|
+
meta: { currencyKey: "currency" },
|
|
451
|
+
},
|
|
452
|
+
|
|
453
|
+
{ type: "divider" },
|
|
454
|
+
|
|
455
|
+
// Shipping
|
|
456
|
+
{
|
|
457
|
+
key: "shippingAddress",
|
|
458
|
+
label: "Shipping Address",
|
|
459
|
+
type: "address",
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
key: "trackingNumber",
|
|
463
|
+
label: "Tracking",
|
|
464
|
+
meta: { copyable: true },
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
{ type: "divider" },
|
|
468
|
+
|
|
469
|
+
// Notes
|
|
470
|
+
{
|
|
471
|
+
key: "notes",
|
|
472
|
+
label: "Notes",
|
|
473
|
+
meta: { truncateLines: 2 },
|
|
474
|
+
emptyBehavior: "hide",
|
|
475
|
+
},
|
|
476
|
+
]}
|
|
477
|
+
/>
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
## Styling
|
|
483
|
+
|
|
484
|
+
The component uses container queries for responsive layouts:
|
|
485
|
+
|
|
486
|
+
`className` lands on the card root, for properties the card doesn't set itself — sizing and placement rather than surface. It manages its own background, radius, elevation and 24px content inset.
|
|
487
|
+
|
|
488
|
+
```tsx
|
|
489
|
+
<DescriptionCard className="max-w-3xl" data={data} fields={fields} />
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Write **plain** Tailwind utilities here — see [Styling AppShell components](../concepts/styling-theming.md#styling-appshell-components).
|
|
493
|
+
|
|
494
|
+
## Accessibility
|
|
495
|
+
|
|
496
|
+
- Proper semantic HTML structure
|
|
497
|
+
- Labels associated with values
|
|
498
|
+
- Keyboard accessible copy buttons
|
|
499
|
+
- Tooltips for truncated content
|
|
500
|
+
- Screen reader friendly
|
|
501
|
+
|
|
502
|
+
## Best Practices
|
|
503
|
+
|
|
504
|
+
### Do:
|
|
505
|
+
|
|
506
|
+
- ✅ Group related fields with dividers
|
|
507
|
+
- ✅ Use appropriate field types for data
|
|
508
|
+
- ✅ Enable `copyable` for IDs and codes
|
|
509
|
+
- ✅ Use `emptyBehavior: "hide"` for optional fields
|
|
510
|
+
- ✅ Provide `badgeVariantMap` for status fields
|
|
511
|
+
|
|
512
|
+
### Don't:
|
|
513
|
+
|
|
514
|
+
- ❌ Put too many fields (consider splitting into tabs)
|
|
515
|
+
- ❌ Use more than 4 columns (hard to read)
|
|
516
|
+
- ❌ Mix unrelated data without dividers
|
|
517
|
+
- ❌ Forget to handle nested data properly
|
|
518
|
+
|
|
519
|
+
## TypeScript
|
|
520
|
+
|
|
521
|
+
`DescriptionCard` is generic over the shape of `data`, which is normally inferred:
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
import { DescriptionCard, type DescriptionCardProps } from "@tailor-platform/app-shell";
|
|
525
|
+
|
|
526
|
+
// Define your data type
|
|
527
|
+
interface Order {
|
|
528
|
+
orderNumber: string;
|
|
529
|
+
customer: string;
|
|
530
|
+
status: "draft" | "pending" | "shipped";
|
|
531
|
+
total: number;
|
|
532
|
+
currency: string;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
declare const orderData: Order;
|
|
536
|
+
|
|
537
|
+
// `data` inside render is typed as Order - no annotation needed
|
|
538
|
+
<DescriptionCard
|
|
539
|
+
data={orderData}
|
|
540
|
+
title="Order"
|
|
541
|
+
fields={[
|
|
542
|
+
{ key: "orderNumber", label: "Order" },
|
|
543
|
+
{ key: "status", label: "Status", type: "badge" },
|
|
544
|
+
{ key: "total", label: "Total", render: (data) => `${data.total} ${data.currency}` },
|
|
545
|
+
]}
|
|
546
|
+
/>;
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
To declare the fields array separately, parameterise `DescriptionCardProps` with your data type so `render` callbacks stay typed:
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
const fields: DescriptionCardProps<Order>["fields"] = [
|
|
553
|
+
{ key: "orderNumber", label: "Order" },
|
|
554
|
+
{ key: "total", label: "Total", render: (data) => `${data.total} ${data.currency}` },
|
|
555
|
+
];
|
|
556
|
+
|
|
557
|
+
<DescriptionCard<Order> data={orderData} title="Order" fields={fields} />;
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
`render` receives `data` typed as your data type, so every key you reach for keeps its declared type. `key` remains a plain `string` accepting dot-notation paths, but it only feeds the built-in `type` renderers — it is not resolved and handed to `render`.
|
|
561
|
+
|
|
562
|
+
## Related Components
|
|
563
|
+
|
|
564
|
+
- [Badge](./badge.md) - Used for status fields
|
|
565
|
+
- [Layout](./layout.md) - Page layout container
|
|
566
|
+
|
|
567
|
+
## Related Concepts
|
|
568
|
+
|
|
569
|
+
- [Styling and Theming](../concepts/styling-theming.md) - Customize appearance
|