commit 717730eed77d658900a9cb4993de2a7607a143b2 Author: xion Date: Thu Mar 27 19:36:35 2025 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..76add87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..bdf66ed --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "@kevisual/components", + "version": "0.0.1", + "description": "center components", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "files": [ + "src" + ], + "keywords": [], + "author": "abearxiong ", + "license": "MIT", + "type": "module", + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/material": "^6.4.7", + "react": "19.0.0", + "react-dom": "19.0.0", + "react-hook-form": "^7.54.2" + }, + "exports": { + ".": "./src/index.tsx", + "./*": "./src/*" + }, + "devDependencies": { + "clsx": "^2.1.1", + "tailwind-merge": "^3.0.2" + } +} \ No newline at end of file diff --git a/src/button/index.tsx b/src/button/index.tsx new file mode 100644 index 0000000..1819886 --- /dev/null +++ b/src/button/index.tsx @@ -0,0 +1,25 @@ +import MuiButton, { ButtonProps } from '@mui/material/Button'; + +export const Button = (props: ButtonProps) => { + return ; +}; + +export const IconButton = (props: ButtonProps) => { + const { variant = 'contained', color = 'primary', sx, children, ...rest } = props; + return ( + + {children} + + ); +}; + +export const IconButtonItem = (props: ButtonProps) => { + const { variant = 'contained', size = 'small', color = 'primary', sx, children, ...rest } = props; + return ( + + {/* */} + + {children} + + ); +}; diff --git a/src/card/CardBlank.tsx b/src/card/CardBlank.tsx new file mode 100644 index 0000000..40143f9 --- /dev/null +++ b/src/card/CardBlank.tsx @@ -0,0 +1,17 @@ +import clsx from 'clsx'; +import twMerge from 'tailwind-merge'; + +type CardBlankProps = { + number?: number; + className?: string; +}; +export const CardBlank = (props: CardBlankProps) => { + const { number = 4, className } = props; + return ( + <> + {new Array(number).fill(0).map((_, index) => { + return
; + })} + + ); +}; diff --git a/src/clsx/index.ts b/src/clsx/index.ts new file mode 100644 index 0000000..de32246 --- /dev/null +++ b/src/clsx/index.ts @@ -0,0 +1,7 @@ +import clsx, { ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export const clsxMerge = (...args: ClassValue[]) => { + return twMerge(clsx(...args)); +}; +export { clsx }; diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..4989af8 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,6 @@ +export * from './theme'; + +/** + * 输入组件, 使用theme的defaultProps + */ +export * from './input/TextField'; diff --git a/src/input/TextField.tsx b/src/input/TextField.tsx new file mode 100644 index 0000000..e2c946e --- /dev/null +++ b/src/input/TextField.tsx @@ -0,0 +1,23 @@ +import { TextField as MuiTextField, TextFieldProps, Tooltip } from '@mui/material'; +import { useTheme } from '../theme'; +import { HelpCircle } from 'lucide-react'; + +export const TextField = (props: TextFieldProps) => { + const theme = useTheme(); + const defaultProps = theme.components?.MuiTextField?.defaultProps; + return ; +}; + +export const TextFieldLabel = ({ children, tips, label }: { children?: React.ReactNode; tips?: string; label?: any }) => { + return ( +
+ {label} + {children} + {tips && ( + + + + )} +
+ ); +}; diff --git a/src/input/index.tsx b/src/input/index.tsx new file mode 100644 index 0000000..2d1fc81 --- /dev/null +++ b/src/input/index.tsx @@ -0,0 +1,47 @@ +import { FormControlLabel, TextField } from '@mui/material'; +import { useForm, Controller } from 'react-hook-form'; +export const InputControl = ({ name, value, onChange }: { name: string; value: string; onChange?: (value: string) => void }) => { + return ( + onChange?.(e.target.value)} + sx={{ + width: '100%', + marginBottom: '16px', + }} + /> + ); +}; +type FormProps = { + onSubmit?: (data: any) => void; + children?: React.ReactNode; +}; +export const FormDemo = (props: FormProps) => { + const { control, handleSubmit } = useForm(); + const { onSubmit = () => {}, children } = props; + + return ( +
+ ( + {error?.message}} + /> + )} + /> + + ); +}; diff --git a/src/modal/Confirm.tsx b/src/modal/Confirm.tsx new file mode 100644 index 0000000..92728ba --- /dev/null +++ b/src/modal/Confirm.tsx @@ -0,0 +1,99 @@ +import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button } from '@mui/material'; +import { useRef, useState } from 'react'; +import type { ModalFuncProps } from 'antd'; +export const Confirm = ({ + open, + onClose, + title, + content, + onConfirm, + okText = '确认', + cancelText = '取消', +}: { + open: boolean; + onClose: () => void; + title: string; + content: string; + onConfirm?: () => void; + okText?: string; + cancelText?: string; +}) => { + return ( + + + {title} + + + {content} + + + + + + + ); +}; + +type Fn = () => void; +export const useModal = () => { + const [open, setOpen] = useState(false); + const [title, setTitle] = useState(''); + const [content, setContent] = useState(''); + const fns = useRef<{ + onConfirm: Fn; + onCancel: Fn; + okText: string; + cancelText: string; + }>({ + onConfirm: () => {}, + onCancel: () => {}, + okText: '确认', + cancelText: '取消', + }); + const modal = { + confirm: (props: ModalFuncProps) => { + setOpen(true); + setTitle(props.title as string); + setContent(props.content as string); + fns.current.onConfirm = async () => { + const isClose = await props.onOk?.(); + if (!isClose) { + setOpen(false); + } + }; + fns.current.onCancel = async () => { + await props.onCancel?.(); + setOpen(false); + }; + fns.current.okText = props.okText as string; + fns.current.cancelText = props.cancelText as string; + }, + cancel: () => { + setOpen(false); + fns.current.onCancel(); + }, + }; + const contextHolder = ( + { + setOpen(false); + fns.current.onCancel(); + }} + title={title} + content={content} + onConfirm={fns.current.onConfirm} + /> + ); + return [modal, contextHolder] as [typeof modal, React.ReactNode]; +}; diff --git a/src/select/TagsInput.tsx b/src/select/TagsInput.tsx new file mode 100644 index 0000000..3151116 --- /dev/null +++ b/src/select/TagsInput.tsx @@ -0,0 +1,58 @@ +import { Fragment, useEffect, useState } from 'react'; +import Autocomplete from '@mui/material/Autocomplete'; +import { TextField, Chip } from '@mui/material'; + +type TagsInputProps = { + value: string[]; + onChange: (value: string[]) => void; + placeholder?: string; + label?: any; + showLabel?: boolean; +}; +export const TagsInput = ({ value, onChange, placeholder = '', label = '', showLabel = false }: TagsInputProps) => { + const [tags, setTags] = useState(value); + useEffect(() => { + setTags(value); + }, [value]); + const randomid = () => { + return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); + }; + return ( + { + // setTags(newValue as string[]); + onChange(newValue as string[]); + }} + sx={{ + width: '100%', + }} + renderTags={(value: string[], getTagProps) => { + const id = randomid(); + const com = value.map((option: string, index: number) => ( + + )); + return {com}; + }} + renderInput={(params) => } + /> + ); +}; diff --git a/src/select/index.tsx b/src/select/index.tsx new file mode 100644 index 0000000..cd73fe6 --- /dev/null +++ b/src/select/index.tsx @@ -0,0 +1,20 @@ +import { MenuItem, Select as MuiSelect, SelectProps as MuiSelectProps } from '@mui/material'; +import React from 'react'; + +type SelectProps = { + options?: { label: string; value: string }[]; +} & MuiSelectProps; + +export const Select = React.forwardRef((props: SelectProps, ref) => { + const { options, ...rest } = props; + console.log(props, 'props'); + return ( + + {options?.map((option) => ( + + {option.label} + + ))} + + ); +}); diff --git a/src/theme/index.tsx b/src/theme/index.tsx new file mode 100644 index 0000000..bea6af9 --- /dev/null +++ b/src/theme/index.tsx @@ -0,0 +1,226 @@ +import { createTheme, Shadows, ThemeOptions } from '@mui/material/styles'; +import { useTheme as useMuiTheme, Theme } from '@mui/material/styles'; +import { amber, red } from '@mui/material/colors'; +import { ThemeProvider } from '@mui/material/styles'; +const generateShadows = (color: string): Shadows => { + return [ + 'none', + `0px 2px 1px -1px ${color}`, + `0px 1px 1px 0px ${color}`, + `0px 1px 3px 0px ${color}`, + `0px 2px 4px -1px ${color}`, + + `0px 3px 5px -1px ${color}`, + `0px 3px 5px -1px ${color}`, + `0px 4px 5px -2px ${color}`, + `0px 5px 5px -3px ${color}`, + `0px 5px 6px -3px ${color}`, + + `0px 6px 6px -3px ${color}`, + `0px 6px 7px -4px ${color}`, + `0px 7px 8px -4px ${color}`, + `0px 7px 8px -4px ${color}`, + `0px 8px 9px -5px ${color}`, + + `0px 8px 9px -5px ${color}`, + `0px 9px 10px -5px ${color}`, + `0px 9px 11px -6px ${color}`, + `0px 10px 12px -6px ${color}`, + `0px 10px 13px -6px ${color}`, + + `0px 11px 13px -7px ${color}`, + `0px 11px 14px -7px ${color}`, + `0px 12px 15px -7px ${color}`, + `0px 12px 16px -8px ${color}`, + `0px 13px 17px -8px ${color}`, + ]; +}; +const primaryMain = amber[300]; // #ffc107 +const secondaryMain = amber[500]; // #ffa000 +export const themeOptions: ThemeOptions = { + // @ts-ignore + // cssVariables: true, + palette: { + primary: { + main: primaryMain, // amber[300] + }, + secondary: { + main: secondaryMain, // amber[500] + }, + divider: amber[200], + common: { + white: secondaryMain, + }, + text: { + primary: amber[600], + secondary: amber[600], + }, + background: { + default: '#ffffff', // 设置默认背景颜色 + // paper: '#f5f5f5', // 设置纸张背景颜色 + }, + error: { + main: red[500], // 设置错误颜色 "#f44336" + }, + }, + shadows: generateShadows('rgba(255, 193, 7, 0.2)'), + typography: { + // fontFamily: 'Roboto, sans-serif', + }, + components: { + MuiButtonBase: { + defaultProps: { + disableRipple: true, + }, + styleOverrides: { + root: { + '&:hover': { + backgroundColor: amber[100], + }, + '&.MuiButton-contained': { + color: '#ffffff', + ':hover': { + color: secondaryMain, + }, + }, + }, + }, + }, + MuiButtonGroup: { + styleOverrides: { + root: { + '& .MuiButton-root': { + borderColor: amber[600], + }, + }, + }, + }, + MuiTextField: { + defaultProps: { + fullWidth: true, + size: 'small', + slotProps: { + inputLabel: { + shrink: true, + }, + }, + }, + styleOverrides: { + root: { + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: amber[300], + }, + '&:hover fieldset': { + borderColor: amber[500], + }, + '& .MuiInputBase-input': { + color: amber[600], + }, + }, + '& .MuiInputLabel-root': { + color: amber[600], + }, + }, + }, + }, + MuiAutocomplete: { + defaultProps: { + size: 'small', + }, + }, + MuiSelect: { + styleOverrides: { + root: { + '& .MuiOutlinedInput-notchedOutline': { + borderColor: amber[300], + }, + '&:hover .MuiOutlinedInput-notchedOutline': { + borderColor: amber[500], + }, + '&.Mui-focused .MuiOutlinedInput-notchedOutline': { + borderColor: amber[500], + }, + '& .MuiSelect-icon': { + color: amber[500], // Set arrow icon color to primary + }, + }, + }, + }, + MuiCard: { + styleOverrides: { + root: { + // border: `1px solid ${amber[300]}`, + }, + }, + }, + MuiIconButton: { + styleOverrides: { + root: { + color: '#ffffff', // Set default font color to white + }, + }, + }, + MuiFormControlLabel: { + defaultProps: { + labelPlacement: 'top', + }, + styleOverrides: { + root: { + color: amber[600], + alignItems: 'flex-start', + '& .MuiFormControlLabel-label': { + textAlign: 'left', + width: '100%', + }, + '& .MuiFormControlLabel-root': { + width: '100%', + }, + '& .MuiInputBase-root': { + width: '100%', + }, + }, + }, + }, + MuiMenuItem: { + styleOverrides: { + root: { + '&.Mui-selected': { + backgroundColor: amber[500], + color: '#ffffff', + '&:hover': { + backgroundColor: amber[600], + color: '#ffffff', + }, + }, + }, + }, + }, + }, +}; + +/** + * https://bareynol.github.io/mui-theme-creator/ + */ +export const theme = createTheme(themeOptions); + +/** + * + * @returns + */ +export const useTheme = () => { + return useMuiTheme(); +}; + +/** + * 自定义主题设置。 + * @param param0 + * @returns + */ +export const CustomThemeProvider = ({ children, themeOptions: customThemeOptions }: { children: React.ReactNode; themeOptions?: ThemeOptions }) => { + const theme = createTheme(customThemeOptions || themeOptions); + return {children}; +}; + +// TODO: think +export const getComponentProps = () => {}; diff --git a/src/theme/wind-theme.css b/src/theme/wind-theme.css new file mode 100644 index 0000000..859b3d1 --- /dev/null +++ b/src/theme/wind-theme.css @@ -0,0 +1,76 @@ +@import 'tailwindcss'; + +@theme { + --color-primary: #ffc107; + --color-secondary: #ffa000; + --color-success: #28a745; + --color-scrollbar-thumb: #999999; + --color-scrollbar-track: rgba(0, 0, 0, 0.1); + --color-scrollbar-thumb-hover: #666666; + --scrollbar-color: #ffc107; /* 滚动条颜色 */ +} + +html, +body { + width: 100%; + height: 100%; + font-size: 16px; + font-family: 'Montserrat', sans-serif; +} +/* font-family */ +@utility font-family-mon { + font-family: 'Montserrat', sans-serif; +} +@utility font-family-rob { + font-family: 'Roboto', sans-serif; +} +@utility font-family-int { + font-family: 'Inter', sans-serif; +} +@utility font-family-orb { + font-family: 'Orbitron', sans-serif; +} +@utility font-family-din { + font-family: 'DIN', sans-serif; +} + +@utility flex-row-center { + @apply flex flex-row items-center justify-center; +} +@utility flex-col-center { + @apply flex flex-col items-center justify-center; +} + +@utility scrollbar { + overflow: auto; + /* 整个滚动条 */ + &::-webkit-scrollbar { + width: 3px; + height: 3px; + } + &::-webkit-scrollbar-track { + background-color: var(--color-scrollbar-track); + } + /* 滚动条有滑块的轨道部分 */ + &::-webkit-scrollbar-track-piece { + background-color: transparent; + border-radius: 1px; + } + + /* 滚动条滑块(竖向:vertical 横向:horizontal) */ + &::-webkit-scrollbar-thumb { + cursor: pointer; + background-color: var(--color-scrollbar-thumb); + border-radius: 5px; + } + + /* 滚动条滑块hover */ + &::-webkit-scrollbar-thumb:hover { + background-color: var(--color-scrollbar-thumb-hover); + } + + /* 同时有垂直和水平滚动条时交汇的部分 */ + &::-webkit-scrollbar-corner { + display: block; /* 修复交汇时出现的白块 */ + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c63af6e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "module": "ESNext", + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "baseUrl": "./", + "typeRoots": [ + "node_modules/@types", + "node_modules/@kevisual/types", + ], + "paths": { + "@kevisual/components/*": [ + "src/*" + ] + }, + /* Linting */ + "strict": true, + "noImplicitAny": false, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src", + "typings.d.ts", + ] +} \ No newline at end of file