@you-agent-factory/components 0.0.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/README.md +223 -0
- package/docs/README.md +83 -0
- package/docs/button.md +119 -0
- package/docs/charts.md +325 -0
- package/docs/data-display-code-panel.md +120 -0
- package/docs/feedback-alert-panel.md +117 -0
- package/docs/feedback-skeleton.md +54 -0
- package/docs/forms-form-field.md +490 -0
- package/docs/forms-input-primitives.md +311 -0
- package/docs/forms-select-primitives.md +362 -0
- package/docs/graphs.md +250 -0
- package/docs/layout-display-primitives.md +211 -0
- package/docs/overlays.md +414 -0
- package/docs/table-data-table.md +331 -0
- package/docs/typography-roles.md +182 -0
- package/docs/widget-frame-recipes.md +273 -0
- package/package.json +161 -0
- package/src/category-paths.ts +20 -0
- package/src/charts/chart-state-panel.tsx +103 -0
- package/src/charts/chart.tsx +287 -0
- package/src/charts/index.ts +21 -0
- package/src/data-display/code-panel.tsx +67 -0
- package/src/data-display/data-table.tsx +180 -0
- package/src/data-display/description-list.tsx +24 -0
- package/src/data-display/index.ts +34 -0
- package/src/data-display/table-layout.ts +12 -0
- package/src/data-display/table.tsx +131 -0
- package/src/feedback/alert-panel-semantics.ts +75 -0
- package/src/feedback/alert-panel.tsx +187 -0
- package/src/feedback/index.ts +21 -0
- package/src/feedback/skeleton.tsx +16 -0
- package/src/forms/index.ts +73 -0
- package/src/forms/package-checkbox.tsx +50 -0
- package/src/forms/package-enum-select.tsx +185 -0
- package/src/forms/package-file-input.tsx +25 -0
- package/src/forms/package-form-field.tsx +202 -0
- package/src/forms/package-input.tsx +25 -0
- package/src/forms/package-native-select.tsx +25 -0
- package/src/forms/package-select.tsx +209 -0
- package/src/forms/package-textarea.tsx +35 -0
- package/src/forms/select-icons.tsx +35 -0
- package/src/graphs/graph-edge-path.ts +241 -0
- package/src/graphs/graph-edge.tsx +130 -0
- package/src/graphs/graph-interactive-example.tsx +187 -0
- package/src/graphs/graph-node-button.tsx +51 -0
- package/src/graphs/graph-node-handle-badge.tsx +108 -0
- package/src/graphs/graph-node-handle.ts +28 -0
- package/src/graphs/graph-node-shell.tsx +111 -0
- package/src/graphs/graph-node-state-indicator.tsx +47 -0
- package/src/graphs/graph-node-state.ts +111 -0
- package/src/graphs/graph-viewport-surface.tsx +27 -0
- package/src/graphs/index.ts +41 -0
- package/src/icons/index.ts +4 -0
- package/src/index.ts +209 -0
- package/src/layout/action-row.tsx +53 -0
- package/src/layout/index.ts +9 -0
- package/src/layout/surface-panel.tsx +91 -0
- package/src/navigation/index.ts +4 -0
- package/src/overlays/collapsible.tsx +22 -0
- package/src/overlays/dialog.tsx +148 -0
- package/src/overlays/index.ts +31 -0
- package/src/overlays/overlay-layout.ts +9 -0
- package/src/overlays/popover.tsx +29 -0
- package/src/overlays/scroll-area.tsx +78 -0
- package/src/primitives/button-link.tsx +22 -0
- package/src/primitives/button.tsx +258 -0
- package/src/primitives/icon-button-shell.tsx +49 -0
- package/src/primitives/index.ts +31 -0
- package/src/primitives/package-text.tsx +29 -0
- package/src/primitives/typography-roles.ts +12 -0
- package/src/primitives/typography.tsx +165 -0
- package/src/recipes/index.ts +61 -0
- package/src/recipes/widget-frame-content.tsx +114 -0
- package/src/recipes/widget-frame-disclosure.tsx +161 -0
- package/src/recipes/widget-frame-layout.ts +40 -0
- package/src/recipes/widget-frame-skeleton.tsx +16 -0
- package/src/recipes/widget-frame-states.tsx +99 -0
- package/src/recipes/widget-frame-typography.ts +19 -0
- package/src/recipes/widget-frame.tsx +84 -0
- package/src/styles/color-palette-presets.css +167 -0
- package/src/styles/color-role-tokens.css +163 -0
- package/src/styles/layout-role-tokens.css +27 -0
- package/src/styles/text-color-role-tokens.css +29 -0
- package/src/styles/typography-role-tokens.css +72 -0
- package/src/styles/typography-role-utilities.css +73 -0
- package/src/styles/typography-roles.css +53 -0
- package/src/styles.css +32 -0
- package/src/testing/index.ts +13 -0
- package/src/testing/render.tsx +16 -0
- package/src/tokens/index.ts +4 -0
- package/src/utilities/cn.ts +5 -0
- package/src/utilities/index.ts +6 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CSSProperties,
|
|
3
|
+
HTMLAttributes,
|
|
4
|
+
MouseEvent as ReactMouseEvent,
|
|
5
|
+
ReactNode,
|
|
6
|
+
} from "react";
|
|
7
|
+
import { createContext, useContext } from "react";
|
|
8
|
+
import {
|
|
9
|
+
Legend as RechartsLegend,
|
|
10
|
+
Tooltip as RechartsTooltip,
|
|
11
|
+
ResponsiveContainer,
|
|
12
|
+
type TooltipContentProps,
|
|
13
|
+
} from "recharts";
|
|
14
|
+
import type {
|
|
15
|
+
LegendPayload,
|
|
16
|
+
Props as RechartsLegendContentProps,
|
|
17
|
+
} from "recharts/types/component/DefaultLegendContent";
|
|
18
|
+
import type {
|
|
19
|
+
NameType,
|
|
20
|
+
ValueType,
|
|
21
|
+
} from "recharts/types/component/DefaultTooltipContent";
|
|
22
|
+
|
|
23
|
+
import { cn } from "../utilities/cn";
|
|
24
|
+
|
|
25
|
+
export interface ChartConfigEntry {
|
|
26
|
+
color: string;
|
|
27
|
+
label: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ChartConfig = Record<string, ChartConfigEntry>;
|
|
31
|
+
|
|
32
|
+
const ChartContext = createContext<ChartConfig | null>(null);
|
|
33
|
+
|
|
34
|
+
export type ChartPresentation = "embedded" | "standalone";
|
|
35
|
+
|
|
36
|
+
// tailwind-exception: intrinsic-sizing
|
|
37
|
+
const CHART_CONTAINER_STANDALONE_CLASS =
|
|
38
|
+
"relative h-[18rem] rounded-2xl border border-outline bg-surface-container-low p-4 text-on-surface";
|
|
39
|
+
// tailwind-exception: intrinsic-sizing
|
|
40
|
+
const CHART_CONTAINER_EMBEDDED_CLASS =
|
|
41
|
+
"relative flex h-full min-h-0 min-w-0 w-full flex-1 flex-col text-on-surface";
|
|
42
|
+
const DEFAULT_CHART_INITIAL_DIMENSION = { height: 288, width: 640 } as const;
|
|
43
|
+
|
|
44
|
+
const CHART_LEGEND_BUTTON_CLASS =
|
|
45
|
+
"inline-flex min-h-0 items-center justify-center rounded-md border border-transparent bg-transparent px-0 py-1 text-left text-xs font-normal text-on-surface-variant focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-af-focus";
|
|
46
|
+
|
|
47
|
+
function chartContainerClassName(
|
|
48
|
+
presentation: ChartPresentation,
|
|
49
|
+
className?: string,
|
|
50
|
+
): string {
|
|
51
|
+
return cn(
|
|
52
|
+
presentation === "embedded"
|
|
53
|
+
? CHART_CONTAINER_EMBEDDED_CLASS
|
|
54
|
+
: CHART_CONTAINER_STANDALONE_CLASS,
|
|
55
|
+
className,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function useChartConfig() {
|
|
60
|
+
const context = useContext(ChartContext);
|
|
61
|
+
|
|
62
|
+
if (context === null) {
|
|
63
|
+
throw new Error("Chart components must be rendered inside ChartContainer.");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return context;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function ChartContainer({
|
|
70
|
+
children,
|
|
71
|
+
className,
|
|
72
|
+
config,
|
|
73
|
+
footer,
|
|
74
|
+
interactionAttributes,
|
|
75
|
+
overlay,
|
|
76
|
+
presentation = "standalone",
|
|
77
|
+
rootAttributes,
|
|
78
|
+
style,
|
|
79
|
+
title,
|
|
80
|
+
}: {
|
|
81
|
+
children: ReactNode;
|
|
82
|
+
className?: string;
|
|
83
|
+
config: ChartConfig;
|
|
84
|
+
footer?: ReactNode;
|
|
85
|
+
interactionAttributes?: HTMLAttributes<HTMLDivElement>;
|
|
86
|
+
overlay?: ReactNode;
|
|
87
|
+
presentation?: ChartPresentation;
|
|
88
|
+
rootAttributes?: Record<string, string>;
|
|
89
|
+
style?: CSSProperties;
|
|
90
|
+
title: string;
|
|
91
|
+
}) {
|
|
92
|
+
const chartSurface = (
|
|
93
|
+
<ResponsiveContainer
|
|
94
|
+
height="100%"
|
|
95
|
+
initialDimension={DEFAULT_CHART_INITIAL_DIMENSION}
|
|
96
|
+
minHeight={0}
|
|
97
|
+
minWidth={0}
|
|
98
|
+
width="100%"
|
|
99
|
+
>
|
|
100
|
+
{children}
|
|
101
|
+
</ResponsiveContainer>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<ChartContext.Provider value={config}>
|
|
106
|
+
<div
|
|
107
|
+
aria-label={title}
|
|
108
|
+
className={chartContainerClassName(presentation, className)}
|
|
109
|
+
data-chart-container=""
|
|
110
|
+
data-chart-presentation={presentation}
|
|
111
|
+
role="img"
|
|
112
|
+
{...interactionAttributes}
|
|
113
|
+
{...rootAttributes}
|
|
114
|
+
style={
|
|
115
|
+
{
|
|
116
|
+
...Object.fromEntries(
|
|
117
|
+
Object.entries(config).map(([key, value]) => [
|
|
118
|
+
`--color-${key}`,
|
|
119
|
+
value.color,
|
|
120
|
+
]),
|
|
121
|
+
),
|
|
122
|
+
...style,
|
|
123
|
+
} as CSSProperties
|
|
124
|
+
}
|
|
125
|
+
>
|
|
126
|
+
{overlay ? (
|
|
127
|
+
<div className="pointer-events-none absolute inset-0">{overlay}</div>
|
|
128
|
+
) : null}
|
|
129
|
+
{footer ? (
|
|
130
|
+
<div className="flex min-h-0 flex-1 basis-0 flex-col">
|
|
131
|
+
<div className="h-full min-h-0 flex-1 basis-0">{chartSurface}</div>
|
|
132
|
+
{footer}
|
|
133
|
+
</div>
|
|
134
|
+
) : (
|
|
135
|
+
chartSurface
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
</ChartContext.Provider>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const ChartTooltip = RechartsTooltip;
|
|
143
|
+
export const ChartLegend = RechartsLegend;
|
|
144
|
+
|
|
145
|
+
export function ChartTooltipContent({
|
|
146
|
+
active,
|
|
147
|
+
className,
|
|
148
|
+
label,
|
|
149
|
+
payload,
|
|
150
|
+
}: TooltipContentProps<ValueType, NameType> & { className?: string }) {
|
|
151
|
+
const config = useChartConfig();
|
|
152
|
+
|
|
153
|
+
if (!active || !payload?.length) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div
|
|
159
|
+
className={cn(
|
|
160
|
+
"grid min-w-40 gap-2 rounded-xl border border-outline bg-surface-container-high px-3 py-2 text-sm shadow-af-card",
|
|
161
|
+
className,
|
|
162
|
+
)}
|
|
163
|
+
>
|
|
164
|
+
<p className="m-0 font-semibold text-on-surface">{String(label)}</p>
|
|
165
|
+
<div className="grid gap-1">
|
|
166
|
+
{payload.map((entry) => {
|
|
167
|
+
const key = entry.dataKey?.toString() ?? "";
|
|
168
|
+
const item = config[key];
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<div
|
|
172
|
+
className="flex items-center justify-between gap-3 text-on-surface-variant"
|
|
173
|
+
key={key}
|
|
174
|
+
>
|
|
175
|
+
<div className="flex items-center gap-2">
|
|
176
|
+
<span
|
|
177
|
+
className="h-2.5 w-2.5 rounded-full"
|
|
178
|
+
style={{
|
|
179
|
+
backgroundColor:
|
|
180
|
+
item?.color ?? entry.color ?? "currentColor",
|
|
181
|
+
}}
|
|
182
|
+
/>
|
|
183
|
+
<span>{item?.label ?? key}</span>
|
|
184
|
+
</div>
|
|
185
|
+
<span className="font-medium text-on-surface">{entry.value}</span>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
})}
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function ChartLegendContent({
|
|
195
|
+
className,
|
|
196
|
+
getToggleLabel,
|
|
197
|
+
hiddenSeries = new Set<string>(),
|
|
198
|
+
itemClassName,
|
|
199
|
+
onToggleSeries,
|
|
200
|
+
payload,
|
|
201
|
+
swatchClassName,
|
|
202
|
+
}: RechartsLegendContentProps & {
|
|
203
|
+
className?: string;
|
|
204
|
+
getToggleLabel?: (label: string, hidden: boolean) => string;
|
|
205
|
+
hiddenSeries?: ReadonlySet<string>;
|
|
206
|
+
itemClassName?: string;
|
|
207
|
+
onToggleSeries?: (key: string) => void;
|
|
208
|
+
swatchClassName?: string;
|
|
209
|
+
}) {
|
|
210
|
+
const config = useChartConfig();
|
|
211
|
+
|
|
212
|
+
if (!payload?.length) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const stopChartInteraction = (event: ReactMouseEvent<HTMLButtonElement>) => {
|
|
217
|
+
event.stopPropagation();
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<div
|
|
222
|
+
className={cn(
|
|
223
|
+
"flex flex-wrap items-center gap-4 pt-3 text-xs text-on-surface-variant",
|
|
224
|
+
className,
|
|
225
|
+
)}
|
|
226
|
+
>
|
|
227
|
+
{payload.map((entry: LegendPayload) => {
|
|
228
|
+
const key = entry.dataKey?.toString() ?? "";
|
|
229
|
+
const item = config[key];
|
|
230
|
+
const label = item?.label ?? key;
|
|
231
|
+
const hidden = hiddenSeries.has(key);
|
|
232
|
+
const content = (
|
|
233
|
+
<>
|
|
234
|
+
<span
|
|
235
|
+
aria-hidden="true"
|
|
236
|
+
className={cn(
|
|
237
|
+
"rounded-full",
|
|
238
|
+
swatchClassName ?? "h-2.5 w-2.5",
|
|
239
|
+
hidden ? "border border-outline bg-surface-container-low" : "",
|
|
240
|
+
)}
|
|
241
|
+
style={{
|
|
242
|
+
backgroundColor: hidden
|
|
243
|
+
? undefined
|
|
244
|
+
: (item?.color ?? entry.color ?? "currentColor"),
|
|
245
|
+
}}
|
|
246
|
+
/>
|
|
247
|
+
<span>{label}</span>
|
|
248
|
+
</>
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
if (!onToggleSeries) {
|
|
252
|
+
return (
|
|
253
|
+
<div
|
|
254
|
+
className={cn("flex items-center gap-2", itemClassName)}
|
|
255
|
+
key={key}
|
|
256
|
+
>
|
|
257
|
+
{content}
|
|
258
|
+
</div>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
<button
|
|
264
|
+
aria-label={getToggleLabel?.(label, hidden) ?? label}
|
|
265
|
+
aria-pressed={!hidden}
|
|
266
|
+
className={cn(
|
|
267
|
+
CHART_LEGEND_BUTTON_CLASS,
|
|
268
|
+
hidden ? "text-on-surface-disabled" : "",
|
|
269
|
+
itemClassName,
|
|
270
|
+
)}
|
|
271
|
+
data-chart-legend-series={key}
|
|
272
|
+
data-chart-legend-series-hidden={hidden ? "true" : "false"}
|
|
273
|
+
key={key}
|
|
274
|
+
onClick={(event) => {
|
|
275
|
+
event.stopPropagation();
|
|
276
|
+
onToggleSeries(key);
|
|
277
|
+
}}
|
|
278
|
+
onMouseDown={stopChartInteraction}
|
|
279
|
+
type="button"
|
|
280
|
+
>
|
|
281
|
+
{content}
|
|
282
|
+
</button>
|
|
283
|
+
);
|
|
284
|
+
})}
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Stable category path for `@you-agent-factory/components/charts`. */
|
|
2
|
+
export const COMPONENTS_CATEGORY = "charts" as const;
|
|
3
|
+
|
|
4
|
+
export type ComponentsCategory = typeof COMPONENTS_CATEGORY;
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
ChartContainer,
|
|
8
|
+
ChartLegend,
|
|
9
|
+
ChartLegendContent,
|
|
10
|
+
ChartTooltip,
|
|
11
|
+
ChartTooltipContent,
|
|
12
|
+
type ChartConfig,
|
|
13
|
+
type ChartConfigEntry,
|
|
14
|
+
type ChartPresentation,
|
|
15
|
+
} from "./chart";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
ChartStatePanel,
|
|
19
|
+
type ChartStatePanelProps,
|
|
20
|
+
type ChartStateStatus,
|
|
21
|
+
} from "./chart-state-panel";
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { forwardRef, type HTMLAttributes } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../utilities/cn";
|
|
4
|
+
|
|
5
|
+
type CodePanelPadding = "compact" | "default";
|
|
6
|
+
type CodePanelSurface = "high" | "low";
|
|
7
|
+
type CodePanelMaxHeight = "none" | "sm" | "md" | "lg";
|
|
8
|
+
|
|
9
|
+
export interface CodePanelProps extends HTMLAttributes<HTMLPreElement> {
|
|
10
|
+
maxHeight?: CodePanelMaxHeight;
|
|
11
|
+
padding?: CodePanelPadding;
|
|
12
|
+
surface?: CodePanelSurface;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const CODE_PANEL_PADDING_CLASS: Record<CodePanelPadding, string> = {
|
|
16
|
+
compact: "p-2",
|
|
17
|
+
default: "p-3",
|
|
18
|
+
};
|
|
19
|
+
const CODE_PANEL_SURFACE_CLASS: Record<CodePanelSurface, string> = {
|
|
20
|
+
high: "bg-surface-container-high",
|
|
21
|
+
low: "bg-surface-container-low",
|
|
22
|
+
};
|
|
23
|
+
const CODE_PANEL_MAX_HEIGHT_CLASS: Record<CodePanelMaxHeight, string> = {
|
|
24
|
+
none: "",
|
|
25
|
+
sm: "max-h-48 overflow-y-auto",
|
|
26
|
+
md: "max-h-72 overflow-y-auto",
|
|
27
|
+
lg: "max-h-96 overflow-y-auto",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const CODE_PANEL_BODY_CODE_CLASS = "font-mono text-code-medium text-code";
|
|
31
|
+
|
|
32
|
+
const CODE_PANEL_CONTAINMENT_CLASS =
|
|
33
|
+
"m-0 min-w-0 w-full max-w-full overflow-x-auto whitespace-pre-wrap rounded-lg border border-outline [overflow-wrap:anywhere] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary";
|
|
34
|
+
|
|
35
|
+
export function codePanelVariants({
|
|
36
|
+
className,
|
|
37
|
+
maxHeight = "none",
|
|
38
|
+
padding = "compact",
|
|
39
|
+
surface = "high",
|
|
40
|
+
}: Pick<CodePanelProps, "className" | "maxHeight" | "padding" | "surface">) {
|
|
41
|
+
return cn(
|
|
42
|
+
CODE_PANEL_CONTAINMENT_CLASS,
|
|
43
|
+
CODE_PANEL_PADDING_CLASS[padding],
|
|
44
|
+
CODE_PANEL_SURFACE_CLASS[surface],
|
|
45
|
+
CODE_PANEL_MAX_HEIGHT_CLASS[maxHeight],
|
|
46
|
+
CODE_PANEL_BODY_CODE_CLASS,
|
|
47
|
+
className,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const CodePanel = forwardRef<HTMLPreElement, CodePanelProps>(
|
|
52
|
+
function CodePanel(
|
|
53
|
+
{ className, maxHeight = "none", padding, surface, tabIndex, ...props },
|
|
54
|
+
ref,
|
|
55
|
+
) {
|
|
56
|
+
const isScrollable = maxHeight !== "none";
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<pre
|
|
60
|
+
className={codePanelVariants({ className, maxHeight, padding, surface })}
|
|
61
|
+
ref={ref}
|
|
62
|
+
tabIndex={tabIndex ?? (isScrollable ? 0 : undefined)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
);
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../utilities/cn";
|
|
4
|
+
import {
|
|
5
|
+
Table,
|
|
6
|
+
type TableSize,
|
|
7
|
+
TableBody,
|
|
8
|
+
TableCaption,
|
|
9
|
+
TableCell,
|
|
10
|
+
TableHead,
|
|
11
|
+
TableHeader,
|
|
12
|
+
TableRow,
|
|
13
|
+
} from "./table";
|
|
14
|
+
|
|
15
|
+
export type DataTableState = "empty" | "error" | "loading" | "success";
|
|
16
|
+
|
|
17
|
+
export interface DataTableColumn<Row> {
|
|
18
|
+
cell: (row: Row) => ReactNode;
|
|
19
|
+
header: ReactNode;
|
|
20
|
+
id: string;
|
|
21
|
+
cellClassName?: string;
|
|
22
|
+
headerClassName?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DataTableProps<Row> {
|
|
26
|
+
columns: DataTableColumn<Row>[];
|
|
27
|
+
data: Row[];
|
|
28
|
+
getRowKey: (row: Row) => string;
|
|
29
|
+
ariaLabel?: string;
|
|
30
|
+
caption?: ReactNode;
|
|
31
|
+
containerClassName?: string;
|
|
32
|
+
containerProps?: HTMLAttributes<HTMLDivElement>;
|
|
33
|
+
emptyMessage?: ReactNode;
|
|
34
|
+
errorMessage?: ReactNode;
|
|
35
|
+
loadingMessage?: ReactNode;
|
|
36
|
+
rowClassName?: (row: Row) => string | undefined;
|
|
37
|
+
size?: TableSize;
|
|
38
|
+
state?: DataTableState;
|
|
39
|
+
tableClassName?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function DataTablePlaceholderBar({ className }: { className?: string }) {
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
aria-hidden="true"
|
|
46
|
+
className={cn("animate-pulse rounded-xl bg-af-overlay", className)}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function DataTableStatusRow({
|
|
52
|
+
children,
|
|
53
|
+
colSpan,
|
|
54
|
+
role,
|
|
55
|
+
ariaBusy,
|
|
56
|
+
ariaLive,
|
|
57
|
+
cellClassName,
|
|
58
|
+
}: {
|
|
59
|
+
children: ReactNode;
|
|
60
|
+
colSpan: number;
|
|
61
|
+
role: "alert" | "status";
|
|
62
|
+
ariaBusy?: boolean;
|
|
63
|
+
ariaLive?: "assertive" | "polite";
|
|
64
|
+
cellClassName?: string;
|
|
65
|
+
}) {
|
|
66
|
+
return (
|
|
67
|
+
<TableRow>
|
|
68
|
+
<TableCell className={cellClassName} colSpan={colSpan}>
|
|
69
|
+
<div
|
|
70
|
+
aria-busy={ariaBusy || undefined}
|
|
71
|
+
aria-live={ariaLive}
|
|
72
|
+
role={role}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
</div>
|
|
76
|
+
</TableCell>
|
|
77
|
+
</TableRow>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function DataTable<Row>({
|
|
82
|
+
columns,
|
|
83
|
+
data,
|
|
84
|
+
getRowKey,
|
|
85
|
+
ariaLabel,
|
|
86
|
+
caption,
|
|
87
|
+
containerClassName,
|
|
88
|
+
containerProps,
|
|
89
|
+
emptyMessage,
|
|
90
|
+
errorMessage,
|
|
91
|
+
loadingMessage,
|
|
92
|
+
rowClassName,
|
|
93
|
+
size,
|
|
94
|
+
state = "success",
|
|
95
|
+
tableClassName,
|
|
96
|
+
}: DataTableProps<Row>) {
|
|
97
|
+
const renderBody = () => {
|
|
98
|
+
if (state === "loading") {
|
|
99
|
+
return (
|
|
100
|
+
<DataTableStatusRow
|
|
101
|
+
ariaBusy
|
|
102
|
+
ariaLive="polite"
|
|
103
|
+
colSpan={columns.length}
|
|
104
|
+
role="status"
|
|
105
|
+
>
|
|
106
|
+
<div className="grid gap-3 py-1">
|
|
107
|
+
{loadingMessage}
|
|
108
|
+
<div aria-hidden="true" className="grid gap-2">
|
|
109
|
+
<DataTablePlaceholderBar className="h-4 w-full max-w-48" />
|
|
110
|
+
<DataTablePlaceholderBar className="h-8 w-full" />
|
|
111
|
+
<DataTablePlaceholderBar className="h-4 w-full max-w-48" />
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</DataTableStatusRow>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (state === "error") {
|
|
119
|
+
return (
|
|
120
|
+
<DataTableStatusRow
|
|
121
|
+
ariaLive="assertive"
|
|
122
|
+
cellClassName="text-af-text-subtle"
|
|
123
|
+
colSpan={columns.length}
|
|
124
|
+
role="alert"
|
|
125
|
+
>
|
|
126
|
+
{errorMessage}
|
|
127
|
+
</DataTableStatusRow>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (state === "empty" || (state === "success" && data.length === 0)) {
|
|
132
|
+
return (
|
|
133
|
+
<DataTableStatusRow
|
|
134
|
+
ariaLive="polite"
|
|
135
|
+
cellClassName="text-af-text-subtle"
|
|
136
|
+
colSpan={columns.length}
|
|
137
|
+
role="status"
|
|
138
|
+
>
|
|
139
|
+
{emptyMessage}
|
|
140
|
+
</DataTableStatusRow>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return data.map((row) => (
|
|
145
|
+
<TableRow className={rowClassName?.(row)} key={getRowKey(row)}>
|
|
146
|
+
{columns.map((column) => (
|
|
147
|
+
<TableCell className={column.cellClassName} key={column.id}>
|
|
148
|
+
{column.cell(row)}
|
|
149
|
+
</TableCell>
|
|
150
|
+
))}
|
|
151
|
+
</TableRow>
|
|
152
|
+
));
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<Table
|
|
157
|
+
aria-label={ariaLabel}
|
|
158
|
+
className={tableClassName}
|
|
159
|
+
containerClassName={containerClassName}
|
|
160
|
+
containerProps={containerProps}
|
|
161
|
+
size={size}
|
|
162
|
+
>
|
|
163
|
+
{caption ? <TableCaption>{caption}</TableCaption> : null}
|
|
164
|
+
<TableHeader>
|
|
165
|
+
<TableRow>
|
|
166
|
+
{columns.map((column) => (
|
|
167
|
+
<TableHead
|
|
168
|
+
className={column.headerClassName}
|
|
169
|
+
key={column.id}
|
|
170
|
+
scope="col"
|
|
171
|
+
>
|
|
172
|
+
{column.header}
|
|
173
|
+
</TableHead>
|
|
174
|
+
))}
|
|
175
|
+
</TableRow>
|
|
176
|
+
</TableHeader>
|
|
177
|
+
<TableBody>{renderBody()}</TableBody>
|
|
178
|
+
</Table>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { forwardRef, type HTMLAttributes } from "react";
|
|
2
|
+
|
|
3
|
+
import { BODY_TEXT_CLASS } from "../primitives/typography-roles";
|
|
4
|
+
import { cn } from "../utilities/cn";
|
|
5
|
+
|
|
6
|
+
export interface DescriptionListProps
|
|
7
|
+
extends HTMLAttributes<HTMLDListElement> {}
|
|
8
|
+
|
|
9
|
+
export const DescriptionList = forwardRef<
|
|
10
|
+
HTMLDListElement,
|
|
11
|
+
DescriptionListProps
|
|
12
|
+
>(function DescriptionList({ className, ...props }, ref) {
|
|
13
|
+
return (
|
|
14
|
+
<dl
|
|
15
|
+
className={cn(
|
|
16
|
+
"m-0 grid min-w-0 gap-1.5 [&_dd]:m-0 [&_dd]:min-w-0 [&_div]:grid [&_div]:min-w-0 [&_div]:gap-2",
|
|
17
|
+
BODY_TEXT_CLASS,
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
ref={ref}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Stable category path for `@you-agent-factory/components/data-display`. */
|
|
2
|
+
export const COMPONENTS_CATEGORY = "data-display" as const;
|
|
3
|
+
|
|
4
|
+
export type ComponentsCategory = typeof COMPONENTS_CATEGORY;
|
|
5
|
+
|
|
6
|
+
export { CodePanel, codePanelVariants } from "./code-panel";
|
|
7
|
+
export type { CodePanelProps } from "./code-panel";
|
|
8
|
+
export { DescriptionList } from "./description-list";
|
|
9
|
+
export type { DescriptionListProps } from "./description-list";
|
|
10
|
+
|
|
11
|
+
export { DataTable } from "./data-table";
|
|
12
|
+
export type {
|
|
13
|
+
DataTableColumn,
|
|
14
|
+
DataTableProps,
|
|
15
|
+
DataTableState,
|
|
16
|
+
} from "./data-table";
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
Table,
|
|
20
|
+
TableBody,
|
|
21
|
+
TableCaption,
|
|
22
|
+
TableCell,
|
|
23
|
+
TableHead,
|
|
24
|
+
TableHeader,
|
|
25
|
+
TableRow,
|
|
26
|
+
} from "./table";
|
|
27
|
+
export type { TableProps, TableSize } from "./table";
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
tableCellTruncateClassName,
|
|
31
|
+
tableCellWrapClassName,
|
|
32
|
+
tableMinWidthWideClassName,
|
|
33
|
+
tableNarrowContainerClassName,
|
|
34
|
+
} from "./table-layout";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Wrap long cell content without forcing page-level horizontal overflow. */
|
|
2
|
+
export const tableCellWrapClassName = "min-w-0 [overflow-wrap:anywhere]";
|
|
3
|
+
|
|
4
|
+
/** Truncate overflowing cell content with ellipsis. */
|
|
5
|
+
export const tableCellTruncateClassName =
|
|
6
|
+
"block min-w-0 max-w-full truncate whitespace-nowrap";
|
|
7
|
+
|
|
8
|
+
/** Minimum table width for wide datasets; pair with a narrow scroll container. */
|
|
9
|
+
export const tableMinWidthWideClassName = "min-w-2xl";
|
|
10
|
+
|
|
11
|
+
/** Scroll container classes for narrow viewport table containment. */
|
|
12
|
+
export const tableNarrowContainerClassName = "min-w-0 overscroll-x-contain";
|