chore: model access statement
This commit is contained in:
@@ -29,6 +29,7 @@ interface AutoTooltipProps extends Omit<TagProps, 'title'> {
|
||||
}
|
||||
|
||||
const StyledTag = styled(Tag)`
|
||||
margin: 0;
|
||||
&.tag-filled {
|
||||
border: none;
|
||||
background-color: var(--ant-color-fill-secondary);
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import { isNotEmptyValue } from '@/utils/index';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import type { SelectProps } from 'antd';
|
||||
import { Form } from 'antd';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import SimpleSelect from './simple-select';
|
||||
import { SealFormItemProps } from './types';
|
||||
import Wrapper from './wrapper';
|
||||
import SelectWrapper from './wrapper/select';
|
||||
|
||||
const SealSelect: React.FC<
|
||||
SelectProps & SealFormItemProps & { showTags?: boolean }
|
||||
> = (props) => {
|
||||
const {
|
||||
label,
|
||||
placeholder,
|
||||
children,
|
||||
required,
|
||||
description,
|
||||
options,
|
||||
allowNull,
|
||||
isInFormItems = true,
|
||||
notFoundContent = null,
|
||||
...rest
|
||||
} = props;
|
||||
const intl = useIntl();
|
||||
const [isFocus, setIsFocus] = useState(false);
|
||||
const inputRef = useRef<any>(null);
|
||||
|
||||
let status = '';
|
||||
|
||||
// the status can be controlled by Form.Item
|
||||
if (isInFormItems) {
|
||||
const statusData = Form?.Item?.useStatus?.();
|
||||
status = statusData?.status || '';
|
||||
} else {
|
||||
status = props.status || '';
|
||||
}
|
||||
|
||||
const _options = useMemo(() => {
|
||||
if (!options?.length) {
|
||||
return [];
|
||||
}
|
||||
const list = cloneDeep(options);
|
||||
return list.map((item: any) => {
|
||||
if (item.locale) {
|
||||
item.label = intl.formatMessage({ id: item.label as string });
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}, [options, intl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
isNotEmptyValue(props.value) ||
|
||||
(allowNull && (props.value === null || props.value === undefined))
|
||||
) {
|
||||
setIsFocus(true);
|
||||
}
|
||||
}, [props.value, allowNull]);
|
||||
|
||||
const handleClickWrapper = () => {
|
||||
if (!props.disabled && !isFocus) {
|
||||
inputRef.current?.focus?.();
|
||||
setIsFocus(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (val: any, options: any) => {
|
||||
if (isNotEmptyValue(val) || (allowNull && val === null)) {
|
||||
setIsFocus(true);
|
||||
} else {
|
||||
setIsFocus(false);
|
||||
}
|
||||
props.onChange?.(val, options);
|
||||
};
|
||||
|
||||
const handleOnFocus = (e: any) => {
|
||||
setIsFocus(true);
|
||||
props.onFocus?.(e);
|
||||
};
|
||||
|
||||
const handleOnBlur = (e: any) => {
|
||||
if (allowNull && props.value === null) {
|
||||
setIsFocus(true);
|
||||
} else if (!props.value) {
|
||||
setIsFocus(false);
|
||||
}
|
||||
props.onBlur?.(e);
|
||||
};
|
||||
|
||||
return (
|
||||
<SelectWrapper>
|
||||
<Wrapper
|
||||
className="seal-select-wrapper"
|
||||
status={status}
|
||||
label={label}
|
||||
isFocus={isFocus}
|
||||
required={required}
|
||||
description={description}
|
||||
disabled={props.disabled}
|
||||
onClick={handleClickWrapper}
|
||||
>
|
||||
<SimpleSelect
|
||||
{...rest}
|
||||
ref={inputRef}
|
||||
options={children ? null : _options}
|
||||
onFocus={handleOnFocus}
|
||||
onBlur={handleOnBlur}
|
||||
onChange={handleChange}
|
||||
notFoundContent={notFoundContent}
|
||||
>
|
||||
{children}
|
||||
</SimpleSelect>
|
||||
</Wrapper>
|
||||
</SelectWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default SealSelect;
|
||||
@@ -2,7 +2,7 @@ import { useIntl } from '@umijs/max';
|
||||
import type { SelectProps } from 'antd';
|
||||
import { Checkbox, Tag } from 'antd';
|
||||
import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { forwardRef, useEffect, useImperativeHandle } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import AutoTooltip from '../auto-tooltip';
|
||||
import BaseSelect from './base/select';
|
||||
@@ -43,218 +43,239 @@ const TagWrapper = styled(Tag)`
|
||||
border-radius: 12px;
|
||||
`;
|
||||
|
||||
const SimpleSelect: React.FC<SelectProps> = (props) => {
|
||||
const intl = useIntl();
|
||||
const { options = [], ...restProps } = props;
|
||||
const SimpleSelect: React.FC<SelectProps & { ref?: any; showTags?: boolean }> =
|
||||
forwardRef((props, ref) => {
|
||||
const intl = useIntl();
|
||||
const { options = [], showTags, ...restProps } = props;
|
||||
|
||||
const [allSelection, setAllSelection] = React.useState<{
|
||||
checked: boolean;
|
||||
indeterminate: boolean;
|
||||
}>({
|
||||
checked: false,
|
||||
indeterminate: false
|
||||
});
|
||||
const [optionsList, setOptionsList] = React.useState<any[]>(options || []);
|
||||
const selectRef = React.useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setOptionsList(options || []);
|
||||
}, [options]);
|
||||
|
||||
const optionRender = (option: any, info: any) => {
|
||||
const { value, label } = option;
|
||||
return (
|
||||
<OptionWrapper>
|
||||
{restProps.value?.includes(value) ? (
|
||||
<Checkbox checked></Checkbox>
|
||||
) : (
|
||||
<Checkbox></Checkbox>
|
||||
)}
|
||||
<AutoTooltip ghost>{label}</AutoTooltip>
|
||||
</OptionWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const handleOnCheckboxChange = (e: CheckboxChangeEvent) => {
|
||||
const isChecked = e.target.checked;
|
||||
const allValues = optionsList?.map((opt: any) => opt.value) || [];
|
||||
|
||||
setAllSelection({
|
||||
checked: isChecked,
|
||||
const [allSelection, setAllSelection] = React.useState<{
|
||||
checked: boolean;
|
||||
indeterminate: boolean;
|
||||
}>({
|
||||
checked: false,
|
||||
indeterminate: false
|
||||
});
|
||||
const [optionsList, setOptionsList] = React.useState<any[]>(options || []);
|
||||
const selectRef = React.useRef<any>(null);
|
||||
const selectorRef = React.useRef<any>(null);
|
||||
|
||||
let allSelectedValues = [...(restProps.value || [])];
|
||||
useEffect(() => {
|
||||
setOptionsList(options || []);
|
||||
}, [options]);
|
||||
|
||||
if (isChecked) {
|
||||
// Select all options
|
||||
allSelectedValues = Array.from(
|
||||
new Set([...allSelectedValues, ...allValues])
|
||||
const optionRender = (option: any, info: any) => {
|
||||
const { value, label } = option;
|
||||
return (
|
||||
<OptionWrapper>
|
||||
{restProps.value?.includes?.(value) ? (
|
||||
<Checkbox checked></Checkbox>
|
||||
) : (
|
||||
<Checkbox></Checkbox>
|
||||
)}
|
||||
<AutoTooltip ghost>{label}</AutoTooltip>
|
||||
</OptionWrapper>
|
||||
);
|
||||
} else {
|
||||
// Deselect all options
|
||||
allSelectedValues = allSelectedValues.filter(
|
||||
(value) => !allValues.includes(value)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
restProps.onChange?.(allSelectedValues, optionsList || []);
|
||||
};
|
||||
const handleOnCheckboxChange = (e: CheckboxChangeEvent) => {
|
||||
const isChecked = e.target.checked;
|
||||
const allValues = optionsList?.map((opt: any) => opt.value) || [];
|
||||
|
||||
const dropdownRender = (originPanel: React.ReactNode) => {
|
||||
return (
|
||||
<DropdownWrapper>
|
||||
{restProps.mode === 'multiple' && (
|
||||
<SelectAllWrapper>
|
||||
<Checkbox
|
||||
checked={allSelection.checked}
|
||||
indeterminate={allSelection.indeterminate}
|
||||
onChange={handleOnCheckboxChange}
|
||||
>
|
||||
{intl.formatMessage({ id: 'common.checbox.all' })}
|
||||
</Checkbox>
|
||||
</SelectAllWrapper>
|
||||
)}
|
||||
{originPanel}
|
||||
</DropdownWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const handleOnChange = (value: any, option: any) => {
|
||||
const selectedValues = Array.isArray(value) ? value : [value];
|
||||
const allSelected = optionsList?.map((opt: any) => opt.value) || [];
|
||||
const isAllSelected = selectedValues.length === allSelected?.length;
|
||||
|
||||
setAllSelection({
|
||||
checked: isAllSelected,
|
||||
indeterminate: !isAllSelected && selectedValues.length > 0
|
||||
});
|
||||
|
||||
restProps.onChange?.(selectedValues, option);
|
||||
};
|
||||
|
||||
const filterOption = (inputValue: string, option: any) => {
|
||||
if (!option || !option.label) return false;
|
||||
return option.label.toLowerCase().includes(inputValue.toLowerCase());
|
||||
};
|
||||
|
||||
const checkAllSelection = (list: Global.BaseOption<string | number>[]) => {
|
||||
if (
|
||||
!restProps.value ||
|
||||
!Array.isArray(restProps.value) ||
|
||||
list.length === 0
|
||||
) {
|
||||
setAllSelection({
|
||||
checked: false,
|
||||
checked: isChecked,
|
||||
indeterminate: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
const selectedValues = new Set(restProps.value);
|
||||
const allValues = list?.map((opt: any) => opt.value) || [];
|
||||
|
||||
const isAllSelected = allValues.every((val: any) =>
|
||||
selectedValues.has(val)
|
||||
);
|
||||
let allSelectedValues = [...(restProps.value || [])];
|
||||
|
||||
const isSomeSelected = allValues.some((val: any) =>
|
||||
selectedValues.has(val)
|
||||
);
|
||||
if (isChecked) {
|
||||
// Select all options
|
||||
allSelectedValues = Array.from(
|
||||
new Set([...allSelectedValues, ...allValues])
|
||||
);
|
||||
} else {
|
||||
// Deselect all options
|
||||
allSelectedValues = allSelectedValues.filter(
|
||||
(value) => !allValues.includes(value)
|
||||
);
|
||||
}
|
||||
|
||||
setAllSelection({
|
||||
checked: isAllSelected,
|
||||
indeterminate: isSomeSelected && !isAllSelected
|
||||
});
|
||||
};
|
||||
restProps.onChange?.(allSelectedValues, optionsList || []);
|
||||
};
|
||||
|
||||
const TagRender = (props: any) => {
|
||||
const { label } = props;
|
||||
const count = props.isMaxTag ? label.slice(0, -3).slice(1) : label;
|
||||
const dropdownRender = (originPanel: React.ReactNode) => {
|
||||
return (
|
||||
<DropdownWrapper>
|
||||
{restProps.mode === 'multiple' && (
|
||||
<SelectAllWrapper>
|
||||
<Checkbox
|
||||
checked={allSelection.checked}
|
||||
indeterminate={allSelection.indeterminate}
|
||||
onChange={handleOnCheckboxChange}
|
||||
>
|
||||
{intl.formatMessage({ id: 'common.checbox.all' })}
|
||||
</Checkbox>
|
||||
</SelectAllWrapper>
|
||||
)}
|
||||
{originPanel}
|
||||
</DropdownWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<TagWrapper
|
||||
bordered={false}
|
||||
style={{
|
||||
height: 24,
|
||||
backgroundColor: 'var(--ant-color-fill-tertiary)',
|
||||
fontSize: 'var(--ant-font-size)'
|
||||
}}
|
||||
className="flex-center"
|
||||
>
|
||||
{intl.formatMessage({ id: 'common.select.count' }, { count: count })}
|
||||
</TagWrapper>
|
||||
);
|
||||
};
|
||||
const handleOnChange = (value: any, option: any) => {
|
||||
const selectedValues = Array.isArray(value) ? value : [value];
|
||||
const allSelected = optionsList?.map((opt: any) => opt.value) || [];
|
||||
const isAllSelected = selectedValues.length === allSelected?.length;
|
||||
|
||||
const handleOnSearch = (value: string) => {
|
||||
if (restProps.onSearch) {
|
||||
restProps.onSearch(value);
|
||||
} else {
|
||||
const filteredOptions = options?.filter((option: any) =>
|
||||
option.label.toLowerCase().includes(value.toLowerCase())
|
||||
) as Global.BaseOption<string | number>[];
|
||||
setOptionsList(filteredOptions || []);
|
||||
checkAllSelection(filteredOptions || []);
|
||||
}
|
||||
};
|
||||
setAllSelection({
|
||||
checked: isAllSelected,
|
||||
indeterminate: !isAllSelected && selectedValues.length > 0
|
||||
});
|
||||
|
||||
const handleOnBlur = (e: any) => {
|
||||
restProps.onBlur?.(e);
|
||||
};
|
||||
restProps.onChange?.(selectedValues, option);
|
||||
};
|
||||
|
||||
const handleOnFocus = (e: any) => {
|
||||
restProps.onFocus?.(e);
|
||||
};
|
||||
const filterOption = (inputValue: string, option: any) => {
|
||||
if (!option || !option.label) return false;
|
||||
return option.label.toLowerCase().includes(inputValue.toLowerCase());
|
||||
};
|
||||
|
||||
const handleOnOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
checkAllSelection(options as Global.BaseOption<string | number>[]);
|
||||
setOptionsList(options || []);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const input = selectRef.current?.querySelector?.('input');
|
||||
|
||||
if (!input) return;
|
||||
|
||||
const handler = (event: KeyboardEvent) => {
|
||||
const checkAllSelection = (list: Global.BaseOption<string | number>[]) => {
|
||||
if (
|
||||
event.key === 'Backspace' &&
|
||||
(input as HTMLInputElement).value === ''
|
||||
!restProps.value ||
|
||||
!Array.isArray(restProps.value) ||
|
||||
list.length === 0
|
||||
) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
setAllSelection({
|
||||
checked: false,
|
||||
indeterminate: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
const selectedValues = new Set(restProps.value);
|
||||
const allValues = list?.map((opt: any) => opt.value) || [];
|
||||
|
||||
const isAllSelected = allValues.every((val: any) =>
|
||||
selectedValues.has(val)
|
||||
);
|
||||
|
||||
const isSomeSelected = allValues.some((val: any) =>
|
||||
selectedValues.has(val)
|
||||
);
|
||||
|
||||
setAllSelection({
|
||||
checked: isAllSelected,
|
||||
indeterminate: isSomeSelected && !isAllSelected
|
||||
});
|
||||
};
|
||||
|
||||
const TagRender = (props: any) => {
|
||||
const { label } = props;
|
||||
const count = props.isMaxTag ? label.slice(0, -3).slice(1) : label;
|
||||
|
||||
return (
|
||||
<TagWrapper
|
||||
bordered={false}
|
||||
closable={props.closable}
|
||||
onClose={props.onClose}
|
||||
style={{
|
||||
height: 24,
|
||||
backgroundColor: 'var(--ant-color-fill-tertiary)',
|
||||
fontSize: 'var(--ant-font-size)'
|
||||
}}
|
||||
className="flex-center"
|
||||
>
|
||||
{showTags
|
||||
? label
|
||||
: intl.formatMessage(
|
||||
{ id: 'common.select.count' },
|
||||
{ count: count }
|
||||
)}
|
||||
</TagWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const handleOnSearch = (value: string) => {
|
||||
if (restProps.onSearch) {
|
||||
restProps.onSearch(value);
|
||||
} else {
|
||||
const filteredOptions = options?.filter((option: any) =>
|
||||
option.label.toLowerCase().includes(value.toLowerCase())
|
||||
) as Global.BaseOption<string | number>[];
|
||||
setOptionsList(filteredOptions || []);
|
||||
checkAllSelection(filteredOptions || []);
|
||||
}
|
||||
};
|
||||
|
||||
input.addEventListener('keydown', handler);
|
||||
|
||||
return () => {
|
||||
input.removeEventListener('keydown', handler);
|
||||
const handleOnBlur = (e: any) => {
|
||||
restProps.onBlur?.(e);
|
||||
};
|
||||
}, [selectRef.current]);
|
||||
|
||||
return (
|
||||
<div ref={selectRef}>
|
||||
<BaseSelect
|
||||
{...restProps}
|
||||
options={optionsList}
|
||||
maxTagCount={0}
|
||||
defaultActiveFirstOption={false}
|
||||
popupRender={dropdownRender}
|
||||
optionRender={optionRender}
|
||||
menuItemSelectedIcon={false}
|
||||
onChange={handleOnChange}
|
||||
tagRender={TagRender}
|
||||
onBlur={handleOnBlur}
|
||||
onFocus={handleOnFocus}
|
||||
onSearch={handleOnSearch}
|
||||
filterOption={filterOption}
|
||||
onOpenChange={handleOnOpenChange}
|
||||
></BaseSelect>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const handleOnFocus = (e: any) => {
|
||||
restProps.onFocus?.(e);
|
||||
};
|
||||
|
||||
const handleOnOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
checkAllSelection(options as Global.BaseOption<string | number>[]);
|
||||
setOptionsList(options || []);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const input = selectRef.current?.querySelector?.('input');
|
||||
|
||||
if (!input) return;
|
||||
|
||||
const handler = (event: KeyboardEvent) => {
|
||||
if (
|
||||
event.key === 'Backspace' &&
|
||||
(input as HTMLInputElement).value === ''
|
||||
) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
input.addEventListener('keydown', handler);
|
||||
|
||||
return () => {
|
||||
input.removeEventListener('keydown', handler);
|
||||
};
|
||||
}, [selectRef.current]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
focus: () => {
|
||||
selectorRef.current?.focus();
|
||||
},
|
||||
blur: () => {
|
||||
selectorRef.current?.blur();
|
||||
}
|
||||
}));
|
||||
|
||||
return (
|
||||
<div ref={selectRef} style={{ width: 'inherit' }}>
|
||||
<BaseSelect
|
||||
{...restProps}
|
||||
ref={selectorRef}
|
||||
options={optionsList}
|
||||
maxTagCount={restProps.maxTagCount || 0}
|
||||
defaultActiveFirstOption={false}
|
||||
popupRender={dropdownRender}
|
||||
optionRender={optionRender}
|
||||
menuItemSelectedIcon={false}
|
||||
onChange={handleOnChange}
|
||||
tagRender={TagRender}
|
||||
onBlur={handleOnBlur}
|
||||
onFocus={handleOnFocus}
|
||||
onSearch={handleOnSearch}
|
||||
filterOption={filterOption}
|
||||
onOpenChange={handleOnOpenChange}
|
||||
>
|
||||
{props.children}
|
||||
</BaseSelect>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default SimpleSelect;
|
||||
|
||||
Reference in New Issue
Block a user