@syscore/ui-library 1.4.0 → 1.5.1
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/client/components/ui/page-header.tsx +145 -0
- package/client/components/ui/standard-table.tsx +554 -0
- package/client/global.css +184 -33
- package/client/lib/concepts-mock-data.ts +797 -0
- package/client/ui/PageHeader.stories.tsx +150 -0
- package/client/ui/Panel.stories.tsx +1 -1
- package/client/ui/StandardTable.stories.tsx +311 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +45 -0
- package/dist/index.es.js +372 -0
- package/package.json +1 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { motion, AnimatePresence } from "motion/react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
// Root Component
|
|
8
|
+
interface PageHeaderProps extends React.ComponentPropsWithoutRef<"header"> {}
|
|
9
|
+
|
|
10
|
+
const PageHeader = React.forwardRef<HTMLElement, PageHeaderProps>(
|
|
11
|
+
({ children, className, ...props }, ref) => {
|
|
12
|
+
return (
|
|
13
|
+
<header ref={ref} className={cn("page-header", className)} {...props}>
|
|
14
|
+
{children}
|
|
15
|
+
</header>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
PageHeader.displayName = "PageHeader";
|
|
20
|
+
|
|
21
|
+
// TopSection - flex container for badges/actions
|
|
22
|
+
interface PageHeaderTopSectionProps
|
|
23
|
+
extends React.ComponentPropsWithoutRef<"div"> {}
|
|
24
|
+
|
|
25
|
+
const PageHeaderTopSection = React.forwardRef<
|
|
26
|
+
HTMLDivElement,
|
|
27
|
+
PageHeaderTopSectionProps
|
|
28
|
+
>(({ children, className, ...props }, ref) => {
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
ref={ref}
|
|
32
|
+
className={cn("page-header-top-section", className)}
|
|
33
|
+
{...props}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
PageHeaderTopSection.displayName = "PageHeaderTopSection";
|
|
40
|
+
|
|
41
|
+
// LeftContent - groups left-side elements
|
|
42
|
+
interface PageHeaderLeftContentProps
|
|
43
|
+
extends React.ComponentPropsWithoutRef<"div"> {}
|
|
44
|
+
|
|
45
|
+
const PageHeaderLeftContent = React.forwardRef<
|
|
46
|
+
HTMLDivElement,
|
|
47
|
+
PageHeaderLeftContentProps
|
|
48
|
+
>(({ children, className, ...props }, ref) => {
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
ref={ref}
|
|
52
|
+
className={cn("page-header-left-content", className)}
|
|
53
|
+
{...props}
|
|
54
|
+
>
|
|
55
|
+
{children}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
PageHeaderLeftContent.displayName = "PageHeaderLeftContent";
|
|
60
|
+
|
|
61
|
+
// Badge - with optional animation
|
|
62
|
+
interface PageHeaderBadgeProps extends React.ComponentPropsWithoutRef<"div"> {
|
|
63
|
+
/** Whether to animate the badge appearance */
|
|
64
|
+
animated?: boolean;
|
|
65
|
+
/** Show badge conditionally */
|
|
66
|
+
show?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const PageHeaderBadge = React.forwardRef<HTMLDivElement, PageHeaderBadgeProps>(
|
|
70
|
+
({ children, animated = false, show = true, className, style, ...props }, ref) => {
|
|
71
|
+
if (animated) {
|
|
72
|
+
return (
|
|
73
|
+
<AnimatePresence>
|
|
74
|
+
{show && (
|
|
75
|
+
<motion.div
|
|
76
|
+
initial={{ opacity: 0, scale: 0.8 }}
|
|
77
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
78
|
+
exit={{ opacity: 0, scale: 0.8 }}
|
|
79
|
+
transition={{ duration: 0.2 }}
|
|
80
|
+
className={className}
|
|
81
|
+
style={style}
|
|
82
|
+
>
|
|
83
|
+
{children}
|
|
84
|
+
</motion.div>
|
|
85
|
+
)}
|
|
86
|
+
</AnimatePresence>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!show) return null;
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div ref={ref} className={className} style={style} {...props}>
|
|
94
|
+
{children}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
PageHeaderBadge.displayName = "PageHeaderBadge";
|
|
100
|
+
|
|
101
|
+
// Title
|
|
102
|
+
interface PageHeaderTitleProps
|
|
103
|
+
extends React.ComponentPropsWithoutRef<"h1"> {}
|
|
104
|
+
|
|
105
|
+
const PageHeaderTitle = React.forwardRef<
|
|
106
|
+
HTMLHeadingElement,
|
|
107
|
+
PageHeaderTitleProps
|
|
108
|
+
>(({ children, className, ...props }, ref) => {
|
|
109
|
+
return (
|
|
110
|
+
<h1 ref={ref} className={cn("page-header-title", className)} {...props}>
|
|
111
|
+
{children}
|
|
112
|
+
</h1>
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
PageHeaderTitle.displayName = "PageHeaderTitle";
|
|
116
|
+
|
|
117
|
+
// Description
|
|
118
|
+
interface PageHeaderDescriptionProps
|
|
119
|
+
extends React.ComponentPropsWithoutRef<"p"> {}
|
|
120
|
+
|
|
121
|
+
const PageHeaderDescription = React.forwardRef<
|
|
122
|
+
HTMLParagraphElement,
|
|
123
|
+
PageHeaderDescriptionProps
|
|
124
|
+
>(({ children, className, ...props }, ref) => {
|
|
125
|
+
return (
|
|
126
|
+
<p
|
|
127
|
+
ref={ref}
|
|
128
|
+
className={cn("page-header-description", className)}
|
|
129
|
+
{...props}
|
|
130
|
+
>
|
|
131
|
+
{children}
|
|
132
|
+
</p>
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
PageHeaderDescription.displayName = "PageHeaderDescription";
|
|
136
|
+
|
|
137
|
+
// Compound exports
|
|
138
|
+
export {
|
|
139
|
+
PageHeader,
|
|
140
|
+
PageHeaderTopSection,
|
|
141
|
+
PageHeaderLeftContent,
|
|
142
|
+
PageHeaderBadge,
|
|
143
|
+
PageHeaderTitle,
|
|
144
|
+
PageHeaderDescription,
|
|
145
|
+
};
|
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { motion, AnimatePresence } from "motion/react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import { Button } from "./button";
|
|
7
|
+
import { UtilityChevronDown } from "../icons/UtilityChevronDown";
|
|
8
|
+
|
|
9
|
+
// Context Definitions
|
|
10
|
+
interface StandardTableContextValue {
|
|
11
|
+
/** Set of currently expanded row values */
|
|
12
|
+
expandedValues: Set<string>;
|
|
13
|
+
/** Check if a row is expanded */
|
|
14
|
+
isExpanded: (value: string) => boolean;
|
|
15
|
+
/** Toggle a specific row's expansion */
|
|
16
|
+
toggle: (value: string) => void;
|
|
17
|
+
/** Expand all rows */
|
|
18
|
+
expandAll: () => void;
|
|
19
|
+
/** Collapse all rows */
|
|
20
|
+
collapseAll: () => void;
|
|
21
|
+
/** Check if any row is expanded */
|
|
22
|
+
hasAnyExpanded: boolean;
|
|
23
|
+
/** Allow multiple rows to be expanded at once */
|
|
24
|
+
allowMultiple: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const StandardTableContext =
|
|
28
|
+
React.createContext<StandardTableContextValue | null>(null);
|
|
29
|
+
|
|
30
|
+
interface StandardTableRowContextValue {
|
|
31
|
+
/** Current row's value */
|
|
32
|
+
value: string;
|
|
33
|
+
/** Whether this row is expanded */
|
|
34
|
+
isExpanded: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const StandardTableRowContext =
|
|
38
|
+
React.createContext<StandardTableRowContextValue | null>(null);
|
|
39
|
+
|
|
40
|
+
// Hooks
|
|
41
|
+
function useStandardTable() {
|
|
42
|
+
const context = React.useContext(StandardTableContext);
|
|
43
|
+
if (!context) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
"StandardTable components must be used within <StandardTable>",
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return context;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function useStandardTableRow() {
|
|
52
|
+
const context = React.useContext(StandardTableRowContext);
|
|
53
|
+
if (!context) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"StandardTableTrigger and StandardTableContent must be used within <StandardTableRow>",
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return context;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// useStandardTableState Hook (for external state management)
|
|
62
|
+
interface UseStandardTableStateOptions {
|
|
63
|
+
/** Allow multiple rows to be expanded at once */
|
|
64
|
+
allowMultiple?: boolean;
|
|
65
|
+
/** Initially expanded row value(s) */
|
|
66
|
+
defaultExpanded?: string | string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface UseStandardTableStateReturn {
|
|
70
|
+
/** Current expanded values */
|
|
71
|
+
expandedValues: Set<string>;
|
|
72
|
+
/** Set expanded values */
|
|
73
|
+
setExpandedValues: (values: Set<string>) => void;
|
|
74
|
+
/** Check if any row is expanded */
|
|
75
|
+
hasAnyExpanded: boolean;
|
|
76
|
+
/** Toggle all rows */
|
|
77
|
+
toggleAll: () => void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function useStandardTableState(
|
|
81
|
+
options: UseStandardTableStateOptions = {},
|
|
82
|
+
): UseStandardTableStateReturn {
|
|
83
|
+
const { allowMultiple = false, defaultExpanded } = options;
|
|
84
|
+
|
|
85
|
+
// Initialize expanded values from default
|
|
86
|
+
const getInitialExpanded = (): Set<string> => {
|
|
87
|
+
if (!defaultExpanded) return new Set();
|
|
88
|
+
if (Array.isArray(defaultExpanded)) return new Set(defaultExpanded);
|
|
89
|
+
return new Set([defaultExpanded]);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const [expandedValues, setExpandedValues] =
|
|
93
|
+
React.useState<Set<string>>(getInitialExpanded);
|
|
94
|
+
const [isAllExpanded, setIsAllExpanded] = React.useState(false);
|
|
95
|
+
|
|
96
|
+
const hasAnyExpanded = React.useMemo(
|
|
97
|
+
() => isAllExpanded || expandedValues.size > 0,
|
|
98
|
+
[isAllExpanded, expandedValues],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const toggleAll = React.useCallback(() => {
|
|
102
|
+
if (hasAnyExpanded) {
|
|
103
|
+
setIsAllExpanded(false);
|
|
104
|
+
setExpandedValues(new Set());
|
|
105
|
+
} else {
|
|
106
|
+
setIsAllExpanded(true);
|
|
107
|
+
setExpandedValues(new Set());
|
|
108
|
+
}
|
|
109
|
+
}, [hasAnyExpanded]);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
expandedValues,
|
|
113
|
+
setExpandedValues,
|
|
114
|
+
hasAnyExpanded,
|
|
115
|
+
toggleAll,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// StandardTable (Root Component)
|
|
120
|
+
interface StandardTableProps extends React.ComponentPropsWithoutRef<"div"> {
|
|
121
|
+
/** Allow multiple rows to be expanded at once (default: false for accordion behavior) */
|
|
122
|
+
allowMultiple?: boolean;
|
|
123
|
+
/** Initially expanded row value(s) (uncontrolled) */
|
|
124
|
+
defaultExpanded?: string | string[];
|
|
125
|
+
/** Controlled expanded values */
|
|
126
|
+
expandedValues?: Set<string>;
|
|
127
|
+
/** Controlled callback when expanded values change */
|
|
128
|
+
onExpandedChange?: (values: Set<string>) => void;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const StandardTable = React.forwardRef<HTMLDivElement, StandardTableProps>(
|
|
132
|
+
(
|
|
133
|
+
{
|
|
134
|
+
allowMultiple = false,
|
|
135
|
+
defaultExpanded,
|
|
136
|
+
expandedValues: controlledExpandedValues,
|
|
137
|
+
onExpandedChange,
|
|
138
|
+
children,
|
|
139
|
+
className,
|
|
140
|
+
...props
|
|
141
|
+
},
|
|
142
|
+
ref,
|
|
143
|
+
) => {
|
|
144
|
+
// Initialize expanded values from default
|
|
145
|
+
const getInitialExpanded = (): Set<string> => {
|
|
146
|
+
if (!defaultExpanded) return new Set();
|
|
147
|
+
if (Array.isArray(defaultExpanded)) return new Set(defaultExpanded);
|
|
148
|
+
return new Set([defaultExpanded]);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const [uncontrolledExpandedValues, setUncontrolledExpandedValues] =
|
|
152
|
+
React.useState<Set<string>>(getInitialExpanded);
|
|
153
|
+
const [isAllExpanded, setIsAllExpanded] = React.useState(false);
|
|
154
|
+
|
|
155
|
+
// Determine if controlled
|
|
156
|
+
const isControlled = controlledExpandedValues !== undefined;
|
|
157
|
+
const expandedValues = isControlled
|
|
158
|
+
? controlledExpandedValues
|
|
159
|
+
: uncontrolledExpandedValues;
|
|
160
|
+
|
|
161
|
+
// Check if any row is expanded
|
|
162
|
+
const hasAnyExpanded = React.useMemo(
|
|
163
|
+
() => isAllExpanded || expandedValues.size > 0,
|
|
164
|
+
[isAllExpanded, expandedValues],
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Check if a specific row is expanded
|
|
168
|
+
const isExpanded = React.useCallback(
|
|
169
|
+
(value: string): boolean => isAllExpanded || expandedValues.has(value),
|
|
170
|
+
[isAllExpanded, expandedValues],
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Toggle a specific row
|
|
174
|
+
const toggle = React.useCallback(
|
|
175
|
+
(value: string) => {
|
|
176
|
+
const newValues = new Set(expandedValues);
|
|
177
|
+
if (newValues.has(value)) {
|
|
178
|
+
newValues.delete(value);
|
|
179
|
+
} else {
|
|
180
|
+
if (!allowMultiple) {
|
|
181
|
+
newValues.clear(); // Accordion mode: close all others
|
|
182
|
+
}
|
|
183
|
+
newValues.add(value);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!isControlled) {
|
|
187
|
+
setUncontrolledExpandedValues(newValues);
|
|
188
|
+
}
|
|
189
|
+
onExpandedChange?.(newValues);
|
|
190
|
+
setIsAllExpanded(false);
|
|
191
|
+
},
|
|
192
|
+
[allowMultiple, expandedValues, isControlled, onExpandedChange],
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Expand all rows
|
|
196
|
+
const expandAll = React.useCallback(() => {
|
|
197
|
+
setIsAllExpanded(true);
|
|
198
|
+
const emptySet = new Set<string>();
|
|
199
|
+
if (!isControlled) {
|
|
200
|
+
setUncontrolledExpandedValues(emptySet);
|
|
201
|
+
}
|
|
202
|
+
onExpandedChange?.(emptySet);
|
|
203
|
+
}, [isControlled, onExpandedChange]);
|
|
204
|
+
|
|
205
|
+
// Collapse all rows
|
|
206
|
+
const collapseAll = React.useCallback(() => {
|
|
207
|
+
setIsAllExpanded(false);
|
|
208
|
+
const emptySet = new Set<string>();
|
|
209
|
+
if (!isControlled) {
|
|
210
|
+
setUncontrolledExpandedValues(emptySet);
|
|
211
|
+
}
|
|
212
|
+
onExpandedChange?.(emptySet);
|
|
213
|
+
}, [isControlled, onExpandedChange]);
|
|
214
|
+
|
|
215
|
+
const contextValue = React.useMemo(
|
|
216
|
+
() => ({
|
|
217
|
+
expandedValues,
|
|
218
|
+
isExpanded,
|
|
219
|
+
toggle,
|
|
220
|
+
expandAll,
|
|
221
|
+
collapseAll,
|
|
222
|
+
hasAnyExpanded,
|
|
223
|
+
allowMultiple,
|
|
224
|
+
}),
|
|
225
|
+
[
|
|
226
|
+
expandedValues,
|
|
227
|
+
isExpanded,
|
|
228
|
+
toggle,
|
|
229
|
+
expandAll,
|
|
230
|
+
collapseAll,
|
|
231
|
+
hasAnyExpanded,
|
|
232
|
+
allowMultiple,
|
|
233
|
+
],
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<StandardTableContext.Provider value={contextValue}>
|
|
238
|
+
<div
|
|
239
|
+
ref={ref}
|
|
240
|
+
className={cn(
|
|
241
|
+
"border border-blue-200 rounded-xl overflow-hidden",
|
|
242
|
+
className,
|
|
243
|
+
)}
|
|
244
|
+
{...props}
|
|
245
|
+
>
|
|
246
|
+
{children}
|
|
247
|
+
</div>
|
|
248
|
+
</StandardTableContext.Provider>
|
|
249
|
+
);
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
StandardTable.displayName = "StandardTable";
|
|
254
|
+
|
|
255
|
+
// StandardTableHeader
|
|
256
|
+
interface StandardTableHeaderProps {
|
|
257
|
+
/** Header title text */
|
|
258
|
+
title: string;
|
|
259
|
+
/** Whether any items are expanded */
|
|
260
|
+
hasExpanded?: boolean;
|
|
261
|
+
/** Toggle all callback */
|
|
262
|
+
onToggleAll?: () => void;
|
|
263
|
+
/** Optional className for the container */
|
|
264
|
+
className?: string;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const StandardTableHeader = React.forwardRef<
|
|
268
|
+
HTMLDivElement,
|
|
269
|
+
StandardTableHeaderProps
|
|
270
|
+
>(({ title, hasExpanded, onToggleAll, className }, ref) => {
|
|
271
|
+
return (
|
|
272
|
+
<div ref={ref} className={cn("standard-table-header", className)}>
|
|
273
|
+
<h2 className="standard-table-header__title body-large">{title}</h2>
|
|
274
|
+
|
|
275
|
+
{onToggleAll && (
|
|
276
|
+
<motion.div
|
|
277
|
+
animate={{ rotate: hasExpanded ? 180 : 0 }}
|
|
278
|
+
transition={{ duration: 0.2 }}
|
|
279
|
+
className="mx-6"
|
|
280
|
+
>
|
|
281
|
+
<Button size="icon" onClick={onToggleAll}>
|
|
282
|
+
<UtilityChevronDown />
|
|
283
|
+
</Button>
|
|
284
|
+
</motion.div>
|
|
285
|
+
)}
|
|
286
|
+
</div>
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
StandardTableHeader.displayName = "StandardTableHeader";
|
|
291
|
+
|
|
292
|
+
// StandardTableHeaderRow (inside StandardTable with context access)
|
|
293
|
+
interface StandardTableHeaderRowProps {
|
|
294
|
+
/** Header title text */
|
|
295
|
+
title: string;
|
|
296
|
+
/** Optional className for the container */
|
|
297
|
+
className?: string;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const StandardTableHeaderRow = React.forwardRef<
|
|
301
|
+
HTMLDivElement,
|
|
302
|
+
StandardTableHeaderRowProps
|
|
303
|
+
>(({ title, className }, ref) => {
|
|
304
|
+
const { hasAnyExpanded, expandAll, collapseAll } = useStandardTable();
|
|
305
|
+
|
|
306
|
+
const handleToggleAll = React.useCallback(() => {
|
|
307
|
+
if (hasAnyExpanded) {
|
|
308
|
+
collapseAll();
|
|
309
|
+
} else {
|
|
310
|
+
expandAll();
|
|
311
|
+
}
|
|
312
|
+
}, [hasAnyExpanded, collapseAll, expandAll]);
|
|
313
|
+
|
|
314
|
+
return (
|
|
315
|
+
<div ref={ref} className={cn("standard-table-header", className)}>
|
|
316
|
+
<h2 className="standard-table-header__title body-large">{title}</h2>
|
|
317
|
+
<motion.div
|
|
318
|
+
animate={{ rotate: hasAnyExpanded ? 180 : 0 }}
|
|
319
|
+
transition={{ duration: 0.2 }}
|
|
320
|
+
style={{ willChange: "transform" }}
|
|
321
|
+
>
|
|
322
|
+
<Button size="icon" onClick={handleToggleAll}>
|
|
323
|
+
<UtilityChevronDown />
|
|
324
|
+
</Button>
|
|
325
|
+
</motion.div>
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
StandardTableHeaderRow.displayName = "StandardTableHeaderRow";
|
|
331
|
+
|
|
332
|
+
// StandardTableRow
|
|
333
|
+
interface StandardTableRowProps extends React.ComponentPropsWithoutRef<"div"> {
|
|
334
|
+
/** Unique value for this row */
|
|
335
|
+
value: string;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const StandardTableRow = React.forwardRef<
|
|
339
|
+
HTMLDivElement,
|
|
340
|
+
StandardTableRowProps
|
|
341
|
+
>(({ value, children, className, ...props }, ref) => {
|
|
342
|
+
const { isExpanded: isExpandedFn } = useStandardTable();
|
|
343
|
+
const isExpanded = isExpandedFn(value);
|
|
344
|
+
|
|
345
|
+
const rowContextValue = React.useMemo(
|
|
346
|
+
() => ({ value, isExpanded }),
|
|
347
|
+
[value, isExpanded],
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<StandardTableRowContext.Provider value={rowContextValue}>
|
|
352
|
+
<div ref={ref} className={cn("standard-table-row", className)} {...props}>
|
|
353
|
+
{children}
|
|
354
|
+
</div>
|
|
355
|
+
</StandardTableRowContext.Provider>
|
|
356
|
+
);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
StandardTableRow.displayName = "StandardTableRow";
|
|
360
|
+
|
|
361
|
+
// StandardTableRowHeader
|
|
362
|
+
type StandardTableRowHeaderProps = React.ComponentPropsWithoutRef<"div">;
|
|
363
|
+
|
|
364
|
+
const StandardTableRowHeader = React.forwardRef<
|
|
365
|
+
HTMLDivElement,
|
|
366
|
+
StandardTableRowHeaderProps
|
|
367
|
+
>(({ children, className, onClick, ...props }, ref) => {
|
|
368
|
+
return (
|
|
369
|
+
<div
|
|
370
|
+
ref={ref}
|
|
371
|
+
onClick={onClick}
|
|
372
|
+
className={cn("standard-table-row-header", className)}
|
|
373
|
+
{...props}
|
|
374
|
+
>
|
|
375
|
+
{children}
|
|
376
|
+
</div>
|
|
377
|
+
);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
StandardTableRowHeader.displayName = "StandardTableRowHeader";
|
|
381
|
+
|
|
382
|
+
// StandardTableTrigger (Chevron Button)
|
|
383
|
+
type StandardTableTriggerProps = React.ComponentPropsWithoutRef<"button">;
|
|
384
|
+
|
|
385
|
+
const StandardTableTrigger = React.forwardRef<
|
|
386
|
+
HTMLButtonElement,
|
|
387
|
+
StandardTableTriggerProps
|
|
388
|
+
>(({ className, onClick, ...props }, ref) => {
|
|
389
|
+
const { toggle } = useStandardTable();
|
|
390
|
+
const { value, isExpanded } = useStandardTableRow();
|
|
391
|
+
|
|
392
|
+
const handleClick = React.useCallback(
|
|
393
|
+
(e: React.MouseEvent<HTMLButtonElement>) => {
|
|
394
|
+
e.stopPropagation(); // Prevent row header click
|
|
395
|
+
toggle(value);
|
|
396
|
+
onClick?.(e);
|
|
397
|
+
},
|
|
398
|
+
[toggle, value, onClick],
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
return (
|
|
402
|
+
<motion.div
|
|
403
|
+
animate={{ rotate: isExpanded ? 180 : 0 }}
|
|
404
|
+
transition={{ duration: 0.2 }}
|
|
405
|
+
style={{ willChange: "transform" }}
|
|
406
|
+
>
|
|
407
|
+
<Button
|
|
408
|
+
ref={ref}
|
|
409
|
+
size="icon"
|
|
410
|
+
variant="clear"
|
|
411
|
+
onClick={handleClick}
|
|
412
|
+
className={cn("standard-table-trigger", className)}
|
|
413
|
+
{...props}
|
|
414
|
+
>
|
|
415
|
+
<UtilityChevronDown />
|
|
416
|
+
</Button>
|
|
417
|
+
</motion.div>
|
|
418
|
+
);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
StandardTableTrigger.displayName = "StandardTableTrigger";
|
|
422
|
+
|
|
423
|
+
// StandardTableContent (Expandable Content Area)
|
|
424
|
+
type StandardTableContentProps = React.ComponentPropsWithoutRef<"div">;
|
|
425
|
+
|
|
426
|
+
const StandardTableContent = React.forwardRef<
|
|
427
|
+
HTMLDivElement,
|
|
428
|
+
StandardTableContentProps
|
|
429
|
+
>(({ children, className, ...props }, ref) => {
|
|
430
|
+
const { isExpanded } = useStandardTableRow();
|
|
431
|
+
|
|
432
|
+
return (
|
|
433
|
+
<AnimatePresence initial={false}>
|
|
434
|
+
{isExpanded && (
|
|
435
|
+
<motion.div
|
|
436
|
+
initial={{ height: 0, opacity: 0 }}
|
|
437
|
+
animate={{ height: "auto", opacity: 1 }}
|
|
438
|
+
exit={{ height: 0, opacity: 0 }}
|
|
439
|
+
transition={{ duration: 0.3, ease: [0.25, 0.46, 0.45, 0.94] }}
|
|
440
|
+
className={cn("standard-table-content", className)}
|
|
441
|
+
style={{ willChange: "opacity" }}
|
|
442
|
+
>
|
|
443
|
+
<div
|
|
444
|
+
ref={ref}
|
|
445
|
+
className="standard-table-content__inner"
|
|
446
|
+
{...props}
|
|
447
|
+
>
|
|
448
|
+
{children}
|
|
449
|
+
</div>
|
|
450
|
+
</motion.div>
|
|
451
|
+
)}
|
|
452
|
+
</AnimatePresence>
|
|
453
|
+
);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
StandardTableContent.displayName = "StandardTableContent";
|
|
457
|
+
|
|
458
|
+
// StandardTableListRow (for nested items like ListRow)
|
|
459
|
+
interface StandardTableListRowProps
|
|
460
|
+
extends React.ComponentPropsWithoutRef<"div"> {
|
|
461
|
+
/** Optional icon element */
|
|
462
|
+
icon?: React.ReactNode;
|
|
463
|
+
/** Optional badge element */
|
|
464
|
+
badge?: React.ReactNode;
|
|
465
|
+
/** Row title */
|
|
466
|
+
title: string;
|
|
467
|
+
/** Optional title className */
|
|
468
|
+
titleClassName?: string;
|
|
469
|
+
/** Optional right-side content */
|
|
470
|
+
rightContent?: React.ReactNode;
|
|
471
|
+
/** Visual variant */
|
|
472
|
+
variant?: "default" | "nested";
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const StandardTableListRow = React.forwardRef<
|
|
476
|
+
HTMLDivElement,
|
|
477
|
+
StandardTableListRowProps
|
|
478
|
+
>(
|
|
479
|
+
(
|
|
480
|
+
{
|
|
481
|
+
icon,
|
|
482
|
+
badge,
|
|
483
|
+
title,
|
|
484
|
+
titleClassName,
|
|
485
|
+
rightContent,
|
|
486
|
+
onClick,
|
|
487
|
+
className,
|
|
488
|
+
variant = "default",
|
|
489
|
+
...props
|
|
490
|
+
},
|
|
491
|
+
ref,
|
|
492
|
+
) => {
|
|
493
|
+
return (
|
|
494
|
+
<div
|
|
495
|
+
ref={ref}
|
|
496
|
+
onClick={onClick}
|
|
497
|
+
className={cn(
|
|
498
|
+
"standard-table-list-row",
|
|
499
|
+
variant === "default"
|
|
500
|
+
? "standard-table-list-row--default"
|
|
501
|
+
: "standard-table-list-row--nested",
|
|
502
|
+
className,
|
|
503
|
+
)}
|
|
504
|
+
{...props}
|
|
505
|
+
>
|
|
506
|
+
{icon}
|
|
507
|
+
{badge}
|
|
508
|
+
<span
|
|
509
|
+
className={cn(
|
|
510
|
+
"standard-table-list-row__title",
|
|
511
|
+
titleClassName || "body-large",
|
|
512
|
+
)}
|
|
513
|
+
>
|
|
514
|
+
{title}
|
|
515
|
+
</span>
|
|
516
|
+
{rightContent}
|
|
517
|
+
</div>
|
|
518
|
+
);
|
|
519
|
+
},
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
StandardTableListRow.displayName = "StandardTableListRow";
|
|
523
|
+
|
|
524
|
+
type StandardTableContainerProps = React.ComponentPropsWithoutRef<"section">;
|
|
525
|
+
|
|
526
|
+
const StandardTableContainer = React.forwardRef<
|
|
527
|
+
HTMLElement,
|
|
528
|
+
StandardTableContainerProps
|
|
529
|
+
>(({ children, className, ...props }, ref) => {
|
|
530
|
+
return (
|
|
531
|
+
<section
|
|
532
|
+
ref={ref}
|
|
533
|
+
className={cn("standard-table-container", className)}
|
|
534
|
+
{...props}
|
|
535
|
+
>
|
|
536
|
+
{children}
|
|
537
|
+
</section>
|
|
538
|
+
);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
StandardTableContainer.displayName = "StandardTableContainer";
|
|
542
|
+
|
|
543
|
+
export {
|
|
544
|
+
useStandardTableState,
|
|
545
|
+
StandardTable,
|
|
546
|
+
StandardTableHeader,
|
|
547
|
+
StandardTableHeaderRow,
|
|
548
|
+
StandardTableRow,
|
|
549
|
+
StandardTableRowHeader,
|
|
550
|
+
StandardTableTrigger,
|
|
551
|
+
StandardTableContent,
|
|
552
|
+
StandardTableListRow,
|
|
553
|
+
StandardTableContainer,
|
|
554
|
+
};
|