Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ac1278a33 | ||
|
|
d20f0d89e5 | ||
|
|
515b78c57f | ||
|
|
8a53575cd1 | ||
|
|
c8b12db1b3 | ||
|
|
e3c4b7a23a | ||
|
|
41a0842026 | ||
|
|
a9164e5e5f | ||
|
|
d7e39095bb | ||
|
|
84ddf2b771 | ||
|
|
cb632adbb4 | ||
|
|
6a1fa42956 | ||
|
|
9002e6a261 | ||
|
|
254d4fe04a | ||
|
|
02e06caca1 | ||
|
|
00033b9c68 | ||
|
|
02863ebc8b | ||
|
|
a31166e3ff | ||
|
|
c7b74bc9b6 | ||
|
|
01621a8786 | ||
|
|
a07e3c21d6 | ||
|
|
fb99b41b3a | ||
|
|
183ca100dc | ||
|
|
203ee5de97 | ||
|
|
58843041db | ||
|
|
3964c22c66 | ||
|
|
ec02a4ef83 | ||
|
|
587cb9facf | ||
|
|
5c5faaf8a1 |
@@ -42,4 +42,6 @@ Thumbs.db
|
|||||||
.nx/workspace-data
|
.nx/workspace-data
|
||||||
|
|
||||||
vite.config.*.timestamp*
|
vite.config.*.timestamp*
|
||||||
vitest.config.*.timestamp*
|
vitest.config.*.timestamp*
|
||||||
|
|
||||||
|
storybook-static
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import nx from '@nx/eslint-plugin';
|
||||||
|
import baseConfig from '../../eslint.config.mjs';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
...baseConfig,
|
||||||
|
...nx.configs['flat/react'],
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
|
||||||
|
// Override or add rules here
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Backoffice</title>
|
||||||
|
<base href="/" />
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
|
<link rel="stylesheet" href="/src/styles.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
'@tailwindcss/postcss': {
|
||||||
|
base: join(import.meta.dirname, '../../'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "backoffice",
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "apps/backoffice/src",
|
||||||
|
"projectType": "application",
|
||||||
|
"tags": [],
|
||||||
|
"// targets": "to see all targets run: nx show project backoffice --web",
|
||||||
|
"targets": {}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,14 @@
|
|||||||
|
import { FC, ReactElement } from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { Navbar } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
|
||||||
|
export const AppLayout: FC = (): ReactElement => {
|
||||||
|
return (
|
||||||
|
<main className="bg-primary-50 min-h-screen">
|
||||||
|
<Navbar />
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppLayout;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { FC, ReactElement } from 'react';
|
||||||
|
|
||||||
|
export const Components: FC = (): ReactElement => {
|
||||||
|
return <>Hallo</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Components;
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
@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';
|
||||||
|
@source "../../../libs/ui/**/*.{ts,tsx}";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-primary-50: #f0f8ff;
|
||||||
|
--color-primary-100: #e1f0fd;
|
||||||
|
--color-primary-200: #bce1fb;
|
||||||
|
--color-primary-300: #81cbf8;
|
||||||
|
--color-primary-400: #3eb0f2;
|
||||||
|
--color-primary-500: #23a1eb;
|
||||||
|
--color-primary-600: #0877c1;
|
||||||
|
--color-primary-700: #085f9c;
|
||||||
|
--color-primary-800: #0b5181;
|
||||||
|
--color-primary-900: #0f446b;
|
||||||
|
--color-primary-950: #0a2b47;
|
||||||
|
|
||||||
|
--color-neutral-50: #f6f6f6;
|
||||||
|
--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-neutral-950: #2b2b2b;
|
||||||
|
|
||||||
|
--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: 3.833rem;
|
||||||
|
/* ~46px */
|
||||||
|
--text-h2: 3.083rem;
|
||||||
|
/* ~37px */
|
||||||
|
--text-h3: 2.417rem;
|
||||||
|
/* ~29px */
|
||||||
|
--text-p1: 1.917rem;
|
||||||
|
/* ~23px */
|
||||||
|
--text-p2: 1.583rem;
|
||||||
|
/* ~19px */
|
||||||
|
--text-p3: 1.25rem;
|
||||||
|
/* 15px */
|
||||||
|
--text-label1: 1rem;
|
||||||
|
/* 12px */
|
||||||
|
--text-label2: 0.833rem;
|
||||||
|
/* ~10px */
|
||||||
|
--text-label3: 0.677rem;
|
||||||
|
/* ~8px */
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import { middleware } from './middleware';
|
||||||
|
import { StrictMode } from 'react';
|
||||||
|
import { createBrowserRouter, RouteObject, RouterProvider } from 'react-router';
|
||||||
|
import {
|
||||||
|
add404PageToRoutesChildren,
|
||||||
|
addErrorElementToRoutes,
|
||||||
|
convertPagesToRoute,
|
||||||
|
QueryProvider,
|
||||||
|
} from '@imphnen-frontend-service/utils';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
const files = import.meta.glob('./app/**/*(page|layout).tsx');
|
||||||
|
const errorFiles = import.meta.glob('./app/**/*error.tsx');
|
||||||
|
const notFoundFiles = import.meta.glob('./app/**/*404.tsx');
|
||||||
|
const loadingFiles = import.meta.glob('./app/**/*loading.tsx');
|
||||||
|
|
||||||
|
const routes = convertPagesToRoute(files, loadingFiles) as RouteObject;
|
||||||
|
addErrorElementToRoutes(errorFiles, routes);
|
||||||
|
add404PageToRoutesChildren(notFoundFiles, routes);
|
||||||
|
|
||||||
|
const router = createBrowserRouter([
|
||||||
|
{
|
||||||
|
...routes,
|
||||||
|
loader: middleware,
|
||||||
|
shouldRevalidate: () => true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('root');
|
||||||
|
|
||||||
|
if (!rootElement) throw new Error('Failed to find the root element');
|
||||||
|
|
||||||
|
createRoot(rootElement).render(
|
||||||
|
<StrictMode>
|
||||||
|
<QueryProvider>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</QueryProvider>
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { LoaderFunctionArgs } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const middleware = async ({ request }: LoaderFunctionArgs) => {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
|
||||||
|
const pathname = url.pathname;
|
||||||
|
|
||||||
|
if (pathname) return null;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"types": [
|
||||||
|
"node",
|
||||||
|
"@nx/react/typings/cssmodule.d.ts",
|
||||||
|
"@nx/react/typings/image.d.ts",
|
||||||
|
"vite/client"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"src/**/*.spec.tsx",
|
||||||
|
"src/**/*.test.tsx",
|
||||||
|
"src/**/*.spec.js",
|
||||||
|
"src/**/*.test.js",
|
||||||
|
"src/**/*.spec.jsx",
|
||||||
|
"src/**/*.test.jsx",
|
||||||
|
"vite.config.ts",
|
||||||
|
"vite.config.mts",
|
||||||
|
"vitest.config.ts",
|
||||||
|
"vitest.config.mts"
|
||||||
|
],
|
||||||
|
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": false,
|
||||||
|
"esModuleInterop": false,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"types": ["vite/client", "vitest"]
|
||||||
|
},
|
||||||
|
"files": [],
|
||||||
|
"include": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.spec.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extends": "../../tsconfig.base.json"
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../../dist/out-tsc",
|
||||||
|
"types": [
|
||||||
|
"vitest/globals",
|
||||||
|
"vitest/importMeta",
|
||||||
|
"vite/client",
|
||||||
|
"node",
|
||||||
|
"vitest",
|
||||||
|
"@nx/react/typings/cssmodule.d.ts",
|
||||||
|
"@nx/react/typings/image.d.ts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"vite.config.ts",
|
||||||
|
"vite.config.mts",
|
||||||
|
"vitest.config.ts",
|
||||||
|
"vitest.config.mts",
|
||||||
|
"src/**/*.test.ts",
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.test.tsx",
|
||||||
|
"src/**/*.spec.tsx",
|
||||||
|
"src/**/*.test.js",
|
||||||
|
"src/**/*.spec.js",
|
||||||
|
"src/**/*.test.jsx",
|
||||||
|
"src/**/*.spec.jsx",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/// <reference types='vitest' />
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
|
||||||
|
|
||||||
|
export default defineConfig(() => ({
|
||||||
|
root: __dirname,
|
||||||
|
cacheDir: '../../node_modules/.vite/apps/backoffice',
|
||||||
|
server: {
|
||||||
|
port: 4200,
|
||||||
|
host: 'localhost',
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
port: 4300,
|
||||||
|
host: 'localhost',
|
||||||
|
},
|
||||||
|
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||||
|
// Uncomment this if you are using workers.
|
||||||
|
// worker: {
|
||||||
|
// plugins: [ nxViteTsPaths() ],
|
||||||
|
// },
|
||||||
|
build: {
|
||||||
|
outDir: '../../dist/apps/backoffice',
|
||||||
|
emptyOutDir: true,
|
||||||
|
reportCompressedSize: true,
|
||||||
|
commonjsOptions: {
|
||||||
|
transformMixedEsModules: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
watch: false,
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||||
|
reporters: ['default'],
|
||||||
|
coverage: {
|
||||||
|
reportsDirectory: '../../coverage/apps/backoffice',
|
||||||
|
provider: 'v8' as const,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -6,8 +6,8 @@
|
|||||||
<base href="/" />
|
<base href="/" />
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/logos/simple.svg" />
|
||||||
<link rel="stylesheet" href="/src/styles.css" />
|
<link rel="stylesheet" href="/src/index.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
'@tailwindcss/postcss': {},
|
'@tailwindcss/postcss': {
|
||||||
|
base: join(import.meta.dirname, '../../'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@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 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';
|
@import 'tailwindcss';
|
||||||
|
@source "../../../libs/ui/**/*.{ts,tsx}";
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-primary-50: #f0f8ff;
|
--color-primary-50: #f0f8ff;
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ export default defineConfig(() => ({
|
|||||||
root: __dirname,
|
root: __dirname,
|
||||||
cacheDir: '../../node_modules/.vite/apps/dimentorin',
|
cacheDir: '../../node_modules/.vite/apps/dimentorin',
|
||||||
server: {
|
server: {
|
||||||
port: 4200,
|
port: 3000,
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
port: 4300,
|
port: 3001,
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
},
|
},
|
||||||
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<base href="/" />
|
<base href="/" />
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/logos/simple.svg" />
|
||||||
<link rel="stylesheet" href="/src/styles.css" />
|
<link rel="stylesheet" href="/src/index.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
'@tailwindcss/postcss': {},
|
'@tailwindcss/postcss': {
|
||||||
|
base: join(import.meta.dirname, '../../'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 6.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 351 KiB |
|
After Width: | Height: | Size: 511 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 1017 B |
|
After Width: | Height: | Size: 941 B |
@@ -4,7 +4,7 @@ import { Navbar } from '@imphnen-frontend-service/ui/organisms';
|
|||||||
|
|
||||||
export const AppLayout: FC = (): ReactElement => {
|
export const AppLayout: FC = (): ReactElement => {
|
||||||
return (
|
return (
|
||||||
<main className="bg-primary-50 min-h-screen px-20 py-[60px]">
|
<main className="bg-primary-50 min-h-screen">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,7 +1,240 @@
|
|||||||
import { FC, ReactElement } from 'react';
|
import { ArrowDownOutlined } from '@ant-design/icons';
|
||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { ModalsGacha } from '@imphnen-frontend-service/ui/organisms';
|
||||||
|
import { cn } from '@imphnen-frontend-service/utils';
|
||||||
|
import { FC, Fragment, ReactElement, useEffect, useState } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
|
interface GachaItemProps {
|
||||||
|
src: string;
|
||||||
|
label: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const Components: FC = (): ReactElement => {
|
export const Components: FC = (): ReactElement => {
|
||||||
return <>Hallo</>;
|
// Pinjem
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
//
|
||||||
|
|
||||||
|
const scrollToRoulette = () => {
|
||||||
|
const rouletteSection = document.getElementById('roulette');
|
||||||
|
if (rouletteSection) {
|
||||||
|
rouletteSection.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const gachaPlaySection = document.getElementById('gacha-play');
|
||||||
|
if (gachaPlaySection) {
|
||||||
|
let currentIndex = 0;
|
||||||
|
const items = gachaPlaySection.children;
|
||||||
|
const totalItems = items.length;
|
||||||
|
|
||||||
|
const scrollItems = () => {
|
||||||
|
if (currentIndex >= totalItems) {
|
||||||
|
currentIndex = 0;
|
||||||
|
}
|
||||||
|
items[currentIndex].scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
inline: 'center',
|
||||||
|
});
|
||||||
|
currentIndex++;
|
||||||
|
};
|
||||||
|
|
||||||
|
const intervalId = setInterval(scrollItems, 2800);
|
||||||
|
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const GachaItem: FC<GachaItemProps> = ({
|
||||||
|
src,
|
||||||
|
label,
|
||||||
|
className,
|
||||||
|
}): ReactElement => {
|
||||||
|
return (
|
||||||
|
<div className="snap-center flex-auto justify-center items-center flex flex-col min-w-full">
|
||||||
|
<div className="max-h-[180px] md:max-h-[270px] md:max-w-[500px] mb-2 md:mb-5 flex flex-1 justify-center items-center">
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
alt=""
|
||||||
|
className={cn('h-[150px] md:h-[255px] object-contain', className)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="font-semibold text-center md:text-p2">{label}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{/* Landing Page */}
|
||||||
|
<section
|
||||||
|
id="landing-page"
|
||||||
|
className="my-12 md:my-24 grid grid-cols-4 md:grid-cols-8 lg:grid-cols-12 justify-items-center max-w-[1280px] items-center justify-center mx-[32px] md:mx-[60px] lg:mx-[80px] xl:mx-auto"
|
||||||
|
>
|
||||||
|
<div className="order-none lg:order-1 flex flex-col col-span-4 md:col-span-8 lg:col-span-4 md:w-full lg:w-fit items-center gap-7 md:gap-13 text-primary-500 mb-10 md:mb-20">
|
||||||
|
<img
|
||||||
|
src="/logos/logo.svg"
|
||||||
|
alt="IMPHNEN Logo"
|
||||||
|
className="w-[170px] md:w-[317px]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="font-semibold text-center">
|
||||||
|
<div className="text-p1 md:text-h2 lg:font-bold text-center">
|
||||||
|
<p>New Merchandise</p>
|
||||||
|
<p>Coming UP</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-base md:text-p2 mt-3">
|
||||||
|
Periode Gacha: 1 - 31 Maret 2025
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-center font-semibold text-p3 md:text-p1">
|
||||||
|
<p>Let's Go Checkout Our Merch &</p>
|
||||||
|
<p>Gacha Your Prize Here</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="bordered"
|
||||||
|
className="flex gap-3 px-3 md:px-5 py-2 md:py-4 bg-primary-100 border border-primary-500 border-dashed rounded md:rounded-md animate-bounce text-label2 md:text-p2"
|
||||||
|
onClick={scrollToRoulette}
|
||||||
|
>
|
||||||
|
<ArrowDownOutlined />
|
||||||
|
<span>Scroll For Gacha</span>
|
||||||
|
<ArrowDownOutlined />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="order-1 lg:order-none flex flex-col col-span-4 justify-center items-center mb-8 relative">
|
||||||
|
<div className="relative h-[150px] md:h-[230px]">
|
||||||
|
<img
|
||||||
|
src="/merch/1.png"
|
||||||
|
alt="Merch 1"
|
||||||
|
className="relative top-[3px] md:top-[6px] left-[4px] md:left-[7px] z-10 w-[165px] md:w-[266px]"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
className="absolute top-0 left-0 min-w-[174px] md:min-w-[280px] z-0"
|
||||||
|
src="/merch/Vector-1.svg"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
className="absolute hidden lg:block -bottom-[50px] -right-[50px]"
|
||||||
|
src="/landing-arrow-1.svg"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="px-3 py-1 text-primary-500 font-semibold text-base md:text-p2 bg-white shadow-md rounded">
|
||||||
|
Official Merch IMPHNEN
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 text-primary-500 font-bold text-p2 md:text-h3">
|
||||||
|
~ 175k ~
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="order-2 flex flex-col col-span-4 justify-center items-center mb-8 relative">
|
||||||
|
<div className="relative h-[130px] md:h-[230px]">
|
||||||
|
<img
|
||||||
|
src="/merch/2.png"
|
||||||
|
alt="Merch 2"
|
||||||
|
className="relative top-[5px] md:top-[8px] left-0 md:left-[5px] z-10 w-[165px] md:w-[277px]"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
className="absolute top-0 -left-[5px] md:-left-[8px] z-0 min-w-[174px] md:min-w-[302px]"
|
||||||
|
src="/merch/Vector-2.svg"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
className="absolute hidden lg:block -bottom-[100px] -left-[55px]"
|
||||||
|
src="/landing-arrow-2.svg"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="px-3 py-1 text-primary-500 font-semibold text-base md:text-p2 bg-white shadow-md rounded">
|
||||||
|
IMPHNEN Mini Merch
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 text-primary-500 font-bold text-p2 md:text-h3">
|
||||||
|
~ 90k ~
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{/* Roulette Page */}
|
||||||
|
<section
|
||||||
|
id="roulette"
|
||||||
|
className="mt-20 pb-50 lg:py-90 mx-[32px] md:mx-[60px] lg:mx-[80px] xl:mx-auto lg:max-w-[1280px] grid grid-cols-4 md:grid-cols-8 lg:grid-cols-12 justify-items-center gap-y-16 md:gap-y-28"
|
||||||
|
>
|
||||||
|
<div className="col-span-4 md:col-span-8 lg:col-span-6 text-primary-500 bg-white rounded-lg md:rounded-xl p-5 md:py-9 md:px-13 shadow md:max-w-[430px]">
|
||||||
|
<header className="mb-4">
|
||||||
|
<h3 className="font-semibold text-p3 md:text-h2">Gacha Roulette</h3>
|
||||||
|
</header>
|
||||||
|
<h4 className="font-semibold my-2 text-base md:text-p1">
|
||||||
|
How to participate
|
||||||
|
</h4>
|
||||||
|
<p className="text-base md:text-p2">
|
||||||
|
Kamu harus melakukan pembelian merch batch 2 di shopee IMPHNEN
|
||||||
|
</p>
|
||||||
|
<h4 className="font-semibold my-2 text-base md:text-p1">
|
||||||
|
How to gacha
|
||||||
|
</h4>
|
||||||
|
<ol className="text-base md:text-p2 list-decimal list-outside ml-4 md:ml-6">
|
||||||
|
<li>First, press the "Spin Now" button</li>
|
||||||
|
<li>
|
||||||
|
If the merch does not match you can reroll by paying IDR5,000
|
||||||
|
</li>
|
||||||
|
<li>If it is appropriate you can submit</li>
|
||||||
|
<li>Then fill in the data for your merch delivery process</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="roulette-spin"
|
||||||
|
className="col-span-4 md:col-span-8 lg:col-span-6 flex flex-col items-center gap-4 md:gap-8 overflow-x-hidden"
|
||||||
|
>
|
||||||
|
<div className="bg-white text-primary-500 font-medium text-p3 md:text-h3 shadow py-2 px-4 md:py-4 md:px-8 max-w-fit rounded-md md:rounded-lg">
|
||||||
|
Here Take Your Prize
|
||||||
|
</div>
|
||||||
|
<section
|
||||||
|
id="gacha-play"
|
||||||
|
className="flex flex-nowrap overflow-auto w-full gap-x-8 snap-x snap-mandatory"
|
||||||
|
>
|
||||||
|
<GachaItem
|
||||||
|
src="/gacha/certificate.png"
|
||||||
|
label="Sertifikat + Laminating"
|
||||||
|
/>
|
||||||
|
<GachaItem
|
||||||
|
src="/gacha/lanyard-id-card.png"
|
||||||
|
label="Lanyard + ID Card"
|
||||||
|
/>
|
||||||
|
<GachaItem src="/gacha/pin.png" label="Pin" />
|
||||||
|
<GachaItem src="/gacha/sticker.png" label="Sticker Isi 3" />
|
||||||
|
<GachaItem src="/gacha/sticker.png" label="Sticker Isi 5" />
|
||||||
|
<GachaItem
|
||||||
|
src="/gacha/gelang-karet.png"
|
||||||
|
label="Gelang Karet"
|
||||||
|
className="h-[86px] md:h-[160px]"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
<Button variant="secondary" size="md">
|
||||||
|
Spin Now
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{/* Diffuser & Cloud */}
|
||||||
|
<div className="sticky bottom-0 h-[86px] md:h-[200px] bg-gradient-to-b from-primary-500/0 to-primary-500/50 to-80%"></div>
|
||||||
|
|
||||||
|
{/**Izin pake untuk debug modals gacha */}
|
||||||
|
<div>
|
||||||
|
<p>Debug : Test Modals Gacha</p>
|
||||||
|
<Button onClick={
|
||||||
|
() => setShowModal(true)
|
||||||
|
}>Tekan</Button>
|
||||||
|
</div>
|
||||||
|
{showModal && createPortal(
|
||||||
|
<ModalsGacha></ModalsGacha>,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
|
{/**Izin pake untuk debug modals gacha */}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Components;
|
export default Components;
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
@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 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';
|
@import 'tailwindcss';
|
||||||
|
@source "../../../libs/ui/**/*.{ts,tsx}";
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-primary-50: #f0f8ff;
|
--color-primary-50: #f0f8ff;
|
||||||
--color-primary-100: #e1f0fd;
|
--color-primary-100: #e1f0fd;
|
||||||
--color-primary-200: #b8e1f8;
|
--color-primary-200: #bce1fb;
|
||||||
--color-primary-300: #a1d4f0;
|
--color-primary-300: #81cbf8;
|
||||||
--color-primary-400: #4aa4da;
|
--color-primary-400: #3eb0f2;
|
||||||
--color-primary-500: #1a8ce6;
|
--color-primary-500: #23a1eb;
|
||||||
--color-primary-600: #1673c7;
|
--color-primary-600: #0877c1;
|
||||||
--color-primary-700: #0f5ca5;
|
--color-primary-700: #085f9c;
|
||||||
--color-primary-800: #0a4780;
|
--color-primary-800: #0b5181;
|
||||||
--color-primary-900: #04305d;
|
--color-primary-900: #0f446b;
|
||||||
|
--color-primary-950: #0a2b47;
|
||||||
|
|
||||||
--color-neutral-50: #f7f7f7;
|
--color-neutral-50: #f6f6f6;
|
||||||
--color-neutral-100: #e7e7e7;
|
--color-neutral-100: #e7e7e7;
|
||||||
--color-neutral-200: #d0d0d0;
|
--color-neutral-200: #d0d0d0;
|
||||||
--color-neutral-300: #b7b7b7;
|
--color-neutral-300: #b7b7b7;
|
||||||
@@ -23,6 +25,7 @@
|
|||||||
--color-neutral-700: #4a4a4a;
|
--color-neutral-700: #4a4a4a;
|
||||||
--color-neutral-800: #3a3a3a;
|
--color-neutral-800: #3a3a3a;
|
||||||
--color-neutral-900: #2f2f2f;
|
--color-neutral-900: #2f2f2f;
|
||||||
|
--color-neutral-950: #2b2b2b;
|
||||||
|
|
||||||
--color-success-50: #e7f2ee;
|
--color-success-50: #e7f2ee;
|
||||||
--color-success-100: #cde6dd;
|
--color-success-100: #cde6dd;
|
||||||
@@ -79,16 +82,28 @@
|
|||||||
|
|
||||||
--font-bai-jamjuree: 'Bai Jamjuree', sans-serif;
|
--font-bai-jamjuree: 'Bai Jamjuree', sans-serif;
|
||||||
|
|
||||||
--text-h1: 3rem; /* ~48px */
|
--text-h1: 3.833rem;
|
||||||
--text-h2: 2.4rem; /* ~38.4px */
|
/* ~46px */
|
||||||
--text-h3: 1.92rem; /* ~30.72px */
|
--text-h2: 3.083rem;
|
||||||
--text-p1: 1.54rem; /* ~24.58px */
|
/* ~37px */
|
||||||
--text-p2: 1.23rem; /* ~19.68px */
|
--text-h3: 2.417rem;
|
||||||
--text-label1: 0.98rem; /* ~15.74px */
|
/* ~29px */
|
||||||
--text-label2: 0.78rem; /* ~12.59px */
|
--text-p1: 1.917rem;
|
||||||
|
/* ~23px */
|
||||||
|
--text-p2: 1.583rem;
|
||||||
|
/* ~19px */
|
||||||
|
--text-p3: 1.25rem;
|
||||||
|
/* 15px */
|
||||||
|
--text-label1: 1rem;
|
||||||
|
/* 12px */
|
||||||
|
--text-label2: 0.833rem;
|
||||||
|
/* ~10px */
|
||||||
|
--text-label3: 0.677rem;
|
||||||
|
/* ~8px */
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|
||||||
*,
|
*,
|
||||||
::after,
|
::after,
|
||||||
::before,
|
::before,
|
||||||
@@ -102,13 +117,16 @@
|
|||||||
width: 6px;
|
width: 6px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
background: var(--color-neutral-50);
|
background: var(--color-neutral-50);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #cccccc;
|
background: #cccccc;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--color-neutral-300);
|
background: var(--color-neutral-300);
|
||||||
}
|
}
|
||||||
@@ -116,5 +134,7 @@
|
|||||||
html {
|
html {
|
||||||
font-family: 'Bai Jamjuree', sans-serif;
|
font-family: 'Bai Jamjuree', sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
|
||||||
@@ -8,11 +8,11 @@ export default defineConfig(() => ({
|
|||||||
root: __dirname,
|
root: __dirname,
|
||||||
cacheDir: '../../node_modules/.vite/apps/gacha',
|
cacheDir: '../../node_modules/.vite/apps/gacha',
|
||||||
server: {
|
server: {
|
||||||
port: 4200,
|
port: 3000,
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
port: 4300,
|
port: 3001,
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
},
|
},
|
||||||
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])],
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type { StorybookConfig } from '@storybook/react-vite';
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
stories: ['../src/**/*.@(mdx|stories.@(js|jsx|ts|tsx))'],
|
||||||
|
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/react-vite',
|
||||||
|
options: {
|
||||||
|
builder: {
|
||||||
|
viteConfigPath: 'vite.config.ts',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
|
||||||
|
// To customize your Vite configuration you can use the viteFinal field.
|
||||||
|
// Check https://storybook.js.org/docs/react/builders/vite#configuration
|
||||||
|
// and https://nx.dev/recipes/storybook/custom-builder-configs
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Preview } from '@storybook/react';
|
||||||
|
import '../src/index.css';
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
parameters: {
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
'@tailwindcss/postcss': {},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { Button } from './button';
|
||||||
|
|
||||||
|
const meta: Meta<typeof Button> = {
|
||||||
|
title: 'Components/Button',
|
||||||
|
component: Button,
|
||||||
|
argTypes: {
|
||||||
|
variant: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['primary', 'secondary', 'success', 'danger'],
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['sm', 'md', 'lg'],
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
control: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof Button>;
|
||||||
|
|
||||||
|
export const Primary: Story = {
|
||||||
|
args: {
|
||||||
|
variant: 'primary',
|
||||||
|
size: 'md',
|
||||||
|
children: 'Primary Button',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Secondary: Story = {
|
||||||
|
args: {
|
||||||
|
variant: 'secondary',
|
||||||
|
size: 'md',
|
||||||
|
children: 'Secondary Button',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Text: Story = {
|
||||||
|
args: {
|
||||||
|
variant: 'text',
|
||||||
|
size: 'md',
|
||||||
|
children: 'Text Button',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Bordered: Story = {
|
||||||
|
args: {
|
||||||
|
variant: 'bordered',
|
||||||
|
size: 'md',
|
||||||
|
children: 'Bordered Button',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Success: Story = {
|
||||||
|
args: {
|
||||||
|
variant: 'success',
|
||||||
|
size: 'md',
|
||||||
|
children: 'Success Button',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Danger: Story = {
|
||||||
|
args: {
|
||||||
|
variant: 'danger',
|
||||||
|
size: 'md',
|
||||||
|
children: 'Danger Button',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Disabled: Story = {
|
||||||
|
args: {
|
||||||
|
variant: 'primary',
|
||||||
|
size: 'md',
|
||||||
|
disabled: true,
|
||||||
|
children: 'Disabled Button',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Large: Story = {
|
||||||
|
args: {
|
||||||
|
variant: 'primary',
|
||||||
|
size: 'lg',
|
||||||
|
children: 'Large Button',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Medium: Story = {
|
||||||
|
args: {
|
||||||
|
variant: 'primary',
|
||||||
|
size: 'md',
|
||||||
|
children: 'Medium Button',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Small: Story = {
|
||||||
|
args: {
|
||||||
|
variant: 'primary',
|
||||||
|
size: 'sm',
|
||||||
|
children: 'Small Button',
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1 +1,4 @@
|
|||||||
export * from './button';
|
export * from './button';
|
||||||
|
export * from './input';
|
||||||
|
export * from './password-input';
|
||||||
|
export * from './textarea';
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./input";
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { Input } from "./input";
|
||||||
|
|
||||||
|
describe("Test Input Component", () => {
|
||||||
|
it("renders the input with placeholder text", () => {
|
||||||
|
render(<Input placeholder="Placeholder" />);
|
||||||
|
expect(screen.getByPlaceholderText("Placeholder")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// it("renders the input with different sizes", () => {
|
||||||
|
// const sizeClasses = {
|
||||||
|
// sm: "text-[10px] max-h-[28px]",
|
||||||
|
// md: "text-[12px] max-h-[30px]",
|
||||||
|
// lg: "text-[15px] max-h-[34px]",
|
||||||
|
// };
|
||||||
|
// Object.entries(sizeClasses).forEach(([size, className]) => {
|
||||||
|
// render(<Input size={size} />);
|
||||||
|
// expect(screen.getByRole("textbox")).toHaveClass(className);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it("renders the input with different types", () => {
|
||||||
|
// const types = ["text", "password", "email", "number"];
|
||||||
|
// types.forEach((type) => {
|
||||||
|
// render(<Input type={type} />);
|
||||||
|
// const input = screen.getByRole("textbox");
|
||||||
|
// expect(input).toHaveAttribute("type", type);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
it("disables the input field when 'disabled' prop is set", () => {
|
||||||
|
render(<Input disabled />);
|
||||||
|
expect(screen.getByRole("textbox")).toBeDisabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { Input } from './input';
|
||||||
|
|
||||||
|
const meta: Meta<typeof Input> = {
|
||||||
|
title: 'Components/Input',
|
||||||
|
component: Input,
|
||||||
|
argTypes: {
|
||||||
|
type: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['text', 'email'],
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['sm', 'md', 'lg'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof Input>;
|
||||||
|
|
||||||
|
export const Large: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'lg',
|
||||||
|
placeholder: 'Placeholder',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Medium: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'md',
|
||||||
|
placeholder: 'Placeholder',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Small: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'sm',
|
||||||
|
placeholder: 'Placeholder',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TextInput: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'md',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EmailInput: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'md',
|
||||||
|
type: 'email',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Disabled: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'md',
|
||||||
|
type: 'text',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithError: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'md',
|
||||||
|
type: 'text',
|
||||||
|
error: 'This field is required',
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import {
|
||||||
|
DetailedHTMLProps,
|
||||||
|
FC,
|
||||||
|
InputHTMLAttributes,
|
||||||
|
ReactElement,
|
||||||
|
} from 'react';
|
||||||
|
import { cn } from '@imphnen-frontend-service/utils';
|
||||||
|
|
||||||
|
type TInputType = 'text' | 'email';
|
||||||
|
type TInputSize = 'sm' | 'md' | 'lg';
|
||||||
|
|
||||||
|
type TInputProps = Omit<
|
||||||
|
DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
|
||||||
|
'size' | 'type'
|
||||||
|
> & {
|
||||||
|
type?: TInputType;
|
||||||
|
size?: TInputSize;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sizeClasses: Record<TInputSize, string> = {
|
||||||
|
sm: 'text-[10px] max-h-[28px]',
|
||||||
|
md: 'text-[12px] max-h-[30px]',
|
||||||
|
lg: 'text-[15px] max-h-[34px]',
|
||||||
|
};
|
||||||
|
|
||||||
|
const disabledClass = 'opacity-50 hover:border-neutral-200 cursor-not-allowed';
|
||||||
|
const errorClass =
|
||||||
|
'border-danger-500 hover:border-danger-500 focus:outline-danger-500';
|
||||||
|
|
||||||
|
export const Input: FC<TInputProps> = ({
|
||||||
|
size = 'md',
|
||||||
|
type,
|
||||||
|
placeholder = 'Placeholder',
|
||||||
|
disabled,
|
||||||
|
error,
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
}): ReactElement => {
|
||||||
|
const mergedClassName = cn(
|
||||||
|
'px-[12px] py-[8px] text-neutral-800 placeholder:text-neutral-300 border border-neutral-200 hover:border-blue-300 focus:outline-1 focus:outline-blue-500 rounded-md font-bai-jamjuree',
|
||||||
|
sizeClasses[size],
|
||||||
|
disabled && disabledClass,
|
||||||
|
error && errorClass,
|
||||||
|
className
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
className={mergedClassName}
|
||||||
|
type={type}
|
||||||
|
disabled={disabled}
|
||||||
|
placeholder={placeholder}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
{error && <p className="text-danger-500 text-xs mt-1">{error}</p>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./password-input";
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { PasswordInput } from './password-input';
|
||||||
|
|
||||||
|
const meta: Meta<typeof PasswordInput> = {
|
||||||
|
title: 'Components/Password Input',
|
||||||
|
component: PasswordInput,
|
||||||
|
argTypes: {
|
||||||
|
size: {
|
||||||
|
control: 'select',
|
||||||
|
options: ['sm', 'md', 'lg'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof PasswordInput>;
|
||||||
|
|
||||||
|
export const Large: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'lg',
|
||||||
|
placeholder: 'Placeholder',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Medium: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'md',
|
||||||
|
placeholder: 'Placeholder',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Small: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'sm',
|
||||||
|
placeholder: 'Placeholder',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Input: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'md',
|
||||||
|
placeholder: 'Placeholder',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Disabled: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'md',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithError: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'md',
|
||||||
|
error: 'This field is required',
|
||||||
|
},
|
||||||
|
};
|
||||||