@smicolon/fasla-ui 0.2.2
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/README.md +141 -0
- package/bin/fasla-ui.mjs +54 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +241 -0
- package/dist/lib/utils.d.ts +9 -0
- package/dist/lib/utils.js +9 -0
- package/dist/motion/index.d.ts +33 -0
- package/dist/motion/index.js +59 -0
- package/dist/registry/index.d.ts +17 -0
- package/dist/registry/index.js +145 -0
- package/dist/tokens/index.d.ts +55 -0
- package/dist/tokens/index.js +34 -0
- package/package.json +97 -0
- package/registry/blocks/app-shell/AppShell.tsx +66 -0
- package/registry/blocks/app-shell/index.ts +1 -0
- package/registry/blocks/data-table/DataTable.tsx +292 -0
- package/registry/blocks/data-table/index.ts +7 -0
- package/registry/blocks/empty-state/EmptyState.test.tsx +120 -0
- package/registry/blocks/empty-state/EmptyState.tsx +164 -0
- package/registry/blocks/empty-state/index.ts +6 -0
- package/registry/blocks/form-section/FormSection.tsx +140 -0
- package/registry/blocks/form-section/index.ts +8 -0
- package/registry/blocks/navbar/Navbar.tsx +190 -0
- package/registry/blocks/navbar/index.ts +16 -0
- package/registry/blocks/page-header/PageHeader.tsx +64 -0
- package/registry/blocks/page-header/index.ts +1 -0
- package/registry/blocks/sidebar/Sidebar.tsx +202 -0
- package/registry/blocks/sidebar/index.ts +16 -0
- package/registry/blocks/stats-card/StatsCard.tsx +134 -0
- package/registry/blocks/stats-card/index.ts +6 -0
- package/registry/effects/animated-gradient/animated-gradient.tsx +136 -0
- package/registry/effects/animated-gradient/index.ts +6 -0
- package/registry/effects/border-beam/border-beam.tsx +132 -0
- package/registry/effects/border-beam/index.ts +6 -0
- package/registry/effects/glow-card/glow-card.tsx +145 -0
- package/registry/effects/glow-card/index.ts +6 -0
- package/registry/effects/shimmer-button/index.ts +1 -0
- package/registry/effects/shimmer-button/shimmer-button.tsx +91 -0
- package/registry/effects/spotlight/index.ts +6 -0
- package/registry/effects/spotlight/spotlight.tsx +162 -0
- package/registry/effects/text-reveal/index.ts +6 -0
- package/registry/effects/text-reveal/text-reveal.tsx +139 -0
- package/registry/effects/typewriter-text/index.ts +6 -0
- package/registry/effects/typewriter-text/typewriter-text.tsx +220 -0
- package/registry/ui/avatar/avatar.tsx +135 -0
- package/registry/ui/avatar/index.ts +1 -0
- package/registry/ui/badge/badge.test.tsx +51 -0
- package/registry/ui/badge/badge.tsx +39 -0
- package/registry/ui/badge/index.ts +1 -0
- package/registry/ui/button/button.test.tsx +87 -0
- package/registry/ui/button/button.tsx +88 -0
- package/registry/ui/button/index.ts +1 -0
- package/registry/ui/card/card.test.tsx +119 -0
- package/registry/ui/card/card.tsx +75 -0
- package/registry/ui/card/index.ts +8 -0
- package/registry/ui/checkbox/checkbox.tsx +113 -0
- package/registry/ui/checkbox/index.ts +1 -0
- package/registry/ui/combobox/combobox.tsx +410 -0
- package/registry/ui/combobox/index.ts +1 -0
- package/registry/ui/input/index.ts +1 -0
- package/registry/ui/input/input.test.tsx +78 -0
- package/registry/ui/input/input.tsx +76 -0
- package/registry/ui/select/index.ts +1 -0
- package/registry/ui/select/select.tsx +94 -0
- package/registry/ui/skeleton/index.ts +7 -0
- package/registry/ui/skeleton/skeleton.test.tsx +104 -0
- package/registry/ui/skeleton/skeleton.tsx +88 -0
- package/registry/ui/switch/index.ts +1 -0
- package/registry/ui/switch/switch.tsx +89 -0
- package/registry/ui/tabs/index.ts +10 -0
- package/registry/ui/tabs/tabs.tsx +150 -0
- package/registry/ui/textarea/index.ts +1 -0
- package/registry/ui/textarea/textarea.tsx +73 -0
- package/registry.json +247 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest"
|
|
2
|
+
import { render, screen } from "@testing-library/react"
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardHeader,
|
|
6
|
+
CardTitle,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardContent,
|
|
9
|
+
CardFooter,
|
|
10
|
+
} from "./card"
|
|
11
|
+
|
|
12
|
+
describe("Card", () => {
|
|
13
|
+
it("renders Card with children", () => {
|
|
14
|
+
render(<Card>Card content</Card>)
|
|
15
|
+
expect(screen.getByText("Card content")).toBeInTheDocument()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it("renders Card with correct base classes", () => {
|
|
19
|
+
render(<Card data-testid="card">Content</Card>)
|
|
20
|
+
const card = screen.getByTestId("card")
|
|
21
|
+
expect(card).toHaveClass("rounded-xl", "border", "bg-card", "shadow")
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it("applies custom className to Card", () => {
|
|
25
|
+
render(<Card className="custom-class" data-testid="card">Content</Card>)
|
|
26
|
+
expect(screen.getByTestId("card")).toHaveClass("custom-class")
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("forwards ref to Card", () => {
|
|
30
|
+
const ref = vi.fn()
|
|
31
|
+
render(<Card ref={ref}>Content</Card>)
|
|
32
|
+
expect(ref).toHaveBeenCalled()
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe("CardHeader", () => {
|
|
37
|
+
it("renders CardHeader with children", () => {
|
|
38
|
+
render(<CardHeader>Header content</CardHeader>)
|
|
39
|
+
expect(screen.getByText("Header content")).toBeInTheDocument()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it("renders CardHeader with correct classes", () => {
|
|
43
|
+
render(<CardHeader data-testid="header">Header</CardHeader>)
|
|
44
|
+
expect(screen.getByTestId("header")).toHaveClass("flex", "flex-col", "p-6")
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("applies custom className to CardHeader", () => {
|
|
48
|
+
render(<CardHeader className="custom-class" data-testid="header">Header</CardHeader>)
|
|
49
|
+
expect(screen.getByTestId("header")).toHaveClass("custom-class")
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe("CardTitle", () => {
|
|
54
|
+
it("renders CardTitle with children", () => {
|
|
55
|
+
render(<CardTitle>My Title</CardTitle>)
|
|
56
|
+
expect(screen.getByText("My Title")).toBeInTheDocument()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it("renders CardTitle with correct classes", () => {
|
|
60
|
+
render(<CardTitle data-testid="title">Title</CardTitle>)
|
|
61
|
+
expect(screen.getByTestId("title")).toHaveClass("font-semibold", "leading-none")
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe("CardDescription", () => {
|
|
66
|
+
it("renders CardDescription with children", () => {
|
|
67
|
+
render(<CardDescription>Description text</CardDescription>)
|
|
68
|
+
expect(screen.getByText("Description text")).toBeInTheDocument()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it("renders CardDescription with correct classes", () => {
|
|
72
|
+
render(<CardDescription data-testid="desc">Description</CardDescription>)
|
|
73
|
+
expect(screen.getByTestId("desc")).toHaveClass("text-sm", "text-muted-foreground")
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe("CardContent", () => {
|
|
78
|
+
it("renders CardContent with children", () => {
|
|
79
|
+
render(<CardContent>Content here</CardContent>)
|
|
80
|
+
expect(screen.getByText("Content here")).toBeInTheDocument()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it("renders CardContent with correct classes", () => {
|
|
84
|
+
render(<CardContent data-testid="content">Content</CardContent>)
|
|
85
|
+
expect(screen.getByTestId("content")).toHaveClass("p-6", "pt-0")
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe("CardFooter", () => {
|
|
90
|
+
it("renders CardFooter with children", () => {
|
|
91
|
+
render(<CardFooter>Footer content</CardFooter>)
|
|
92
|
+
expect(screen.getByText("Footer content")).toBeInTheDocument()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it("renders CardFooter with correct classes", () => {
|
|
96
|
+
render(<CardFooter data-testid="footer">Footer</CardFooter>)
|
|
97
|
+
expect(screen.getByTestId("footer")).toHaveClass("flex", "items-center", "p-6", "pt-0")
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
describe("Full Card composition", () => {
|
|
102
|
+
it("renders a complete card with all parts", () => {
|
|
103
|
+
render(
|
|
104
|
+
<Card>
|
|
105
|
+
<CardHeader>
|
|
106
|
+
<CardTitle>Card Title</CardTitle>
|
|
107
|
+
<CardDescription>Card description</CardDescription>
|
|
108
|
+
</CardHeader>
|
|
109
|
+
<CardContent>Main content</CardContent>
|
|
110
|
+
<CardFooter>Footer actions</CardFooter>
|
|
111
|
+
</Card>
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
expect(screen.getByText("Card Title")).toBeInTheDocument()
|
|
115
|
+
expect(screen.getByText("Card description")).toBeInTheDocument()
|
|
116
|
+
expect(screen.getByText("Main content")).toBeInTheDocument()
|
|
117
|
+
expect(screen.getByText("Footer actions")).toBeInTheDocument()
|
|
118
|
+
})
|
|
119
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../../../src/lib/utils"
|
|
3
|
+
|
|
4
|
+
const Card = React.forwardRef<
|
|
5
|
+
HTMLDivElement,
|
|
6
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
7
|
+
>(({ className, ...props }, ref) => (
|
|
8
|
+
<div
|
|
9
|
+
ref={ref}
|
|
10
|
+
className={cn(
|
|
11
|
+
"rounded-xl border bg-card text-card-foreground shadow",
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
))
|
|
17
|
+
Card.displayName = "Card"
|
|
18
|
+
|
|
19
|
+
const CardHeader = React.forwardRef<
|
|
20
|
+
HTMLDivElement,
|
|
21
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
22
|
+
>(({ className, ...props }, ref) => (
|
|
23
|
+
<div
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
))
|
|
29
|
+
CardHeader.displayName = "CardHeader"
|
|
30
|
+
|
|
31
|
+
const CardTitle = React.forwardRef<
|
|
32
|
+
HTMLDivElement,
|
|
33
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
34
|
+
>(({ className, ...props }, ref) => (
|
|
35
|
+
<div
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={cn("font-semibold leading-none tracking-tight", className)}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
))
|
|
41
|
+
CardTitle.displayName = "CardTitle"
|
|
42
|
+
|
|
43
|
+
const CardDescription = React.forwardRef<
|
|
44
|
+
HTMLDivElement,
|
|
45
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
46
|
+
>(({ className, ...props }, ref) => (
|
|
47
|
+
<div
|
|
48
|
+
ref={ref}
|
|
49
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
))
|
|
53
|
+
CardDescription.displayName = "CardDescription"
|
|
54
|
+
|
|
55
|
+
const CardContent = React.forwardRef<
|
|
56
|
+
HTMLDivElement,
|
|
57
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
58
|
+
>(({ className, ...props }, ref) => (
|
|
59
|
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
60
|
+
))
|
|
61
|
+
CardContent.displayName = "CardContent"
|
|
62
|
+
|
|
63
|
+
const CardFooter = React.forwardRef<
|
|
64
|
+
HTMLDivElement,
|
|
65
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
66
|
+
>(({ className, ...props }, ref) => (
|
|
67
|
+
<div
|
|
68
|
+
ref={ref}
|
|
69
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
))
|
|
73
|
+
CardFooter.displayName = "CardFooter"
|
|
74
|
+
|
|
75
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../../../src/lib/utils"
|
|
3
|
+
|
|
4
|
+
export interface CheckboxProps
|
|
5
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "type"> {
|
|
6
|
+
/** Label text */
|
|
7
|
+
label?: string
|
|
8
|
+
/** Description text */
|
|
9
|
+
description?: string
|
|
10
|
+
/** Error state */
|
|
11
|
+
error?: boolean
|
|
12
|
+
/** Indeterminate state */
|
|
13
|
+
indeterminate?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
|
17
|
+
({ className, label, description, error, indeterminate, id, ...props }, ref) => {
|
|
18
|
+
const inputRef = React.useRef<HTMLInputElement>(null)
|
|
19
|
+
|
|
20
|
+
React.useImperativeHandle(ref, () => inputRef.current!)
|
|
21
|
+
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
if (inputRef.current) {
|
|
24
|
+
inputRef.current.indeterminate = indeterminate ?? false
|
|
25
|
+
}
|
|
26
|
+
}, [indeterminate])
|
|
27
|
+
|
|
28
|
+
const inputId = id || React.useId()
|
|
29
|
+
|
|
30
|
+
const checkbox = (
|
|
31
|
+
<div className="relative flex items-center">
|
|
32
|
+
<input
|
|
33
|
+
type="checkbox"
|
|
34
|
+
ref={inputRef}
|
|
35
|
+
id={inputId}
|
|
36
|
+
className="peer sr-only"
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
<div
|
|
40
|
+
className={cn(
|
|
41
|
+
"flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border transition-colors",
|
|
42
|
+
"peer-focus-visible:outline-none peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2",
|
|
43
|
+
"peer-checked:border-primary peer-checked:bg-primary peer-checked:text-primary-foreground",
|
|
44
|
+
"peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
45
|
+
error
|
|
46
|
+
? "border-destructive peer-checked:border-destructive peer-checked:bg-destructive"
|
|
47
|
+
: "border-input",
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
>
|
|
51
|
+
<svg
|
|
52
|
+
width="12"
|
|
53
|
+
height="12"
|
|
54
|
+
viewBox="0 0 24 24"
|
|
55
|
+
fill="none"
|
|
56
|
+
stroke="currentColor"
|
|
57
|
+
strokeWidth="3"
|
|
58
|
+
strokeLinecap="round"
|
|
59
|
+
strokeLinejoin="round"
|
|
60
|
+
className={cn(
|
|
61
|
+
"opacity-0 peer-checked:opacity-100",
|
|
62
|
+
indeterminate ? "hidden" : "block"
|
|
63
|
+
)}
|
|
64
|
+
>
|
|
65
|
+
<polyline points="20 6 9 17 4 12" />
|
|
66
|
+
</svg>
|
|
67
|
+
{indeterminate && (
|
|
68
|
+
<svg
|
|
69
|
+
width="12"
|
|
70
|
+
height="12"
|
|
71
|
+
viewBox="0 0 24 24"
|
|
72
|
+
fill="none"
|
|
73
|
+
stroke="currentColor"
|
|
74
|
+
strokeWidth="3"
|
|
75
|
+
strokeLinecap="round"
|
|
76
|
+
>
|
|
77
|
+
<line x1="5" y1="12" x2="19" y2="12" />
|
|
78
|
+
</svg>
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if (!label && !description) {
|
|
85
|
+
return checkbox
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="flex items-start gap-3">
|
|
90
|
+
{checkbox}
|
|
91
|
+
<div className="grid gap-1 leading-none">
|
|
92
|
+
{label && (
|
|
93
|
+
<label
|
|
94
|
+
htmlFor={inputId}
|
|
95
|
+
className={cn(
|
|
96
|
+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
97
|
+
error && "text-destructive"
|
|
98
|
+
)}
|
|
99
|
+
>
|
|
100
|
+
{label}
|
|
101
|
+
</label>
|
|
102
|
+
)}
|
|
103
|
+
{description && (
|
|
104
|
+
<p className="text-xs text-muted-foreground">{description}</p>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
Checkbox.displayName = "Checkbox"
|
|
112
|
+
|
|
113
|
+
export { Checkbox }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Checkbox, type CheckboxProps } from "./checkbox"
|