@tailor-platform/app-shell 1.15.0 → 1.17.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.
Files changed (109) hide show
  1. package/dist/app-shell-core.css +1 -1
  2. package/dist/app-shell.d.ts +811 -131
  3. package/dist/app-shell.js +7155 -5797
  4. package/dist/{appshell-DLVBFo5J.js → appshell-CahhZy7v.js} +656 -524
  5. package/dist/semantic-contrast.test.ts +154 -0
  6. package/dist/style-entry.d.ts +147 -92
  7. package/dist/testing.d.ts +152 -95
  8. package/dist/testing.js +1 -1
  9. package/dist/theme-bridge.test.ts +101 -1
  10. package/dist/theme.bridge.css +91 -0
  11. package/dist/theme.css +6 -1
  12. package/dist/themes/_template.css +7 -1
  13. package/dist/themes/bloom.css +0 -6
  14. package/dist/themes/cream.css +0 -6
  15. package/dist/themes/default.css +389 -69
  16. package/dist/vite-plugin.d.ts +147 -92
  17. package/package.json +8 -8
  18. package/skills/app-shell-patterns/SKILL.md +108 -19
  19. package/skills/app-shell-patterns/references/api/create-ai-gateway-client.md +115 -0
  20. package/skills/app-shell-patterns/references/api/create-typed-paths.md +117 -0
  21. package/skills/app-shell-patterns/references/api/define-i18n-labels.md +143 -0
  22. package/skills/app-shell-patterns/references/api/define-module.md +247 -0
  23. package/skills/app-shell-patterns/references/api/define-resource.md +307 -0
  24. package/skills/app-shell-patterns/references/api/guards/hidden.md +202 -0
  25. package/skills/app-shell-patterns/references/api/guards/overview.md +354 -0
  26. package/skills/app-shell-patterns/references/api/guards/pass.md +129 -0
  27. package/skills/app-shell-patterns/references/api/guards/redirect-to.md +307 -0
  28. package/skills/app-shell-patterns/references/api/router/use-location.md +240 -0
  29. package/skills/app-shell-patterns/references/api/router/use-navigate.md +138 -0
  30. package/skills/app-shell-patterns/references/api/router/use-params.md +218 -0
  31. package/skills/app-shell-patterns/references/api/use-ai-chat.md +110 -0
  32. package/skills/app-shell-patterns/references/api/use-app-shell-config.md +132 -0
  33. package/skills/app-shell-patterns/references/api/use-app-shell-data.md +150 -0
  34. package/skills/app-shell-patterns/references/api/use-app-shell.md +150 -0
  35. package/skills/app-shell-patterns/references/api/use-auth.md +145 -0
  36. package/skills/app-shell-patterns/references/api/use-override-breadcrumb.md +74 -0
  37. package/skills/app-shell-patterns/references/api/use-page-meta.md +96 -0
  38. package/skills/app-shell-patterns/references/api/use-register-command-palette-actions.md +90 -0
  39. package/skills/app-shell-patterns/references/api/use-resolved-locale.md +67 -0
  40. package/skills/app-shell-patterns/references/api/use-route-error.md +214 -0
  41. package/skills/app-shell-patterns/references/api/use-theme.md +108 -0
  42. package/skills/app-shell-patterns/references/api/use-time-zone.md +80 -0
  43. package/skills/app-shell-patterns/references/api/use-toast.md +104 -0
  44. package/skills/app-shell-patterns/references/components/action-panel.md +168 -0
  45. package/skills/app-shell-patterns/references/components/activity-card.md +142 -0
  46. package/skills/app-shell-patterns/references/components/ai-chat.md +315 -0
  47. package/skills/app-shell-patterns/references/components/alert.md +202 -0
  48. package/skills/app-shell-patterns/references/components/app-shell.md +530 -0
  49. package/skills/app-shell-patterns/references/components/appearance-switcher.md +87 -0
  50. package/skills/app-shell-patterns/references/components/attachment.md +138 -0
  51. package/skills/app-shell-patterns/references/components/autocomplete.md +278 -0
  52. package/skills/app-shell-patterns/references/components/avatar.md +80 -0
  53. package/skills/app-shell-patterns/references/components/badge.md +309 -0
  54. package/skills/app-shell-patterns/references/components/button.md +164 -0
  55. package/skills/app-shell-patterns/references/components/card.md +181 -0
  56. package/skills/app-shell-patterns/references/components/checkbox.md +169 -0
  57. package/skills/app-shell-patterns/references/components/combobox.md +318 -0
  58. package/skills/app-shell-patterns/references/components/command-palette.md +413 -0
  59. package/skills/app-shell-patterns/references/components/csv-importer.md +214 -0
  60. package/skills/app-shell-patterns/references/components/data-table.md +1168 -0
  61. package/skills/app-shell-patterns/references/components/date-picker.md +259 -0
  62. package/skills/app-shell-patterns/references/components/default-header.md +75 -0
  63. package/skills/app-shell-patterns/references/components/default-sidebar.md +136 -0
  64. package/skills/app-shell-patterns/references/components/description-card.md +569 -0
  65. package/skills/app-shell-patterns/references/components/dialog.md +164 -0
  66. package/skills/app-shell-patterns/references/components/document-progress-card.md +154 -0
  67. package/skills/app-shell-patterns/references/components/filter-rail.md +165 -0
  68. package/skills/app-shell-patterns/references/components/form.md +378 -0
  69. package/skills/app-shell-patterns/references/components/global-header-layout.md +132 -0
  70. package/skills/app-shell-patterns/references/components/grid.md +184 -0
  71. package/skills/app-shell-patterns/references/components/input.md +83 -0
  72. package/skills/app-shell-patterns/references/components/layout.md +539 -0
  73. package/skills/app-shell-patterns/references/components/menu.md +218 -0
  74. package/skills/app-shell-patterns/references/components/metric-card.md +94 -0
  75. package/skills/app-shell-patterns/references/components/select.md +283 -0
  76. package/skills/app-shell-patterns/references/components/sheet.md +237 -0
  77. package/skills/app-shell-patterns/references/components/sidebar-group.md +472 -0
  78. package/skills/app-shell-patterns/references/components/sidebar-item.md +458 -0
  79. package/skills/app-shell-patterns/references/components/sidebar-layout.md +258 -0
  80. package/skills/app-shell-patterns/references/components/sidebar-section.md +114 -0
  81. package/skills/app-shell-patterns/references/components/spinner.md +81 -0
  82. package/skills/app-shell-patterns/references/components/table.md +188 -0
  83. package/skills/app-shell-patterns/references/components/tabs.md +158 -0
  84. package/skills/app-shell-patterns/references/components/textarea.md +122 -0
  85. package/skills/app-shell-patterns/references/components/timeline.md +317 -0
  86. package/skills/app-shell-patterns/references/components/toolbar.md +138 -0
  87. package/skills/app-shell-patterns/references/components/tooltip.md +144 -0
  88. package/skills/app-shell-patterns/references/components/with-guard.md +445 -0
  89. package/skills/app-shell-patterns/references/concepts/authentication.md +301 -0
  90. package/skills/app-shell-patterns/references/concepts/custom-components.md +59 -0
  91. package/skills/app-shell-patterns/references/concepts/file-based-routing.md +356 -0
  92. package/skills/app-shell-patterns/references/{fundamental → concepts}/graphql.md +31 -18
  93. package/skills/app-shell-patterns/references/concepts/modules-and-resources.md +229 -0
  94. package/skills/app-shell-patterns/references/concepts/routing-navigation.md +425 -0
  95. package/skills/app-shell-patterns/references/concepts/sidebar-navigation.md +420 -0
  96. package/skills/app-shell-patterns/references/concepts/styling-theming.md +785 -0
  97. package/skills/app-shell-patterns/references/migrations.md +55 -0
  98. package/skills/app-shell-patterns/references/pages/{detail.md → document-detail.md} +10 -33
  99. package/skills/app-shell-patterns/references/patterns/form-composer.md +43 -45
  100. package/skills/app-shell-patterns/references/patterns/form-modal.md +37 -92
  101. package/skills/app-shell-patterns/references/patterns/form-sectioned.md +12 -38
  102. package/skills/app-shell-patterns/references/patterns/form-single-page.md +11 -32
  103. package/skills/app-shell-patterns/references/patterns/form-wizard.md +12 -53
  104. package/skills/app-shell-patterns/references/patterns/interaction-confirm.md +11 -27
  105. package/skills/app-shell-patterns/references/patterns/interaction-multi-select.md +136 -139
  106. package/skills/app-shell-patterns/references/patterns/interaction-toast.md +10 -34
  107. package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +13 -88
  108. package/skills/app-shell-patterns/references/fundamental/components.md +0 -1009
  109. package/skills/app-shell-patterns/references/fundamental/design-system.md +0 -440
@@ -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