Skip to content

Commit

Permalink
Merge pull request #948 from merico-dev/939-multiple-selector-with-fi…
Browse files Browse the repository at this point in the history
…xed-width

939 multiple selector with fixed width
  • Loading branch information
GerilLeto authored May 18, 2023
2 parents e59873e + 6710741 commit cfcbb91
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 28 deletions.
2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devtable/api",
"version": "8.64.0",
"version": "8.65.0",
"description": "",
"main": "index.js",
"scripts": {
Expand Down
3 changes: 2 additions & 1 deletion dashboard/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devtable/dashboard",
"version": "8.64.0",
"version": "8.65.0",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
Expand Down Expand Up @@ -38,6 +38,7 @@
"monaco-editor": "0.36.0",
"performant-array-to-tree": "1.11.0",
"popmotion": "^11.0.3",
"rc-select": "14.1.0",
"rc-tree-select": "5.5.5",
"reactflow": "^11.5.3"
},
Expand Down
5 changes: 3 additions & 2 deletions dashboard/src/filter/filter-multi-select/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ export const FilterEditorMultiSelect = observer(function _FilterEditorMultiSelec
return (
<>
<TextInput
label="Min-width"
label="Width"
description="At least 160px"
value={config.min_width}
onChange={(e) => config.setMinWidth(e.currentTarget.value)}
placeholder="200px"
placeholder="default: 200px"
/>
<Divider label="Configure options" labelPosition="center" />
<Stack spacing={10} sx={{ position: 'relative', minHeight: '50px' }}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { MultiSelect } from '@mantine/core';
import { observer } from 'mobx-react-lite';
import { useContentModelContext } from '~/contexts';
import { FilterModelInstance } from '../../model';
import { IFilterConfig_MultiSelect } from '../../model/filters/filter/multi-select';
import { FilterSelectItem } from '../select-item';
import { FilterModelInstance } from '../../../model';
import { IFilterConfig_MultiSelect } from '../../../model/filters/filter/multi-select';
import { MultiSelectWidget } from './widget';

interface IFilterMultiSelect extends Omit<FilterModelInstance, 'key' | 'type' | 'config'> {
config: IFilterConfig_MultiSelect;
Expand All @@ -17,25 +16,16 @@ export const FilterMultiSelect = observer(({ label, config, value, onChange }: I
const { state } = model.getDataStuffByID(config.options_query_id);
const loading = state === 'loading';

const minWidth = config.min_width ? config.min_width : '200px';
const width = config.min_width ? config.min_width : '200px';
const disabled = usingRemoteOptions ? loading : false;
return (
<MultiSelect
<MultiSelectWidget
label={label}
data={config.options}
options={config.options}
style={{ minWidth: '160px', width, maxWidth: disabled ? width : 'unset', borderColor: '#e9ecef' }}
disabled={disabled}
value={value}
onChange={onChange}
styles={{
root: {
minWidth,
maxWidth: disabled ? minWidth : 'unset',
},
input: {
borderColor: '#e9ecef',
},
}}
itemComponent={FilterSelectItem}
/>
);
});
167 changes: 167 additions & 0 deletions dashboard/src/filter/filter-multi-select/render/widget.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { createStyles, MantineNumberSize } from '@mantine/core';

export interface MultiSelectWidgetStylesParams {
radius?: MantineNumberSize;
}

export default createStyles((theme, { radius = 4 }: MultiSelectWidgetStylesParams) => ({
root: {
borderRadius: theme.fn.radius(radius),
display: 'flex',
flexWrap: 'nowrap',
border: '1px solid #ced4da',
paddingLeft: '0px',
paddingRight: '30px', // for the clear icon
backgroundColor: '#fff',
transition: 'border-color 100ms ease',
borderColor: '#e9ecef',
'&.rc-select.rc-select-open': {
borderColor: '#228be6 !important',
'.rc-select-selection-overflow-item-rest': {
display: 'none',
},
'.rc-select-selection-overflow-item-suffix': {
display: 'block',
},
},
'.rc-select-selector': {
height: 'auto',
lineHeight: 1.55,
paddingLeft: '12px',
resize: 'none',
boxSizing: 'border-box',
fontSize: '14px',
width: '100%',
color: '#000',
display: 'block',
textAlign: 'left',
minHeight: '36px',
cursor: 'pointer',
flexGrow: 1,
},
'.rc-select-selection-search-mirror': {
display: 'none',
},
'.rc-select-selection-search': {
flexGrow: 1,
width: 'auto !important',
},
'.rc-select-selection-overflow': {
display: 'flex',
minHeight: '34px',
alignItems: 'center',
flexWrap: 'nowrap',
marginLeft: 'calc(-10px / 2)',
boxSizing: 'border-box',
},
'.rc-select-selection-overflow-item': {
display: 'flex',
alignItems: 'center',
backgroundColor: '#f1f3f5',
color: '#495057',
height: '24px',
paddingLeft: '12px',
paddingRight: '12px',
fontWeight: 500,
fontSize: '12px',
borderRadius: '4px',
cursor: 'default',
userSelect: 'none',
maxWidth: 'calc(100% - 20px)',
margin: 'calc(10px / 2 - 2px) calc(10px / 2)',
},
'.rc-select-selection-overflow-item-rest': {
cursor: 'pointer',
},
'.rc-select-selection-overflow-item-suffix': {
display: 'none',
backgroundColor: 'transparent',
width: '100%',
maxWidth: '100%',
height: '28px',
margin: 0,
paddingLeft: 0,
paddingRight: 0,
},
input: {
flex: 1,
minWidth: '60px',
backgroundColor: 'transparent',
border: 0,
outline: 0,
fontSize: '14px',
padding: 0,
marginLeft: 0,
// appearance: 'none',
color: 'inherit',
height: '28px',
lineHeight: '32px',
cursor: 'pointer',
width: '100%',
'&::-webkit-search-decoration, &::-webkit-search-cancel-button, &::-webkit-search-results-button, &::-webkit-search-results-decoration':
{
display: 'none',
},
},
'.rc-select-clear': {
marginRight: '-24px',
alignSelf: 'center',
cursor: 'pointer',
},
'&.rc-select-disabled': {
backgroundColor: 'rgb(241, 243, 245)',
color: 'rgb(144, 146, 150)',
opacity: 0.6,
'&, .rc-select-selector, input': {
cursor: 'not-allowed',
},
},
},
label: { fontSize: theme.fontSizes.sm, fontWeight: 500, color: '#212529' },

dropdown: {
fontSize: theme.fontSizes.xs,
zIndex: 300,
backgroundColor: '#fff',
border: '1px solid #e9ecef',
marginTop: 6,
padding: 0,
boxShadow: '0 1px 3px rgb(0 0 0 / 5%), rgb(0 0 0 / 5%) 0px 10px 15px -5px, rgb(0 0 0 / 4%) 0px 7px 7px -5px',
borderRadius: '4px',
'&.rc-select-dropdown-slide-up-leave-active': {
display: 'none',
},
'.rc-select-item-empty': {
padding: '8px 12px',
},
'.rc-select-item-option': {
boxSizing: 'border-box',
textAlign: 'left',
width: '100%',
padding: '8px 12px 8px 2px',
cursor: 'pointer',
fontSize: '14px',
color: '#000',
borderRadius: '4px',
display: 'flex',
flexWrap: 'nowrap',
overflow: 'hidden',
'&:hover': {
backgroundColor: '#f1f3f5',
},
flexDirection: 'row-reverse',
'.rc-select-item-option-state': {
flexGrow: 0,
flexShrink: 0,
width: '30px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
},
'.rc-select-item-option-content': {
flexGrow: 1,
},
},
},
}));
114 changes: 114 additions & 0 deletions dashboard/src/filter/filter-multi-select/render/widget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {
Badge,
CloseButton,
DefaultProps,
Group,
MantineNumberSize,
Selectors,
Stack,
Text,
Tooltip,
} from '@mantine/core';
import Select, { Option } from 'rc-select';
import { useMemo, useState } from 'react';
import useStyles, { MultiSelectWidgetStylesParams } from './widget.styles';

export type TSelectOption = {
label: string;
value: string;
description: string;
};

type StylesNames = Selectors<typeof useStyles>;

interface IProps extends DefaultProps<StylesNames, MultiSelectWidgetStylesParams> {
radius?: MantineNumberSize;
style?: Record<string, any>;
label: string;
value: string[];
onChange: (v: string[]) => void;
options: TSelectOption[];
disabled: boolean;
}

export const MultiSelectWidget = ({
disabled,
// styling props
classNames,
styles,
unstyled,
radius,
style,
// data props
label,
value,
onChange,
options,
}: IProps) => {
const { classes, cx } = useStyles({ radius }, { name: 'MultiSelectWidget', classNames, styles, unstyled });
const [showTooltip, setShowTooltip] = useState(value?.length > 0);
const handleDropdownVisibleChange = (visible: boolean) => {
setShowTooltip(visible);
};
const tooltipVisible = showTooltip && value?.length > 0;

const [keyword, setKeyword] = useState('');
const filteredOptions = useMemo(() => {
if (!keyword) {
return options;
}

const k = keyword.toLowerCase();
const match = (o: TSelectOption) => o.description.toLowerCase().includes(k) || o.label.toLowerCase().includes(k);
return options.filter(match);
}, [keyword, options]);

return (
<Stack spacing={3}>
<Group position="apart">
<Text className={classes.label}>{label}</Text>
{tooltipVisible && (
<Tooltip label={`${value.length} selected`}>
<Badge>{value.length}</Badge>
</Tooltip>
)}
</Group>
<Select
disabled={disabled}
allowClear
className={cx(classes.root, 'check-select')}
dropdownClassName={cx(classes.dropdown, '')}
onDropdownVisibleChange={handleDropdownVisibleChange}
transitionName="rc-select-dropdown-slide-up"
choiceTransitionName="rc-select-selection__choice-zoom"
style={style}
clearIcon={() => <CloseButton />}
value={value}
onChange={onChange}
onSelect={console.log}
mode="multiple"
maxTagCount={0}
maxTagTextLength={10}
maxTagPlaceholder={(valueList) => `${valueList.length} selected`}
searchValue={keyword}
onSearch={setKeyword}
filterOption={false}
>
{filteredOptions.map((o) => (
<Option key={o.value}>
<Group noWrap>
<div>
<Text size="sm" data-role="label">
{o.label}
</Text>
<Text size="xs" color="dimmed" data-role="description">
{o.description}
</Text>
</div>
</Group>
</Option>
))}
</Select>
</Stack>
);
};
4 changes: 2 additions & 2 deletions dashboard/src/filter/filter-tree-select/render/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ export const FilterTreeSelect = observer(({ label, config, value, onChange }: IF
onChange(newValue, true);
}, [config.default_selection_count, treeData]);

const minWidth = config.min_width ? config.min_width : '200px';
const width = config.min_width ? config.min_width : '200px';

const usingRemoteOptions = !!config.options_query_id;
const disabled = usingRemoteOptions ? loading : false;
return (
<FilterTreeSelectWidget
disabled={disabled}
style={{ minWidth, maxWidth: disabled ? minWidth : 'unset', borderColor: '#e9ecef' }}
style={{ minWidth: '160px', width, maxWidth: disabled ? width : 'unset', borderColor: '#e9ecef' }}
value={value}
onChange={(v: string[]) => onChange(v, false)}
// treeData={config.options}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devtable/root",
"version": "8.64.0",
"version": "8.65.0",
"private": true,
"workspaces": [
"api",
Expand Down
2 changes: 1 addition & 1 deletion settings-form/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@devtable/settings-form",
"version": "8.64.0",
"version": "8.65.0",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
Expand Down
Loading

0 comments on commit cfcbb91

Please sign in to comment.