@sproutsocial/seeds-react-table 1.0.50 → 1.0.51
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/.turbo/turbo-build.log +32 -32
- package/CHANGELOG.md +8 -0
- package/dist/esm/v2/index.js +216 -29
- package/dist/esm/v2/index.js.map +1 -1
- package/dist/table-v2.css +87 -0
- package/dist/v2/index.d.mts +17 -12
- package/dist/v2/index.d.ts +17 -12
- package/dist/v2/index.js +216 -29
- package/dist/v2/index.js.map +1 -1
- package/package.json +5 -4
- package/src/table-v2.css +87 -0
- package/src/v2/TableV2.tsx +17 -91
- package/src/v2/TableV2Hybrid.tsx +277 -0
- package/src/v2/TableV2Tailwind.tsx +177 -0
- package/src/v2/__tests__/table-v2.test.tsx +233 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid v2 Table components.
|
|
3
|
+
*
|
|
4
|
+
* Each exported sub-component renders the fast pure-CSS-class (Tailwind) path by
|
|
5
|
+
* default and transparently falls back to the styled-components path via the
|
|
6
|
+
* documented `needsStyledComponents` single gate — i.e. when a consumer passes
|
|
7
|
+
* styled-system props (px, color, width, …) or extends the component with
|
|
8
|
+
* `styled()` (detected through an injected `theme` prop or the generated
|
|
9
|
+
* `sc-XXXX` className). This mirrors the established Button/Badge hybrid pattern
|
|
10
|
+
* and preserves 100% backward compatibility with the existing styled-components
|
|
11
|
+
* API.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import React, { useId } from "react";
|
|
15
|
+
import { type TableV2Props } from "./TableV2Types";
|
|
16
|
+
import { needsStyledComponents } from "@sproutsocial/seeds-react-system-props";
|
|
17
|
+
import {
|
|
18
|
+
StyledTableV2Container,
|
|
19
|
+
StyledTableV2,
|
|
20
|
+
StyledCaption,
|
|
21
|
+
StyledTableHeader,
|
|
22
|
+
StyledTableBody,
|
|
23
|
+
StyledTableRow,
|
|
24
|
+
StyledTableHeaderCell,
|
|
25
|
+
StyledTableCell,
|
|
26
|
+
} from "./styles";
|
|
27
|
+
import {
|
|
28
|
+
TableV2ContainerTailwind,
|
|
29
|
+
TableRootTailwind,
|
|
30
|
+
CaptionTailwind,
|
|
31
|
+
TableHeaderTailwind,
|
|
32
|
+
TableBodyTailwind,
|
|
33
|
+
TableRowTailwind,
|
|
34
|
+
TableHeaderCellTailwind,
|
|
35
|
+
TableCellTailwind,
|
|
36
|
+
} from "./TableV2Tailwind";
|
|
37
|
+
|
|
38
|
+
type HybridProps = {
|
|
39
|
+
className?: string;
|
|
40
|
+
children?: React.ReactNode;
|
|
41
|
+
[key: string]: any;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const TableContainerContext = React.createContext<string | null>(null);
|
|
45
|
+
|
|
46
|
+
export const useTableContainerId = () =>
|
|
47
|
+
React.useContext(TableContainerContext);
|
|
48
|
+
|
|
49
|
+
export const TableWrapper = React.forwardRef<
|
|
50
|
+
HTMLDivElement,
|
|
51
|
+
{ children: React.ReactElement } & HybridProps
|
|
52
|
+
>(({ children, className, ...rest }, ref) => {
|
|
53
|
+
const containerId = useId();
|
|
54
|
+
|
|
55
|
+
// Region semantics stay identical on both paths.
|
|
56
|
+
const containerProps = {
|
|
57
|
+
role: "region",
|
|
58
|
+
tabIndex: 0,
|
|
59
|
+
id: containerId,
|
|
60
|
+
"aria-labelledby": containerId,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<TableContainerContext.Provider value={containerId}>
|
|
65
|
+
{needsStyledComponents({ ...rest, className }) ? (
|
|
66
|
+
<StyledTableV2Container
|
|
67
|
+
className={className}
|
|
68
|
+
ref={ref}
|
|
69
|
+
{...containerProps}
|
|
70
|
+
{...rest}
|
|
71
|
+
>
|
|
72
|
+
{children}
|
|
73
|
+
</StyledTableV2Container>
|
|
74
|
+
) : (
|
|
75
|
+
<TableV2ContainerTailwind
|
|
76
|
+
className={className}
|
|
77
|
+
ref={ref}
|
|
78
|
+
{...containerProps}
|
|
79
|
+
{...rest}
|
|
80
|
+
>
|
|
81
|
+
{children}
|
|
82
|
+
</TableV2ContainerTailwind>
|
|
83
|
+
)}
|
|
84
|
+
</TableContainerContext.Provider>
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
TableWrapper.displayName = "TableV2.TableWrapper";
|
|
89
|
+
|
|
90
|
+
export const TableRoot = React.forwardRef<HTMLTableElement, HybridProps>(
|
|
91
|
+
({ className, children, ...rest }, ref) => {
|
|
92
|
+
if (needsStyledComponents({ ...rest, className })) {
|
|
93
|
+
return (
|
|
94
|
+
<StyledTableV2 className={className} ref={ref} {...rest}>
|
|
95
|
+
{children}
|
|
96
|
+
</StyledTableV2>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return (
|
|
100
|
+
<TableRootTailwind className={className} ref={ref} {...rest}>
|
|
101
|
+
{children}
|
|
102
|
+
</TableRootTailwind>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
TableRoot.displayName = "TableV2.Table";
|
|
108
|
+
|
|
109
|
+
export const TableCaption = React.forwardRef<
|
|
110
|
+
HTMLTableCaptionElement,
|
|
111
|
+
HybridProps & { isVisible?: boolean }
|
|
112
|
+
>(({ className, children, isVisible = true, ...rest }, ref) => {
|
|
113
|
+
if (needsStyledComponents({ ...rest, className })) {
|
|
114
|
+
return (
|
|
115
|
+
<StyledCaption
|
|
116
|
+
className={className}
|
|
117
|
+
isVisible={isVisible}
|
|
118
|
+
ref={ref}
|
|
119
|
+
{...rest}
|
|
120
|
+
>
|
|
121
|
+
{children}
|
|
122
|
+
</StyledCaption>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return (
|
|
126
|
+
<CaptionTailwind
|
|
127
|
+
className={className}
|
|
128
|
+
isVisible={isVisible}
|
|
129
|
+
ref={ref}
|
|
130
|
+
{...rest}
|
|
131
|
+
>
|
|
132
|
+
{children}
|
|
133
|
+
</CaptionTailwind>
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
TableCaption.displayName = "TableV2.Caption";
|
|
138
|
+
|
|
139
|
+
export const TableHeader = React.forwardRef<
|
|
140
|
+
HTMLTableSectionElement,
|
|
141
|
+
HybridProps
|
|
142
|
+
>(({ className, children, ...rest }, ref) => {
|
|
143
|
+
if (needsStyledComponents({ ...rest, className })) {
|
|
144
|
+
return (
|
|
145
|
+
<StyledTableHeader className={className} ref={ref} {...rest}>
|
|
146
|
+
{children}
|
|
147
|
+
</StyledTableHeader>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return (
|
|
151
|
+
<TableHeaderTailwind className={className} ref={ref} {...rest}>
|
|
152
|
+
{children}
|
|
153
|
+
</TableHeaderTailwind>
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
TableHeader.displayName = "TableV2.TableHeader";
|
|
158
|
+
|
|
159
|
+
export const TableBody = React.forwardRef<HTMLTableSectionElement, HybridProps>(
|
|
160
|
+
({ className, children, ...rest }, ref) => {
|
|
161
|
+
if (needsStyledComponents({ ...rest, className })) {
|
|
162
|
+
return (
|
|
163
|
+
<StyledTableBody className={className} ref={ref} {...rest}>
|
|
164
|
+
{children}
|
|
165
|
+
</StyledTableBody>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return (
|
|
169
|
+
<TableBodyTailwind className={className} ref={ref} {...rest}>
|
|
170
|
+
{children}
|
|
171
|
+
</TableBodyTailwind>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
TableBody.displayName = "TableV2.TableBody";
|
|
177
|
+
|
|
178
|
+
export const TableRow = React.forwardRef<HTMLTableRowElement, HybridProps>(
|
|
179
|
+
({ className, children, ...rest }, ref) => {
|
|
180
|
+
if (needsStyledComponents({ ...rest, className })) {
|
|
181
|
+
return (
|
|
182
|
+
<StyledTableRow className={className} ref={ref} {...rest}>
|
|
183
|
+
{children}
|
|
184
|
+
</StyledTableRow>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
return (
|
|
188
|
+
<TableRowTailwind className={className} ref={ref} {...rest}>
|
|
189
|
+
{children}
|
|
190
|
+
</TableRowTailwind>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
TableRow.displayName = "TableV2.TableRow";
|
|
196
|
+
|
|
197
|
+
export const TableHeaderCell = React.forwardRef<
|
|
198
|
+
HTMLTableCellElement,
|
|
199
|
+
HybridProps
|
|
200
|
+
>(({ className, children, ...rest }, ref) => {
|
|
201
|
+
if (needsStyledComponents({ ...rest, className })) {
|
|
202
|
+
return (
|
|
203
|
+
<StyledTableHeaderCell className={className} ref={ref} {...rest}>
|
|
204
|
+
{children}
|
|
205
|
+
</StyledTableHeaderCell>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return (
|
|
209
|
+
<TableHeaderCellTailwind className={className} ref={ref} {...rest}>
|
|
210
|
+
{children}
|
|
211
|
+
</TableHeaderCellTailwind>
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
TableHeaderCell.displayName = "TableV2.TableHeaderCell";
|
|
216
|
+
|
|
217
|
+
export const TableCell = React.forwardRef<HTMLTableCellElement, HybridProps>(
|
|
218
|
+
({ className, children, ...rest }, ref) => {
|
|
219
|
+
if (needsStyledComponents({ ...rest, className })) {
|
|
220
|
+
return (
|
|
221
|
+
<StyledTableCell className={className} ref={ref} {...rest}>
|
|
222
|
+
{children}
|
|
223
|
+
</StyledTableCell>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
return (
|
|
227
|
+
<TableCellTailwind className={className} ref={ref} {...rest}>
|
|
228
|
+
{children}
|
|
229
|
+
</TableCellTailwind>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
TableCell.displayName = "TableV2.TableCell";
|
|
235
|
+
|
|
236
|
+
const TableV2 = ({
|
|
237
|
+
caption,
|
|
238
|
+
displayCaption = true,
|
|
239
|
+
columns,
|
|
240
|
+
data,
|
|
241
|
+
}: TableV2Props) => {
|
|
242
|
+
const containerId = useTableContainerId();
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<TableWrapper>
|
|
246
|
+
<TableRoot>
|
|
247
|
+
<TableCaption id={containerId ?? undefined} isVisible={displayCaption}>
|
|
248
|
+
{caption}
|
|
249
|
+
</TableCaption>
|
|
250
|
+
<TableHeader>
|
|
251
|
+
<TableRow>
|
|
252
|
+
{columns.map((column, index) => (
|
|
253
|
+
<TableHeaderCell key={index} scope="col">
|
|
254
|
+
{column}
|
|
255
|
+
</TableHeaderCell>
|
|
256
|
+
))}
|
|
257
|
+
</TableRow>
|
|
258
|
+
</TableHeader>
|
|
259
|
+
<TableBody>
|
|
260
|
+
{data.map((row, rowIndex) => (
|
|
261
|
+
<TableRow key={rowIndex}>
|
|
262
|
+
{row.map((cell, cellIndex) => (
|
|
263
|
+
<TableCell key={cellIndex}>{cell}</TableCell>
|
|
264
|
+
))}
|
|
265
|
+
</TableRow>
|
|
266
|
+
))}
|
|
267
|
+
</TableBody>
|
|
268
|
+
</TableRoot>
|
|
269
|
+
</TableWrapper>
|
|
270
|
+
);
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Export the full Table component as Table (main export)
|
|
274
|
+
export const Table = TableV2;
|
|
275
|
+
|
|
276
|
+
// Default export
|
|
277
|
+
export default TableV2;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind CSS implementation of the v2 Table sub-components.
|
|
3
|
+
* Uses Seeds table CSS classes from table-v2.css.
|
|
4
|
+
*
|
|
5
|
+
* These are the pure semantic-HTML + CSS-class leaves the hybrid wrappers render
|
|
6
|
+
* on the fast path. They forward `className` and `ref` so `styled(Component)`
|
|
7
|
+
* extensions keep working, and pass standard HTML attributes (scope, id, role,
|
|
8
|
+
* tabIndex, …) straight through to the DOM. Every leaf honors the polymorphic
|
|
9
|
+
* `as` prop (rendering a dynamic element instead of a hard-coded tag) so
|
|
10
|
+
* `<TableCell as="th" scope="row">` produces a real `<th scope="row">` with the
|
|
11
|
+
* `rowheader` role and `<TableRow as="div">` renders a `<div>`, matching the
|
|
12
|
+
* styled-components path. Because `as` is destructured out, it never leaks onto
|
|
13
|
+
* the DOM node; a component-valued `as` (e.g. `styled(TableRow)`) re-enters the
|
|
14
|
+
* hybrid, which then detects the styled signal and routes to styled-components.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import React from "react";
|
|
18
|
+
|
|
19
|
+
// Utility for merging class names properly
|
|
20
|
+
function cn(
|
|
21
|
+
...inputs: (string | undefined | null | false | Record<string, boolean>)[]
|
|
22
|
+
): string {
|
|
23
|
+
const classes: string[] = [];
|
|
24
|
+
|
|
25
|
+
for (const input of inputs) {
|
|
26
|
+
if (!input) continue;
|
|
27
|
+
|
|
28
|
+
if (typeof input === "string") {
|
|
29
|
+
classes.push(input);
|
|
30
|
+
} else if (typeof input === "object") {
|
|
31
|
+
for (const [key, value] of Object.entries(input)) {
|
|
32
|
+
if (value) {
|
|
33
|
+
classes.push(key);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return classes.join(" ");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type TailwindProps = {
|
|
43
|
+
className?: string;
|
|
44
|
+
children?: React.ReactNode;
|
|
45
|
+
[key: string]: any;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const TableV2ContainerTailwind = React.forwardRef<
|
|
49
|
+
HTMLDivElement,
|
|
50
|
+
TailwindProps
|
|
51
|
+
>(({ className, children, as: Component = "div", ...rest }, ref) => (
|
|
52
|
+
<Component
|
|
53
|
+
ref={ref}
|
|
54
|
+
className={cn("seeds-table-v2-container", className)}
|
|
55
|
+
{...rest}
|
|
56
|
+
>
|
|
57
|
+
{children}
|
|
58
|
+
</Component>
|
|
59
|
+
));
|
|
60
|
+
|
|
61
|
+
TableV2ContainerTailwind.displayName = "TableV2.ContainerTailwind";
|
|
62
|
+
|
|
63
|
+
export const TableRootTailwind = React.forwardRef<
|
|
64
|
+
HTMLTableElement,
|
|
65
|
+
TailwindProps
|
|
66
|
+
>(({ className, children, as: Component = "table", ...rest }, ref) => (
|
|
67
|
+
<Component ref={ref} className={cn("seeds-table-v2", className)} {...rest}>
|
|
68
|
+
{children}
|
|
69
|
+
</Component>
|
|
70
|
+
));
|
|
71
|
+
|
|
72
|
+
TableRootTailwind.displayName = "TableV2.TableTailwind";
|
|
73
|
+
|
|
74
|
+
export const CaptionTailwind = React.forwardRef<
|
|
75
|
+
HTMLTableCaptionElement,
|
|
76
|
+
TailwindProps & { isVisible?: boolean }
|
|
77
|
+
>(
|
|
78
|
+
(
|
|
79
|
+
{
|
|
80
|
+
className,
|
|
81
|
+
children,
|
|
82
|
+
isVisible = true,
|
|
83
|
+
as: Component = "caption",
|
|
84
|
+
...rest
|
|
85
|
+
},
|
|
86
|
+
ref
|
|
87
|
+
) => (
|
|
88
|
+
<Component
|
|
89
|
+
ref={ref}
|
|
90
|
+
className={cn(
|
|
91
|
+
"seeds-table-v2-caption",
|
|
92
|
+
{ "seeds-table-v2-caption-hidden": !isVisible },
|
|
93
|
+
className
|
|
94
|
+
)}
|
|
95
|
+
{...rest}
|
|
96
|
+
>
|
|
97
|
+
{children}
|
|
98
|
+
</Component>
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
CaptionTailwind.displayName = "TableV2.CaptionTailwind";
|
|
103
|
+
|
|
104
|
+
export const TableHeaderTailwind = React.forwardRef<
|
|
105
|
+
HTMLTableSectionElement,
|
|
106
|
+
TailwindProps
|
|
107
|
+
>(({ className, children, as: Component = "thead", ...rest }, ref) => (
|
|
108
|
+
<Component
|
|
109
|
+
ref={ref}
|
|
110
|
+
className={cn("seeds-table-v2-header", className)}
|
|
111
|
+
{...rest}
|
|
112
|
+
>
|
|
113
|
+
{children}
|
|
114
|
+
</Component>
|
|
115
|
+
));
|
|
116
|
+
|
|
117
|
+
TableHeaderTailwind.displayName = "TableV2.TableHeaderTailwind";
|
|
118
|
+
|
|
119
|
+
export const TableBodyTailwind = React.forwardRef<
|
|
120
|
+
HTMLTableSectionElement,
|
|
121
|
+
TailwindProps
|
|
122
|
+
>(({ className, children, as: Component = "tbody", ...rest }, ref) => (
|
|
123
|
+
<Component
|
|
124
|
+
ref={ref}
|
|
125
|
+
className={cn("seeds-table-v2-body", className)}
|
|
126
|
+
{...rest}
|
|
127
|
+
>
|
|
128
|
+
{children}
|
|
129
|
+
</Component>
|
|
130
|
+
));
|
|
131
|
+
|
|
132
|
+
TableBodyTailwind.displayName = "TableV2.TableBodyTailwind";
|
|
133
|
+
|
|
134
|
+
export const TableRowTailwind = React.forwardRef<
|
|
135
|
+
HTMLTableRowElement,
|
|
136
|
+
TailwindProps
|
|
137
|
+
>(({ className, children, as: Component = "tr", ...rest }, ref) => (
|
|
138
|
+
<Component
|
|
139
|
+
ref={ref}
|
|
140
|
+
className={cn("seeds-table-v2-row", className)}
|
|
141
|
+
{...rest}
|
|
142
|
+
>
|
|
143
|
+
{children}
|
|
144
|
+
</Component>
|
|
145
|
+
));
|
|
146
|
+
|
|
147
|
+
TableRowTailwind.displayName = "TableV2.TableRowTailwind";
|
|
148
|
+
|
|
149
|
+
export const TableHeaderCellTailwind = React.forwardRef<
|
|
150
|
+
HTMLTableCellElement,
|
|
151
|
+
TailwindProps
|
|
152
|
+
>(({ className, children, as: Component = "th", ...rest }, ref) => (
|
|
153
|
+
<Component
|
|
154
|
+
ref={ref}
|
|
155
|
+
className={cn("seeds-table-v2-header-cell", className)}
|
|
156
|
+
{...rest}
|
|
157
|
+
>
|
|
158
|
+
{children}
|
|
159
|
+
</Component>
|
|
160
|
+
));
|
|
161
|
+
|
|
162
|
+
TableHeaderCellTailwind.displayName = "TableV2.TableHeaderCellTailwind";
|
|
163
|
+
|
|
164
|
+
export const TableCellTailwind = React.forwardRef<
|
|
165
|
+
HTMLTableCellElement,
|
|
166
|
+
TailwindProps
|
|
167
|
+
>(({ className, children, as: Component = "td", ...rest }, ref) => (
|
|
168
|
+
<Component
|
|
169
|
+
ref={ref}
|
|
170
|
+
className={cn("seeds-table-v2-cell", className)}
|
|
171
|
+
{...rest}
|
|
172
|
+
>
|
|
173
|
+
{children}
|
|
174
|
+
</Component>
|
|
175
|
+
));
|
|
176
|
+
|
|
177
|
+
TableCellTailwind.displayName = "TableV2.TableCellTailwind";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import styled from "styled-components";
|
|
2
3
|
import { render, screen } from "@sproutsocial/seeds-react-testing-library";
|
|
4
|
+
import { theme } from "@sproutsocial/seeds-react-theme";
|
|
3
5
|
import TableV2, {
|
|
4
6
|
TableWrapper,
|
|
5
7
|
Table,
|
|
@@ -371,4 +373,235 @@ describe("TableV2", () => {
|
|
|
371
373
|
expect(rows).toHaveLength(101);
|
|
372
374
|
});
|
|
373
375
|
});
|
|
376
|
+
|
|
377
|
+
describe("Hybrid routing", () => {
|
|
378
|
+
it("renders the Tailwind (CSS-class) path by default", () => {
|
|
379
|
+
render(
|
|
380
|
+
<TableV2 caption="Default" columns={mockColumns} data={mockData} />
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
expect(screen.getByRole("table")).toHaveClass("seeds-table-v2");
|
|
384
|
+
expect(screen.getAllByRole("cell")[0]).toHaveClass("seeds-table-v2-cell");
|
|
385
|
+
expect(screen.getAllByRole("columnheader")[0]).toHaveClass(
|
|
386
|
+
"seeds-table-v2-header-cell"
|
|
387
|
+
);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("routes to the styled-components path when a system prop is passed", () => {
|
|
391
|
+
render(
|
|
392
|
+
<TableWrapper>
|
|
393
|
+
<TableRoot>
|
|
394
|
+
<TableBody>
|
|
395
|
+
<TableRow>
|
|
396
|
+
<TableCell px={400}>Styled Cell</TableCell>
|
|
397
|
+
</TableRow>
|
|
398
|
+
</TableBody>
|
|
399
|
+
</TableRoot>
|
|
400
|
+
</TableWrapper>
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
const cell = screen.getByText("Styled Cell");
|
|
404
|
+
// The styled-components fallback does not carry the Tailwind class...
|
|
405
|
+
expect(cell).not.toHaveClass("seeds-table-v2-cell");
|
|
406
|
+
// ...and the system prop must not leak onto the DOM node.
|
|
407
|
+
expect(cell).not.toHaveAttribute("px");
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("routes to the styled-components path for a styled() extension carrying a theme prop", () => {
|
|
411
|
+
render(
|
|
412
|
+
<TableWrapper>
|
|
413
|
+
<TableRoot>
|
|
414
|
+
<TableBody>
|
|
415
|
+
<TableRow>
|
|
416
|
+
<TableCell theme={theme}>Themed Cell</TableCell>
|
|
417
|
+
</TableRow>
|
|
418
|
+
</TableBody>
|
|
419
|
+
</TableRoot>
|
|
420
|
+
</TableWrapper>
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
const cell = screen.getByText("Themed Cell");
|
|
424
|
+
// A styled() extension injects a theme prop; it must route to the
|
|
425
|
+
// styled-components path, not the Tailwind leaf...
|
|
426
|
+
expect(cell).not.toHaveClass("seeds-table-v2-cell");
|
|
427
|
+
// ...and the theme object must not leak onto the DOM node.
|
|
428
|
+
expect(cell).not.toHaveAttribute("theme");
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it("routes to the styled-components path for a styled() extension with an sc- className", () => {
|
|
432
|
+
render(
|
|
433
|
+
<TableWrapper>
|
|
434
|
+
<TableRoot>
|
|
435
|
+
<TableBody>
|
|
436
|
+
<TableRow>
|
|
437
|
+
<TableCell className="sc-abc123">Sc Cell</TableCell>
|
|
438
|
+
</TableRow>
|
|
439
|
+
</TableBody>
|
|
440
|
+
</TableRoot>
|
|
441
|
+
</TableWrapper>
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
const cell = screen.getByText("Sc Cell");
|
|
445
|
+
// The generated sc-XXXX class is the styled() signal even without a
|
|
446
|
+
// theme prop, so this must not land on the Tailwind leaf.
|
|
447
|
+
expect(cell).not.toHaveClass("seeds-table-v2-cell");
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it("visually hides the caption when isVisible is false but keeps it in the DOM", () => {
|
|
451
|
+
render(
|
|
452
|
+
<TableWrapper>
|
|
453
|
+
<TableRoot>
|
|
454
|
+
<TableCaption isVisible={false}>Hidden Caption</TableCaption>
|
|
455
|
+
</TableRoot>
|
|
456
|
+
</TableWrapper>
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
const caption = screen.getByText("Hidden Caption");
|
|
460
|
+
expect(caption).toBeInTheDocument();
|
|
461
|
+
expect(caption).toHaveClass("seeds-table-v2-caption-hidden");
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it("keeps the caption visible when isVisible is true", () => {
|
|
465
|
+
render(
|
|
466
|
+
<TableWrapper>
|
|
467
|
+
<TableRoot>
|
|
468
|
+
<TableCaption isVisible={true}>Visible Caption</TableCaption>
|
|
469
|
+
</TableRoot>
|
|
470
|
+
</TableWrapper>
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
const caption = screen.getByText("Visible Caption");
|
|
474
|
+
expect(caption).toBeVisible();
|
|
475
|
+
expect(caption).not.toHaveClass("seeds-table-v2-caption-hidden");
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
describe("Polymorphic `as` prop", () => {
|
|
480
|
+
it("renders TableCell as a th with the rowheader role when as='th' scope='row'", () => {
|
|
481
|
+
render(
|
|
482
|
+
<TableWrapper>
|
|
483
|
+
<TableRoot>
|
|
484
|
+
<TableBody>
|
|
485
|
+
<TableRow>
|
|
486
|
+
<TableCell as="th" scope="row">
|
|
487
|
+
Row Header
|
|
488
|
+
</TableCell>
|
|
489
|
+
</TableRow>
|
|
490
|
+
</TableBody>
|
|
491
|
+
</TableRoot>
|
|
492
|
+
</TableWrapper>
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
const cell = screen.getByRole("rowheader", { name: "Row Header" });
|
|
496
|
+
expect(cell.tagName).toBe("TH");
|
|
497
|
+
expect(cell).toHaveAttribute("scope", "row");
|
|
498
|
+
expect(cell).toHaveClass("seeds-table-v2-cell");
|
|
499
|
+
// `as` is destructured out and must not leak onto the DOM node.
|
|
500
|
+
expect(cell).not.toHaveAttribute("as");
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("renders TableCell as a td (role cell) by default", () => {
|
|
504
|
+
render(
|
|
505
|
+
<TableWrapper>
|
|
506
|
+
<TableRoot>
|
|
507
|
+
<TableBody>
|
|
508
|
+
<TableRow>
|
|
509
|
+
<TableCell>Body Cell</TableCell>
|
|
510
|
+
</TableRow>
|
|
511
|
+
</TableBody>
|
|
512
|
+
</TableRoot>
|
|
513
|
+
</TableWrapper>
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
const cell = screen.getByRole("cell", { name: "Body Cell" });
|
|
517
|
+
expect(cell.tagName).toBe("TD");
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it("renders TableHeaderCell as a th by default", () => {
|
|
521
|
+
render(
|
|
522
|
+
<TableWrapper>
|
|
523
|
+
<TableRoot>
|
|
524
|
+
<TableHeader>
|
|
525
|
+
<TableRow>
|
|
526
|
+
<TableHeaderCell scope="col">Column Header</TableHeaderCell>
|
|
527
|
+
</TableRow>
|
|
528
|
+
</TableHeader>
|
|
529
|
+
</TableRoot>
|
|
530
|
+
</TableWrapper>
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
const header = screen.getByRole("columnheader", {
|
|
534
|
+
name: "Column Header",
|
|
535
|
+
});
|
|
536
|
+
expect(header.tagName).toBe("TH");
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("renders a non-cell leaf (TableRow) as the requested element for a string `as`", () => {
|
|
540
|
+
render(
|
|
541
|
+
<TableWrapper>
|
|
542
|
+
<TableRoot>
|
|
543
|
+
<TableBody>
|
|
544
|
+
<TableRow as="div">
|
|
545
|
+
<TableCell>Cell in a div row</TableCell>
|
|
546
|
+
</TableRow>
|
|
547
|
+
</TableBody>
|
|
548
|
+
</TableRoot>
|
|
549
|
+
</TableWrapper>
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
const row = screen
|
|
553
|
+
.getByText("Cell in a div row")
|
|
554
|
+
.closest(".seeds-table-v2-row");
|
|
555
|
+
expect(row).not.toBeNull();
|
|
556
|
+
// The requested element is rendered while the Tailwind class is kept...
|
|
557
|
+
expect(row?.tagName).toBe("DIV");
|
|
558
|
+
// ...and `as` is destructured out, so it must not leak onto the DOM node.
|
|
559
|
+
expect(row).not.toHaveAttribute("as");
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it("renders a non-cell leaf (TableBody) as the requested element for a string `as`", () => {
|
|
563
|
+
render(
|
|
564
|
+
<TableWrapper>
|
|
565
|
+
<TableRoot>
|
|
566
|
+
<TableBody as="section">
|
|
567
|
+
<TableRow>
|
|
568
|
+
<TableCell>Cell in a section body</TableCell>
|
|
569
|
+
</TableRow>
|
|
570
|
+
</TableBody>
|
|
571
|
+
</TableRoot>
|
|
572
|
+
</TableWrapper>
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
const body = screen
|
|
576
|
+
.getByText("Cell in a section body")
|
|
577
|
+
.closest(".seeds-table-v2-body");
|
|
578
|
+
expect(body).not.toBeNull();
|
|
579
|
+
expect(body?.tagName).toBe("SECTION");
|
|
580
|
+
expect(body).not.toHaveAttribute("as");
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("routes a non-cell leaf to the styled-components path for a component-valued `as`", () => {
|
|
584
|
+
const StyledRow = styled(TableRow)``;
|
|
585
|
+
render(
|
|
586
|
+
<TableWrapper>
|
|
587
|
+
<TableRoot>
|
|
588
|
+
<TableBody>
|
|
589
|
+
<TableRow as={StyledRow}>
|
|
590
|
+
<TableCell>Cell in a styled row</TableCell>
|
|
591
|
+
</TableRow>
|
|
592
|
+
</TableBody>
|
|
593
|
+
</TableRoot>
|
|
594
|
+
</TableWrapper>
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
const row = screen.getByText("Cell in a styled row").closest("tr");
|
|
598
|
+
// A component-valued `as` (the documented `tableRowProps={{ as: styled(...) }}`
|
|
599
|
+
// pattern) re-enters the hybrid, which detects the injected styled signal and
|
|
600
|
+
// renders a real <tr> via the styled-components path...
|
|
601
|
+
expect(row).not.toBeNull();
|
|
602
|
+
// ...and the component reference must never leak onto the DOM as an `as`
|
|
603
|
+
// attribute, which was the reported regression.
|
|
604
|
+
expect(row).not.toHaveAttribute("as");
|
|
605
|
+
});
|
|
606
|
+
});
|
|
374
607
|
});
|