diff --git a/lp-code/package-lock.json b/lp-code/package-lock.json index f4ee3fb..b104816 100644 --- a/lp-code/package-lock.json +++ b/lp-code/package-lock.json @@ -10,14 +10,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", @@ -1915,6 +1920,20 @@ } } }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -2668,6 +2687,27 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3241,6 +3281,33 @@ "dev": true, "license": "ISC" }, + "node_modules/framer-motion": { + "version": "12.35.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.35.1.tgz", + "integrity": "sha512-rL8cLrjYZNShZqKV3U0Qj6Y5WDiZXYEM5giiTLfEqsIZxtspzMDCkKmrO5po76jWfvOg04+Vk+sfBvTD0iMmLw==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.35.1", + "motion-utils": "^12.29.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3929,6 +3996,21 @@ "node": "*" } }, + "node_modules/motion-dom": { + "version": "12.35.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.35.1.tgz", + "integrity": "sha512-7n6r7TtNOsH2UFSAXzTkfzOeO5616v9B178qBIjmu/WgEyJK0uqwytCEhwKBTuM/HJA40ptAw7hLFpxtPAMRZQ==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.29.2" + } + }, + "node_modules/motion-utils": { + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", + "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4192,6 +4274,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -4415,8 +4498,6 @@ "node": ">=8" } }, -<<<<<<< Updated upstream -======= "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -4434,7 +4515,6 @@ "url": "https://github.com/sponsors/dcastil" } }, ->>>>>>> Stashed changes "node_modules/tailwindcss": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", @@ -4556,6 +4636,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/lp-code/package.json b/lp-code/package.json index 4eb4a18..93b5972 100644 --- a/lp-code/package.json +++ b/lp-code/package.json @@ -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", diff --git a/lp-code/src/components/ui/button/Button.test.tsx b/lp-code/src/components/ui/button/Button.test.tsx new file mode 100644 index 0000000..63f9596 --- /dev/null +++ b/lp-code/src/components/ui/button/Button.test.tsx @@ -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() + + 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() + + 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() + + 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() + + const btn = screen.getByRole("button") + + await user.click(btn) + + expect(handleClick).toHaveBeenCalledTimes(1) + }) + +}) \ No newline at end of file diff --git a/lp-code/src/components/ui/button/Button.tsx b/lp-code/src/components/ui/button/Button.tsx new file mode 100644 index 0000000..c48404e --- /dev/null +++ b/lp-code/src/components/ui/button/Button.tsx @@ -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 & + VariantProps +/** + * 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( + ({ className, variant, size, ...props }, ref) => { + return ( +