feat: add app and config
This commit is contained in:
@@ -1 +1,6 @@
|
||||
export * from './theme';
|
||||
export * from './theme';
|
||||
|
||||
/**
|
||||
* 输入组件, 使用theme的defaultProps
|
||||
*/
|
||||
export * from './input/TextField';
|
||||
|
||||
23
packages/components/src/input/TextField.tsx
Normal file
23
packages/components/src/input/TextField.tsx
Normal file
@@ -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 <MuiTextField {...defaultProps} {...props} />;
|
||||
};
|
||||
|
||||
export const TextFieldLabel = ({ children, tips, label }: { children?: React.ReactNode; tips?: string; label?: any }) => {
|
||||
return (
|
||||
<div className='flex items-center gap-1'>
|
||||
{label}
|
||||
{children}
|
||||
{tips && (
|
||||
<Tooltip title={tips}>
|
||||
<HelpCircle size={16} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -6,9 +6,10 @@ type TagsInputProps = {
|
||||
value: string[];
|
||||
onChange: (value: string[]) => void;
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
label?: any;
|
||||
showLabel?: boolean;
|
||||
};
|
||||
export const TagsInput = ({ value, onChange, placeholder = 'Add a tag', label = 'Tags' }: TagsInputProps) => {
|
||||
export const TagsInput = ({ value, onChange, placeholder = '', label = '', showLabel = false }: TagsInputProps) => {
|
||||
const [tags, setTags] = useState<string[]>(value);
|
||||
useEffect(() => {
|
||||
setTags(value);
|
||||
@@ -26,6 +27,9 @@ export const TagsInput = ({ value, onChange, placeholder = 'Add a tag', label =
|
||||
// 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) => (
|
||||
@@ -33,6 +37,13 @@ export const TagsInput = ({ value, onChange, placeholder = 'Add a tag', label =
|
||||
variant='outlined'
|
||||
sx={{
|
||||
borderColor: 'primary.main',
|
||||
borderRadius: '4px',
|
||||
'&:hover': {
|
||||
borderColor: 'primary.main',
|
||||
},
|
||||
'& .MuiChip-deleteIcon': {
|
||||
color: 'secondary.main',
|
||||
},
|
||||
}}
|
||||
label={option}
|
||||
{...getTagProps({ index })}
|
||||
@@ -41,7 +52,7 @@ export const TagsInput = ({ value, onChange, placeholder = 'Add a tag', label =
|
||||
));
|
||||
return <Fragment key={id}>{com}</Fragment>;
|
||||
}}
|
||||
renderInput={(params) => <TextField {...params} variant='outlined' label={label} placeholder={placeholder} />}
|
||||
renderInput={(params) => <TextField {...params} label={showLabel ? label : ''} placeholder={placeholder} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -35,19 +35,21 @@ const generateShadows = (color: string): Shadows => {
|
||||
`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: '#ffc107', // amber[300]
|
||||
main: primaryMain, // amber[300]
|
||||
},
|
||||
secondary: {
|
||||
main: '#ffa000', // amber[500]
|
||||
main: secondaryMain, // amber[500]
|
||||
},
|
||||
divider: amber[200],
|
||||
common: {
|
||||
white: '#ffa000',
|
||||
white: secondaryMain,
|
||||
},
|
||||
text: {
|
||||
primary: amber[600],
|
||||
@@ -78,7 +80,7 @@ export const themeOptions: ThemeOptions = {
|
||||
'&.MuiButton-contained': {
|
||||
color: '#ffffff',
|
||||
':hover': {
|
||||
color: amber[500],
|
||||
color: secondaryMain,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -96,6 +98,7 @@ export const themeOptions: ThemeOptions = {
|
||||
MuiTextField: {
|
||||
defaultProps: {
|
||||
fullWidth: true,
|
||||
size: 'small',
|
||||
slotProps: {
|
||||
inputLabel: {
|
||||
shrink: true,
|
||||
@@ -121,6 +124,11 @@ export const themeOptions: ThemeOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiAutocomplete: {
|
||||
defaultProps: {
|
||||
size: 'small',
|
||||
},
|
||||
},
|
||||
MuiSelect: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
@@ -156,7 +164,10 @@ export const themeOptions: ThemeOptions = {
|
||||
MuiFormControlLabel: {
|
||||
defaultProps: {
|
||||
labelPlacement: 'top',
|
||||
sx: {
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: amber[600],
|
||||
alignItems: 'flex-start',
|
||||
'& .MuiFormControlLabel-label': {
|
||||
textAlign: 'left',
|
||||
@@ -170,9 +181,18 @@ export const themeOptions: ThemeOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiMenuItem: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: amber[600],
|
||||
'&.Mui-selected': {
|
||||
backgroundColor: amber[500],
|
||||
color: '#ffffff',
|
||||
'&:hover': {
|
||||
backgroundColor: amber[600],
|
||||
color: '#ffffff',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -201,3 +221,6 @@ export const CustomThemeProvider = ({ children, themeOptions: customThemeOptions
|
||||
const theme = createTheme(customThemeOptions || themeOptions);
|
||||
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
|
||||
};
|
||||
|
||||
// TODO: think
|
||||
export const getComponentProps = () => {};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export { KeyParse, keysTips } from './pages/file/modules/key-parse';
|
||||
export { PermissionManager } from './pages/file/modules/PermissionManager.tsx';
|
||||
export { PermissionModal, usePermissionModal } from './pages/file/modules/PermissionModal.tsx';
|
||||
export { iText } from './i-text/index.ts';
|
||||
export * from './pages/upload/app';
|
||||
export * from './pages/upload/app';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import ReactDatePicker from 'antd/es/date-picker';
|
||||
import { styled, useTheme } from '@mui/material';
|
||||
import 'antd/es/date-picker/style/index';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
interface DatePickerProps {
|
||||
value?: Date | null;
|
||||
onChange?: (date: Date | null) => void;
|
||||
@@ -8,13 +9,14 @@ interface DatePickerProps {
|
||||
}
|
||||
|
||||
export const DatePickerCom = ({ value, onChange, className }: DatePickerProps) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const primaryColor = theme.palette.primary.main;
|
||||
return (
|
||||
<div className={className}>
|
||||
<ReactDatePicker
|
||||
placement='topLeft'
|
||||
placeholder='请选择日期'
|
||||
placeholder={t('Select Date')}
|
||||
value={value}
|
||||
showNow={false}
|
||||
// showTime={true}
|
||||
|
||||
@@ -7,10 +7,10 @@ import { toast } from 'react-toastify';
|
||||
import { create } from 'zustand';
|
||||
import { uniq } from 'lodash-es';
|
||||
import { DatePicker } from './DatePicker';
|
||||
import { SelectPicker } from './SelectPicker';
|
||||
import { DialogKey } from './DialogKey';
|
||||
import { keysTips, KeyParse } from '../../modules/key-parse';
|
||||
import { KeyShareSelect, KeyTextField } from '../../modules/PermissionManager';
|
||||
import { TagsInput } from '@kevisual/center-components/select/TagsInput.tsx';
|
||||
export const setShareKeysOperate = (value: 'public' | 'protected' | 'private') => {
|
||||
const keys = ['password', 'usernames', 'expiration-time'];
|
||||
const deleteKeys = keys.map((item) => {
|
||||
@@ -187,7 +187,7 @@ export const MetaForm = () => {
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<form>
|
||||
<form className='flex flex-col gap-1 pb-4'>
|
||||
{keys.map((key) => {
|
||||
let control: React.ReactNode | null = null;
|
||||
if (key === 'share') {
|
||||
@@ -195,7 +195,7 @@ export const MetaForm = () => {
|
||||
} else if (key === 'expiration-time') {
|
||||
control = <DatePicker value={formData[key]} onChange={(date) => handleFormDataChange(key, date)} />;
|
||||
} else if (key === 'usernames') {
|
||||
control = <SelectPicker value={formData[key] || []} onChange={(value) => handleFormDataChange(key, value)} />;
|
||||
control = <TagsInput value={formData[key] || []} showLabel={false} onChange={(value) => handleFormDataChange(key, value)} />;
|
||||
} else {
|
||||
control = <KeyTextField name={key} value={formData[key] || ''} onChange={(value) => handleFormDataChange(key, value)} />;
|
||||
}
|
||||
@@ -226,23 +226,7 @@ export const MetaForm = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div key={key} className='flex flex-col gap-2'>
|
||||
<FormControlLabel
|
||||
key={key}
|
||||
label={<Label />}
|
||||
labelPlacement='top'
|
||||
control={control}
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
'& .MuiFormControlLabel-label': {
|
||||
textAlign: 'left',
|
||||
width: '100%',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return <FormControlLabel key={key} labelPlacement={'top'} label={<Label />} control={control} />;
|
||||
})}
|
||||
</form>
|
||||
<DialogKey onAdd={addMetaKey} />
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { styled } from '@mui/material';
|
||||
import Select from 'antd/es/select';
|
||||
import 'antd/es/select/style/index';
|
||||
|
||||
interface SelectPickerProps {
|
||||
value: string[];
|
||||
onChange: (value: string[]) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const SelectPickerCom = ({ value, onChange, className }: SelectPickerProps) => {
|
||||
return <Select className={className} style={{ width: '100%' }} popupClassName='hidden' showSearch={false} mode='tags' value={value} onChange={onChange} />;
|
||||
};
|
||||
export const SelectPicker = styled(SelectPickerCom)(({ theme }) => ({
|
||||
|
||||
'& .ant-select-selection-item': {
|
||||
backgroundColor: 'var(--color-primary) !important',
|
||||
color: 'white !important',
|
||||
},
|
||||
'& svg': {
|
||||
color: 'white !important',
|
||||
},
|
||||
'& svg:hover': {
|
||||
color: '#ccc !important',
|
||||
},
|
||||
'& .ant-select-arrow': {
|
||||
// color: 'var(--color-primary) !important',
|
||||
display: 'none !important',
|
||||
},
|
||||
}));
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { KeyParse, getTips } from './key-parse';
|
||||
import { FormControlLabel, TextField, Select, MenuItem, FormGroup, Tooltip } from '@mui/material';
|
||||
import { FormControlLabel, TextField, Select, MenuItem, Tooltip } from '@mui/material';
|
||||
import { DatePicker } from '../draw/modules/DatePicker';
|
||||
import { SelectPicker } from '../draw/modules/SelectPicker';
|
||||
import { TagsInput } from '@kevisual/center-components/select/TagsInput.tsx';
|
||||
import { HelpCircle } from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
export const KeyShareSelect = ({ name, value, onChange }: { name: string; value: string; onChange?: (value: string) => void }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Select
|
||||
variant='outlined'
|
||||
@@ -18,14 +19,14 @@ export const KeyShareSelect = ({ name, value, onChange }: { name: string; value:
|
||||
width: '100%',
|
||||
marginBottom: '16px',
|
||||
}}>
|
||||
<MenuItem value='public' title='公开'>
|
||||
公开
|
||||
<MenuItem value='public' title={t('Public')}>
|
||||
{t('Public')}
|
||||
</MenuItem>
|
||||
<MenuItem value='protected' title='受保护'>
|
||||
受保护
|
||||
<MenuItem value='protected' title={t('Protected')}>
|
||||
{t('Protected')}
|
||||
</MenuItem>
|
||||
<MenuItem value='private' title='私有'>
|
||||
私有
|
||||
<MenuItem value='private' title={t('Private')}>
|
||||
{t('Private')}
|
||||
</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
@@ -85,8 +86,9 @@ export const PermissionManager = ({ value, onChange, className }: PermissionMana
|
||||
};
|
||||
const tips = getTips('share', i18n.language);
|
||||
return (
|
||||
<form className={clsx('flex flex-col gap-2', className)}>
|
||||
<form className={clsx('flex flex-col w-full gap-2', className)}>
|
||||
<FormControlLabel
|
||||
labelPlacement='top'
|
||||
control={<KeyShareSelect name='share' value={formData?.share} onChange={(value) => onChangeValue('share', value)} />}
|
||||
label={
|
||||
<div className='flex items-center gap-1'>
|
||||
@@ -99,38 +101,26 @@ export const PermissionManager = ({ value, onChange, className }: PermissionMana
|
||||
/>
|
||||
{keys.map((item: any) => {
|
||||
let control: React.ReactNode | null = null;
|
||||
const tips = getTips(item);
|
||||
|
||||
const label = (
|
||||
<div className='flex items-center gap-1'>
|
||||
{t(item)}
|
||||
{tips && (
|
||||
<Tooltip title={tips}>
|
||||
<HelpCircle size={16} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
if (item === 'expiration-time') {
|
||||
control = <DatePicker value={formData[item] || ''} onChange={(date) => onChangeValue(item, date)} />;
|
||||
control = <DatePicker className='mt-1' value={formData[item] || ''} onChange={(date) => onChangeValue(item, date)} />;
|
||||
} else if (item === 'usernames') {
|
||||
control = <SelectPicker value={formData[item] || []} onChange={(value) => onChangeValue(item, value)} />;
|
||||
control = <TagsInput value={formData[item] || []} onChange={(value) => onChangeValue(item, value)} />;
|
||||
} else {
|
||||
control = <KeyTextField name={item} value={formData[item] || ''} onChange={(value) => onChangeValue(item, value)} />;
|
||||
}
|
||||
const tips = getTips(item);
|
||||
return (
|
||||
<FormControlLabel
|
||||
labelPlacement='top'
|
||||
key={item}
|
||||
control={control}
|
||||
label={
|
||||
<div className='flex items-center gap-1'>
|
||||
{item}
|
||||
{tips && (
|
||||
<Tooltip title={tips}>
|
||||
<HelpCircle size={16} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
'& .MuiFormControlLabel-label': {
|
||||
textAlign: 'left',
|
||||
width: '100%',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return <FormControlLabel key={item} labelPlacement='top' control={control} label={label} />;
|
||||
})}
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PermissionManager } from './PermissionManager';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
type PermissionModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
value: Record<string, any>;
|
||||
onChange: (value: Record<string, any>) => void;
|
||||
onSave: () => void;
|
||||
};
|
||||
export const PermissionModal = ({ open, onClose, value, onChange, onSave }: PermissionModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose}>
|
||||
<DialogTitle>{t('app.share')}</DialogTitle>
|
||||
<DialogContent sx={{ width: '400px' }}>
|
||||
<PermissionManager value={value} onChange={onChange} />
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>{t('Cancel')}</Button>
|
||||
<Button color='primary' onClick={onSave}>
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
type UsePermissionModalProps = {
|
||||
/**
|
||||
* 保存回调
|
||||
*/
|
||||
onSave: (values: Record<string, any>) => Promise<boolean>;
|
||||
};
|
||||
export const usePermissionModal = ({ onSave }: UsePermissionModalProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [formData, setFormData] = useState<Record<string, any>>({});
|
||||
const onChange = (value: Record<string, any>) => {
|
||||
setFormData(value);
|
||||
};
|
||||
const _onSave = async () => {
|
||||
const res = await onSave(formData);
|
||||
if (res) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
const contextHolder = <PermissionModal open={open} onClose={() => setOpen(false)} value={formData} onChange={onChange} onSave={_onSave} />;
|
||||
return {
|
||||
open,
|
||||
setOpen: (open: boolean, fromData?: any) => {
|
||||
setOpen(open);
|
||||
if (open) {
|
||||
setFormData(fromData ?? {});
|
||||
}
|
||||
},
|
||||
setFormData: (values: any) => {
|
||||
setFormData({ ...formData, ...values });
|
||||
},
|
||||
contextHolder,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user