Skip to content

Commit

Permalink
Merge pull request #42 from daodaoedu/feature/group
Browse files Browse the repository at this point in the history
Feature/group
  • Loading branch information
JohnsonMao authored Mar 1, 2024
2 parents 1cfba1e + 93cdc72 commit 9db683f
Show file tree
Hide file tree
Showing 29 changed files with 907 additions and 184 deletions.
64 changes: 58 additions & 6 deletions components/Group/Form/Fields/AreaCheckbox.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useEffect, useState } from 'react';
import Box from '@mui/material/Box';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
Expand All @@ -9,27 +10,78 @@ export default function AreaCheckbox({
itemValue,
name,
value,
onChange,
control,
}) {
const [isPhysicalArea, setIsPhysicalArea] = useState(false);

const getPhysicalArea = (data) =>
options.find((option) => data.includes(option.name));

const handleChange = (val) =>
control.onChange({ target: { name, value: val } });

const physicalAreaValue = getPhysicalArea(value)?.name || '';

const toggleIsPhysicalArea = () => {
const updatedValue = value.filter((v) => !getPhysicalArea([v]));
handleChange(updatedValue);
setIsPhysicalArea((pre) => !pre);
};

const handleCheckboxChange = (_value) => {
const updatedValue = value.includes(_value)
? value.filter((v) => v !== _value)
: [...value, _value];
handleChange(updatedValue);
};

const handlePhysicalAreaChange = ({ target }) => {
const updatedValue = value
.filter((v) => !getPhysicalArea([v]))
.concat(target.value);
handleChange(updatedValue);
};

const physicalAreaControl = {
onChange: handlePhysicalAreaChange,
onBlur: handlePhysicalAreaChange,
};

useEffect(() => {
if (value.find((v) => getPhysicalArea([v]))) setIsPhysicalArea(true);
}, [value]);

return (
<>
<Box sx={{ display: 'flex', label: { whiteSpace: 'nowrap' } }}>
<FormControlLabel control={<Checkbox />} label="實體活動" />
<FormControlLabel
control={<Checkbox onClick={toggleIsPhysicalArea} />}
label="實體活動"
checked={isPhysicalArea}
/>
<Select
name={name}
options={options}
placeholder="地點"
value={value}
value={physicalAreaValue}
itemLabel={itemLabel}
itemValue={itemValue}
onChange={onChange}
control={physicalAreaControl}
/>
</Box>
<div>
<FormControlLabel control={<Checkbox />} label="線上" />
<FormControlLabel
control={<Checkbox onClick={() => handleCheckboxChange('線上')} />}
label="線上"
checked={value.includes('線上')}
/>
</div>
<div>
<FormControlLabel control={<Checkbox />} label="待討論" />
<FormControlLabel
control={<Checkbox onClick={() => handleCheckboxChange('待討論')} />}
label="待討論"
checked={value.includes('待討論')}
/>
</div>
</>
);
Expand Down
11 changes: 8 additions & 3 deletions components/Group/Form/Fields/Select.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { useState } from 'react';
import FormControl from '@mui/material/FormControl';
import MuiSelect from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';

export default function Select({
id,
name,
value,
placeholder,
options = [],
itemLabel = 'label',
fullWidth = true,
multiple,
onChange,
sx,
disabled,
control,
value,
error,
}) {
const getValue = (any, key) => (typeof any === 'object' ? any[key] : any);
const renderValue = (selected) => {
Expand All @@ -37,7 +40,8 @@ export default function Select({
...sx,
}}
value={value}
onChange={onChange}
disabled={disabled}
{...control}
>
{placeholder && (
<MenuItem disabled value="" sx={{ fontSize: 14 }}>
Expand All @@ -53,6 +57,7 @@ export default function Select({
</MenuItem>
))}
</MuiSelect>
<span className="error-message">{error}</span>
</FormControl>
);
}
30 changes: 19 additions & 11 deletions components/Group/Form/Fields/TagsField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,44 @@ import ClearIcon from '@mui/icons-material/Clear';
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
import { StyledChip, StyledTagsField } from '../Form.styled';

function TagsField({ label, helperText, ...props }) {
const [tags, setTags] = useState([]);
function TagsField({ name, helperText, control, value = [] }) {
const [input, setInput] = useState('');
const [error, setError] = useState('');

const handleInput = (e) => {
const { value } = e.target;
if (value.length > 8) setError('標籤最多 8 個字');
const _value = e.target.value;
if (_value.length > 8) setError('標籤最多 8 個字');
else setError('');
setInput(value);
setInput(_value);
};

const handleKeyDown = (e) => {
if (error) return;
const tag = input.trim();
if (e.key !== 'Enter' || !tag) return;
if (tags.indexOf(tag) > -1) return;
setTags((pre) => [...pre, tag]);
if (value.indexOf(tag) > -1) return;
setInput('');
control.onChange({
target: {
name,
value: [...value, tag],
},
});
};

const handleDelete = (tag) => () => {
setTags((pre) => pre.filter((t) => t !== tag));
control.onChange({
target: {
name,
value: value.filter((t) => t !== tag),
},
});
};

return (
<>
<StyledTagsField>
{tags.map((tag) => (
{value.map((tag) => (
<StyledChip
key={tag}
label={tag}
Expand All @@ -42,9 +51,8 @@ function TagsField({ label, helperText, ...props }) {
onDelete={handleDelete(tag)}
/>
))}
{tags.length < 8 && (
{value.length < 8 && (
<input
{...props}
value={input}
onChange={handleInput}
onKeyDown={handleKeyDown}
Expand Down
17 changes: 4 additions & 13 deletions components/Group/Form/Fields/TextField.jsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import MuiTextField from '@mui/material/TextField';
import { useEffect, useState } from 'react';

export default function TextField({
id,
placeholder,
multiline,
name,
value,
onChange,
helperText,
max,
errorMessage,
control,
value,
error,
}) {
const [error, setError] = useState('');

useEffect(() => {
if (value.length > max) setError(errorMessage);
else setError('');
}, [max, value]);

return (
<>
<MuiTextField
Expand All @@ -31,8 +22,8 @@ export default function TextField({
value={value}
multiline={multiline}
rows={multiline && 10}
onChange={onChange}
helperText={helperText}
{...control}
/>
<span className="error-message">{error}</span>
</>
Expand Down
14 changes: 11 additions & 3 deletions components/Group/Form/Fields/Upload.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import DeleteSvg from '@/public/assets/icons/delete.svg';
import { StyledUpload } from '../Form.styled';
import UploadSvg from './UploadSvg';

export default function Upload({ name, onChange }) {
const [preview, setPreview] = useState('');
export default function Upload({ name, value, control }) {
const [preview, setPreview] = useState(value || '');
const [error, setError] = useState('');
const inputRef = useRef();

Expand All @@ -17,17 +17,25 @@ export default function Upload({ name, onChange }) {
value: file,
},
};
onChange(event);
control.onChange(event);
};

const handleFile = (file) => {
const imageType = /image.*/;
const maxSize = 500 * 1024; // 500 KB

setPreview('');
setError('');
if (!file.type.match(imageType)) {
setError('僅支援上傳圖片唷!');
return;
}

if (file.size > maxSize) {
setError('圖片最大限制 500 KB');
return;
}

const reader = new FileReader();
reader.onload = (e) => setPreview(e.target.result);
reader.readAsDataURL(file);
Expand Down
58 changes: 18 additions & 40 deletions components/Group/Form/Fields/index.jsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,34 @@
import { useId } from 'react';
import AreaCheckbox from './AreaCheckbox';
import Select from './Select';
import TagsField from './TagsField';
import TextField from './TextField';
import Upload from './Upload';
import Wrapper from './Wrapper';
import useWrapperProps from './useWrapperProps';

const Fields = {};
const withWrapper = (Component) => (props) => {
const id = useId();
const formItemId = `form-item-${id}`;
const { required, label, tooltip } = props;

Fields.AreaCheckbox = (props) => {
const wrapperProps = useWrapperProps(props);
return (
<Wrapper {...wrapperProps}>
<AreaCheckbox {...props} />
<Wrapper
id={formItemId}
required={required}
label={label}
tooltip={tooltip}
>
<Component {...props} />
</Wrapper>
);
};

Fields.Select = (props) => {
const wrapperProps = useWrapperProps(props);
return (
<Wrapper {...wrapperProps}>
<Select {...props} />
</Wrapper>
);
};

Fields.TagsField = (props) => {
const wrapperProps = useWrapperProps(props);
return (
<Wrapper {...wrapperProps}>
<TagsField {...props} />
</Wrapper>
);
};

Fields.TextField = (props) => {
const wrapperProps = useWrapperProps(props);
return (
<Wrapper {...wrapperProps}>
<TextField {...props} />
</Wrapper>
);
};

Fields.Upload = (props) => {
const wrapperProps = useWrapperProps(props);
return (
<Wrapper {...wrapperProps}>
<Upload {...props} />
</Wrapper>
);
const Fields = {
AreaCheckbox: withWrapper(AreaCheckbox),
Select: withWrapper(Select),
TagsField: withWrapper(TagsField),
TextField: withWrapper(TextField),
Upload: withWrapper(Upload),
};

export default Fields;
8 changes: 0 additions & 8 deletions components/Group/Form/Fields/useWrapperProps.jsx

This file was deleted.

12 changes: 12 additions & 0 deletions components/Group/Form/Form.styled.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ export const StyledChip = styled(Chip)`
}
`;

export const StyledSwitchWrapper = styled.div`
padding: 4px 16px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
font-weight: 500;
color: #293a3d;
border: 1px solid rgba(0, 0, 0, 0.23);
border-radius: 4px;
`;

export const StyledTagsField = styled.div(
({ theme }) => `
position: relative;
Expand Down
Loading

0 comments on commit 9db683f

Please sign in to comment.