Compare commits

..

6 Commits

Author SHA1 Message Date
59d53bb1e6 update 2025-06-18 15:34:55 +08:00
ba10b53377 bump 2025-05-14 23:50:50 +08:00
e8d95a6798 temp 2025-05-10 14:27:14 +08:00
bdf6243bd9 bump 2025-04-07 19:17:10 +08:00
6d52707ad3 temp 2025-04-06 23:22:16 +08:00
82cc4dab87 add provider 2025-04-06 01:47:31 +08:00
6 changed files with 194 additions and 9 deletions

View File

@@ -15,12 +15,13 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@mui/material": "^7.0.1", "@mui/material": "^7.1.1",
"re-resizable": "^6.11.2", "re-resizable": "^6.11.2",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"react-draggable": "^4.4.6", "react-draggable": "^4.4.6",
"react-hook-form": "^7.55.0" "react-hook-form": "^7.58.1",
"react-i18next": "^15.5.3"
}, },
"exports": { "exports": {
".": "./src/index.tsx", ".": "./src/index.tsx",
@@ -28,7 +29,7 @@
}, },
"devDependencies": { "devDependencies": {
"clsx": "^2.1.1", "clsx": "^2.1.1",
"tailwind-merge": "^3.1.0" "tailwind-merge": "^3.3.1"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@@ -1,7 +1,8 @@
import { useRef } from 'react'; import { useEffect, useRef, useState } from 'react';
import Draggable from 'react-draggable'; import Draggable from 'react-draggable';
import { clsxMerge } from '../clsx'; import { clsxMerge } from '../clsx';
import { Resizable } from 're-resizable'; import { Resizable } from 're-resizable';
import { X } from 'lucide-react';
type DragModalProps = { type DragModalProps = {
title?: React.ReactNode; title?: React.ReactNode;
@@ -10,6 +11,7 @@ type DragModalProps = {
containerClassName?: string; containerClassName?: string;
handleClassName?: string; handleClassName?: string;
contentClassName?: string; contentClassName?: string;
focus?: boolean;
/** /**
* 默认大小, 单位为px * 默认大小, 单位为px
* width: defaultSize.width || 320 * width: defaultSize.width || 320
@@ -28,7 +30,7 @@ export const DragModal = (props: DragModalProps) => {
<Draggable <Draggable
nodeRef={dragRef as any} nodeRef={dragRef as any}
onStop={(e, data) => { onStop={(e, data) => {
console.log(e, data); // console.log(e, data);
}} }}
handle='.handle' handle='.handle'
grid={[1, 1]} grid={[1, 1]}
@@ -39,7 +41,7 @@ export const DragModal = (props: DragModalProps) => {
y: 0, y: 0,
}}> }}>
<div <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} ref={dragRef}
style={props.style}> style={props.style}>
<div className={clsxMerge('handle cursor-move border-b border-gray-200 py-2 px-4', props.handleClassName)}>{props.title || 'Move'}</div> <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> </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
View 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;
};

View File

@@ -8,8 +8,9 @@ type TagsInputProps = {
placeholder?: string; placeholder?: string;
label?: any; label?: any;
showLabel?: boolean; 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); const [tags, setTags] = useState<string[]>(value);
useEffect(() => { useEffect(() => {
setTags(value); setTags(value);
@@ -21,7 +22,7 @@ export const TagsInput = ({ value, onChange, placeholder = '', label = '', showL
<Autocomplete <Autocomplete
multiple multiple
freeSolo freeSolo
options={[]} options={options || []}
value={tags} value={tags}
onChange={(event, newValue) => { onChange={(event, newValue) => {
// setTags(newValue as string[]); // setTags(newValue as string[]);

22
src/theme/Provider.tsx Normal file
View 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>
);
};

View File

@@ -38,5 +38,7 @@ type ToastLoginProps = {
* }); * });
*/ */
export const toastLogin = (props: ToastLoginProps = {}) => { export const toastLogin = (props: ToastLoginProps = {}) => {
toast.info(<LoginMessage {...props} />); toast.info(<LoginMessage {...props} />, {
autoClose: 5000 * 3,
});
}; };