chore: initial commit

This commit is contained in:
Maulana Sodiqin
2025-03-13 02:03:13 +07:00
parent d86d77c154
commit 4ec41164f8
67 changed files with 6111 additions and 1259 deletions
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
+7
View File
@@ -0,0 +1,7 @@
# ui
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test ui` to execute the unit tests via [Vitest](https://vitest.dev/).
+12
View File
@@ -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: {},
},
];
+12
View File
@@ -0,0 +1,12 @@
{
"name": "@imphnen-frontend-service/ui",
"version": "0.0.1",
"main": "./index.js",
"types": "./index.d.ts",
"exports": {
".": {
"import": "./index.mjs",
"require": "./index.js"
}
}
}
+23
View File
@@ -0,0 +1,23 @@
{
"name": "ui",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/ui/src",
"projectType": "library",
"tags": [],
"targets": {
"nx-release-publish": {
"options": {
"packageRoot": "dist/{projectRoot}"
}
}
},
"release": {
"version": {
"generatorOptions": {
"packageRoot": "dist/{projectRoot}",
"currentVersionResolver": "git-tag",
"fallbackCurrentVersionResolver": "disk"
}
}
}
}
+48
View File
@@ -0,0 +1,48 @@
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Button } from './button';
describe('Test Button Component', () => {
it('renders the button with children text', () => {
render(<Button>Click Me</Button>);
expect(screen.getByText('Click Me')).toBeInTheDocument();
});
it('calls onClick when clicked', async () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click Me</Button>);
const user = userEvent.setup();
await user.click(screen.getByText('Click Me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('applies the correct variant class', () => {
render(<Button variant="danger">Delete</Button>);
const button = screen.getByText('Delete');
expect(button).toHaveClass('bg-danger-500');
expect(button).toHaveClass('hover:bg-danger-600');
expect(button).toHaveClass('text-white');
});
it("disables the button when 'disabled' prop is set", async () => {
const handleClick = vi.fn();
render(
<Button variant="primary" disabled onClick={handleClick}>
Disabled
</Button>
);
const user = userEvent.setup();
const button = screen.getByText('Disabled');
expect(button).toBeDisabled();
await user.click(button);
expect(handleClick).not.toHaveBeenCalled();
});
});
+67
View File
@@ -0,0 +1,67 @@
import {
FC,
ReactElement,
ButtonHTMLAttributes,
DetailedHTMLProps,
} from 'react';
import { cn } from '@imphnen-frontend-service/utils';
type TButtonVariant =
| 'primary'
| 'secondary'
| 'success'
| 'danger'
| 'text'
| 'bordered';
type TButtonSize = 'sm' | 'md' | 'lg';
type TButtonProps = DetailedHTMLProps<
ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
> & {
variant?: TButtonVariant;
size?: TButtonSize;
};
const variantClasses: Record<TButtonVariant, string> = {
primary: 'bg-primary-500 hover:bg-primary-600 text-white shadow-md',
secondary:
'bg-white hover:text-primary-600 hover:bg-gray-50 text-primary-500 shadow-md',
text: 'bg-transparent hover:text-primary-600 hover:bg-gray-50 text-primary-500',
bordered:
'border border-primary-500 hover:border-primary-600 bg-transparent hover:text-primary-600 hover:bg-gray-50 text-primary-500',
success: 'bg-success-500 hover:bg-success-600 text-white shadow-md',
danger: 'bg-danger-500 hover:bg-danger-600 text-white shadow-md',
};
const sizeClasses: Record<TButtonSize, string> = {
sm: 'text-[12px] max-h-[36px]',
md: 'text-[15px] max-h-[40px]',
lg: 'text-[19px] max-h-[44px]',
};
const disabledClass = 'opacity-50 cursor-not-allowed';
export const Button: FC<TButtonProps> = ({
variant = 'primary',
size = 'md',
disabled,
className,
children,
...rest
}): ReactElement => {
const mergedClassName = cn(
'inline-flex items-center justify-center font-[600] rounded-lg px-[16px] py-[10px]',
'transition-colors duration-200 cursor-pointer',
sizeClasses[size],
variantClasses[variant],
disabled && disabledClass,
className
);
return (
<button className={mergedClassName} disabled={disabled} {...rest}>
{children}
</button>
);
};
+1
View File
@@ -0,0 +1 @@
export * from './button';
+1
View File
@@ -0,0 +1 @@
export * from './button';
+1
View File
@@ -0,0 +1 @@
export * from './navbar';
+1
View File
@@ -0,0 +1 @@
export * from './navbar';
+74
View File
@@ -0,0 +1,74 @@
import { MenuOutlined } from '@ant-design/icons';
import { FC, ReactElement, useState } from 'react';
import { Link } from 'react-router-dom';
export const Navbar: FC = (): ReactElement => {
const [isDropdownOpen, setDropdownOpen] = useState(false);
return (
<header
className="bg-white shadow-sm rounded-lg min-h-[47px] max-h-[47px] md:min-h-[60px] md:max-h-[60px] lg:min-h-[71px] lg:max-h-[71px] flex justify-between w-full max-w-[1280px] xl:mx-auto sticky"
role="navigation"
>
<div className="flex w-full items-center justify-between px-6 py-3">
<div className="flex items-center">
<img
src="/logos/simple.svg"
alt="IMPHNEN Logo"
className="h-8 w-auto"
/>
</div>
<nav className="w-full flex justify-end">
<ul className="items-center gap-x-8 font-semibold hidden md:flex">
<li>
<Link
to="#"
className="text-primary-500 hover:text-primary-600 transition-colors"
>
Home
</Link>
</li>
<li>
<Link to="#" className="text-gray-600 transition-colors">
Merch Gacha
</Link>
</li>
</ul>
<button
className={`md:hidden duration-200 ${
isDropdownOpen ? 'transform rotate-90' : ''
}`}
onClick={() => setDropdownOpen(!isDropdownOpen)}
>
<MenuOutlined style={{ color: '#1a8ce6' }} />
</button>
<div className="relative">
{isDropdownOpen && (
<div className="absolute right-0 top-0 mt-5">
<ul className="mt-2 w-48 bg-white shadow-md border rounded-[16px] border-gray-200 px-5 py-3">
<li>
<Link
to="#"
className="block text-primary-500 hover:text-primary-600 transition-colors px-4 py-2 text-center font-semibold"
>
Home
</Link>
</li>
<li>
<Link
to="#"
className="block text-gray-600 transition-colors px-4 py-2 text-center font-semibold"
>
Merch Gacha
</Link>
</li>
</ul>
</div>
)}
</div>
</nav>
</div>
</header>
);
};
+21
View File
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"types": ["vite/client", "vitest", "@testing-library/jest-dom"]
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../tsconfig.base.json"
}
+35
View File
@@ -0,0 +1,35 @@
{
"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": [
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"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"
],
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
}
+28
View File
@@ -0,0 +1,28 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
]
},
"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"
]
}
+64
View File
@@ -0,0 +1,64 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import * as path from 'path';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../node_modules/.vite/libs/ui',
plugins: [
react(),
nxViteTsPaths(),
nxCopyAssetsPlugin(['*.md']),
dts({
entryRoot: 'src',
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}),
],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: '../../dist/libs/ui',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: [
'src/atoms/index.ts',
'src/molecules/index.ts',
'src/organisms/index.ts',
],
name: 'ui',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es' as const],
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react', 'react-dom', 'react/jsx-runtime'],
},
},
test: {
watch: false,
globals: true,
setupFiles: ['./vitest.setup.ts'],
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../../coverage/libs/ui',
provider: 'v8' as const,
},
},
}));
+1
View File
@@ -0,0 +1 @@
import '@testing-library/jest-dom';
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
+7
View File
@@ -0,0 +1,7 @@
# utils
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test utils` to execute the unit tests via [Vitest](https://vitest.dev/).
+12
View File
@@ -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: {},
},
];
+12
View File
@@ -0,0 +1,12 @@
{
"name": "@imphnen-frontend-service/utils",
"version": "0.0.1",
"main": "./index.js",
"types": "./index.d.ts",
"exports": {
".": {
"import": "./index.mjs",
"require": "./index.js"
}
}
}
+23
View File
@@ -0,0 +1,23 @@
{
"name": "utils",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/utils/src",
"projectType": "library",
"tags": [],
"targets": {
"nx-release-publish": {
"options": {
"packageRoot": "dist/{projectRoot}"
}
}
},
"release": {
"version": {
"generatorOptions": {
"packageRoot": "dist/{projectRoot}",
"currentVersionResolver": "git-tag",
"fallbackCurrentVersionResolver": "disk"
}
}
}
}
+7
View File
@@ -0,0 +1,7 @@
import axios, { AxiosRequestConfig } from 'axios';
const config: AxiosRequestConfig = {
baseURL: import.meta.env.VITE_API_URL,
};
export const api = axios.create(config);
+1
View File
@@ -0,0 +1 @@
export * from './api';
+4
View File
@@ -0,0 +1,4 @@
export * from './react-query';
export * from './react-router';
export * from './tailwind-merge';
export * from './axios';
+3
View File
@@ -0,0 +1,3 @@
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient();
+2
View File
@@ -0,0 +1,2 @@
export * from './client';
export * from './provider';
+11
View File
@@ -0,0 +1,11 @@
import { QueryClientProvider } from '@tanstack/react-query';
import { FC, PropsWithChildren, ReactElement } from 'react';
import { queryClient } from './client';
export const QueryProvider: FC<PropsWithChildren> = (props): ReactElement => {
return (
<QueryClientProvider client={queryClient}>
{props.children}
</QueryClientProvider>
);
};
@@ -0,0 +1,393 @@
import { lazy, LazyExoticComponent, ReactElement } from 'react';
import { ActionFunction, LoaderFunction, RouteObject } from 'react-router';
interface PageModuleExports {
default: () => ReactElement;
loader?: LoaderFunction;
action?: ActionFunction;
permissions?: Array<string>;
}
interface LoadingModuleExports {
default: () => ReactElement;
}
interface RouteHandle {
pageType: 'page' | 'layout';
}
interface ExtendedRouteObject extends Omit<RouteObject, 'handle' | 'children'> {
handle?: RouteHandle;
children?: ExtendedRouteObject[];
HydrateFallback?: React.ComponentType;
}
type PageModule = () => Promise<PageModuleExports>;
const separator = '\\';
export function convertPagesToRoute(
files: Record<string, () => Promise<unknown>>,
loadingFiles: Record<string, () => Promise<unknown>> = {}
): ExtendedRouteObject {
let routes: ExtendedRouteObject = { path: '/' };
Object.entries(files).forEach(([filePath, importer]) => {
const segments = getRouteSegmentsFromFilePath(filePath);
const page = lazy(importer as PageModule);
const loadingComponent = findMatchingLoadingComponent(
filePath,
loadingFiles
);
const route = createRoute({
PageComponent: page,
LoadingComponent: loadingComponent,
segments,
async action(args) {
const result = (await importer()) as PageModuleExports;
return 'action' in result ? result.action?.(args) : null;
},
async loader(args) {
const result = (await importer()) as PageModuleExports;
return 'loader' in result ? result.loader?.(args) : null;
},
});
routes = mergeRoutes(routes, route);
});
return routes;
}
function findMatchingLoadingComponent(
filePath: string,
loadingFiles: Record<string, () => Promise<unknown>>
) {
const loadingPath = filePath.replace(/(page|layout)\.tsx$/, 'loading.tsx');
const groupMatch = filePath.match(/\([^/]+\//);
const groupLoadingPath = groupMatch ? `/${groupMatch[0]}loading.tsx` : null;
const globalLoadingPath = './app/loading.tsx';
const loader =
loadingFiles[loadingPath] ||
(groupLoadingPath && loadingFiles[groupLoadingPath]) ||
loadingFiles[globalLoadingPath];
if (!loader) return undefined;
return lazy(loader as () => Promise<LoadingModuleExports>);
}
function mergeRoutes(
target: ExtendedRouteObject,
source: ExtendedRouteObject
): ExtendedRouteObject {
if (target.path !== source.path)
throw new Error(
`Paths do not match: "${target.path}" and "${source.path}"`
);
if (!target.children) {
target.children = [];
}
if (source.handle?.pageType === 'layout') {
if (!target.element) {
target.element = source.element;
target.HydrateFallback = source.HydrateFallback;
target.action = source.action;
target.loader = source.loader;
target.handle = source.handle;
target.errorElement = source.errorElement;
target.children = target.children ?? [];
} else if (target.handle?.pageType === 'page') {
target = swapTargetRouteAsIndexRouteAndUpdateWithRoute(target, source);
}
return target;
}
if (
source.handle?.pageType === 'page' &&
!target.children.some((child) => child.index)
) {
target.children.unshift({
index: true,
element: source.element,
HydrateFallback: source.HydrateFallback,
action: source.action,
loader: source.loader,
handle: source.handle,
});
return target;
}
if (
target.handle?.pageType === 'layout' &&
source.handle?.pageType === 'page'
) {
target = addRouteAsIndexRouteForTargetRoute(target, source);
return target;
}
if (source.children) {
target.children = target.children ?? [];
source.children.forEach((sourceChild) => {
const matchingChild = target.children?.find(
(targetChild) => targetChild.path === sourceChild.path
);
if (matchingChild) mergeRoutes(matchingChild, sourceChild);
else target.children?.push(sourceChild);
});
}
return target;
}
function swapTargetRouteAsIndexRouteAndUpdateWithRoute(
target: ExtendedRouteObject,
route: ExtendedRouteObject
): ExtendedRouteObject {
target.children = target.children ?? [];
target.children.push({
index: true,
element: target.element,
HydrateFallback: target.HydrateFallback,
action: target.action,
loader: target.loader,
handle: target.handle,
errorElement: target.errorElement,
});
target.element = route.element;
target.HydrateFallback = route.HydrateFallback;
target.action = route.action;
target.loader = route.loader;
target.handle = route.handle;
target.errorElement = route.errorElement;
return target;
}
function addRouteAsIndexRouteForTargetRoute(
target: ExtendedRouteObject,
route: ExtendedRouteObject
): ExtendedRouteObject {
target.children = target.children ?? [];
target.children.push({
index: true,
element: route.element,
HydrateFallback: route.HydrateFallback,
action: route.action,
loader: route.loader,
handle: route.handle,
errorElement: route.errorElement,
});
return target;
}
function createRoute(args: {
segments: string[];
PageComponent: LazyExoticComponent<() => ReactElement>;
LoadingComponent?: LazyExoticComponent<() => ReactElement>;
loader?: LoaderFunction;
action?: ActionFunction;
guard?: () => Promise<boolean>;
}): ExtendedRouteObject {
const [current, ...rest] = args.segments;
const [cleanPath, pageType] = current.split(separator);
const route: ExtendedRouteObject = { path: cleanPath };
if (pageType === 'page' || pageType === 'layout') {
route.element = <args.PageComponent />;
route.HydrateFallback =
args.LoadingComponent ?? (() => <div>Loading...</div>);
route.action = args.action;
route.loader = async (...props) => {
return args.loader?.(...props);
};
route.handle = { pageType: pageType as 'layout' | 'page' };
}
if (rest.length > 0) {
const nextSegment = rest[0].split(separator)[0];
if (nextSegment === 'update' || nextSegment === 'edit') {
return {
path: `${cleanPath}/${nextSegment}`,
element: <args.PageComponent />,
HydrateFallback: args.LoadingComponent ?? (() => <div>Loading...</div>),
action: args.action,
loader: args.loader,
handle: { pageType: pageType as 'layout' | 'page' },
};
}
const childRoute = createRoute({ ...args, segments: rest });
if (!route.children) {
route.children = [];
}
if (cleanPath.startsWith(':')) {
route.children.unshift(childRoute);
} else {
route.children.push(childRoute);
}
}
return route;
}
export function getRouteSegmentsFromFilePath(
filePath: string,
transformer = (segment: string, prevSegment: string) =>
`${prevSegment}${separator}${getFileNameWithoutExtension(segment)}`
): string[] {
const segments = filePath
.replace('/app', '')
.split('/')
.filter(
(segment) => !segment.startsWith('(index)') && !segment.startsWith('_')
)
.map((segment) => {
if (segment.startsWith('.')) return '/';
if (segment.startsWith('('))
return (
getParamFromSegment(segment).replace('(', '').replace(')', '') + '?'
);
if (segment.startsWith('[')) return getParamFromSegment(segment);
return segment;
});
return getRouteSegments(segments[0], segments, transformer);
}
function getFileNameWithoutExtension(file: string) {
return file.split('.')[0];
}
function getRouteSegments(
segment: string,
segments: string[],
transformer: (seg: string, prev: string) => string,
entries: string[] = [],
index = 0
): string[] {
if (index > segments.length)
throw new Error('Cannot exceed total number of segments');
if (index === segments.length - 1) {
entries.push(transformer(segment, String(entries.pop())));
return entries;
}
const nextIndex = index + 1;
if (!segment.startsWith(':')) entries.push(segment);
else entries.push(`${entries.pop()}/${segment}`);
return getRouteSegments(
segments[nextIndex],
segments,
transformer,
entries,
nextIndex
);
}
function getParamFromSegment(segment: string) {
if (segment.includes('...')) return '*';
return segment.replace('[', ':').replace(']', '');
}
export function addErrorElementToRoutes(
errorFiles: Record<string, () => Promise<unknown>>,
routes: RouteObject
) {
Object.entries(errorFiles).forEach(([filePath, importer]) => {
const segments = getRouteSegmentsFromFilePath(
filePath,
(_, prevSegment) => prevSegment
);
const ErrorBoundary = lazy(
importer as () => Promise<{ default: () => ReactElement }>
);
setRoute(segments, routes, (route) => {
route.errorElement = <ErrorBoundary />;
return route;
});
});
}
export function add404PageToRoutesChildren(
notFoundFiles: Record<string, () => Promise<unknown>>,
routes: RouteObject
) {
Object.entries(notFoundFiles).forEach(([filePath, importer]) => {
const segments = getRouteSegmentsFromFilePath(
filePath,
(_, prevSegment) => prevSegment
);
const NotFound = lazy(
importer as () => Promise<{ default: () => ReactElement }>
);
setRoute(segments, routes, (route) => {
if (route.children) {
set404NonPage(routes, <NotFound />);
route.children.push({ path: '*', element: <NotFound /> });
} else {
const tempRoute = Object.assign({}, route);
route.children = route.children ?? [];
route.children.push({
index: true,
element: tempRoute.element,
action: tempRoute.action,
loader: tempRoute.loader,
});
route.children.push({ path: '*', element: <NotFound /> });
delete route.element;
delete route.action;
delete route.loader;
}
return route;
});
});
}
function set404NonPage(routes: RouteObject, notFoundElement: ReactElement) {
if (
routes.path &&
routes.children?.length &&
!routes.path.includes('?') &&
!routes.path.includes('/') &&
!routes.children.some((child) => child.index)
) {
routes.children.push({
index: true,
element: notFoundElement,
});
}
routes.children?.forEach((route) => set404NonPage(route, notFoundElement));
}
function setRoute(
segments: string[],
route: RouteObject,
updater: (route: RouteObject) => RouteObject
): void {
let temp = route;
segments.forEach((_segment, i) => {
const isLastSegment = i === segments.length - 1;
if (isLastSegment) return (temp = updater(temp));
if (!isLastSegment) {
const nextSegment = segments[i + 1];
const index = temp.children?.findIndex(
(child) => child.path === nextSegment
);
if (typeof index !== 'number' || index === -1) {
const msg = `Segment ${nextSegment} does not exist among the children of route with path ${temp.path}`;
throw new Error(msg);
}
temp = temp.children?.[index] as RouteObject;
}
});
}
+1
View File
@@ -0,0 +1 @@
export * from './file-based-routing';
+6
View File
@@ -0,0 +1,6 @@
import { ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export const cn = (...inputs: ClassValue[]) => {
return twMerge(clsx(inputs));
};
+1
View File
@@ -0,0 +1 @@
export * from './cn';
+21
View File
@@ -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.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../tsconfig.base.json"
}
+35
View File
@@ -0,0 +1,35 @@
{
"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": [
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"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"
],
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
}
+28
View File
@@ -0,0 +1,28 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
]
},
"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"
]
}
+59
View File
@@ -0,0 +1,59 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import * as path from 'path';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../node_modules/.vite/libs/utils',
plugins: [
react(),
nxViteTsPaths(),
nxCopyAssetsPlugin(['*.md']),
dts({
entryRoot: 'src',
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
}),
],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: '../../dist/libs/utils',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'utils',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es' as const],
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react', 'react-dom', 'react/jsx-runtime'],
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../../coverage/libs/utils',
provider: 'v8' as const,
},
},
}));