feat: primitives (Wordmark, Container, CTA) with tests
This commit is contained in:
1 parent
48a37395aa
commit
7d12e35600
9 files changed
+98
-2
No files matched your search
@@ -0,0 +1,16 @@
|
|||||||
|
type Variant = 'light' | 'dark';
|
||||||
|
const styles: Record<Variant, string> = {
|
||||||
|
light: 'bg-ink text-base hover:bg-ink/90',
|
||||||
|
dark: 'bg-gold-bright text-ink hover:bg-gold-bright/90',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function CTA({ href, label, variant = 'light', className = '' }: { href: string; label: string; variant?: Variant; className?: string }) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
className={`inline-block font-sans font-bold uppercase tracking-wider px-7 py-3.5 text-sm transition-colors ${styles[variant]} ${className}`}
|
||||||
|
>
|
||||||
|
{label} <span aria-hidden="true">→</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import type { ElementType, ReactNode } from 'react';
|
||||||
|
|
||||||
|
export function Container({ as: Tag = 'div', className = '', children }: { as?: ElementType; className?: string; children: ReactNode }) {
|
||||||
|
return <Tag className={`mx-auto w-full max-w-content px-6 md:px-12 ${className}`}>{children}</Tag>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { wordmark } from '@/lib/content';
|
||||||
|
|
||||||
|
type Size = 'sm' | 'md' | 'lg' | 'xl';
|
||||||
|
const sizeClass: Record<Size, string> = {
|
||||||
|
sm: 'text-xl',
|
||||||
|
md: 'text-2xl',
|
||||||
|
lg: 'text-3xl',
|
||||||
|
xl: 'text-5xl',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Wordmark({ size = 'lg', onDark = false }: { size?: Size; onDark?: boolean }) {
|
||||||
|
const { before, accent, after, dot } = wordmark.parts;
|
||||||
|
const baseColor = onDark ? 'text-cream' : 'text-ink';
|
||||||
|
const goldColor = onDark ? 'text-gold-bright' : 'text-gold';
|
||||||
|
return (
|
||||||
|
<span className={`font-serif italic font-bold tracking-tight leading-none ${sizeClass[size]} ${baseColor}`}>
|
||||||
|
{before}
|
||||||
|
<span data-accent="true" className={`not-italic font-black ${goldColor}`}>{accent}</span>
|
||||||
|
{after}
|
||||||
|
<span className={goldColor}>{dot}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { CTA } from '../CTA';
|
||||||
|
|
||||||
|
describe('<CTA />', () => {
|
||||||
|
it('renders the label with arrow', () => {
|
||||||
|
render(<CTA href="#contacto" label="Define tu siguiente movimiento" />);
|
||||||
|
expect(screen.getByRole('link', { name: /define tu siguiente movimiento/i })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('uses the href', () => {
|
||||||
|
render(<CTA href="#contacto" label="x" />);
|
||||||
|
expect(screen.getByRole('link').getAttribute('href')).toBe('#contacto');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { Wordmark } from '../Wordmark';
|
||||||
|
|
||||||
|
describe('<Wordmark />', () => {
|
||||||
|
it('renders the wordmark in lowercase', () => {
|
||||||
|
render(<Wordmark />);
|
||||||
|
const text = screen.getByText(/cre/).closest('span')?.textContent;
|
||||||
|
expect(text?.toLowerCase()).toBe('creeer.');
|
||||||
|
});
|
||||||
|
it('renders the accent "e" separately for gold styling', () => {
|
||||||
|
const { container } = render(<Wordmark />);
|
||||||
|
const accent = container.querySelector('[data-accent="true"]');
|
||||||
|
expect(accent).not.toBeNull();
|
||||||
|
expect(accent?.textContent).toBe('e');
|
||||||
|
});
|
||||||
|
it('never renders uppercase CREEER', () => {
|
||||||
|
const { container } = render(<Wordmark />);
|
||||||
|
expect(container.textContent).not.toMatch(/CREEER/);
|
||||||
|
expect(container.textContent).not.toMatch(/Creeer/);
|
||||||
|
});
|
||||||
|
});
|
||||||
+3
-1
@@ -6,7 +6,9 @@
|
|||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"framer-motion": "^11.18.2",
|
"framer-motion": "^11.18.2",
|
||||||
|
|||||||
+1
-1
@@ -22,5 +22,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules", "vitest.config.ts"]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineConfig } from 'vitest/config';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
globals: true,
|
||||||
|
setupFiles: ['./vitest.setup.ts'],
|
||||||
|
},
|
||||||
|
resolve: { alias: { '@': path.resolve(__dirname, './') } },
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import '@testing-library/jest-dom/vitest';
|
||||||
Reference in new issue
Block a user