@sproutsocial/seeds-react-list 0.3.41 → 0.4.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/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +12 -0
- package/dist/esm/index.js +223 -44
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.mts +27 -6
- package/dist/index.d.ts +27 -6
- package/dist/index.js +223 -53
- package/dist/index.js.map +1 -1
- package/dist/list.css +112 -0
- package/package.json +3 -3
- package/src/List.tsx +13 -9
- package/src/ListHybrid.tsx +28 -0
- package/src/ListItem.tsx +7 -21
- package/src/ListItemButton.tsx +8 -37
- package/src/ListItemButtonHybrid.tsx +28 -0
- package/src/ListItemButtonStyled.tsx +46 -0
- package/src/ListItemButtonTailwind.tsx +36 -0
- package/src/ListItemContent.tsx +7 -48
- package/src/ListItemContentHybrid.tsx +28 -0
- package/src/ListItemContentStyled.tsx +59 -0
- package/src/ListItemContentTailwind.tsx +56 -0
- package/src/ListItemHybrid.tsx +34 -0
- package/src/ListItemStyled.tsx +41 -0
- package/src/ListItemTailwind.tsx +35 -0
- package/src/ListItemTypes.ts +32 -3
- package/src/ListStyled.tsx +19 -0
- package/src/ListTailwind.tsx +32 -0
- package/src/ListTypes.ts +25 -2
- package/src/__tests__/ListContract.test.tsx +137 -0
- package/src/__tests__/ListItemButton.test.tsx +57 -0
- package/src/__tests__/ListItemContent.test.tsx +92 -0
- package/src/__tests__/ListRouting.test.tsx +102 -0
- package/src/cn.ts +27 -0
- package/src/list.css +112 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render } from "@sproutsocial/seeds-react-testing-library";
|
|
3
|
+
import { theme } from "@sproutsocial/seeds-react-theme";
|
|
4
|
+
import List from "../List";
|
|
5
|
+
import ListItem from "../ListItem";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Routing and static-CSS tests for the List/ListItem Tailwind hybrid.
|
|
9
|
+
*
|
|
10
|
+
* The default (Tailwind) path renders semantic `seeds-list*` classes and no
|
|
11
|
+
* runtime CSS-in-JS. The styled-components path is retained for compatibility
|
|
12
|
+
* signals — system props (hasStyledProps), and styled() extensions
|
|
13
|
+
* (isStyledExtension) — and is the only path that resolves styled-system rules.
|
|
14
|
+
*
|
|
15
|
+
* CSS files are mocked in jest, so the Tailwind path is asserted via class
|
|
16
|
+
* names and the styled path via resolved style rules.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
describe("List routing", () => {
|
|
20
|
+
it("renders the Tailwind path by default", () => {
|
|
21
|
+
const { container } = render(
|
|
22
|
+
<List>
|
|
23
|
+
<li>Item</li>
|
|
24
|
+
</List>
|
|
25
|
+
);
|
|
26
|
+
expect(container.querySelector("ul")).toHaveClass("seeds-list");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("places semantic class before consumer className", () => {
|
|
30
|
+
const { container } = render(
|
|
31
|
+
<List className="consumer">
|
|
32
|
+
<li>Item</li>
|
|
33
|
+
</List>
|
|
34
|
+
);
|
|
35
|
+
expect(container.querySelector("ul")?.className).toBe(
|
|
36
|
+
"seeds-list consumer"
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("routes to the styled-components path when a system prop is present", () => {
|
|
41
|
+
const { container } = render(
|
|
42
|
+
<List p={300}>
|
|
43
|
+
<li>Item</li>
|
|
44
|
+
</List>
|
|
45
|
+
);
|
|
46
|
+
const list = container.querySelector("ul") as HTMLElement;
|
|
47
|
+
// The styled Container emits its reset via styled-components (not a class),
|
|
48
|
+
// so a resolved list-style rule + the absence of the semantic class proves
|
|
49
|
+
// the styled path was taken.
|
|
50
|
+
expect(list).toHaveStyleRule("list-style", "none");
|
|
51
|
+
expect(list).not.toHaveClass("seeds-list");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("routes to the styled-components path for a styled() extension className", () => {
|
|
55
|
+
const { container } = render(
|
|
56
|
+
<List className="sc-abc123">
|
|
57
|
+
<li>Item</li>
|
|
58
|
+
</List>
|
|
59
|
+
);
|
|
60
|
+
const list = container.querySelector("ul") as HTMLElement;
|
|
61
|
+
// Only the styled path emits the list-reset rule.
|
|
62
|
+
expect(list).toHaveStyleRule("list-style", "none");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("ListItem routing", () => {
|
|
67
|
+
it("renders the Tailwind path by default", () => {
|
|
68
|
+
const { container } = render(<ListItem>Item</ListItem>);
|
|
69
|
+
expect(container.querySelector("li")).toHaveClass("seeds-list-item");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("adds the bordered modifier class when isBordered", () => {
|
|
73
|
+
const { container } = render(<ListItem isBordered>Item</ListItem>);
|
|
74
|
+
const item = container.querySelector("li") as HTMLElement;
|
|
75
|
+
expect(item).toHaveClass("seeds-list-item");
|
|
76
|
+
expect(item).toHaveClass("seeds-list-item-bordered");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("omits the bordered modifier class when not bordered", () => {
|
|
80
|
+
const { container } = render(<ListItem>Item</ListItem>);
|
|
81
|
+
expect(container.querySelector("li")).not.toHaveClass(
|
|
82
|
+
"seeds-list-item-bordered"
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("places semantic class before consumer className", () => {
|
|
87
|
+
const { container } = render(
|
|
88
|
+
<ListItem className="consumer">Item</ListItem>
|
|
89
|
+
);
|
|
90
|
+
expect(container.querySelector("li")?.className).toBe(
|
|
91
|
+
"seeds-list-item consumer"
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("routes to the styled-components path when a system prop is present", () => {
|
|
96
|
+
const { container } = render(<ListItem p={300}>Item</ListItem>);
|
|
97
|
+
const item = container.querySelector("li") as HTMLElement;
|
|
98
|
+
// Only the styled path resolves styled-system padding.
|
|
99
|
+
expect(item).toHaveStyleRule("padding", theme.space[300]);
|
|
100
|
+
expect(item).not.toHaveClass("seeds-list-item");
|
|
101
|
+
});
|
|
102
|
+
});
|
package/src/cn.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal className combiner. Mirrored inline from Card/Avatar —
|
|
3
|
+
* @sproutsocial/seeds-react-utilities does NOT export a `cn` helper.
|
|
4
|
+
*
|
|
5
|
+
* Falsy inputs are dropped; object inputs contribute each truthy key.
|
|
6
|
+
*/
|
|
7
|
+
export function cn(
|
|
8
|
+
...inputs: (string | undefined | null | false | Record<string, boolean>)[]
|
|
9
|
+
): string {
|
|
10
|
+
const classes: string[] = [];
|
|
11
|
+
|
|
12
|
+
for (const input of inputs) {
|
|
13
|
+
if (!input) continue;
|
|
14
|
+
|
|
15
|
+
if (typeof input === "string") {
|
|
16
|
+
classes.push(input);
|
|
17
|
+
} else if (typeof input === "object") {
|
|
18
|
+
for (const [key, value] of Object.entries(input)) {
|
|
19
|
+
if (value) {
|
|
20
|
+
classes.push(key);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return classes.join(" ");
|
|
27
|
+
}
|
package/src/list.css
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeds List component classes
|
|
3
|
+
* Use these instead of writing out individual Tailwind utility classes.
|
|
4
|
+
*
|
|
5
|
+
* Requires @sproutsocial/seeds-react-theme/dist/theme-all.css imported for
|
|
6
|
+
* CSS variable definitions and dark mode support.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* <ul class="seeds-list">
|
|
10
|
+
* <li class="seeds-list-item">…</li>
|
|
11
|
+
* <li class="seeds-list-item seeds-list-item-bordered">…</li>
|
|
12
|
+
* </ul>
|
|
13
|
+
*
|
|
14
|
+
* Rules live in `@layer components` so that consumer Tailwind utilities
|
|
15
|
+
* (e.g. mt-300, p-300) win in the cascade and can override these component
|
|
16
|
+
* styles, rather than tying/beating them on specificity.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
@layer components {
|
|
20
|
+
/* List container: strip the browser's default list chrome. */
|
|
21
|
+
.seeds-list {
|
|
22
|
+
margin: 0;
|
|
23
|
+
padding: 0;
|
|
24
|
+
list-style: none;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* List item: rounded row that stacks with top spacing. The legacy component
|
|
28
|
+
applies `mt={300}` to every item (including the first), so the class does
|
|
29
|
+
the same rather than collapsing the first item's margin. */
|
|
30
|
+
.seeds-list-item {
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
margin-top: var(--space-300);
|
|
33
|
+
border-radius: var(--radius-outer);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Bordered items read as a card/button: 1px container border + padding.
|
|
37
|
+
borderWidths[500] resolves to 1px; no --border-width var is emitted. */
|
|
38
|
+
.seeds-list-item-bordered {
|
|
39
|
+
border: 1px solid var(--color-container-border-base);
|
|
40
|
+
padding: var(--space-300);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* ListItemContent ---------------------------------------------------- */
|
|
44
|
+
|
|
45
|
+
/* Outer row: main content left, aside/actions right, baseline-aligned. */
|
|
46
|
+
.seeds-list-item-content {
|
|
47
|
+
display: flex;
|
|
48
|
+
justify-content: space-between;
|
|
49
|
+
align-items: baseline;
|
|
50
|
+
border-radius: var(--radius-outer);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Main content grows to fill; icon sits to the left of the text block. */
|
|
54
|
+
.seeds-list-item-content-main {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Icon spacing previously came from Icon's mr={300} system prop. */
|
|
60
|
+
.seeds-list-item-content-icon {
|
|
61
|
+
margin-right: var(--space-300);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.seeds-list-item-content-text {
|
|
65
|
+
flex: 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Details byline spacing previously came from its mt={200} system prop.
|
|
69
|
+
The details node is a Text.Byline, so it also carries `.seeds-text`, whose
|
|
70
|
+
`margin: 0` sits later in the aggregated component layer and would reset a
|
|
71
|
+
single-class rule. Qualify with the parent to raise specificity above
|
|
72
|
+
`.seeds-text` while staying inside the components layer (consumer utilities
|
|
73
|
+
still win). */
|
|
74
|
+
.seeds-list-item-content .seeds-list-item-content-details {
|
|
75
|
+
margin-top: var(--space-200);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Aside/actions cluster on the right; does not shrink. */
|
|
79
|
+
.seeds-list-item-content-aside {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: var(--space-300);
|
|
83
|
+
flex-shrink: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.seeds-list-item-content-actions {
|
|
87
|
+
display: flex;
|
|
88
|
+
align-items: center;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* ListItemButton ----------------------------------------------------- */
|
|
92
|
+
|
|
93
|
+
/* Container reads as a card. radii[500] resolves to 6px (--radius-inner). */
|
|
94
|
+
.seeds-list-item-button {
|
|
95
|
+
position: relative;
|
|
96
|
+
box-sizing: border-box;
|
|
97
|
+
border: 1px solid var(--color-container-border-base);
|
|
98
|
+
padding: var(--space-300);
|
|
99
|
+
border-radius: var(--radius-inner);
|
|
100
|
+
background-color: var(--color-container-bg-base);
|
|
101
|
+
color: var(--color-text-body);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Transparent overlay button that captures clicks across the whole card. */
|
|
105
|
+
.seeds-list-item-button-overlay {
|
|
106
|
+
position: absolute;
|
|
107
|
+
top: 0;
|
|
108
|
+
left: 0;
|
|
109
|
+
width: 100%;
|
|
110
|
+
height: 100%;
|
|
111
|
+
}
|
|
112
|
+
}
|