notionsoft-ui 1.0.38 → 1.0.40
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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Card.stories.tsx
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardHeader,
|
|
6
|
+
CardTitle,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardContent,
|
|
9
|
+
CardFooter,
|
|
10
|
+
CardAction,
|
|
11
|
+
} from "./card";
|
|
12
|
+
|
|
13
|
+
const meta: Meta<typeof Card> = {
|
|
14
|
+
title: "Components/Card",
|
|
15
|
+
component: Card,
|
|
16
|
+
tags: ["autodocs"],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
type Story = StoryObj<typeof Card>;
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {
|
|
23
|
+
render: () => (
|
|
24
|
+
<Card className="max-w-md">
|
|
25
|
+
<CardHeader>
|
|
26
|
+
<CardTitle>Card Title</CardTitle>
|
|
27
|
+
<CardDescription>
|
|
28
|
+
This is a short description for the card.
|
|
29
|
+
</CardDescription>
|
|
30
|
+
</CardHeader>
|
|
31
|
+
|
|
32
|
+
<CardContent>
|
|
33
|
+
<p className="text-sm">
|
|
34
|
+
This is the main content area of the card. You can place any elements
|
|
35
|
+
here.
|
|
36
|
+
</p>
|
|
37
|
+
</CardContent>
|
|
38
|
+
|
|
39
|
+
<CardFooter>
|
|
40
|
+
<span className="text-sm text-muted-foreground">
|
|
41
|
+
Footer content goes here
|
|
42
|
+
</span>
|
|
43
|
+
</CardFooter>
|
|
44
|
+
</Card>
|
|
45
|
+
),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const WithAction: Story = {
|
|
49
|
+
render: () => (
|
|
50
|
+
<Card className="max-w-md">
|
|
51
|
+
<CardHeader>
|
|
52
|
+
<CardTitle>Card with Action</CardTitle>
|
|
53
|
+
<CardDescription>
|
|
54
|
+
This card demonstrates the CardAction slot.
|
|
55
|
+
</CardDescription>
|
|
56
|
+
<CardAction>
|
|
57
|
+
<button className="text-sm font-medium text-primary">Action</button>
|
|
58
|
+
</CardAction>
|
|
59
|
+
</CardHeader>
|
|
60
|
+
|
|
61
|
+
<CardContent>
|
|
62
|
+
<p className="text-sm">
|
|
63
|
+
Card content with an action button aligned to the header.
|
|
64
|
+
</p>
|
|
65
|
+
</CardContent>
|
|
66
|
+
</Card>
|
|
67
|
+
),
|
|
68
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { cn } from "@/utils/cn";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
5
|
+
return (
|
|
6
|
+
<div
|
|
7
|
+
data-slot="card"
|
|
8
|
+
className={cn(
|
|
9
|
+
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
|
10
|
+
className
|
|
11
|
+
)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
data-slot="card-header"
|
|
21
|
+
className={cn(
|
|
22
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
data-slot="card-title"
|
|
34
|
+
className={cn("leading-none font-semibold", className)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
data-slot="card-description"
|
|
44
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
data-slot="card-action"
|
|
54
|
+
className={cn(
|
|
55
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
56
|
+
className
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
data-slot="card-content"
|
|
67
|
+
className={cn("px-6", className)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="card-footer"
|
|
77
|
+
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
Card,
|
|
85
|
+
CardHeader,
|
|
86
|
+
CardFooter,
|
|
87
|
+
CardTitle,
|
|
88
|
+
CardAction,
|
|
89
|
+
CardDescription,
|
|
90
|
+
CardContent,
|
|
91
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Pagination.stories.tsx
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import Pagination from "./pagination";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Pagination> = {
|
|
6
|
+
title: "Components/Pagination",
|
|
7
|
+
component: Pagination,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: "centered",
|
|
10
|
+
},
|
|
11
|
+
argTypes: {
|
|
12
|
+
lastPage: {
|
|
13
|
+
control: { type: "number", min: 1 },
|
|
14
|
+
},
|
|
15
|
+
onPageChange: {
|
|
16
|
+
action: "page changed",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
|
|
23
|
+
type Story = StoryObj<typeof Pagination>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Default pagination
|
|
27
|
+
*/
|
|
28
|
+
export const Default: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
lastPage: 10,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Small number of pages (no ellipsis)
|
|
36
|
+
*/
|
|
37
|
+
export const SmallDataset: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
lastPage: 5,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Large dataset with ellipsis behavior
|
|
45
|
+
*/
|
|
46
|
+
export const LargeDataset: Story = {
|
|
47
|
+
args: {
|
|
48
|
+
lastPage: 50,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Single page (navigation disabled)
|
|
54
|
+
*/
|
|
55
|
+
export const SinglePage: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
lastPage: 1,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
|
|
4
|
+
interface PaginationProps {
|
|
5
|
+
lastPage: number;
|
|
6
|
+
onPageChange: (page: number) => void;
|
|
7
|
+
}
|
|
8
|
+
const Pagination: React.FC<PaginationProps> = ({ lastPage, onPageChange }) => {
|
|
9
|
+
const [currentPage, setCurrentPage] = useState<number>(1);
|
|
10
|
+
const handlePrevPage = () => {
|
|
11
|
+
if (currentPage > 1) {
|
|
12
|
+
setCurrentPage(currentPage - 1);
|
|
13
|
+
onPageChange(currentPage - 1);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const handleNextPage = () => {
|
|
18
|
+
if (currentPage < lastPage) {
|
|
19
|
+
setCurrentPage(currentPage + 1);
|
|
20
|
+
onPageChange(currentPage + 1);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const handlePageClick = (page: number) => {
|
|
25
|
+
setCurrentPage(page);
|
|
26
|
+
onPageChange(page);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Function to generate pagination numbers with ellipsis for large datasets
|
|
30
|
+
const generatePageNumbers = () => {
|
|
31
|
+
const pages: (number | string)[] = [];
|
|
32
|
+
const siblingCount = 1; // Number of pages to show around current page
|
|
33
|
+
|
|
34
|
+
if (lastPage <= 7) {
|
|
35
|
+
// If less than 7 pages, show all pages
|
|
36
|
+
for (let i = 1; i <= lastPage; i++) {
|
|
37
|
+
pages.push(i);
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
// Always show first, last, and nearby pages
|
|
41
|
+
pages.push(1);
|
|
42
|
+
if (currentPage > siblingCount + 2) pages.push("...");
|
|
43
|
+
for (
|
|
44
|
+
let i = Math.max(2, currentPage - siblingCount);
|
|
45
|
+
i <= Math.min(lastPage - 1, currentPage + siblingCount);
|
|
46
|
+
i++
|
|
47
|
+
) {
|
|
48
|
+
pages.push(i);
|
|
49
|
+
}
|
|
50
|
+
if (currentPage < lastPage - siblingCount - 1) pages.push("...");
|
|
51
|
+
pages.push(lastPage);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return pages;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const pageNumbers = generatePageNumbers();
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="flex justify-center items-center select-none h-7 text-sm gap-x-1 text-primary-foreground">
|
|
61
|
+
{/* Previous Button */}
|
|
62
|
+
<ChevronLeft
|
|
63
|
+
onClick={() => {
|
|
64
|
+
if (currentPage !== 1) handlePrevPage();
|
|
65
|
+
}}
|
|
66
|
+
className={`size-7 rounded transition-[color,background-color,transform] cursor-pointer rtl:rotate-180 self-center p-2 ${
|
|
67
|
+
currentPage === 1
|
|
68
|
+
? "text-primary/50"
|
|
69
|
+
: "text-primary hover:scale-110 hover:bg-fourth/10"
|
|
70
|
+
}`}
|
|
71
|
+
/>
|
|
72
|
+
{/* Page Numbers */}
|
|
73
|
+
{pageNumbers.map((page, index) =>
|
|
74
|
+
typeof page === "number" ? (
|
|
75
|
+
<button
|
|
76
|
+
key={index}
|
|
77
|
+
onClick={() => handlePageClick(page)}
|
|
78
|
+
className={`size-8 cursor-pointer rounded transition-colors ${
|
|
79
|
+
currentPage === page
|
|
80
|
+
? "bg-fourth text-primary-foreground text-sm"
|
|
81
|
+
: "text-secondary-foreground hover:bg-fourth/10 transition-transform hover:scale-105 text-xs font-semibold"
|
|
82
|
+
}`}
|
|
83
|
+
>
|
|
84
|
+
{page}
|
|
85
|
+
</button>
|
|
86
|
+
) : (
|
|
87
|
+
<span key={index} className="px-4 py-2 text-primary">
|
|
88
|
+
...
|
|
89
|
+
</span>
|
|
90
|
+
)
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
{/* Next Button */}
|
|
94
|
+
|
|
95
|
+
<ChevronRight
|
|
96
|
+
onClick={() => {
|
|
97
|
+
if (currentPage !== lastPage) handleNextPage();
|
|
98
|
+
}}
|
|
99
|
+
className={`size-8 rounded transition-[color,background-color,transform] cursor-pointer rtl:rotate-180 self-center p-2 ${
|
|
100
|
+
currentPage !== lastPage
|
|
101
|
+
? "text-primary hover:scale-110 hover:bg-fourth/10"
|
|
102
|
+
: "text-primary/50"
|
|
103
|
+
}`}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export default Pagination;
|