revamp template

This commit is contained in:
fro1991
2025-02-01 16:44:51 +07:00
parent c432df568a
commit 276a289580
1212 changed files with 112762 additions and 0 deletions

8
src/hooks/index.ts Normal file
View File

@ -0,0 +1,8 @@
export * from './useIsMounted';
export * from './useMatchPath';
export * from './useMediaQuery';
export * from './useResponsive';
export * from './useScrollPosition';
export * from './useViewport';
export * from './useBodyClasses';
export * from './useCallApi';

2
src/hooks/types.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
export type TResponsiveBreakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | number;
export type TResponsiveQuery = 'up' | 'down' | 'between';

View File

@ -0,0 +1,22 @@
import { useEffect } from 'react';
const useBodyClasses = (classNames: string) => {
useEffect(() => {
// Split classNames by spaces, including multi-line support
const classes = classNames.split(/\s+/).filter(Boolean); // Filter out empty strings
// Add each class to the body element when the component mounts
classes.forEach((className) => {
document.body.classList.add(className);
});
// Cleanup function to remove classes when the component unmounts
return () => {
classes.forEach((className) => {
document.body.classList.remove(className);
});
};
}, [classNames]); // Re-run the effect if classNames changes
};
export default useBodyClasses;

91
src/hooks/useCallApi.ts Normal file
View File

@ -0,0 +1,91 @@
import { useCallback } from 'react';
import axios from 'axios';
import { jsonToQueryParams } from '@/utils';
const useCallApi = () => {
const GetExportData = useCallback(async (url: string, param: any, fileName: string) => {
try {
url = `${url}?` + jsonToQueryParams(param);
const currentDate = new Date().toISOString().split('T')[0];
fileName = `${fileName}${currentDate}.xlsx`;
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
return true;
} catch (error: any) {
const { response } = error;
return {
status: false,
message: response?.data?.error ?? response?.data?.message ?? 'Unknown error',
data: null
};
}
}, []);
const GetData = useCallback(async (url: string, field: any) => {
try {
const response = await axios.get(url, { params: field });
const result = response.data;
if (result.status) {
return { status: true, message: 'Success fetch data', data: result.data };
}
} catch (error: any) {
const { response } = error;
return { status: false, message: response.data.error ?? response.data.message, data: null };
}
}, []);
const PostData = useCallback(async (url: string, field: any) => {
try {
const response = await axios.post(url, field);
const result = response.data;
if (result.status) {
return { status: true, message: result.data };
}
} catch (error: any) {
const { response } = error;
return { status: false, message: response.data.error ?? response.data.message };
}
}, []);
const PutData = useCallback(async (url: string, field: any) => {
try {
const response = await axios.put(url, field);
const result = response.data;
if (result.status) {
return { status: true, message: result.data };
}
} catch (error: any) {
const { response } = error;
return { status: false, message: response.data.error ?? response.data.message };
}
}, []);
const DeleteData = useCallback(async (url: string, field: any) => {
try {
const response = await axios.delete(url, field);
const result = response.data;
if (result.status) {
return { status: true, message: result.data };
}
} catch (error: any) {
const { response } = error;
return { status: false, message: response.data.error ?? response.data.message };
}
}, []);
return { GetData, PostData, PutData, DeleteData, GetExportData };
};
export { useCallApi };

18
src/hooks/useIsMounted.ts Normal file
View File

@ -0,0 +1,18 @@
// see: https://usehooks-ts.com/react-hook/use-is-mounted
import { useCallback, useEffect, useRef } from 'react';
const useIsMounted = () => {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return useCallback(() => isMounted.current, []);
};
export { useIsMounted };

24
src/hooks/useMatchPath.ts Normal file
View File

@ -0,0 +1,24 @@
import { matchPath, useLocation } from 'react-router-dom';
interface IUseMatchPath {
match: boolean;
isExternal: boolean;
}
const useMatchPath = (path: string, mode = 'default'): IUseMatchPath => {
const { pathname } = useLocation();
let match: boolean = false;
if (mode === 'default' && matchPath({ path, end: true }, pathname)) {
match = true;
} else if (mode === 'full' && matchPath({ path, end: false }, pathname)) {
match = true;
}
return {
match,
isExternal: path.startsWith('http') || path.startsWith('//')
};
};
export { useMatchPath };

View File

@ -0,0 +1,34 @@
import { useEffect, useState } from 'react';
const getMatches = (query: string): boolean => {
// Prevents SSR issues
if (typeof window !== 'undefined') {
return window.matchMedia(query).matches;
}
return false;
};
const useMediaQuery = (query: string): boolean => {
const [matches, setMatches] = useState<boolean>(getMatches(query));
useEffect(() => {
function handleChange() {
setMatches(getMatches(query));
}
const matchMedia = window.matchMedia(query);
// Triggered at the first client-side load and if query changes
handleChange();
matchMedia.addEventListener('change', handleChange);
return () => {
matchMedia.removeEventListener('change', handleChange);
};
}, [query]);
return matches;
};
export { useMediaQuery };

View File

@ -0,0 +1,38 @@
/* eslint-disable react-hooks/rules-of-hooks */
import tailwindConfig from 'tailwindcss/defaultConfig';
import { useMediaQuery } from './useMediaQuery';
export type TResponsiveBreakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | number;
export type TResponsiveQuery = 'up' | 'down' | 'between';
const breakpoints: TResponsiveBreakpoint[] = ['sm', 'md', 'lg', 'xl', '2xl'];
const useResponsive = (
query: TResponsiveQuery,
key?: TResponsiveBreakpoint,
start?: TResponsiveBreakpoint,
end?: TResponsiveBreakpoint
) => {
const screens = tailwindConfig?.theme?.screens as Record<string, TResponsiveBreakpoint>;
if (query === 'up' && key) {
key = breakpoints.includes(key) && screens ? screens[key] : key;
return useMediaQuery(`(min-width: ${key})`);
}
if (query === 'down' && key) {
key = breakpoints.includes(key) && screens ? screens[key] : key;
return useMediaQuery(`(max-width: ${key})`);
}
if (query === 'between' && start && end) {
start = breakpoints.includes(start) && screens ? screens[start] : start;
end = breakpoints.includes(end) && screens ? screens[end] : end;
return useMediaQuery(`(min-width: ${start}) and (max-width: ${end})`);
}
};
export { useResponsive };

View File

@ -0,0 +1,23 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { useResponsive, TResponsiveBreakpoint } from '.';
export default function useResponsiveProp(prop: any, defaultProp: any = null) {
let value = prop;
if (prop) {
for (const condition in prop) {
const breakpoint = prop[condition] as TResponsiveBreakpoint;
if (condition === 'up' && useResponsive('up', breakpoint)) {
value = prop[condition][breakpoint];
} else if (condition === 'down' && useResponsive('down', breakpoint)) {
value = prop[condition][breakpoint];
}
}
}
value = value ?? defaultProp;
return value;
}
export { useResponsiveProp };

View File

@ -0,0 +1,34 @@
import { useEffect, useState, RefObject } from 'react';
interface IUseScrollPositionProps {
targetRef?: RefObject<HTMLElement | Document | undefined>; // Ref to the scrollable element
}
const useScrollPosition = ({ targetRef }: IUseScrollPositionProps = {}): number => {
const [scrollPosition, setScrollPosition] = useState<number>(0);
useEffect(() => {
// If the ref is not provided or its current value is null, fall back to document
const target = targetRef?.current || document;
const scrollable = target === document ? window : target;
const updatePosition = () => {
// Determine if we're scrolling the document or a specific element
const scrollY = target === document ? window.scrollY : (target as HTMLElement).scrollTop;
setScrollPosition(scrollY);
};
scrollable.addEventListener('scroll', updatePosition);
// Set the initial position
updatePosition();
return () => {
scrollable.removeEventListener('scroll', updatePosition);
};
}, [targetRef]);
return scrollPosition;
};
export { useScrollPosition };

26
src/hooks/useViewport.ts Normal file
View File

@ -0,0 +1,26 @@
import { useEffect, useState } from 'react';
type TUseViewport = [number, number];
const useViewport = (): TUseViewport => {
const [dimensions, setDimensions] = useState<TUseViewport>([
window.innerHeight,
window.innerWidth
]);
useEffect(() => {
const handleResize = (): void => {
setDimensions([window.innerHeight, window.innerWidth]);
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return dimensions;
};
export { useViewport };