Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 89 additions & 3 deletions lp-code/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions lp-code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@
"dependencies": {
"@gsap/react": "^2.1.2",
"@tailwindcss/vite": "^4.1.18",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.34.5",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.1.18"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
Expand Down
60 changes: 60 additions & 0 deletions lp-code/src/components/ui/button/Button.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { describe, it, expect, vi } from "vitest"
import { Button } from "./Button"

describe("Button component",
() => {

/**
* Testa se o botão renderiza o texto corretamente
*
* */
it("renders button text", () => {
render(<Button>Click</Button>)

const btn = screen.getByRole("button")

expect(btn).toBeInTheDocument()
expect(btn).toHaveTextContent("Click")
})

/**
* Testa se a variante primária é aplicada por padrão
*/
it("applies primary variant by default", () => {
render(<Button>Click</Button>)

const btn = screen.getByRole("button")

expect(btn.className).toContain("bg-blue-600")
})

/**
* Testa se a variante secundária é aplicada quando especificada
*/
it("applies secondary variant", () => {
render(<Button variant="secondary">Click</Button>)

const btn = screen.getByRole("button")

expect(btn.className).toContain("bg-gray-200")
})

/**
* Testa se o evento onClick é disparado corretamente
*/
it("fires onClick", async () => {
const user = userEvent.setup()
const handleClick = vi.fn()

render(<Button onClick={handleClick}>Click</Button>)

const btn = screen.getByRole("button")

await user.click(btn)

expect(handleClick).toHaveBeenCalledTimes(1)
})

})
33 changes: 33 additions & 0 deletions lp-code/src/components/ui/button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { forwardRef } from "react"
import type { ButtonHTMLAttributes } from "react"
import type { VariantProps } from "class-variance-authority"
import { buttonVariants } from "./button.variants"
import { cn } from "../../../lib/cn"

type ButtonProps =
ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof buttonVariants>
/**
* Button Component
*
* Componente base do Design System.
* Suporta variantes visuais (primary, secondary, ghost)
* e tamanhos (sm, md, lg) via class-variance-authority.
*
* Implementado com forwardRef para permitir:
* - Integração com bibliotecas de animação (ex: Framer Motion)
* - Controle imperativo externo (focus, etc.)
*/
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
ref={ref}
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
)
}
)

Button.displayName = "Button"
23 changes: 23 additions & 0 deletions lp-code/src/components/ui/button/button.variants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { cva } from "class-variance-authority"

export const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none",
{
variants: {
variant: {
primary: "bg-blue-600 text-white hover:bg-blue-700",
secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300",
ghost: "bg-transparent hover:bg-gray-100"
},
size: {
sm: "h-8 px-3 text-sm",
md: "h-10 px-4 text-sm",
lg: "h-12 px-6 text-base"
}
},
defaultVariants: {
variant: "primary",
size: "md"
}
}
)
31 changes: 31 additions & 0 deletions lp-code/src/components/ui/card/Card.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { render, screen } from "@testing-library/react"
import { describe, it, expect } from "vitest"
import { Card } from "./Card"

describe("Card component", () => {

/**
* Verifica se o Card renderiza corretamente
*/
it("renders card component", () => {
render(<Card>Content</Card>)

const card = screen.getByText("Content")

expect(card).toBeInTheDocument()
})

/**
* Verifica se children são renderizados
*/
it("renders children correctly", () => {
render(
<Card>
<p>Card text</p>
</Card>
)

expect(screen.getByText("Card text")).toBeInTheDocument()
})

})
51 changes: 51 additions & 0 deletions lp-code/src/components/ui/card/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { ReactNode } from "react"
import { cardVariants, type CardVariants } from "./card.variants"
import { cn } from "../../../lib/cn"

type Props = CardVariants & {
icon?: ReactNode
title?: string
description?: string
children?: ReactNode
className?: string
}
/**
* Card Component
*
* Componente estruturado para exibição de conteúdo
* com suporte a:
* - icon opcional
* - title
* - description
* - children customizados
*
* Mantém separação entre estrutura e estilo (via cardVariants).
*/
export function Card({
icon,
title,
description,
children,
className,
variant,
}: Props) {
return (
<div className={cn(cardVariants({ variant }), className)}>
{icon && <div className="mb-4">{icon}</div>}

{title && (
<h3 className="text-xl font-semibold mb-2">
{title}
</h3>
)}

{description && (
<p className="text-muted-foreground mb-4">
{description}
</p>
)}

{children}
</div>
)
}
Loading