feat: setup
This commit is contained in:
@@ -3,6 +3,11 @@
|
||||
"version": "0.0.1",
|
||||
"main": "./index.js",
|
||||
"types": "./index.d.ts",
|
||||
"devDependencies": {
|
||||
"tailwindcss": "^4.0.13",
|
||||
"postcss": "^8.5.3",
|
||||
"@tailwindcss/postcss": "^4.0.13"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./index.mjs",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
};
|
||||
@@ -1 +1,2 @@
|
||||
import '../index.css';
|
||||
export * from './button';
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Bai+Jamjuree:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700&display=swap');
|
||||
@import 'tailwindcss';
|
||||
|
||||
@theme {
|
||||
--color-primary-50: #f0f8ff;
|
||||
--color-primary-100: #e1f0fd;
|
||||
--color-primary-200: #b8e1f8;
|
||||
--color-primary-300: #a1d4f0;
|
||||
--color-primary-400: #4aa4da;
|
||||
--color-primary-500: #1a8ce6;
|
||||
--color-primary-600: #1673c7;
|
||||
--color-primary-700: #0f5ca5;
|
||||
--color-primary-800: #0a4780;
|
||||
--color-primary-900: #04305d;
|
||||
|
||||
--color-neutral-50: #f7f7f7;
|
||||
--color-neutral-100: #e7e7e7;
|
||||
--color-neutral-200: #d0d0d0;
|
||||
--color-neutral-300: #b7b7b7;
|
||||
--color-neutral-400: #9f9f9f;
|
||||
--color-neutral-500: #7e7e7e;
|
||||
--color-neutral-600: #5d5d5d;
|
||||
--color-neutral-700: #4a4a4a;
|
||||
--color-neutral-800: #3a3a3a;
|
||||
--color-neutral-900: #2f2f2f;
|
||||
|
||||
--color-success-50: #e7f2ee;
|
||||
--color-success-100: #cde6dd;
|
||||
--color-success-200: #9bccbc;
|
||||
--color-success-300: #78bdab;
|
||||
--color-success-400: #4ca88f;
|
||||
--color-success-500: #349078;
|
||||
--color-success-600: #2f7c66;
|
||||
--color-success-700: #26624f;
|
||||
--color-success-800: #1e4a3b;
|
||||
--color-success-900: #1b513b;
|
||||
|
||||
--color-info-50: #e7f4f5;
|
||||
--color-info-100: #cce9ea;
|
||||
--color-info-200: #99d3d5;
|
||||
--color-info-300: #78c4c7;
|
||||
--color-info-400: #3aa9ad;
|
||||
--color-info-500: #279599;
|
||||
--color-info-600: #207d82;
|
||||
--color-info-700: #1a666b;
|
||||
--color-info-800: #124c52;
|
||||
--color-info-900: #093d43;
|
||||
|
||||
--color-warning-50: #fffce6;
|
||||
--color-warning-100: #fff8cc;
|
||||
--color-warning-200: #fef39b;
|
||||
--color-warning-300: #fde967;
|
||||
--color-warning-400: #fbd93d;
|
||||
--color-warning-500: #f3c215;
|
||||
--color-warning-600: #d7a20f;
|
||||
--color-warning-700: #aa7e0c;
|
||||
--color-warning-800: #805c07;
|
||||
--color-warning-900: #665c00;
|
||||
|
||||
--color-danger-50: #ffe7e7;
|
||||
--color-danger-100: #ffcece;
|
||||
--color-danger-200: #ff9f9f;
|
||||
--color-danger-300: #ff6e6e;
|
||||
--color-danger-400: #fa4646;
|
||||
--color-danger-500: #ef1f1f;
|
||||
--color-danger-600: #d11515;
|
||||
--color-danger-700: #a51111;
|
||||
--color-danger-800: #7f0c0c;
|
||||
--color-danger-900: #5d2d2d;
|
||||
|
||||
--radius-none: 0px;
|
||||
--radius-sm: 2px;
|
||||
--radius-md: 4px;
|
||||
--radius-lg: 8px;
|
||||
--radius-xl: 12px;
|
||||
--radius-2xl: 16px;
|
||||
--radius-3xl: 24px;
|
||||
--radius-full: 50%;
|
||||
|
||||
--font-bai-jamjuree: 'Bai Jamjuree', sans-serif;
|
||||
|
||||
--text-h1: 3rem; /* ~48px */
|
||||
--text-h2: 2.4rem; /* ~38.4px */
|
||||
--text-h3: 1.92rem; /* ~30.72px */
|
||||
--text-p1: 1.54rem; /* ~24.58px */
|
||||
--text-p2: 1.23rem; /* ~19.68px */
|
||||
--text-label1: 0.98rem; /* ~15.74px */
|
||||
--text-label2: 0.78rem; /* ~12.59px */
|
||||
}
|
||||
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-neutral-200, currentColor);
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--color-neutral-50);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #cccccc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-neutral-300);
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: 'Bai Jamjuree', sans-serif;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import '../index.css';
|
||||
export {};
|
||||
@@ -1 +1,2 @@
|
||||
import '../index.css';
|
||||
export * from './navbar';
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
fireEvent,
|
||||
RenderResult,
|
||||
} from '@testing-library/react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { Navbar } from './navbar';
|
||||
|
||||
// Interface untuk hasil matchMedia
|
||||
interface MatchMediaResult {
|
||||
matches: boolean;
|
||||
media: string;
|
||||
onchange: null;
|
||||
addListener: ReturnType<typeof vi.fn>;
|
||||
removeListener: ReturnType<typeof vi.fn>;
|
||||
addEventListener: ReturnType<typeof vi.fn>;
|
||||
removeEventListener: ReturnType<typeof vi.fn>;
|
||||
dispatchEvent: ReturnType<typeof vi.fn>;
|
||||
}
|
||||
|
||||
// Fungsi untuk mock matchMedia dengan tipe yang tepat
|
||||
function mockMatchMedia(width: { matches: boolean }): void {
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: vi.fn().mockImplementation(
|
||||
(query: string): MatchMediaResult => ({
|
||||
matches: width.matches,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
})
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
describe('Navbar', () => {
|
||||
// Setup dan cleanup untuk setiap test
|
||||
beforeEach(() => {
|
||||
// Reset DOM sebelum setiap test
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Cleanup setelah setiap test
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
// Test yang sudah ada sebelumnya
|
||||
it('renders the logo', () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<Navbar />
|
||||
</BrowserRouter>
|
||||
);
|
||||
const logo: HTMLElement = screen.getByAltText(/IMPHNEN Logo/i);
|
||||
expect(logo).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the Home link', () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<Navbar />
|
||||
</BrowserRouter>
|
||||
);
|
||||
const homeLink: HTMLElement = screen.getByRole('link', { name: /home/i });
|
||||
expect(homeLink).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the Merch Gacha link', () => {
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<Navbar />
|
||||
</BrowserRouter>
|
||||
);
|
||||
const merchLink: HTMLElement = screen.getByRole('link', {
|
||||
name: /merch gacha/i,
|
||||
});
|
||||
expect(merchLink).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Test tambahan untuk responsifitas
|
||||
|
||||
// Untuk Desktop Screen
|
||||
it('displays horizontal menu on desktop screens', () => {
|
||||
// Set viewport untuk desktop (di atas 768px)
|
||||
mockMatchMedia({ matches: true });
|
||||
|
||||
// Setup
|
||||
global.innerWidth = 1024;
|
||||
global.dispatchEvent(new Event('resize'));
|
||||
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<Navbar />
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||
// Cek apakah menu horizontal ditampilkan
|
||||
const navItems: HTMLElement = screen.getByRole('list');
|
||||
expect(navItems).toHaveClass('md:flex');
|
||||
|
||||
// Hamburger menu tidak terlihat di desktop
|
||||
const hamburgerButton: HTMLElement = screen.getByRole('button');
|
||||
expect(hamburgerButton).toHaveClass('md:hidden');
|
||||
});
|
||||
|
||||
// Untuk Mobile Screen
|
||||
it('displays hamburger menu on mobile screens and can toggle dropdown', () => {
|
||||
// Set viewport untuk mobile (di bawah 768px)
|
||||
mockMatchMedia({ matches: false });
|
||||
|
||||
// Setup
|
||||
global.innerWidth = 375;
|
||||
global.dispatchEvent(new Event('resize'));
|
||||
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<Navbar />
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||
// Hamburger menu harus terlihat
|
||||
const hamburgerButton: HTMLElement = screen.getByRole('button');
|
||||
expect(hamburgerButton).toBeVisible();
|
||||
|
||||
// Klik hamburger menu untuk membuka dropdown
|
||||
fireEvent.click(hamburgerButton);
|
||||
|
||||
// Dropdown menu harus terlihat setelah diklik
|
||||
const dropdownMenu: HTMLElement[] = screen.getAllByRole('list');
|
||||
expect(dropdownMenu[1]).toBeVisible();
|
||||
|
||||
// Klik lagi untuk menutup dropdown
|
||||
fireEvent.click(hamburgerButton);
|
||||
|
||||
// Dropdown tidak lagi terlihat (karena state berubah)
|
||||
const updatedDropdownMenus: HTMLElement[] = screen.getAllByRole('list');
|
||||
expect(updatedDropdownMenus.length).toBe(1); // Hanya menu horizontal yang tersisa
|
||||
});
|
||||
|
||||
// Untuk Tablet Screen
|
||||
it('has correct height on tablet screens', () => {
|
||||
// Set viewport untuk tablet (antara mobile dan desktop)
|
||||
mockMatchMedia({ matches: true });
|
||||
|
||||
// Setup
|
||||
global.innerWidth = 768;
|
||||
global.dispatchEvent(new Event('resize'));
|
||||
|
||||
const { container }: RenderResult = render(
|
||||
<BrowserRouter>
|
||||
<Navbar />
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||
// Navbar harus memiliki kelas height untuk tablet
|
||||
const header: HTMLElement | null = container.querySelector('header');
|
||||
expect(header).not.toBeNull();
|
||||
if (header) {
|
||||
expect(header).toHaveClass('md:min-h-[60px]');
|
||||
expect(header).toHaveClass('md:max-h-[60px]');
|
||||
}
|
||||
});
|
||||
|
||||
// Untuk Max Width pada 4K Screen
|
||||
it('has max width constraint on large screens', () => {
|
||||
// Set viewport untuk layar besar
|
||||
mockMatchMedia({ matches: true });
|
||||
|
||||
// Setup
|
||||
global.innerWidth = 3840; // 4K resolution
|
||||
global.dispatchEvent(new Event('resize'));
|
||||
|
||||
const { container }: RenderResult = render(
|
||||
<BrowserRouter>
|
||||
<Navbar />
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||
// Navbar harus memiliki max-width dan posisi tengah
|
||||
const header: HTMLElement | null = container.querySelector('header');
|
||||
expect(header).not.toBeNull();
|
||||
if (header) {
|
||||
expect(header).toHaveClass('max-w-[1280px]');
|
||||
expect(header).toHaveClass('xl:mx-auto');
|
||||
}
|
||||
});
|
||||
|
||||
// Test tambahan untuk memeriksa peran dan aksesibilitas
|
||||
it('has correct ARIA role for navigation', () => {
|
||||
const { container }: RenderResult = render(
|
||||
<BrowserRouter>
|
||||
<Navbar />
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||
const header: HTMLElement | null = container.querySelector('header');
|
||||
expect(header).toHaveAttribute('role', 'navigation');
|
||||
});
|
||||
});
|
||||
@@ -5,12 +5,14 @@ import dts from 'vite-plugin-dts';
|
||||
import * as path from 'path';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
export default defineConfig(() => ({
|
||||
root: __dirname,
|
||||
cacheDir: '../../node_modules/.vite/libs/ui',
|
||||
plugins: [
|
||||
react(),
|
||||
tailwindcss(),
|
||||
nxViteTsPaths(),
|
||||
nxCopyAssetsPlugin(['*.md']),
|
||||
dts({
|
||||
|
||||
Reference in New Issue
Block a user