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 |
No files matched your search
@@ -43,3 +43,5 @@ Thumbs.db
|
|||||||
|
|
||||||
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;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<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/index.css" />
|
<link rel="stylesheet" href="/src/index.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -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 |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="47" height="62" viewBox="0 0 47 62" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.5 38.4999L9.19156 38.4166L8.5 38.4999ZM23.5 42.4999L24.0479 42.9301L23.5 42.4999ZM1.45141 0.804946C1.06764 0.831723 0.778242 1.16453 0.805019 1.5483L1.24135 7.80212C1.26813 8.18588 1.60094 8.47528 1.9847 8.4485C2.36847 8.42173 2.65787 8.08892 2.63109 7.70515L2.24324 2.1462L7.80219 1.75835C8.18595 1.73157 8.47535 1.39876 8.44858 1.015C8.4218 0.631233 8.08899 0.341836 7.70523 0.368611L1.45141 0.804946ZM44.8219 58.1179C44.6435 57.777 44.2226 57.6453 43.8818 57.8237C43.5409 58.002 43.40Line truncated
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="79" height="88" viewBox="0 0 79 88" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M1.00004 87.8788C0.5147 87.8788 0.121257 87.4853 0.121261 87C0.121265 86.5147 0.514715 86.1212 1.00006 86.1212L1.00004 87.8788ZM43.4999 69.5L44.1213 70.1214L43.4999 69.5ZM74.1915 0.677276C74.6459 0.506859 75.1525 0.737104 75.3229 1.19154L78.1 8.59705C78.2704 9.05149 78.0401 9.55803 77.5857 9.72845C77.1313 9.89887 76.6247 9.66862 76.4543 9.21418L73.9858 2.6315L67.4031 5.10001C66.9487 5.27042 66.4421 5.04018 66.2717 4.58574C66.1013 4.1313 66.3316 3.62475 66.786 3.45434L74.1915 0.677276ZMLine truncated
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
<svg width="317" height="170" viewBox="0 0 317 170" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<rect width="317" height="169.613" fill="url(#pattern0_164_317)"/>
|
||||||
|
<defs>
|
||||||
|
<pattern id="pattern0_164_317" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||||
|
<use xlink:href="#image0_164_317" transform="scale(0.00184502 0.00344828)"/>
|
||||||
|
</pattern>
|
||||||
|
<image id="image0_164_317" width="542" height="290" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAh4AAAEiCAYAAAC2k9sBAAAABGdBTUEAALGPC/xhBQAACklpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAAEiJnVN3WJP3Fj7f92UPVkLY8LGXbIEAIiOsCMgQWaIQkgBhhBASQMWFiApWFBURnEhVxILVCkidiOKgKLhnQYqIWotVXDjuH9yntX167+3t+9f7vOec5/zOec8PgBESJpHmomoAOVKFPDrYH49PSMTJvYACFUjgBCAQ5svCZwXFAADwA3l4fnSwP/wBr28AAgBw1S4kEsfh/4O6UCZXACCRAOAiEucLAZBSAMguVMgUAMgYALBTs2QKAJQAAGx5fEIiAKoNAOz0ST4FANipk9wXANiiHKLine truncated
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 351 KiB |
@@ -1,9 +1,9 @@
|
|||||||
<svg width="138" height="52" viewBox="0 0 138 52" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg width="138" height="52" viewBox="0 0 138 52" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<rect width="137.505" height="51.7538" fill="url(#pattern0_410_578)"/>
|
<rect width="137.505" height="51.7538" fill="url(#pattern0_164_316)"/>
|
||||||
<defs>
|
<defs>
|
||||||
<pattern id="pattern0_410_578" patternContentUnits="objectBoundingBox" width="1" height="1">
|
<pattern id="pattern0_164_316" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||||
<use xlink:href="#image0_410_578" transform="matrix(0.000440723 0 0 0.00117096 -0.173204 -1.3185)"/>
|
<use xlink:href="#image0_164_316" transform="matrix(0.000440723 0 0 0.00117096 -0.173204 -1.3185)"/>
|
||||||
</pattern>
|
</pattern>
|
||||||
<image id="image0_410_578" width="3000" height="3000" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAC7gAAAu4CAYAAABxFfV5AAAACXBIWXMAAC4jAAAuIwF4pT92AAX6AElEQVR4nOzaMQEAIAzAsIF/z0MGPRIFFdCzuwMAAAAAAAAAAAAAAL/d3wEAAAAAAAAAAAAAADBjcAcAAAAAAAAAAAAAIMLgDgAAAAAAAAAAAABAgsEdAAAAAAAAAAAAAIAEgzsAAAAAAAAAAAAAAAkGdwAAAAAAAAAAAAAAEgzuAAAAAAAAAAAAAAAkGNwBAAAAAAAAAAAAAEgwuAMAAAAAAAAAAAAAkGBwBwAAAAAAAAAAAAAgweAOAAAAAAAAAAAAAECCwR0AAAAAAAAAAAAAgASDOwAAAAAAAAAAAAAACQZ3AAAALine truncated
|
<image id="image0_164_316" width="3000" height="3000" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAC7gAAAu4CAYAAABxFfV5AAAACXBIWXMAAC4jAAAuIwF4pT92AAX6AElEQVR4nOzaMQEAIAzAsIF/z0MGPRIFFdCzuwMAAAAAAAAAAAAAAL/d3wEAAAAAAAAAAAAAADBjcAcAAAAAAAAAAAAAIMLgDgAAAAAAAAAAAABAgsEdAAAAAAAAAAAAAIAEgzsAAAAAAAAAAAAAAAkGdwAAAAAAAAAAAAAAEgzuAAAAAAAAAAAAAAAkGNwBAAAAAAAAAAAAAEgwuAMAAAAAAAAAAAAAkGBwBwAAAAAAAAAAAAAgweAOAAAAAAAAAAAAAECCwR0AAAAAAAAAAAAAgASDOwAAAAAAAAAAAAAACQZ3AAAALine truncated
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 511 KiB After Width: | Height: | Size: 511 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 49 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="283" height="222" viewBox="0 0 283 222" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M41.2124 71.9365L30.3686 94.0759L1 115.312L8.2292 133.385L15.0066 149.65L26.3022 157.783L30.3686 179.471L35.7905 221.491L73.7438 218.328L104.92 214.713L124.8 221.491L181.278 218.328L192.574 210.195L205.225 214.713C213.207 214.864 229.894 215.075 232.786 214.713C235.678 214.352 241.22 219.081 243.63 221.491H263.962L276.613 196.64L281.583 166.368L272.999 154.169L263.962 143.325L254.926 133.385L235.949 127.963V121.185C236.702 116.065 238.298 105.1 238.66 102.209C239.021 99.3171 245.437 90Line truncated
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1017 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="304" height="200" viewBox="0 0 304 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M21.5132 38.0564L0.729248 148.302V175.411C13.531 178.725 40.038 185.351 43.6526 185.351C47.2672 185.351 66.8463 187.761 76.184 188.966L92.9015 167.278H112.33H134.469C137.632 167.278 151.639 173.152 160.675 175.411C169.712 177.67 181.911 183.544 188.688 188.966C195.466 194.388 199.984 193.032 205.858 197.551C210.557 201.165 224.985 199.057 231.612 197.551H258.721L284.023 188.966C288.542 183.243 297.578 170.893 297.578 167.278V139.717V105.378L303 64.7141L291.704 38.0564L278.602 32.6345L2Line truncated
|
||||||
|
</svg>
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
@@ -3,11 +3,6 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"main": "./index.js",
|
"main": "./index.js",
|
||||||
"types": "./index.d.ts",
|
"types": "./index.d.ts",
|
||||||
"devDependencies": {
|
|
||||||
"tailwindcss": "^4.0.13",
|
|
||||||
"postcss": "^8.5.3",
|
|
||||||
"@tailwindcss/postcss": "^4.0.13"
|
|
||||||
},
|
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"import": "./index.mjs",
|
"import": "./index.mjs",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
'@tailwindcss/postcss': {},
|
'@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,2 +1,4 @@
|
|||||||
import '../index.css';
|
|
||||||
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',
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import {
|
||||||
|
DetailedHTMLProps,
|
||||||
|
FC,
|
||||||
|
InputHTMLAttributes,
|
||||||
|
ReactElement,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { cn } from '@imphnen-frontend-service/utils';
|
||||||
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
|
import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
type TPasswordInputType = 'password';
|
||||||
|
type TPasswordInputSize = 'sm' | 'md' | 'lg';
|
||||||
|
|
||||||
|
type TPasswordInputProps = Omit<
|
||||||
|
DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
|
||||||
|
'size' | 'type'
|
||||||
|
> & {
|
||||||
|
type?: TPasswordInputType;
|
||||||
|
size?: TPasswordInputSize;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sizeClasses: Record<TPasswordInputSize, string> = {
|
||||||
|
sm: 'text-[10px] max-h-[28px]',
|
||||||
|
md: 'text-[12px] max-h-[30px]',
|
||||||
|
lg: 'text-[15px] max-h-[34px]',
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconSizeClasses: Record<TPasswordInputSize, string> = {
|
||||||
|
sm: 'text-[10px]',
|
||||||
|
md: 'text-[12px]',
|
||||||
|
lg: 'text-[16px]',
|
||||||
|
};
|
||||||
|
|
||||||
|
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 PasswordInput: FC<TPasswordInputProps> = ({
|
||||||
|
size = 'md',
|
||||||
|
placeholder = 'Placeholder',
|
||||||
|
disabled,
|
||||||
|
error,
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
}): ReactElement => {
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
|
const togglePasswordVisibility = () => {
|
||||||
|
if (!disabled) setShowPassword((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'relative inline-flex items-center border border-neutral-200 rounded-md',
|
||||||
|
mergedClassName
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="focus:outline-0"
|
||||||
|
type={showPassword ? 'text' : 'password'}
|
||||||
|
disabled={disabled}
|
||||||
|
placeholder={placeholder}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
<div className="absolute end-0 px-[12px] h-full flex items-center">
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
size={size}
|
||||||
|
onClick={togglePasswordVisibility}
|
||||||
|
className={cn(
|
||||||
|
'relative aspect-square -me-[10px] p-[6px]',
|
||||||
|
iconSizeClasses[size],
|
||||||
|
disabled && 'cursor-not-allowed'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{showPassword ? (
|
||||||
|
<EyeInvisibleOutlined
|
||||||
|
style={{
|
||||||
|
color: error
|
||||||
|
? 'var(--color-danger-500)'
|
||||||
|
: 'var(--color-neutral-500)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<EyeOutlined
|
||||||
|
style={{
|
||||||
|
color: error
|
||||||
|
? 'var(--color-danger-500)'
|
||||||
|
: 'var(--color-neutral-500)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{error && <p className="text-danger-500 text-xs mt-1">{error}</p>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./textarea";
|
||||||