@flamingo-stack/openframe-frontend-core 0.0.342 → 0.0.343-snapshot.20260630133421
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/{chunk-NLJUVRVX.cjs → chunk-2GO2FK6O.cjs} +12 -12
- package/dist/{chunk-NLJUVRVX.cjs.map → chunk-2GO2FK6O.cjs.map} +1 -1
- package/dist/{chunk-G5BJ5EJ3.js → chunk-6VKKIVYU.js} +2 -2
- package/dist/{chunk-M4JER3ZJ.js → chunk-7KQLWKM2.js} +2846 -2765
- package/dist/chunk-7KQLWKM2.js.map +1 -0
- package/dist/{chunk-HHEN6U6D.cjs → chunk-AZT66THN.cjs} +25 -25
- package/dist/{chunk-HHEN6U6D.cjs.map → chunk-AZT66THN.cjs.map} +1 -1
- package/dist/{chunk-EEAUFJOJ.cjs → chunk-CABQCNG6.cjs} +56 -56
- package/dist/{chunk-EEAUFJOJ.cjs.map → chunk-CABQCNG6.cjs.map} +1 -1
- package/dist/{chunk-6DKQW5OB.js → chunk-D7MGOMNT.js} +2 -2
- package/dist/{chunk-NBZR5OEQ.cjs → chunk-D7Y5B7JK.cjs} +15 -15
- package/dist/{chunk-NBZR5OEQ.cjs.map → chunk-D7Y5B7JK.cjs.map} +1 -1
- package/dist/{chunk-TV3BIALE.js → chunk-G5VOYM2H.js} +2 -2
- package/dist/{chunk-NXDUUG3E.cjs → chunk-GGKCEZJI.cjs} +5 -5
- package/dist/{chunk-NXDUUG3E.cjs.map → chunk-GGKCEZJI.cjs.map} +1 -1
- package/dist/{chunk-SSWHDTUV.js → chunk-JYXN6WEX.js} +4 -4
- package/dist/{chunk-YHHGM3EL.cjs → chunk-KFB7NUC7.cjs} +37 -37
- package/dist/{chunk-YHHGM3EL.cjs.map → chunk-KFB7NUC7.cjs.map} +1 -1
- package/dist/{chunk-D6OFQCBQ.js → chunk-L5RSMSTC.js} +4 -4
- package/dist/{chunk-MQXK5YZJ.js → chunk-LJWNUSKD.js} +2 -2
- package/dist/{chunk-NRRBPW33.cjs → chunk-LP2VM3E6.cjs} +105 -24
- package/dist/chunk-LP2VM3E6.cjs.map +1 -0
- package/dist/{chunk-PRVRFN5Y.cjs → chunk-NBFUR64A.cjs} +31 -31
- package/dist/{chunk-PRVRFN5Y.cjs.map → chunk-NBFUR64A.cjs.map} +1 -1
- package/dist/{chunk-3GNUN3UF.cjs → chunk-NSDBZFMP.cjs} +7 -7
- package/dist/{chunk-3GNUN3UF.cjs.map → chunk-NSDBZFMP.cjs.map} +1 -1
- package/dist/{chunk-2L6PCJGJ.cjs → chunk-OI5FCI3I.cjs} +26 -26
- package/dist/{chunk-2L6PCJGJ.cjs.map → chunk-OI5FCI3I.cjs.map} +1 -1
- package/dist/{chunk-MGYDI2R4.cjs → chunk-OWFV5FND.cjs} +9 -9
- package/dist/{chunk-MGYDI2R4.cjs.map → chunk-OWFV5FND.cjs.map} +1 -1
- package/dist/{chunk-VVKLQXWF.js → chunk-X5XTYAEM.js} +2 -2
- package/dist/{chunk-K4NVJM6V.cjs → chunk-XAHPNJVR.cjs} +62 -62
- package/dist/{chunk-K4NVJM6V.cjs.map → chunk-XAHPNJVR.cjs.map} +1 -1
- package/dist/{chunk-KDFQXI6L.js → chunk-XE4FGAQI.js} +2 -2
- package/dist/{chunk-VZM3OH26.js → chunk-XPTB5EMQ.js} +4 -4
- package/dist/{chunk-GGHWEV6K.js → chunk-Z24XK3VU.js} +2 -2
- package/dist/{chunk-YHXX7WEL.js → chunk-ZFPVKIAR.js} +4 -4
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/index.cjs +123 -121
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +11 -9
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/dashboard-info-card.d.ts +10 -6
- package/dist/components/ui/dashboard-info-card.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +4 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +3 -1
- package/dist/components/ui/stacked-rows-panel.d.ts +58 -0
- package/dist/components/ui/stacked-rows-panel.d.ts.map +1 -0
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/src/components/chat/approval-batch-message.tsx +2 -2
- package/src/components/ui/dashboard-info-card.tsx +41 -19
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/stacked-rows-panel.tsx +187 -0
- package/src/stories/DashboardInfoCard.stories.tsx +7 -7
- package/src/stories/StackedRowsPanel.stories.tsx +165 -0
- package/dist/chunk-M4JER3ZJ.js.map +0 -1
- package/dist/chunk-NRRBPW33.cjs.map +0 -1
- /package/dist/{chunk-G5BJ5EJ3.js.map → chunk-6VKKIVYU.js.map} +0 -0
- /package/dist/{chunk-6DKQW5OB.js.map → chunk-D7MGOMNT.js.map} +0 -0
- /package/dist/{chunk-TV3BIALE.js.map → chunk-G5VOYM2H.js.map} +0 -0
- /package/dist/{chunk-SSWHDTUV.js.map → chunk-JYXN6WEX.js.map} +0 -0
- /package/dist/{chunk-D6OFQCBQ.js.map → chunk-L5RSMSTC.js.map} +0 -0
- /package/dist/{chunk-MQXK5YZJ.js.map → chunk-LJWNUSKD.js.map} +0 -0
- /package/dist/{chunk-VVKLQXWF.js.map → chunk-X5XTYAEM.js.map} +0 -0
- /package/dist/{chunk-KDFQXI6L.js.map → chunk-XE4FGAQI.js.map} +0 -0
- /package/dist/{chunk-VZM3OH26.js.map → chunk-XPTB5EMQ.js.map} +0 -0
- /package/dist/{chunk-GGHWEV6K.js.map → chunk-Z24XK3VU.js.map} +0 -0
- /package/dist/{chunk-YHXX7WEL.js.map → chunk-ZFPVKIAR.js.map} +0 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import type * as React from "react"
|
|
4
|
+
import { cn } from "../../utils/cn"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* StackedRowsPanel — a single bordered card that stacks rows inside it.
|
|
8
|
+
*
|
|
9
|
+
* Each row can be:
|
|
10
|
+
* - a single full-width element (e.g. title + subtitle) — pass one column,
|
|
11
|
+
* - N columns (each a value + label, possibly a tag/avatar) — pass several columns,
|
|
12
|
+
* - an arbitrary node (e.g. a nested table) — pass `content`.
|
|
13
|
+
*
|
|
14
|
+
* Border-radius behaviour is handled entirely by the container: `rounded-md` +
|
|
15
|
+
* `overflow-clip` clip the first/last rows to the rounded corners. So a single row
|
|
16
|
+
* rounds all four corners, while many rows visually round only the top corners of
|
|
17
|
+
* the first row and the bottom corners of the last — with no per-row corner logic.
|
|
18
|
+
*
|
|
19
|
+
* Cells render the ODS `value` (`text-h4`) over `label` (`text-h6`) pattern with an
|
|
20
|
+
* optional leading icon, matching the app's detail layouts. Pass a node as `value`
|
|
21
|
+
* (e.g. a `Tag`) to render a status chip with a caption beneath it.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export type PanelColumnAlign = "left" | "center" | "right"
|
|
25
|
+
export type PanelHideAt = "md" | "lg" | "xl"
|
|
26
|
+
|
|
27
|
+
export interface PanelColumn {
|
|
28
|
+
/** Stable key for the column. */
|
|
29
|
+
key: string
|
|
30
|
+
/** Custom cell content (tag, avatar + text, button…). Takes precedence over `value`/`label`. */
|
|
31
|
+
content?: React.ReactNode
|
|
32
|
+
/** Primary value rendered in the ODS h4 style when `content` is not set. */
|
|
33
|
+
value?: React.ReactNode
|
|
34
|
+
/** Secondary label rendered under the value (ODS h6, secondary colour). */
|
|
35
|
+
label?: string
|
|
36
|
+
/** Icon shown before the value. */
|
|
37
|
+
icon?: React.ReactNode
|
|
38
|
+
/** When set, the cell becomes an external link. */
|
|
39
|
+
href?: string
|
|
40
|
+
/** Tailwind width class. Defaults to `flex-[1_0_0] min-w-0`. */
|
|
41
|
+
width?: string
|
|
42
|
+
/** Horizontal alignment of the column content. Defaults to `left`. */
|
|
43
|
+
align?: PanelColumnAlign
|
|
44
|
+
/** Hide the column below this breakpoint (mirrors the DataTable `hideAt` concept). */
|
|
45
|
+
hideAt?: PanelHideAt
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface PanelRow {
|
|
49
|
+
/** Stable key for the row. */
|
|
50
|
+
id: string
|
|
51
|
+
/** One column → full-width row; several columns → a columns row. */
|
|
52
|
+
columns?: PanelColumn[]
|
|
53
|
+
/** Arbitrary content (e.g. a nested table). Takes precedence over `columns`. */
|
|
54
|
+
content?: React.ReactNode
|
|
55
|
+
/** Per-row overrides (e.g. height, or `p-0` for a nested table that should reach the edges). */
|
|
56
|
+
className?: string
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface StackedRowsPanelProps {
|
|
60
|
+
rows: PanelRow[]
|
|
61
|
+
/** Optional uppercase caption rendered above the card. */
|
|
62
|
+
title?: string
|
|
63
|
+
className?: string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const ALIGN_CLASS: Record<PanelColumnAlign, string> = {
|
|
67
|
+
left: "justify-start items-start",
|
|
68
|
+
center: "justify-center items-center",
|
|
69
|
+
right: "justify-end items-end",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Horizontal alignment for value/label cells. `left` keeps the default stretch so
|
|
73
|
+
// the value/label can truncate; center/right shrink to content and align the cell.
|
|
74
|
+
const CELL_ITEMS_ALIGN: Record<PanelColumnAlign, string> = {
|
|
75
|
+
left: "",
|
|
76
|
+
center: "items-center",
|
|
77
|
+
right: "items-end",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const HIDE_CLASS: Record<PanelHideAt, string> = {
|
|
81
|
+
md: "hidden md:flex",
|
|
82
|
+
lg: "hidden lg:flex",
|
|
83
|
+
xl: "hidden xl:flex",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Base column-row class. Row height is content-driven via the min-heights, matching
|
|
87
|
+
// the joined-row pattern used across the detail layouts (56px mobile, 80px tablet+).
|
|
88
|
+
const ROW_CLASS =
|
|
89
|
+
"flex items-center gap-[var(--spacing-system-m)] px-[var(--spacing-system-m)] min-h-14 md:min-h-20 border-b border-ods-border last:border-b-0"
|
|
90
|
+
|
|
91
|
+
function CellValue({ column }: { column: PanelColumn }) {
|
|
92
|
+
const valueNode = (
|
|
93
|
+
<div
|
|
94
|
+
className="text-ods-text-primary text-h4 truncate"
|
|
95
|
+
title={typeof column.value === "string" ? column.value : undefined}
|
|
96
|
+
>
|
|
97
|
+
{column.value}
|
|
98
|
+
</div>
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<>
|
|
103
|
+
<div className="flex items-center gap-[var(--spacing-system-xxs)] min-w-0">
|
|
104
|
+
{column.icon && <span className="shrink-0">{column.icon}</span>}
|
|
105
|
+
{column.href ? (
|
|
106
|
+
<a
|
|
107
|
+
href={column.href}
|
|
108
|
+
target="_blank"
|
|
109
|
+
rel="noreferrer"
|
|
110
|
+
className="min-w-0 transition-opacity hover:opacity-80"
|
|
111
|
+
>
|
|
112
|
+
{valueNode}
|
|
113
|
+
</a>
|
|
114
|
+
) : (
|
|
115
|
+
valueNode
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
{column.label ? <p className="text-ods-text-secondary text-h6 truncate">{column.label}</p> : null}
|
|
119
|
+
</>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function PanelCell({ column }: { column: PanelColumn }) {
|
|
124
|
+
const widthClass = column.width ?? "flex-[1_0_0] min-w-0"
|
|
125
|
+
const hideClass = column.hideAt ? HIDE_CLASS[column.hideAt] : "flex"
|
|
126
|
+
|
|
127
|
+
if (column.content !== undefined) {
|
|
128
|
+
return (
|
|
129
|
+
<div className={cn(hideClass, "flex-col", ALIGN_CLASS[column.align ?? "left"], widthClass)}>{column.content}</div>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<div className={cn(hideClass, "flex-col justify-center", CELL_ITEMS_ALIGN[column.align ?? "left"], widthClass)}>
|
|
135
|
+
<CellValue column={column} />
|
|
136
|
+
</div>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function PanelRowView({ row }: { row: PanelRow }) {
|
|
141
|
+
// Arbitrary content (e.g. a nested table): keep it edge-to-edge unless the caller
|
|
142
|
+
// overrides via `className`. No fixed height — let the content size itself.
|
|
143
|
+
if (row.content !== undefined) {
|
|
144
|
+
return <div className={cn("border-b border-ods-border last:border-b-0", row.className)}>{row.content}</div>
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const columns = row.columns ?? []
|
|
148
|
+
const isFullWidth = columns.length === 1
|
|
149
|
+
|
|
150
|
+
// A single-column row is a full-width title/subtitle row: pad symmetrically
|
|
151
|
+
// (ODS `m`) instead of using a fixed column-row height.
|
|
152
|
+
const rowClass = isFullWidth
|
|
153
|
+
? cn(
|
|
154
|
+
"flex items-center gap-[var(--spacing-system-m)] p-[var(--spacing-system-m)] border-b border-ods-border last:border-b-0",
|
|
155
|
+
row.className,
|
|
156
|
+
)
|
|
157
|
+
: cn(ROW_CLASS, row.className)
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<div className={rowClass}>
|
|
161
|
+
{columns.map(column => (
|
|
162
|
+
<PanelCell key={column.key} column={column} />
|
|
163
|
+
))}
|
|
164
|
+
</div>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function StackedRowsPanel({ rows, title, className }: StackedRowsPanelProps) {
|
|
169
|
+
const card = (
|
|
170
|
+
<div className="flex flex-col overflow-clip rounded-md border border-ods-border bg-ods-card">
|
|
171
|
+
{rows.map(row => (
|
|
172
|
+
<PanelRowView key={row.id} row={row} />
|
|
173
|
+
))}
|
|
174
|
+
</div>
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if (!title) {
|
|
178
|
+
return <div className={className}>{card}</div>
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div className={cn("flex flex-col gap-[var(--spacing-system-xxs)]", className)}>
|
|
183
|
+
<p className="text-h5 uppercase text-ods-text-secondary">{title}</p>
|
|
184
|
+
{card}
|
|
185
|
+
</div>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
@@ -30,7 +30,7 @@ const meta = {
|
|
|
30
30
|
},
|
|
31
31
|
progressSize: {
|
|
32
32
|
control: { type: 'number' },
|
|
33
|
-
description: 'Ring diameter in px (number), or `{ base, lg }` for a responsive ring. Default 56.',
|
|
33
|
+
description: 'Ring diameter in px (number), or `{ base, md?, lg? }` for a responsive ring. Default 56.',
|
|
34
34
|
},
|
|
35
35
|
href: { control: 'text', description: 'Navigation URL — renders as a Next.js Link with pointer cursor' },
|
|
36
36
|
tooltip: { control: 'text', description: 'Tooltip content for the question-mark icon' },
|
|
@@ -276,7 +276,7 @@ export const DeviceStatusesOverview: Story = {
|
|
|
276
276
|
showProgress
|
|
277
277
|
progressVariant="success"
|
|
278
278
|
percentageDisplay="plain"
|
|
279
|
-
progressSize={{ base: 24,
|
|
279
|
+
progressSize={{ base: 24, md: 56 }}
|
|
280
280
|
/>
|
|
281
281
|
<DashboardInfoCard
|
|
282
282
|
title="Offline Devices"
|
|
@@ -285,7 +285,7 @@ export const DeviceStatusesOverview: Story = {
|
|
|
285
285
|
showProgress
|
|
286
286
|
progressVariant="error"
|
|
287
287
|
percentageDisplay="plain"
|
|
288
|
-
progressSize={{ base: 24,
|
|
288
|
+
progressSize={{ base: 24, md: 56 }}
|
|
289
289
|
/>
|
|
290
290
|
<DashboardInfoCard
|
|
291
291
|
title="Pending Devices"
|
|
@@ -294,7 +294,7 @@ export const DeviceStatusesOverview: Story = {
|
|
|
294
294
|
showProgress
|
|
295
295
|
progressVariant="warning"
|
|
296
296
|
percentageDisplay="plain"
|
|
297
|
-
progressSize={{ base: 24,
|
|
297
|
+
progressSize={{ base: 24, md: 56 }}
|
|
298
298
|
/>
|
|
299
299
|
<DashboardInfoCard
|
|
300
300
|
title="Archived Devices"
|
|
@@ -303,7 +303,7 @@ export const DeviceStatusesOverview: Story = {
|
|
|
303
303
|
showProgress
|
|
304
304
|
progressVariant="info"
|
|
305
305
|
percentageDisplay="plain"
|
|
306
|
-
progressSize={{ base: 24,
|
|
306
|
+
progressSize={{ base: 24, md: 56 }}
|
|
307
307
|
/>
|
|
308
308
|
</div>
|
|
309
309
|
),
|
|
@@ -311,7 +311,7 @@ export const DeviceStatusesOverview: Story = {
|
|
|
311
311
|
docs: {
|
|
312
312
|
description: {
|
|
313
313
|
story:
|
|
314
|
-
'Matches the Figma "Devices Overview" — green/red/amber/grey rings with neutral percentages via `percentageDisplay="plain"`, and a responsive ring (`progressSize={{ base: 24,
|
|
314
|
+
'Matches the Figma "Devices Overview" — green/red/amber/grey rings with neutral percentages via `percentageDisplay="plain"`, and a responsive ring (`progressSize={{ base: 24, md: 56 }}`) that is 24px on mobile and 56px on tablet and desktop (from the `md` breakpoint up).',
|
|
315
315
|
},
|
|
316
316
|
},
|
|
317
317
|
},
|
|
@@ -320,7 +320,7 @@ export const DeviceStatusesOverview: Story = {
|
|
|
320
320
|
/**
|
|
321
321
|
* `progressSize` controls the ring diameter (stroke scales with it). Left: the
|
|
322
322
|
* 24px mobile spec. Right: the 56px desktop default. In production pass
|
|
323
|
-
* `progressSize={{ base: 24,
|
|
323
|
+
* `progressSize={{ base: 24, md: 56 }}` for the responsive switch.
|
|
324
324
|
*/
|
|
325
325
|
export const ProgressSizeComparison: Story = {
|
|
326
326
|
render: () => (
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
import { type ColumnDef, DataTable, type Row, useDataTable } from '../components/ui/data-table'
|
|
4
|
+
import { EntityImage } from '../components/ui/entity-image'
|
|
5
|
+
import { StackedRowsPanel } from '../components/ui/stacked-rows-panel'
|
|
6
|
+
import { Tag } from '../components/ui/tag'
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
title: 'UI/StackedRowsPanel',
|
|
10
|
+
component: StackedRowsPanel,
|
|
11
|
+
parameters: {
|
|
12
|
+
layout: 'padded',
|
|
13
|
+
docs: {
|
|
14
|
+
description: {
|
|
15
|
+
component:
|
|
16
|
+
'A single bordered card that stacks rows inside it. Each row can be one full-width element ' +
|
|
17
|
+
'(title + subtitle), several columns (value + label, optionally a tag/avatar), or an arbitrary ' +
|
|
18
|
+
'node such as a nested table. The container handles all rounding: one row rounds every corner, ' +
|
|
19
|
+
'while many rows round only the first row’s top corners and the last row’s bottom corners.',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
tags: ['autodocs'],
|
|
24
|
+
} satisfies Meta<typeof StackedRowsPanel>
|
|
25
|
+
|
|
26
|
+
export default meta
|
|
27
|
+
type Story = StoryObj<typeof meta>
|
|
28
|
+
|
|
29
|
+
/** Header card: a full-width title/description row followed by a columns row. */
|
|
30
|
+
export const HeaderCard: Story = {
|
|
31
|
+
args: {
|
|
32
|
+
rows: [
|
|
33
|
+
{
|
|
34
|
+
id: 'title',
|
|
35
|
+
columns: [
|
|
36
|
+
{
|
|
37
|
+
key: 'title',
|
|
38
|
+
value: 'Monitors sustained high processor utilization indicating performance bottlenecks',
|
|
39
|
+
label: 'Description',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'meta',
|
|
45
|
+
columns: [
|
|
46
|
+
{ key: 'severity', value: 'Critical', label: 'Severity' },
|
|
47
|
+
{ key: 'status', value: <Tag label="Failing" variant="error" />, label: 'Status' },
|
|
48
|
+
{ key: 'author', value: 'Jane Doe', label: 'Author' },
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Standard table: uniform multi-column rows (avatar + name/email, role, status tag). */
|
|
56
|
+
export const StandardTable: Story = {
|
|
57
|
+
args: {
|
|
58
|
+
rows: [
|
|
59
|
+
{
|
|
60
|
+
id: 'jane',
|
|
61
|
+
columns: [
|
|
62
|
+
{
|
|
63
|
+
key: 'user',
|
|
64
|
+
content: (
|
|
65
|
+
<div className="flex items-center gap-[var(--spacing-system-m)] min-w-0">
|
|
66
|
+
<EntityImage alt="Jane Doe" fallbackText="Jane Doe" className="size-8 rounded-full" />
|
|
67
|
+
<div className="flex flex-col min-w-0">
|
|
68
|
+
<span className="text-ods-text-primary text-h4 truncate">Jane Doe</span>
|
|
69
|
+
<span className="text-ods-text-secondary text-h6 truncate">jane.doe@example.com</span>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
),
|
|
73
|
+
},
|
|
74
|
+
{ key: 'role', value: 'Admin', hideAt: 'md' },
|
|
75
|
+
{ key: 'status', content: <Tag label="Active" variant="success" />, align: 'right' },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'john',
|
|
80
|
+
columns: [
|
|
81
|
+
{
|
|
82
|
+
key: 'user',
|
|
83
|
+
content: (
|
|
84
|
+
<div className="flex items-center gap-[var(--spacing-system-m)] min-w-0">
|
|
85
|
+
<EntityImage alt="John Roe" fallbackText="John Roe" className="size-8 rounded-full" />
|
|
86
|
+
<div className="flex flex-col min-w-0">
|
|
87
|
+
<span className="text-ods-text-primary text-h4 truncate">John Roe</span>
|
|
88
|
+
<span className="text-ods-text-secondary text-h6 truncate">john.roe@example.com</span>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
),
|
|
92
|
+
},
|
|
93
|
+
{ key: 'role', value: 'Admin', hideAt: 'md' },
|
|
94
|
+
{ key: 'status', content: <Tag label="Offline" variant="grey" />, align: 'right' },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** A single row rounds all four corners. */
|
|
102
|
+
export const SingleRow: Story = {
|
|
103
|
+
args: {
|
|
104
|
+
rows: [{ id: 'only', columns: [{ key: 'only', value: 'High CPU Usage >85%', label: 'Policy' }] }],
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface DemoDevice {
|
|
109
|
+
id: string
|
|
110
|
+
name: string
|
|
111
|
+
status: 'NON-COMPLIANT' | 'COMPLIANT'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const DEMO_DEVICES: DemoDevice[] = [
|
|
115
|
+
{ id: '1', name: "John's Device", status: 'NON-COMPLIANT' },
|
|
116
|
+
{ id: '2', name: 'Workstation-04', status: 'COMPLIANT' },
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
function NestedDevicesTable() {
|
|
120
|
+
const columns = useMemo<ColumnDef<DemoDevice>[]>(
|
|
121
|
+
() => [
|
|
122
|
+
{
|
|
123
|
+
accessorKey: 'name',
|
|
124
|
+
header: 'DEVICE',
|
|
125
|
+
cell: ({ row }: { row: Row<DemoDevice> }) => (
|
|
126
|
+
<div className="flex flex-col">
|
|
127
|
+
<span className="text-ods-text-primary text-h4 truncate">{row.original.name}</span>
|
|
128
|
+
<span className="text-ods-text-secondary text-h6 truncate">Last Online: 2 hours ago</span>
|
|
129
|
+
</div>
|
|
130
|
+
),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
accessorKey: 'status',
|
|
134
|
+
header: 'STATUS',
|
|
135
|
+
cell: ({ row }: { row: Row<DemoDevice> }) => (
|
|
136
|
+
<Tag label={row.original.status} variant={row.original.status === 'COMPLIANT' ? 'success' : 'error'} />
|
|
137
|
+
),
|
|
138
|
+
meta: { width: 'w-[180px]' },
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
[],
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
const table = useDataTable<DemoDevice>({
|
|
145
|
+
data: DEMO_DEVICES,
|
|
146
|
+
columns,
|
|
147
|
+
getRowId: (row: DemoDevice) => row.id,
|
|
148
|
+
enableSorting: false,
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<DataTable table={table}>
|
|
153
|
+
<DataTable.Header />
|
|
154
|
+
<DataTable.Body />
|
|
155
|
+
</DataTable>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** A panel row can host an arbitrary node — here a nested DataTable. */
|
|
160
|
+
export const WithNestedTable: Story = {
|
|
161
|
+
args: {
|
|
162
|
+
title: 'Devices',
|
|
163
|
+
rows: [{ id: 'devices', className: 'p-[var(--spacing-system-m)]', content: <NestedDevicesTable /> }],
|
|
164
|
+
},
|
|
165
|
+
}
|