Compare commits
6 Commits
967c2f94f2
...
main
Author | SHA1 | Date | |
---|---|---|---|
59d53bb1e6 | |||
ba10b53377 | |||
e8d95a6798 | |||
bdf6243bd9 | |||
6d52707ad3 | |||
82cc4dab87 |
@@ -15,12 +15,13 @@
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/material": "^7.0.1",
|
||||
"@mui/material": "^7.1.1",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-draggable": "^4.4.6",
|
||||
"react-hook-form": "^7.55.0"
|
||||
"react-hook-form": "^7.58.1",
|
||||
"react-i18next": "^15.5.3"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.tsx",
|
||||
@@ -28,7 +29,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"tailwind-merge": "^3.1.0"
|
||||
"tailwind-merge": "^3.3.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { useRef } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import Draggable from 'react-draggable';
|
||||
import { clsxMerge } from '../clsx';
|
||||
import { Resizable } from 're-resizable';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
type DragModalProps = {
|
||||
title?: React.ReactNode;
|
||||
@@ -10,6 +11,7 @@ type DragModalProps = {
|
||||
containerClassName?: string;
|
||||
handleClassName?: string;
|
||||
contentClassName?: string;
|
||||
focus?: boolean;
|
||||
/**
|
||||
* 默认大小, 单位为px
|
||||
* width: defaultSize.width || 320
|
||||
@@ -28,7 +30,7 @@ export const DragModal = (props: DragModalProps) => {
|
||||
<Draggable
|
||||
nodeRef={dragRef as any}
|
||||
onStop={(e, data) => {
|
||||
console.log(e, data);
|
||||
// console.log(e, data);
|
||||
}}
|
||||
handle='.handle'
|
||||
grid={[1, 1]}
|
||||
@@ -39,7 +41,7 @@ export const DragModal = (props: DragModalProps) => {
|
||||
y: 0,
|
||||
}}>
|
||||
<div
|
||||
className={clsxMerge('absolute top-0 left-0 bg-white rounded-md border border-gray-200 shadow-sm', props.containerClassName)}
|
||||
className={clsxMerge('absolute top-0 left-0 bg-white rounded-md border border-gray-200 shadow-sm', props.focus ? 'z-30' : '', props.containerClassName)}
|
||||
ref={dragRef}
|
||||
style={props.style}>
|
||||
<div className={clsxMerge('handle cursor-move border-b border-gray-200 py-2 px-4', props.handleClassName)}>{props.title || 'Move'}</div>
|
||||
@@ -63,3 +65,53 @@ export const DragModal = (props: DragModalProps) => {
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
|
||||
type DragModalTitleProps = {
|
||||
title?: React.ReactNode;
|
||||
className?: string;
|
||||
onClose?: () => void;
|
||||
children?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
};
|
||||
export const DragModalTitle = (props: DragModalTitleProps) => {
|
||||
return (
|
||||
<div
|
||||
className={clsxMerge('flex flex-row items-center justify-between', props.className)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
props.onClick?.();
|
||||
}}>
|
||||
<div className='text-sm font-medium text-gray-700'>
|
||||
{props.title}
|
||||
{props.children}
|
||||
</div>
|
||||
<div
|
||||
className='text-gray-500 cursor-pointer p-2 hover:bg-gray-100 rounded-md'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
props.onClose?.();
|
||||
}}>
|
||||
<X className='w-4 h-4 ' />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const getComputedHeight = () => {
|
||||
const height = window.innerHeight;
|
||||
const width = window.innerWidth;
|
||||
return { height, width };
|
||||
};
|
||||
|
||||
export const useComputedHeight = () => {
|
||||
const [computedHeight, setComputedHeight] = useState({
|
||||
height: 0,
|
||||
width: 0,
|
||||
});
|
||||
useEffect(() => {
|
||||
const height = window.innerHeight;
|
||||
const width = window.innerWidth;
|
||||
setComputedHeight({ height, width });
|
||||
}, []);
|
||||
return computedHeight;
|
||||
};
|
||||
|
107
src/router/index.tsx
Normal file
107
src/router/index.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
|
||||
// 路由上下文
|
||||
type RouterContextType = {
|
||||
pathname: string;
|
||||
navigate: (to: string) => void;
|
||||
};
|
||||
|
||||
export const RouterContext = createContext<RouterContextType | null>(null);
|
||||
|
||||
// 使用路由上下文的Hook
|
||||
export const useRouter = () => {
|
||||
const context = useContext(RouterContext);
|
||||
if (!context) {
|
||||
throw new Error('useRouter必须在RouterProvider内部使用');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
// 路由项定义
|
||||
export type RouterItem = {
|
||||
path: string;
|
||||
element: React.ReactNode;
|
||||
};
|
||||
|
||||
// Router组件 - 路由系统的根组件
|
||||
type RouterProps = {
|
||||
routes?: RouterItem[];
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
export const Route = ({ path, element }: RouteProps) => {
|
||||
return <>{element}</>;
|
||||
};
|
||||
export const Router = ({ routes, children }: RouterProps) => {
|
||||
const [pathname, setPathname] = useState(window.location.pathname);
|
||||
|
||||
// 导航方法
|
||||
const navigate = (to: string) => {
|
||||
window.history.pushState(null, '', to);
|
||||
setPathname(to);
|
||||
};
|
||||
|
||||
// 监听浏览器前进后退
|
||||
useEffect(() => {
|
||||
const handlePopState = () => {
|
||||
setPathname(window.location.pathname);
|
||||
};
|
||||
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
return () => window.removeEventListener('popstate', handlePopState);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<RouterContext.Provider value={{ pathname, navigate }}>
|
||||
<Routes>
|
||||
{routes?.map((route, index) => (
|
||||
<Route key={index} path={route.path} element={route.element} />
|
||||
))}
|
||||
{children}
|
||||
</Routes>
|
||||
</RouterContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// Route组件 - 定义单个路由
|
||||
type RouteProps = {
|
||||
path: string;
|
||||
element: React.ReactNode;
|
||||
};
|
||||
|
||||
// Routes组件 - 渲染匹配的路由
|
||||
type RoutesProps = {
|
||||
children: React.ReactNode | React.ReactNode[];
|
||||
};
|
||||
|
||||
export const Routes = ({ children }: RoutesProps) => {
|
||||
const { pathname } = useRouter();
|
||||
|
||||
// 将children转换为数组处理
|
||||
const childrenArray = React.Children.toArray(children);
|
||||
|
||||
// 查找匹配的路由
|
||||
const matchedRoute = childrenArray.find((child) => {
|
||||
if (React.isValidElement(child) && typeof child.type === 'function') {
|
||||
const routeProps = child.props as RouteProps;
|
||||
|
||||
// 简单路径匹配,可以扩展为支持参数等更复杂功能
|
||||
if (routeProps.path === pathname || (routeProps.path === '/' && pathname === '')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 支持通配符路由
|
||||
if (routeProps.path.endsWith('*') && pathname.startsWith(routeProps.path.slice(0, -1))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// 返回匹配的路由元素或空
|
||||
if (matchedRoute && React.isValidElement(matchedRoute)) {
|
||||
// @ts-ignore
|
||||
return <>{matchedRoute.props?.element}</>;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
@@ -8,8 +8,9 @@ type TagsInputProps = {
|
||||
placeholder?: string;
|
||||
label?: any;
|
||||
showLabel?: boolean;
|
||||
options?: string[];
|
||||
};
|
||||
export const TagsInput = ({ value, onChange, placeholder = '', label = '', showLabel = false }: TagsInputProps) => {
|
||||
export const TagsInput = ({ value, onChange, placeholder = '', label = '', showLabel = false, options = [] }: TagsInputProps) => {
|
||||
const [tags, setTags] = useState<string[]>(value);
|
||||
useEffect(() => {
|
||||
setTags(value);
|
||||
@@ -21,7 +22,7 @@ export const TagsInput = ({ value, onChange, placeholder = '', label = '', showL
|
||||
<Autocomplete
|
||||
multiple
|
||||
freeSolo
|
||||
options={[]}
|
||||
options={options || []}
|
||||
value={tags}
|
||||
onChange={(event, newValue) => {
|
||||
// setTags(newValue as string[]);
|
||||
|
22
src/theme/Provider.tsx
Normal file
22
src/theme/Provider.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import { CustomThemeProvider } from '.';
|
||||
|
||||
type ToastProviderProps = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
export const ToastProvider = ({ children }: ToastProviderProps) => {
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<ToastContainer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const Provider = ({ children }) => {
|
||||
return (
|
||||
<CustomThemeProvider>
|
||||
{children}
|
||||
<ToastProvider />
|
||||
</CustomThemeProvider>
|
||||
);
|
||||
};
|
@@ -38,5 +38,7 @@ type ToastLoginProps = {
|
||||
* });
|
||||
*/
|
||||
export const toastLogin = (props: ToastLoginProps = {}) => {
|
||||
toast.info(<LoginMessage {...props} />);
|
||||
toast.info(<LoginMessage {...props} />, {
|
||||
autoClose: 5000 * 3,
|
||||
});
|
||||
};
|
||||
|
Reference in New Issue
Block a user