@stackshift-ui/card 6.0.11-beta.2 → 7.0.0-beta.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stackshift-ui/card",
3
3
  "description": "",
4
- "version": "6.0.11-beta.2",
4
+ "version": "7.0.0-beta.0",
5
5
  "private": false,
6
6
  "sideEffects": false,
7
7
  "main": "./dist/index.js",
@@ -13,6 +13,7 @@
13
13
  ],
14
14
  "author": "WebriQ <info@webriq.com>",
15
15
  "devDependencies": {
16
+ "@testing-library/jest-dom": "^6.5.0",
16
17
  "@testing-library/react": "^16.0.1",
17
18
  "@types/node": "^22.7.0",
18
19
  "@types/react": "^18.3.9",
@@ -29,20 +30,20 @@
29
30
  "typescript": "^5.6.2",
30
31
  "vite-tsconfig-paths": "^5.0.1",
31
32
  "vitest": "^2.1.1",
32
- "@stackshift-ui/eslint-config": "6.0.10-beta.2",
33
- "@stackshift-ui/typescript-config": "6.0.10-beta.2"
33
+ "@stackshift-ui/eslint-config": "6.0.10",
34
+ "@stackshift-ui/typescript-config": "6.0.10"
34
35
  },
35
36
  "dependencies": {
36
37
  "classnames": "^2.5.1",
37
- "@stackshift-ui/scripts": "6.0.10-beta.2",
38
- "@stackshift-ui/system": "6.0.11-beta.2"
38
+ "@stackshift-ui/scripts": "6.1.0-beta.0",
39
+ "@stackshift-ui/system": "6.1.0-beta.0"
39
40
  },
40
41
  "peerDependencies": {
42
+ "@stackshift-ui/system": ">=6.1.0-beta.0",
41
43
  "@types/react": "16.8 - 19",
42
44
  "next": "10 - 14",
43
45
  "react": "16.8 - 19",
44
- "react-dom": "16.8 - 19",
45
- "@stackshift-ui/system": ">=6.0.11-beta.2"
46
+ "react-dom": "16.8 - 19"
46
47
  },
47
48
  "peerDependenciesMeta": {
48
49
  "next": {
package/src/card.test.tsx CHANGED
@@ -1,13 +1,328 @@
1
1
  import { cleanup, render, screen } from "@testing-library/react";
2
2
  import { afterEach, describe, test } from "vitest";
3
- import { Card } from "./card";
3
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./card";
4
4
 
5
- describe.concurrent("card", () => {
5
+ describe.concurrent("Card Components", () => {
6
6
  afterEach(cleanup);
7
7
 
8
- test("Common: Card - test if renders without errors", ({ expect }) => {
9
- const clx = "my-class";
10
- render(<Card className={clx} />);
11
- expect(screen.getByTestId("div").classList).toContain(clx);
8
+ describe("Card", () => {
9
+ afterEach(cleanup);
10
+
11
+ test("Common: Card - test if renders without errors", ({ expect }) => {
12
+ const { unmount } = render(<Card data-testid="card-1" />);
13
+ expect(screen.getByTestId("card-1")).toBeInTheDocument();
14
+ unmount();
15
+ });
16
+
17
+ test("Common: Card - test if renders with custom className", ({ expect }) => {
18
+ const customClass = "my-custom-class";
19
+ const { unmount } = render(
20
+ <Card data-testid="card-with-custom-class" className={customClass} />,
21
+ );
22
+ expect(screen.getByTestId("card-with-custom-class")).toHaveClass(customClass);
23
+ unmount();
24
+ });
25
+
26
+ test("applies default classes", ({ expect }) => {
27
+ const { unmount } = render(<Card data-testid="card-with-default-classes" />);
28
+ const card = screen.getByTestId("card-with-default-classes");
29
+ expect(card).toHaveClass(
30
+ "rounded-lg",
31
+ "border",
32
+ "bg-card",
33
+ "text-card-foreground",
34
+ "shadow-sm",
35
+ );
36
+ unmount();
37
+ });
38
+
39
+ test("Common: Card - test if renders with custom id and role", ({ expect }) => {
40
+ const { unmount } = render(
41
+ <Card data-testid="card-with-id-and-role" id="test-id" role="region" />,
42
+ );
43
+ const card = screen.getByTestId("card-with-id-and-role");
44
+ expect(card).toHaveAttribute("id", "test-id");
45
+ expect(card).toHaveAttribute("role", "region");
46
+ unmount();
47
+ });
48
+
49
+ test("Common: Card - test if renders with custom content", ({ expect }) => {
50
+ const { unmount } = render(
51
+ <Card data-testid="card-with-content">
52
+ <span>Card With custom content</span>
53
+ </Card>,
54
+ );
55
+ expect(screen.getByText("Card With custom content")).toBeInTheDocument();
56
+ unmount();
57
+ });
58
+ });
59
+
60
+ describe("CardHeader", () => {
61
+ afterEach(cleanup);
62
+
63
+ test("Common: CardHeader - test if renders without errors", ({ expect }) => {
64
+ const { unmount } = render(<CardHeader data-testid="card-header" />);
65
+ expect(screen.getByTestId("card-header")).toBeInTheDocument();
66
+ unmount();
67
+ });
68
+
69
+ test("Common: CardHeader - test default classes", ({ expect }) => {
70
+ const { unmount } = render(<CardHeader data-testid="card-header-with-default-classes" />);
71
+ const header = screen.getByTestId("card-header-with-default-classes");
72
+ expect(header).toHaveClass("flex", "flex-col", "space-y-1.5", "p-6");
73
+ unmount();
74
+ });
75
+
76
+ test("Common: CardHeader - test if renders with custom className", ({ expect }) => {
77
+ const customClass = "custom-header-class";
78
+ const { unmount } = render(
79
+ <CardHeader data-testid="card-header-with-custom-class" className={customClass} />,
80
+ );
81
+ expect(screen.getByTestId("card-header-with-custom-class")).toHaveClass(customClass);
82
+ unmount();
83
+ });
84
+ });
85
+
86
+ describe("CardTitle", () => {
87
+ afterEach(cleanup);
88
+
89
+ test("Common: CardTitle - test if renders without errors", ({ expect }) => {
90
+ const { unmount } = render(<CardTitle data-testid="card-title" />);
91
+ expect(screen.getByTestId("card-title")).toBeInTheDocument();
92
+ unmount();
93
+ });
94
+
95
+ test("Common: CardTitle - test default classes", ({ expect }) => {
96
+ const { unmount } = render(<CardTitle data-testid="card-title-with-default-classes" />);
97
+ const title = screen.getByTestId("card-title-with-default-classes");
98
+ expect(title).toHaveClass("text-2xl", "font-semibold", "leading-none", "tracking-tight");
99
+ unmount();
100
+ });
101
+
102
+ test("Common: CardTitle - test if renders with custom className", ({ expect }) => {
103
+ const customClass = "custom-title-class";
104
+ const { unmount } = render(
105
+ <CardTitle data-testid="card-title-with-custom-class" className={customClass} />,
106
+ );
107
+ expect(screen.getByTestId("card-title-with-custom-class")).toHaveClass(customClass);
108
+ unmount();
109
+ });
110
+
111
+ test("Common: CardTitle - test if renders with custom content", ({ expect }) => {
112
+ const { unmount } = render(
113
+ <CardTitle data-testid="card-title-with-content">Card Title With Content</CardTitle>,
114
+ );
115
+ expect(screen.getByText("Card Title With Content")).toBeInTheDocument();
116
+ unmount();
117
+ });
118
+ });
119
+
120
+ describe("CardDescription", () => {
121
+ afterEach(cleanup);
122
+
123
+ test("Common: CardDescription - test if renders without errors", ({ expect }) => {
124
+ const { unmount } = render(<CardDescription data-testid="card-description" />);
125
+ expect(screen.getByTestId("card-description")).toBeInTheDocument();
126
+ unmount();
127
+ });
128
+
129
+ test("Common: CardDescription - test default classes", ({ expect }) => {
130
+ const { unmount } = render(
131
+ <CardDescription data-testid="card-description-with-default-classes" />,
132
+ );
133
+ const description = screen.getByTestId("card-description-with-default-classes");
134
+ expect(description.getAttribute("class")).toContain("text-sm text-muted-foreground");
135
+ unmount();
136
+ });
137
+
138
+ test("Common: CardDescription - test if renders with custom className", ({ expect }) => {
139
+ const customClass = "custom-description-class";
140
+ const { unmount } = render(
141
+ <CardDescription
142
+ data-testid="card-description-with-custom-class"
143
+ className={customClass}
144
+ />,
145
+ );
146
+ expect(
147
+ screen.getByTestId("card-description-with-custom-class").getAttribute("class"),
148
+ ).toContain(customClass);
149
+ unmount();
150
+ });
151
+
152
+ test("Common: CardDescription - test if renders with custom content", ({ expect }) => {
153
+ const { unmount } = render(
154
+ <CardDescription data-testid="card-description">Test Description</CardDescription>,
155
+ );
156
+ expect(screen.getByText("Test Description")).toBeInTheDocument();
157
+ unmount();
158
+ });
159
+ });
160
+
161
+ describe("CardContent", () => {
162
+ afterEach(cleanup);
163
+
164
+ test("Common: CardContent - test if renders without errors", ({ expect }) => {
165
+ const { unmount } = render(<CardContent data-testid="card-content" />);
166
+ expect(screen.getByTestId("card-content")).toBeInTheDocument();
167
+ unmount();
168
+ });
169
+
170
+ test("Common: CardContent - test default classes", ({ expect }) => {
171
+ const { unmount } = render(<CardContent data-testid="card-content-with-default-classes" />);
172
+ const content = screen.getByTestId("card-content-with-default-classes");
173
+ expect(content.getAttribute("class")).toContain("p-6 pt-0");
174
+ unmount();
175
+ });
176
+
177
+ test("Common: CardContent - test if renders with custom className", ({ expect }) => {
178
+ const customClass = "custom-content-class";
179
+ const { unmount } = render(
180
+ <CardContent data-testid="card-content-with-custom-class" className={customClass} />,
181
+ );
182
+ expect(screen.getByTestId("card-content-with-custom-class").getAttribute("class")).toContain(
183
+ customClass,
184
+ );
185
+ unmount();
186
+ });
187
+
188
+ test("Common: CardContent - test if renders with custom content", ({ expect }) => {
189
+ const { unmount } = render(
190
+ <CardContent data-testid="card-content">Test Content</CardContent>,
191
+ );
192
+ expect(screen.getByText("Test Content")).toBeInTheDocument();
193
+ unmount();
194
+ });
195
+ });
196
+
197
+ describe("CardFooter", () => {
198
+ afterEach(cleanup);
199
+
200
+ test("Common: CardFooter - test if renders without errors", ({ expect }) => {
201
+ const { unmount } = render(<CardFooter data-testid="card-footer" />);
202
+ expect(screen.getByTestId("card-footer")).toBeInTheDocument();
203
+ unmount();
204
+ });
205
+
206
+ test("Common: CardFooter - test default classes", ({ expect }) => {
207
+ const { unmount } = render(<CardFooter data-testid="card-footer-with-default-classes" />);
208
+ const footer = screen.getByTestId("card-footer-with-default-classes");
209
+ expect(footer.getAttribute("class")).toContain("flex items-center p-6 pt-0");
210
+ unmount();
211
+ });
212
+
213
+ test("Common: CardFooter - test if renders with custom className", ({ expect }) => {
214
+ const customClass = "custom-footer-class";
215
+ const { unmount } = render(
216
+ <CardFooter data-testid="card-footer-with-custom-class" className={customClass} />,
217
+ );
218
+ expect(screen.getByTestId("card-footer-with-custom-class").getAttribute("class")).toContain(
219
+ customClass,
220
+ );
221
+ unmount();
222
+ });
223
+
224
+ test("Common: CardFooter - test if renders with custom content", ({ expect }) => {
225
+ const { unmount } = render(<CardFooter data-testid="card-footer">Footer Content</CardFooter>);
226
+ expect(screen.getByText("Footer Content")).toBeInTheDocument();
227
+ unmount();
228
+ });
229
+ });
230
+
231
+ describe("Card Integration", () => {
232
+ afterEach(cleanup);
233
+
234
+ test("Common: Card - test if renders with all components", ({ expect }) => {
235
+ const { unmount } = render(
236
+ <Card data-testid="card-with-all-components">
237
+ <CardHeader data-testid="card-header-with-all-components">
238
+ <CardTitle data-testid="card-title-with-all-components">Card Title</CardTitle>
239
+ <CardDescription data-testid="card-description-with-all-components">
240
+ Card Description
241
+ </CardDescription>
242
+ </CardHeader>
243
+ <CardContent data-testid="card-content-with-all-components">
244
+ <p>This is the card content.</p>
245
+ </CardContent>
246
+ <CardFooter data-testid="card-footer-with-all-components">
247
+ <button>Action Button</button>
248
+ </CardFooter>
249
+ </Card>,
250
+ );
251
+
252
+ // Verify all components are rendered
253
+ expect(screen.getByTestId("card-with-all-components")).toBeInTheDocument();
254
+ expect(screen.getByTestId("card-header-with-all-components")).toBeInTheDocument();
255
+ expect(screen.getByTestId("card-title-with-all-components")).toBeInTheDocument();
256
+ expect(screen.getByTestId("card-description-with-all-components")).toBeInTheDocument();
257
+ expect(screen.getByTestId("card-content-with-all-components")).toBeInTheDocument();
258
+ expect(screen.getByTestId("card-footer-with-all-components")).toBeInTheDocument();
259
+
260
+ // Verify content is rendered correctly
261
+ expect(screen.getByText("Card Title")).toBeInTheDocument();
262
+ expect(screen.getByText("Card Description")).toBeInTheDocument();
263
+ expect(screen.getByText("This is the card content.")).toBeInTheDocument();
264
+ expect(screen.getByText("Action Button")).toBeInTheDocument();
265
+ unmount();
266
+ });
267
+
268
+ test("Common: Card - test if components are rendered in correct order", ({ expect }) => {
269
+ const { unmount } = render(
270
+ <Card data-testid="card-content-order">
271
+ <CardHeader data-testid="card-header-content-order">
272
+ <CardTitle data-testid="card-title-content-order">Title</CardTitle>
273
+ </CardHeader>
274
+ <CardContent data-testid="card-content-content-order">Content</CardContent>
275
+ </Card>,
276
+ );
277
+
278
+ const card = screen.getByTestId("card-content-order");
279
+ const header = screen.getByTestId("card-header-content-order");
280
+ const title = screen.getByTestId("card-title-content-order");
281
+ const content = screen.getByTestId("card-content-content-order");
282
+
283
+ // Verify hierarchy
284
+ expect(card).toContainElement(header);
285
+ expect(card).toContainElement(content);
286
+ expect(header).toContainElement(title);
287
+ unmount();
288
+ });
289
+ });
290
+
291
+ describe("Accessibility", () => {
292
+ afterEach(cleanup);
293
+
294
+ test("Common: Card - test if renders with correct role", ({ expect }) => {
295
+ const { unmount } = render(
296
+ <Card data-testid="card" role="article">
297
+ <CardHeader data-testid="card-header">
298
+ <CardTitle data-testid="card-title">Accessible Title</CardTitle>
299
+ <CardDescription data-testid="card-description">Accessible Description</CardDescription>
300
+ </CardHeader>
301
+ <CardContent data-testid="card-content">Accessible Content</CardContent>
302
+ <CardFooter data-testid="card-footer">Accessible Footer</CardFooter>
303
+ </Card>,
304
+ );
305
+
306
+ expect(screen.getByRole("article")).toBeInTheDocument();
307
+ expect(screen.getByText("Accessible Title")).toBeInTheDocument();
308
+ expect(screen.getByText("Accessible Description")).toBeInTheDocument();
309
+ unmount();
310
+ });
311
+
312
+ test("Common: Card - test if renders with ARIA attributes", ({ expect }) => {
313
+ const { unmount } = render(
314
+ <Card
315
+ data-testid="card-with-aria"
316
+ aria-label="Test card"
317
+ aria-describedby="card-description">
318
+ <CardContent id="card-description">Card with ARIA attributes</CardContent>
319
+ </Card>,
320
+ );
321
+
322
+ const card = screen.getByTestId("card-with-aria");
323
+ expect(card).toHaveAttribute("aria-label", "Test card");
324
+ expect(card).toHaveAttribute("aria-describedby", "card-description");
325
+ unmount();
326
+ });
12
327
  });
13
328
  });
package/src/card.tsx CHANGED
@@ -1,38 +1,83 @@
1
- import { DefaultComponent, useStackShiftUIComponents } from "@stackshift-ui/system";
2
- import type { ElementType, HTMLProps, ReactNode } from "react";
3
- import cn from "classnames";
1
+ import * as React from "react";
4
2
 
5
- type CardBorderRadius = "none" | "sm" | "md" | "lg" | "xl" | "global";
6
-
7
- export interface CardProps extends Omit<HTMLProps<HTMLElement>, "as"> {
8
- borderRadius?: CardBorderRadius;
9
- children?: ReactNode;
10
- className?: string;
11
- as?: ElementType;
12
- }
3
+ import { cn, DefaultComponent, useStackShiftUIComponents } from "@stackshift-ui/system";
13
4
 
14
5
  const displayName = "Card";
6
+ const displayNameHeader = "CardHeader";
7
+ const displayNameTitle = "CardTitle";
8
+ const displayNameDescription = "CardDescription";
9
+ const displayNameContent = "CardContent";
10
+ const displayNameFooter = "CardFooter";
15
11
 
16
- export const Card: React.FC<CardProps> = ({ borderRadius, children, className, as, ...props }) => {
17
- const { [displayName]: Component = DefaultComponent } = useStackShiftUIComponents();
12
+ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
13
+ ({ className, ...props }, ref) => {
14
+ const { [displayName]: Component = DefaultComponent } = useStackShiftUIComponents();
18
15
 
19
- const radiusMap = {
20
- none: "rounded-none",
21
- sm: "rounded-sm",
22
- md: "rounded-md",
23
- lg: "rounded-lg",
24
- xl: "rounded-xl",
25
- global: "rounded-global",
26
- };
27
- const borderRadiusClass = radiusMap[borderRadius ?? "global"];
16
+ return (
17
+ <Component
18
+ ref={ref}
19
+ className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
20
+ {...props}
21
+ />
22
+ );
23
+ },
24
+ );
25
+ Card.displayName = displayName;
28
26
 
29
- const classes = `border border-solid border-slate-300 p-4 shadow-sm ${borderRadiusClass}`;
27
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
28
+ ({ className, ...props }, ref) => {
29
+ const { [displayNameHeader]: Component = DefaultComponent } = useStackShiftUIComponents();
30
30
 
31
- return (
32
- <Component as={as} className={cn(classes, className)} {...props} data-testid={displayName}>
33
- {children}
34
- </Component>
35
- );
36
- };
31
+ return (
32
+ <Component ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
33
+ );
34
+ },
35
+ );
36
+ CardHeader.displayName = displayNameHeader;
37
37
 
38
- Card.displayName = displayName;
38
+ const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
39
+ ({ className, ...props }, ref) => {
40
+ const { [displayNameTitle]: Component = DefaultComponent } = useStackShiftUIComponents();
41
+ return (
42
+ <Component
43
+ ref={ref}
44
+ className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
45
+ {...props}
46
+ />
47
+ );
48
+ },
49
+ );
50
+ CardTitle.displayName = displayNameTitle;
51
+
52
+ const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
53
+ ({ className, ...props }, ref) => {
54
+ const { [displayNameDescription]: Component = DefaultComponent } = useStackShiftUIComponents();
55
+
56
+ return (
57
+ <Component ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
58
+ );
59
+ },
60
+ );
61
+ CardDescription.displayName = displayNameDescription;
62
+
63
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
64
+ ({ className, ...props }, ref) => {
65
+ const { [displayNameContent]: Component = DefaultComponent } = useStackShiftUIComponents();
66
+
67
+ return <Component ref={ref} className={cn("p-6 pt-0", className)} {...props} />;
68
+ },
69
+ );
70
+ CardContent.displayName = displayNameContent;
71
+
72
+ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
73
+ ({ className, ...props }, ref) => {
74
+ const { [displayNameFooter]: Component = DefaultComponent } = useStackShiftUIComponents();
75
+
76
+ return (
77
+ <Component ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
78
+ );
79
+ },
80
+ );
81
+ CardFooter.displayName = displayNameFooter;
82
+
83
+ export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };
@@ -0,0 +1,4 @@
1
+ import '@testing-library/jest-dom';
2
+
3
+ export { };
4
+