From 6cb4274c6edce4e20173453e6138d6585db22714 Mon Sep 17 00:00:00 2001 From: GongFlying <150640661+gzcqqqqqqqq1@users.noreply.github.com> Date: Tue, 28 Apr 2026 17:36:01 +0800 Subject: [PATCH 01/39] update base ui --- src/AsyncUserSelect/index.css | 57 ++++++++++++--- src/AsyncUserSelect/index.js | 41 +++++++---- src/DTableCustomizeSelect/index.css | 29 ++++++-- .../utils/filter-item-utils.js | 4 +- .../widgets/collaborator-filter/index.js | 2 +- .../widgets/filter-list/index.css | 4 +- src/DTableGroupSelect/index.css | 73 +++++++++++++++++-- src/DTableGroupSelect/index.js | 2 +- src/DTableGroupSelect/select-option-group.css | 25 +++---- src/DTableGroupSelect/select-option-group.js | 21 +++++- src/DTableSelect/index.js | 8 +- src/DTableSelect/utils.js | 62 ++++++++++++++-- src/SelectOptionGroup/index.css | 36 ++++++--- src/SelectOptionGroup/index.js | 41 +++++++++-- 14 files changed, 315 insertions(+), 90 deletions(-) diff --git a/src/AsyncUserSelect/index.css b/src/AsyncUserSelect/index.css index 6b3bd912..b17efc86 100644 --- a/src/AsyncUserSelect/index.css +++ b/src/AsyncUserSelect/index.css @@ -3,6 +3,7 @@ height: auto !important; min-height: 38px; max-width: calc(100% - 10px); + padding: 0.4375rem 1rem 0.4375rem 0.5rem; } .dtable-ui-selected-users-container .dtable-ui-user-select-placeholder { @@ -23,34 +24,45 @@ position: relative; } -.dtable-ui-user-select-container .dtable-ui-user-search-container { - padding: 10px 10px 0 10px; -} - .dtable-ui-user-select-container .dtable-ui-user-search-container input { - height: 28px; + height: 32px; } .dtable-ui-user-select-container .dtable-ui-user-list-container { min-height: 160px; max-height: 200px; - margin: 10px 0; + padding: 8px !important; overflow: auto; + position: relative; +} + +.dtable-ui-user-select-container .form-control { + border: none; + border-bottom: 1px solid rgb(0 40 100 / 12%) !important; + border-radius: 4px 4px 0 0; + padding: .375rem 1rem !important; +} + +.dtable-ui-user-select-container .form-control:focus { + border-color: transparent !important; + border-bottom: 1px solid rgb(0 40 100 / 12%) !important; + box-shadow: none !important; } .dtable-ui-user-select-container .dtable-ui-user-list-container .dtable-ui-user-item-container { display: flex; align-items: center; justify-content: space-between; - height: 30px; - padding: 0 10px; + height: 32px; + padding: 0 8px; font-size: 14px; cursor: pointer; + border-radius: 4px; } .dtable-ui-user-select-container .dtable-ui-user-list-container .dtable-ui-user-item-container:hover, .dtable-ui-user-select-container .dtable-ui-user-list-container .dtable-ui-user-item-container-highlight { - background: #f5f5f5; + background-color: rgba(0, 0, 0, 0.04); cursor: pointer; } @@ -66,16 +78,33 @@ background: transparent; } -.dtable-ui-user-list-container .dtable-ui-collaborator-check-icon .dtable-icon-check-mark { - color: #798d99; +.dtable-ui-user-list-container .dtable-ui-collaborator-check-icon .dtable-icon-check { + color: #666666; + font-size: 14px; +} + +.dtable-ui-user-select-container .dtable-ui-user-search-container .search-clear-btn { + position: absolute; + top: 5px; + right: 10px; + height: 22px; + line-height: 22px; + width: 28px; + text-align: center; + color: #999; + cursor: pointer; +} + +.dtable-ui-user-select-container .dtable-ui-user-search-container .search-clear-btn .dtable-icon-x { font-size: 12px; + color: #999999; } /* user-item */ .dtable-ui-user-item { display: inline-flex; align-items: center; - margin: 2px 10px 2px 0; + margin: 2px 4px 2px 0; padding: 0 8px 0 2px; height: 20px; font-size: 13px; @@ -125,3 +154,7 @@ color: #666666; cursor: pointer; } + +.dtable-ui-selected-users-container .dtable-ui-users-input { + padding-right: 37px; +} diff --git a/src/AsyncUserSelect/index.js b/src/AsyncUserSelect/index.js index c624ec44..c6030515 100644 --- a/src/AsyncUserSelect/index.js +++ b/src/AsyncUserSelect/index.js @@ -191,20 +191,22 @@ const AsyncUserSelect = ({ className, emptyPlaceholder = '', searchPlaceholder = <>
- {selectedUsers.map((user, index) => { - return ( - - ); - })} - {selectedUsers.length === 0 && ( -
- {emptyPlaceholder || getLocale('Search_users')} -
- )} +
+ {selectedUsers.map((user, index) => { + return ( + + ); + })} + {selectedUsers.length === 0 && ( +
+ {emptyPlaceholder || getLocale('Search_users')} +
+ )} +
{selectorRef.current && ( + {searchValue && ( +
onSearchValueChanged('')} + title={getLocale('Clear_search_text')} + > + +
+ )}
{searchedUsers.length > 0 && ( @@ -239,7 +250,7 @@ const AsyncUserSelect = ({ className, emptyPlaceholder = '', searchPlaceholder = {selectedUsers.find(u => u.email === user.email) && (
- +
)}
diff --git a/src/DTableCustomizeSelect/index.css b/src/DTableCustomizeSelect/index.css index c425303e..bd68e391 100644 --- a/src/DTableCustomizeSelect/index.css +++ b/src/DTableCustomizeSelect/index.css @@ -4,7 +4,7 @@ .dtable-select.custom-select { display: flex; - padding: 0 10px; + padding: 0 16px; border-radius: 3px; align-items: center; justify-content: space-between; @@ -39,14 +39,10 @@ .dtable-select .dtable-icon-down3 { display: inline-block; font-size: 12px; - color: #999; + color: #666666; transition: all 0.1s; } -.dtable-select .dtable-icon-down3:hover { - color: #555; -} - .dtable-select .selected-option { display: flex; flex: 1; @@ -98,3 +94,24 @@ font-size: 14px; white-space: nowrap; } + +.option-group .option-group-selector-multiple-select .option-group-search, +.option-group .option-group-selector-single-select .option-group-search { + padding: 0 0 8px 0 !important; +} + +.option-group-selector-multiple-select .form-control, +.option-group-selector-single-select .form-control { + border: none; + border-bottom: 1px solid rgb(0 40 100 / 12%) !important; + border-radius: 4px 4px 0 0; + padding: .375rem .5rem !important; + height: 32px; +} + +.option-group-selector-multiple-select .form-control:focus, +.option-group-selector-single-select .form-control:focus { + border-color: transparent !important; + border-bottom: 1px solid rgb(0 40 100 / 12%) !important; + box-shadow: none !important; +} diff --git a/src/DTableFiltersPopover/utils/filter-item-utils.js b/src/DTableFiltersPopover/utils/filter-item-utils.js index 3f7c3fe0..42a7316e 100644 --- a/src/DTableFiltersPopover/utils/filter-item-utils.js +++ b/src/DTableFiltersPopover/utils/filter-item-utils.js @@ -72,7 +72,7 @@ class FilterItemUtils {
{option.name}
- {selectedOption?.id === option.id && } + {selectedOption?.id === option.id && }
) @@ -86,7 +86,7 @@ class FilterItemUtils {
{option.name}
- {filterTerm.indexOf(option.id) > -1 && } + {filterTerm.indexOf(option.id) > -1 && }
) diff --git a/src/DTableFiltersPopover/widgets/collaborator-filter/index.js b/src/DTableFiltersPopover/widgets/collaborator-filter/index.js index d9efc883..f84ab1c8 100644 --- a/src/DTableFiltersPopover/widgets/collaborator-filter/index.js +++ b/src/DTableFiltersPopover/widgets/collaborator-filter/index.js @@ -51,7 +51,7 @@ class CollaboratorFilter extends Component {
- {isSelected && } + {isSelected && }
diff --git a/src/DTableFiltersPopover/widgets/filter-list/index.css b/src/DTableFiltersPopover/widgets/filter-list/index.css index 0b076a0c..39412c41 100644 --- a/src/DTableFiltersPopover/widgets/filter-list/index.css +++ b/src/DTableFiltersPopover/widgets/filter-list/index.css @@ -205,7 +205,7 @@ .option-group-dtable-ui-collaborator-selector .option:hover, .option-group-dtable-ui-collaborator-selector .option.option-active { color: #212529; - background-color: #f7f7f7; + background-color: #f5f5f5; } .dtable-ui-filters-list .dtable-ui-multiple-select-selector .option.option-active .select-option-name, @@ -333,7 +333,7 @@ display: inline-flex; align-items: center; height: 20px; - margin-right: 10px; + margin-right: 4px; padding: 0 8px 0 2px; font-size: 13px; border-radius: 10px; diff --git a/src/DTableGroupSelect/index.css b/src/DTableGroupSelect/index.css index 142f671c..ab1667eb 100644 --- a/src/DTableGroupSelect/index.css +++ b/src/DTableGroupSelect/index.css @@ -4,7 +4,7 @@ .group-select.custom-select { display: flex; - padding: 5px 10px; + padding: 7px 8px !important; border-radius: 3px; align-items: center; justify-content: space-between; @@ -79,18 +79,81 @@ } .group-select .selected-option-show .selected-option-item { - background-color: rgb(240, 240, 240); - border-radius: 16px; + background-color: #F7F7F5; + border-radius: 4px !important; display: flex; align-items: center; + height: 24px; } .group-select .selected-option-show .selected-option-item .selected-option-item-name { - font-size: 13px; + font-size: 14px !important; color: #212529; + margin-left: 6px; + margin-right: 8px } .group-select .selected-option-show .selected-option-item .dtable-icon-x { cursor: pointer; - color: rgb(103, 103, 103); + color: #666666; + font-size: 12px; + margin-right: 6px; +} + +.seatable-group-select .option-group-search { + padding: 0 !important; +} + +.seatable-group-select .form-control { + border: none; + border-bottom: 1px solid rgb(0 40 100 / 12%) !important; + border-radius: 4px 4px 0 0; + padding: .375rem 1rem !important; + height: 32px; +} + +.seatable-group-select .form-control:focus { + border-color: transparent !important; + border-bottom: 1px solid rgb(0 40 100 / 12%) !important; + box-shadow: none !important; +} + +.option-group-content .dtable-icon-check { + color: #666666; + font-size: 14px; + margin-left: 16px; +} + +.option-group-content .label-check-icon { + width: 30px; +} + +.seatable-group-select .clear-search-text { + position: absolute; + display: inline-block; + height: 12px; + width: 12px; + line-height: 12px; + top: 10px !important; + right: 18px; +} + +.seatable-group-select .seatable-icon-btn:not(.disabled):hover { + background-color:#FFFFFF; +} + +.option-group-dtable-filter-select-column .seatable-icon-btn { + height: 12px !important; + width: 12px !important; + font-size: 12px !important; +} + +.option-group-dtable-filter-select-column .clear-search-text { + position: absolute; + display: inline-block; + height: 12px; + width: 12px; + line-height: 12px; + top: 12px; + right: 18px; } diff --git a/src/DTableGroupSelect/index.js b/src/DTableGroupSelect/index.js index 03437265..6f31aeee 100644 --- a/src/DTableGroupSelect/index.js +++ b/src/DTableGroupSelect/index.js @@ -75,7 +75,7 @@ class DTableGroupSelect extends Component { {selectedOptions.length > 0 ? {selectedOptions.map(item => - + {item.name} { this.props.onDeleteOption(item); }}> diff --git a/src/DTableGroupSelect/select-option-group.css b/src/DTableGroupSelect/select-option-group.css index 90f53cc9..555b279e 100644 --- a/src/DTableGroupSelect/select-option-group.css +++ b/src/DTableGroupSelect/select-option-group.css @@ -2,7 +2,6 @@ display: block; width: 100%; line-height: 24px; - padding: 6px 10px; clear: both; font-weight: 400; text-align: inherit; @@ -14,26 +13,26 @@ display: flex; align-items: center; justify-content: space-between; + padding: 0.25rem 8px; } -.group-selector .option .dtable-icon-check-mark { - font-size: 12px; - color: #798d99; +.group-selector .option .dtable-icon-check{ + font-size: 14px; + color: #666666; + height: 14px !important; + display: flex; + align-items: center; + justify-content: center; } .group-selector .option.option-active { - background-color: #20a0ff; - color: #fff; + background-color: #f5f5f5; + color: #212529; cursor: pointer; } -.group-selector .option.option-active .dtable-icon-check-mark, -.group-selector .option.option-active .select-option-name { - color: #fff !important; -} - -.group-selector .option .select-option-name .single-select-option { - margin: 0 0 0 12px; +.group-selector .option.option-active .dtable-icon-check-mark { + color: #666666 !important; } .group-selector .option .select-option-name .multiple-select-option { diff --git a/src/DTableGroupSelect/select-option-group.js b/src/DTableGroupSelect/select-option-group.js index 2fd83a94..3dd85ec3 100644 --- a/src/DTableGroupSelect/select-option-group.js +++ b/src/DTableGroupSelect/select-option-group.js @@ -4,6 +4,8 @@ import classnames from 'classnames'; import DTableSearchInput from '../DTableSearchInput'; import Option from './option'; import KeyCodes from '../SelectOptionGroup/KeyCodes'; +import IconButton from '../IconButton'; +import { getLocale } from '../lang'; import './select-option-group.css'; @@ -153,7 +155,7 @@ class SelectOptionGroup extends Component { disableHover={this.state.disableHover} >
{option.label}
- {isSelected && } + {isSelected && } ); }); @@ -170,24 +172,35 @@ class SelectOptionGroup extends Component { style = { position: 'fixed', left: position.x, - top: position.y + position.height, + top: position.y + position.height + 4, minWidth: position.width, opacity: 0, }; } return (
this.optionGroupRef = ref} style={style} onMouseDown={this.onMouseDown} > -
+
{ + return ( + + + + ); + }, + }} />
this.optionGroupContentRef = ref}> diff --git a/src/DTableSelect/index.js b/src/DTableSelect/index.js index 45a87799..f20e248c 100644 --- a/src/DTableSelect/index.js +++ b/src/DTableSelect/index.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Select from 'react-select'; -import { MenuSelectStyle, DropdownIndicator, ClearIndicator, MenuList, Option } from './utils'; +import { MenuSelectStyle, DropdownIndicator, ClearIndicator, MenuList, Option, processOptionsWithClear, createHandleChange } from './utils'; export default class DTableSelect extends React.Component { @@ -49,11 +49,12 @@ export default class DTableSelect extends React.Component { const { options, onChange, value, isSearchable, placeholder, isMulti, menuPosition, isClearable, noOptionsMessage, classNamePrefix, style, innerRef, isDisabled, form, customFilterOption, autoFocus, className, closeMenuOnSelect, onMenuClose, components: userComponents } = this.props; const mergedComponents = { Option, DropdownIndicator, MenuList, ClearIndicator, ...userComponents }; + const processedOptions = processOptionsWithClear(options, isClearable); return ( { return options; }; -const handleSelectChange = (selectedOption, actionMeta, onChangeCallback) => { - if (selectedOption && selectedOption.value === '__clear__') { +const handleSelectChange = (selectedOption, actionMeta, onChangeCallback, isMulti) => { + if (isMulti && Array.isArray(selectedOption)) { + const hasClear = selectedOption && selectedOption.some(opt => opt && opt.value === '__clear__'); + if (hasClear) { + onChangeCallback([], { ...actionMeta, action: 'clear' }); + return; + } + } else if (selectedOption && selectedOption.value === '__clear__') { onChangeCallback(null, { ...actionMeta, action: 'clear' }); - } else { - onChangeCallback(selectedOption, actionMeta); + return; } + + onChangeCallback(selectedOption, actionMeta); }; -const createHandleChange = (onChange) => { +const createHandleChange = (onChange, isMulti) => { return (selectedOption, actionMeta) => { - handleSelectChange(selectedOption, actionMeta, onChange); + handleSelectChange(selectedOption, actionMeta, onChange, isMulti); }; }; diff --git a/src/SelectOptionGroup/index.css b/src/SelectOptionGroup/index.css index 866eb99d..3f86a68c 100644 --- a/src/SelectOptionGroup/index.css +++ b/src/SelectOptionGroup/index.css @@ -41,6 +41,7 @@ flex: 1; max-height: 252px; overflow-y: auto; + padding: 8px; } .option-group .option-group-top { @@ -51,7 +52,7 @@ display: block; width: 100%; line-height: 24px; - padding: 0.25rem 10px; + padding: 0.25rem 8px; clear: both; font-weight: 400; text-align: inherit; @@ -60,30 +61,40 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + border-radius: 4px; } -.option.option-active { - background-color: #f5f5f5; - color: #212529; - cursor: pointer; +.option-group .option-group-search .form-control { + border: none; + border-bottom: 1px solid rgb(0 40 100 / 12%) !important; + border-radius: 4px 4px 0 0; + padding: .375rem 1rem !important; + height: 32px; } -.option:hover .header-icon .dtable-font, -.option:hover .header-icon .multicolor-icon, -.option.option-active .select-option-name { - color: #fff; +.option-group .option-group-search .form-control:focus { + border-color: transparent !important; + border-bottom: 1px solid rgb(0 40 100 / 12%) !important; + box-shadow: none !important; } -.option.option-active .header-icon .dtable-font, -.option.option-active .header-icon .multicolor-icon { - color: #fff; + +.option-group-content .label-check-icon { + margin-left: 16px; } -.option:not(.option-active):hover .header-icon .dtable-font, -.option:not(.option-active):hover .header-icon .multicolor-icon { - color: #aaa; +.option-group-content .dtable-icon-check { + font-size: 14px !important; + color: #666666; } +.option.option-active { + background-color: #f5f5f5; + color: #212529; + cursor: pointer; +} + + .option .select-option-name .multiple-select-option { margin: 0; } diff --git a/src/SelectOptionGroup/index.js b/src/SelectOptionGroup/index.js index bd67aa80..7b7da239 100644 --- a/src/SelectOptionGroup/index.js +++ b/src/SelectOptionGroup/index.js @@ -5,8 +5,6 @@ import PropTypes from 'prop-types'; import DTableSearchInput from '../DTableSearchInput'; import KeyCodes from './KeyCodes'; import ClickOutside from '../ClickOutside'; -import IconButton from '../IconButton'; -import { getLocale } from '../lang'; import './index.css'; @@ -227,11 +225,7 @@ class SelectOptionGroup extends Component { isClearable={true} components={{ ClearIndicator: (props) => { - return ( - - - - ); + return { e.stopPropagation(); e.nativeEvent.stopImmediatePropagation(); props.clearValue(); }}/>; }, }} /> From ee7a5b31f0198dd1475ffc6c84af6a332982f394 Mon Sep 17 00:00:00 2001 From: GongFlying <150640661+gzcqqqqqqqq1@users.noreply.github.com> Date: Mon, 18 May 2026 10:43:05 +0800 Subject: [PATCH 03/39] update codes --- .../index.js | 16 ++ src/DTableCustomizeSearchInput/index.css | 82 ++++++ src/DTableCustomizeSearchInput/index.js | 136 ++++++++++ src/DTableCustomizeSelect/index.css | 103 ++++---- src/DTableCustomizeSelect/index.js | 31 ++- .../select-option-group/KeyCodes.js | 104 ++++++++ .../select-option-group/index.css | 122 +++++++++ .../select-option-group/index.js | 244 ++++++++++++++++++ .../select-option-group/option.js | 55 ++++ src/DTableCustomizeSelect/util.js | 5 + src/DTableGroupSelect/index.css | 5 +- src/DTableGroupSelect/select-option-group.css | 5 +- src/DTableGroupSelect/select-option-group.js | 20 +- src/DTableSelect/index.js | 5 +- .../select-dropdown-indicator/index.css | 12 + .../select-dropdown-indicator/index.js | 14 + src/DTableSelect/select-style.js | 147 +++++++++++ src/DTableSelect/user-select.css | 53 +--- src/DTableSelect/utils.js | 229 ---------------- src/SelectOptionGroup/index.css | 4 +- src/assets/icons/close.svg | 1 + src/assets/icons/down.svg | 3 + src/index.js | 1 + 23 files changed, 1028 insertions(+), 369 deletions(-) create mode 100644 src/DTableCustomizeSearchInput/index.css create mode 100644 src/DTableCustomizeSearchInput/index.js create mode 100644 src/DTableCustomizeSelect/select-option-group/KeyCodes.js create mode 100644 src/DTableCustomizeSelect/select-option-group/index.css create mode 100644 src/DTableCustomizeSelect/select-option-group/index.js create mode 100644 src/DTableCustomizeSelect/select-option-group/option.js create mode 100644 src/DTableCustomizeSelect/util.js create mode 100644 src/DTableSelect/select-dropdown-indicator/index.css create mode 100644 src/DTableSelect/select-dropdown-indicator/index.js create mode 100644 src/DTableSelect/select-style.js delete mode 100644 src/DTableSelect/utils.js create mode 100644 src/assets/icons/close.svg create mode 100644 src/assets/icons/down.svg diff --git a/src/DTableCustomizeCollaboratorSelect/index.js b/src/DTableCustomizeCollaboratorSelect/index.js index 727a42c4..742f4f22 100644 --- a/src/DTableCustomizeCollaboratorSelect/index.js +++ b/src/DTableCustomizeCollaboratorSelect/index.js @@ -47,6 +47,20 @@ class CollaboratorSelect extends Component { }); }; + onClick = (event) => { + const target = event.target; + const name = target.className; + const { isShowSelectOptions } = this.state; + if (!isShowSelectOptions || name === 'select-placeholder' || name.includes('icon-fork-number') || this.selector.contains(target)) { + return; + } + const { isUsePopover } = this.props; + if ((isUsePopover && !this.collaboratorSelectPopoverRef.contains(target)) +|| (!isUsePopover && !this.selector.contains(target))) { + this.closeSelect(); + } + } + onMousedown = (event) => { const name = event.target.className; if (name === 'select-placeholder' || name.includes('icon-fork-number')) { @@ -87,6 +101,7 @@ class CollaboratorSelect extends Component { supportMultipleSelect={this.props.supportMultipleSelect} stopClickEvent={true} isShowSelected={true} + onClickOutside={this.onClick} /> ); } @@ -114,6 +129,7 @@ class CollaboratorSelect extends Component { supportMultipleSelect={this.props.supportMultipleSelect} stopClickEvent={true} isShowSelected={true} + onClickOutside={this.onClick} /> ); diff --git a/src/DTableCustomizeSearchInput/index.css b/src/DTableCustomizeSearchInput/index.css new file mode 100644 index 00000000..6a04661b --- /dev/null +++ b/src/DTableCustomizeSearchInput/index.css @@ -0,0 +1,82 @@ + +.seatable-search-input { + width: 100%; + padding: 6px 10px; + min-width: 170px; + position: relative; + padding: 0; + height: 32px; + border-bottom: 1px solid var(--bs-border-color); +} + +.seatable-search-input .form-control { + height: 32px; + border: none; + padding: .375rem 0 .375rem 1rem; + cursor: pointer; + border-bottom: 1px solid var(--bs-border-color); +} + +.seatable-search-input-multiple .form-control:focus, +.seatable-search-input .form-control:focus { + outline: none; + box-shadow: none; +} + +.seatable-search-input .input-icon-addon { + z-index: 1; + pointer-events: auto; + min-width: 2.75rem !important; +} + +.seatable-search-input .input-icon-addon svg { + color: var(--bs-body-secondary-color); + cursor: pointer; +} + +.seatable-search-input .none-search-result { + height: 100px; + width: 100%; + padding: 10px; + color: var(--bs-body-secondary-color); +} + + +.seatable-search-input-multiple { + width: 100%; + padding: 6px 10px; + min-width: 170px; + position: relative; + height: 40px; +} + +.seatable-search-input-multiple .form-control { + height: 30px; + padding: .375rem 2.5rem .375rem 1rem; + cursor: pointer; +} + +.seatable-search-input-multiple .input-icon-addon { + z-index: 1; + pointer-events: auto; + min-width: 2.75rem !important; + top: 10px; + height: 30px; + right: 10px; +} + +.seatable-search-input-multiple .input-icon-addon svg { + color: var(--bs-body-secondary-color); + cursor: pointer; +} + +.seatable-search-input-multiple .none-search-result { + height: 100px; + width: 100%; + padding: 10px; + color: var(--bs-body-secondary-color); +} + +.multiple-selects-editor-list .seatable-search-input-multiple { + padding: 10px 10px 0; +} diff --git a/src/DTableCustomizeSearchInput/index.js b/src/DTableCustomizeSearchInput/index.js new file mode 100644 index 00000000..a9a4ce89 --- /dev/null +++ b/src/DTableCustomizeSearchInput/index.js @@ -0,0 +1,136 @@ +import React, { Component, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import DTableIcon from '../DTableIcon'; + +import './index.css'; + +const propTypes = { + placeholder: PropTypes.string, + autoFocus: PropTypes.bool, + className: PropTypes.string, + onChange: PropTypes.func.isRequired, + onKeyDown: PropTypes.func, + wait: PropTypes.number, + disabled: PropTypes.bool, + style: PropTypes.object, + isClearable: PropTypes.bool, + clearValue: PropTypes.func, + clearClassName: PropTypes.string, + value: PropTypes.string, +}; + +class DTableCustomizeSearchInput extends Component { + + constructor(props) { + super(props); + this.state = { + searchValue: props.value || '', + }; + this.isInputtingChinese = false; + this.timer = null; + this.inputRef = null; + } + + componentDidMount() { + if (this.props.autoFocus && this.inputRef && this.inputRef !== document.activeElement) { + setTimeout(() => { + this.inputRef.focus(); + }, 0); + } + } + + UNSAFE_componentWillReceiveProps(nextProps) { + if (nextProps.value !== this.props.value) { + this.setState({ searchValue: nextProps.value }); + } + } + + componentWillUnmount() { + this.timer && clearTimeout(this.timer); + this.timer = null; + this.inputRef = null; + } + + onCompositionStart = () => { + this.isInputtingChinese = true; + }; + + onChange = (e) => { + this.timer && clearTimeout(this.timer); + const { onChange, wait = 100 } = this.props; + let text = e.target.value; + this.setState({ searchValue: text || '' }, () => { + if (this.isInputtingChinese) return; + this.timer = setTimeout(() => { + onChange && onChange(this.state.searchValue.trim()); + }, wait); + }); + }; + + onCompositionEnd = (e) => { + this.isInputtingChinese = false; + this.onChange(e); + }; + + clearSearch = (e) => { + e && e.stopPropagation && e.stopPropagation(); + const { clearValue } = this.props; + this.setState({ searchValue: '' }, () => { + clearValue && clearValue(); + }); + }; + + setFocus = (isSelectAllText) => { + if (this.inputRef === document.activeElement) return; + this.inputRef.focus(); + if (isSelectAllText) { + const txtLength = this.state.searchValue.length; + this.inputRef.setSelectionRange(0, txtLength); + } + }; + + renderClear = () => { + const { isClearable, clearClassName } = this.props; + const { searchValue } = this.state; + if (!isClearable || !searchValue) return null; + + return ( + + + + ); + }; + + render() { + const { placeholder, autoFocus, className, onKeyDown, disabled = false, style = {}, isClearable, isMultiple = false } = this.props; + const { searchValue } = this.state; + const inputWidth = (isClearable && searchValue && !isMultiple) ? 'calc(100% - 40px)' : '100%'; + style.width = inputWidth; + + return ( + + this.inputRef = ref} + /> + {this.renderClear()} + + ); + } +} + +DTableCustomizeSearchInput.propTypes = propTypes; + +export default DTableCustomizeSearchInput; diff --git a/src/DTableCustomizeSelect/index.css b/src/DTableCustomizeSelect/index.css index bd68e391..1af518ac 100644 --- a/src/DTableCustomizeSelect/index.css +++ b/src/DTableCustomizeSelect/index.css @@ -1,117 +1,108 @@ -.dtable-select { +.seatable-customize-select { position: relative; -} - -.dtable-select.custom-select { display: flex; padding: 0 16px; - border-radius: 3px; + border-radius: 4px; align-items: center; justify-content: space-between; max-width: 900px; user-select: none; - -webkit-user-select: none; - -moz-user-select: none; - -o-user-select: none; - -ms-user-select: none; text-align: left; + line-height: 1.5; + background-image: none; + font-size: 14px; + color: var(--bs-body-color); + border: 1px solid rgba(0, 40, 100, 0.12); } -.dtable-select.custom-select:focus, -.dtable-select.custom-select.focus { - border-color: #1991eb !important; - box-shadow: 0 0 0 2px rgba(70, 127, 207, 0.25); +.seatable-customize-select:focus, +.seatable-customize-select.focus { + border-color: #3E84F7 !important; } -.dtable-select.custom-select.disabled:focus, -.dtable-select.custom-select.focus.disabled, -.dtable-select.custom-select.disabled:hover { +.seatable-customize-select.disabled:focus, +.seatable-customize-select.focus.disabled, +.seatable-customize-select.disabled:hover { border-color: rgba(0, 40, 100, 0.12) !important; box-shadow: unset; cursor: default; } -.dtable-select.custom-select:hover { +.seatable-customize-select:hover { cursor: pointer; - border-color: rgb(179, 179, 179); + border-color: var(--bs-border-color); } -.dtable-select .dtable-icon-down3 { - display: inline-block; - font-size: 12px; - color: #666666; - transition: all 0.1s; +.seatable-customize-select .seatable-multicolor-icon-down { + width: 12px; + height: 12px; + color: var(--bs-icon-color); } -.dtable-select .selected-option { +.seatable-customize-select .selected-option { display: flex; flex: 1; overflow: hidden; flex-wrap: nowrap; align-items: center; justify-content: space-between; - background: #fff; + background: inherit; } -.dtable-select.dtable-ui-collaborator-selector .option-group .option-group-content, -.dtable-select.selector-group .option-group .option-group-content { +.seatable-check-color { + color: var(--bs-icon-color); +} + +.seatable-customize-select.dtable-ui-collaborator-selector .seatable-option-group .seatable-option-group-content, +.seatable-customize-select.selector-group .seatable-option-group .seatable-option-group-content { padding: 10px; } -.dtable-select.custom-select.dtable-ui-collaborator-selector .option-group .option-group-content { +.seatable-customize-select.dtable-ui-collaborator-selector .seatable-option-group .seatable-option-group-content { padding: 10px 0; } -.dtable-select.custom-select.dtable-ui-collaborator-selector .option { +.seatable-customize-select.dtable-ui-collaborator-selector .option { padding: 5px 0 5px 10px !important; line-height: 20px; } -.dtable-select.selector-group .option { +.seatable-customize-select.selector-group .option { height: 30px; display: flex; align-items: center; } -.dtable-select.selector-group .select-group-option { +.seatable-customize-select.selector-group .select-group-option { justify-content: space-between; } -.dtable-select.selector-group .selected-option .selected-group { +.seatable-customize-select.selector-group .selected-option .selected-group { padding: 0 2px; background: #eceff4; border-radius: 3px; } -.dtable-select .selected-option-show { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.dtable-select .select-placeholder { +.seatable-customize-select .select-placeholder { line-height: 1; font-size: 14px; white-space: nowrap; + color: #868E96; + margin-right: 8px; } -.option-group .option-group-selector-multiple-select .option-group-search, -.option-group .option-group-selector-single-select .option-group-search { - padding: 0 0 8px 0 !important; -} - -.option-group-selector-multiple-select .form-control, -.option-group-selector-single-select .form-control { - border: none; - border-bottom: 1px solid rgb(0 40 100 / 12%) !important; - border-radius: 4px 4px 0 0; - padding: .375rem .5rem !important; - height: 32px; +.seatable-customize-select .selected-option-show { + height: 20px; + line-height: 20px; + margin-right: 8px; + width: calc(100% - 20px); + white-space: nowrap; } -.option-group-selector-multiple-select .form-control:focus, -.option-group-selector-single-select .form-control:focus { - border-color: transparent !important; - border-bottom: 1px solid rgb(0 40 100 / 12%) !important; - box-shadow: none !important; +.seatable-customize-select .multiple-check-icon, +.seatable-customize-select .header-icon .dtable-font, +.seatable-option-group .select-label .header-icon .dtable-font { + color: var(--bs-icon-secondary-color) !important; + cursor: default; + font-size: 14px; } diff --git a/src/DTableCustomizeSelect/index.js b/src/DTableCustomizeSelect/index.js index 8cb78673..219ae099 100644 --- a/src/DTableCustomizeSelect/index.js +++ b/src/DTableCustomizeSelect/index.js @@ -1,8 +1,10 @@ import React, { Component } from 'react'; -import SelectOptionGroup from '../SelectOptionGroup'; import PropTypes from 'prop-types'; import classnames from 'classnames'; +import SelectOptionGroup from './select-option-group'; import ModalPortal from '../ModalPortal'; +import { onKeyDown } from './util'; +import DTableIcon from '../DTableIcon'; import './index.css'; @@ -23,8 +25,8 @@ class DTableCustomizeSelect extends Component { */ if (this.state.isShowSelectOptions) event.stopPropagation(); let eventClassName = event.target.className; - if (this.props.isLocked || eventClassName.indexOf('option-search-control') > -1 || eventClassName === 'option-group-search') return; - // Prevent closing by pressing the spacebar in the search input + if (this.props.isLocked || eventClassName.indexOf('option-search-control') > -1 || eventClassName === 'seatable-option-group-search') return; + // Prevent closing by pressing the space bar in the search input if (event.target.value === '') return; this.setState({ isShowSelectOptions: !this.state.isShowSelectOptions @@ -74,29 +76,32 @@ class DTableCustomizeSelect extends Component { }; render() { - let { className, value, options, placeholder, searchable, searchPlaceholder, noOptionsPlaceholder, - isLocked, isInModal, addOptionAble, component } = this.props; + const { className, value, options, placeholder, searchable, searchPlaceholder, noOptionsPlaceholder, + isLocked, isInModal, component } = this.props; + return (
this.selector = node} - className={classnames('dtable-select custom-select', + className={classnames('seatable-customize-select custom-select', { 'focus': this.state.isShowSelectOptions }, { 'disabled': isLocked }, className )} - onClick={this.onSelectToggle}> + onClick={this.onSelectToggle} + onKeyDown={onKeyDown} + >
{value && value.label ? {value.label} - : - {placeholder} + : {placeholder} } - {!isLocked && } + {!isLocked && }
{this.state.isShowSelectOptions && !isInModal && ( { + this.resetMenuStyle(); + }, 1); + } + + componentWillUnmount() { + this.filterOptions = null; + this.timer && clearTimeout(this.timer); + window.removeEventListener('keydown', this.onHotKey); + } + + resetMenuStyle = () => { + const { isInModal, position } = this.props; + const { top, height } = this.optionGroupRef.getBoundingClientRect(); + if (isInModal) { + if (position.y + position.height + height > window.innerHeight) { + this.optionGroupRef.style.top = (position.y - height) + 'px'; + } + this.optionGroupRef.style.opacity = 1; + } + else { + if (height + top > window.innerHeight) { + const borderWidth = 2; + this.optionGroupRef.style.top = -1 * (height + borderWidth) + 'px'; + } + } + }; + + onHotKey = (event) => { + const keyCode = event.keyCode; + if (keyCode === KeyCodes.UpArrow) { + this.onPressUp(); + } else if (keyCode === KeyCodes.DownArrow) { + this.onPressDown(); + } else if (keyCode === KeyCodes.Enter) { + let option = this.filterOptions && this.filterOptions[this.state.activeIndex]; + if (option) { + this.props.onSelectOption(option.value); + if (!this.props.supportMultipleSelect) { + this.props.closeSelect(); + } + } + } else if (keyCode === KeyCodes.Tab || keyCode === KeyCodes.Escape) { + this.props.closeSelect(); + } + }; + + onPressUp = () => { + if (this.state.activeIndex > 0) { + this.setState({ activeIndex: this.state.activeIndex - 1 }, () => { + this.scrollContent(); + }); + } + }; + + onPressDown = () => { + if (this.filterOptions && this.state.activeIndex < this.filterOptions.length - 1) { + this.setState({ activeIndex: this.state.activeIndex + 1 }, () => { + this.scrollContent(); + }); + } + }; + + onMouseDown = (e) => { + const { isInModal } = this.props; + // prevent event propagation when click option or search input + if (isInModal) { + e.stopPropagation(); + e.nativeEvent.stopImmediatePropagation(); + } + }; + + scrollContent = () => { + const { offsetHeight, scrollTop } = this.optionGroupContentRef; + this.setState({ disableHover: true }); + this.timer = setTimeout(() => { + this.setState({ disableHover: false }); + }, 500); + if (this.state.activeIndex * OPTION_HEIGHT === 0) { + this.optionGroupContentRef.scrollTop = 0; + return; + } + + if (this.state.activeIndex * OPTION_HEIGHT < scrollTop) { + this.optionGroupContentRef.scrollTop = scrollTop - OPTION_HEIGHT; + } + else if (this.state.activeIndex * OPTION_HEIGHT > offsetHeight + scrollTop) { + this.optionGroupContentRef.scrollTop = scrollTop + OPTION_HEIGHT; + } + }; + + changeIndex = (index) => { + this.setState({ activeIndex: index }); + }; + + onChangeSearch = (searchVal) => { + let value = searchVal || ''; + if (value !== this.state.searchVal) { + this.setState({ searchVal: value, activeIndex: -1, }); + } + }; + + clearSearch = () => { + this.setState({ searchVal: '', activeIndex: -1, }); + }; + + renderOptGroup = (searchVal) => { + let { noOptionsPlaceholder, onSelectOption, value } = this.props; + this.filterOptions = this.props.getFilterOptions(searchVal); + if (this.filterOptions.length === 0) { + return ( +
{noOptionsPlaceholder}
+ ); + } + return this.filterOptions.map((opt, i) => { + let key = opt.value.column ? opt.value.column.key : i; + let isActive = this.state.activeIndex === i; + let isSelected = false; + if (value) { + // Handle both single value and array of values + if (Array.isArray(value.value)) { + isSelected = value.value.includes(opt.value); + } else { + isSelected = opt.value === value.value || JSON.stringify(opt.value) === JSON.stringify(value.value); + } + } + return ( + + ); + }); + }; + + render() { + const { searchable, searchPlaceholder, top, left, minWidth, value, isShowSelected, isInModal, position, + className } = this.props; + let { searchVal } = this.state; + let style = { top: top || 0, left: left || 0 }; + if (minWidth) { + style = { top: top || 0, left: left || 0, minWidth }; + } + if (isInModal) { + style = { + position: 'fixed', + left: position.x, + top: position.y + position.height, + minWidth: position.width, + opacity: 0, + }; + } + style.top = style.top + 4; + return ( + +
this.optionGroupRef = ref} + style={style} + onMouseDown={this.onMouseDown} + > + {isShowSelected && +
e.stopPropagation()}>{value.label || ''}
+ } + {searchable && ( +
+ +
+ )} +
this.optionGroupContentRef = ref}> + {this.renderOptGroup(searchVal)} +
+
+
+ ); + } +} + +SelectOptionGroup.propTypes = { + top: PropTypes.number, + left: PropTypes.number, + minWidth: PropTypes.number, + options: PropTypes.array, + onSelectOption: PropTypes.func, + searchable: PropTypes.bool, + component: PropTypes.object, + searchPlaceholder: PropTypes.string, + noOptionsPlaceholder: PropTypes.string, + onClickOutside: PropTypes.func.isRequired, + closeSelect: PropTypes.func.isRequired, + getFilterOptions: PropTypes.func.isRequired, + supportMultipleSelect: PropTypes.bool, + value: PropTypes.object, + isShowSelected: PropTypes.bool, + stopClickEvent: PropTypes.bool, + isInModal: PropTypes.bool, + position: PropTypes.object, + className: PropTypes.string, +}; + +export default SelectOptionGroup; diff --git a/src/DTableCustomizeSelect/select-option-group/option.js b/src/DTableCustomizeSelect/select-option-group/option.js new file mode 100644 index 00000000..19e5d0db --- /dev/null +++ b/src/DTableCustomizeSelect/select-option-group/option.js @@ -0,0 +1,55 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { onKeyDown } from '../util'; + +class Option extends Component { + + onSelectOption = (event) => { + if (this.props.supportMultipleSelect) { + event.stopPropagation(); + } + this.props.onSelectOption(this.props.value, event); + }; + + onMouseEnter = () => { + if (!this.props.disableHover) { + this.props.changeIndex(this.props.index); + } + }; + + onMouseLeave = () => { + if (!this.props.disableHover) { + this.props.changeIndex(-1); + } + }; + + render() { + return ( +
+ {this.props.children} +
+ ); + } +} + +Option.propTypes = { + index: PropTypes.number, + isActive: PropTypes.bool, + changeIndex: PropTypes.func, + value: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), + children: PropTypes.oneOfType([PropTypes.node, PropTypes.string]), + onSelectOption: PropTypes.func, + supportMultipleSelect: PropTypes.bool, + disableHover: PropTypes.bool, +}; + +export default Option; diff --git a/src/DTableCustomizeSelect/util.js b/src/DTableCustomizeSelect/util.js new file mode 100644 index 00000000..aa13d984 --- /dev/null +++ b/src/DTableCustomizeSelect/util.js @@ -0,0 +1,5 @@ +export const onKeyDown = (e) => { + if (e.key === 'Enter' || e.key === 'Space') { + e.target.click(); + } +}; diff --git a/src/DTableGroupSelect/index.css b/src/DTableGroupSelect/index.css index 23cd841b..19684c98 100644 --- a/src/DTableGroupSelect/index.css +++ b/src/DTableGroupSelect/index.css @@ -108,7 +108,7 @@ border: none; border-bottom: 1px solid rgb(0 40 100 / 12%) !important; border-radius: 4px 4px 0 0; - padding: .375rem 1rem !important; + padding: .375rem 0 .375rem 1rem; height: 32px; } @@ -119,7 +119,6 @@ } .option-group-content .dtable-icon-check { - color: #666666; + color: var(--bs-icon-color); font-size: 14px; - margin-left: 16px; } diff --git a/src/DTableGroupSelect/select-option-group.css b/src/DTableGroupSelect/select-option-group.css index 555b279e..c4c29e27 100644 --- a/src/DTableGroupSelect/select-option-group.css +++ b/src/DTableGroupSelect/select-option-group.css @@ -14,6 +14,7 @@ align-items: center; justify-content: space-between; padding: 0.25rem 8px; + border-radius: 4px; } .group-selector .option .dtable-icon-check{ @@ -31,10 +32,6 @@ cursor: pointer; } -.group-selector .option.option-active .dtable-icon-check-mark { - color: #666666 !important; -} - .group-selector .option .select-option-name .multiple-select-option { margin: 0; } diff --git a/src/DTableGroupSelect/select-option-group.js b/src/DTableGroupSelect/select-option-group.js index 8711d7fb..112448da 100644 --- a/src/DTableGroupSelect/select-option-group.js +++ b/src/DTableGroupSelect/select-option-group.js @@ -1,11 +1,9 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import DTableSearchInput from '../DTableSearchInput'; +import DTableCustomizeSearchInput from '../DTableCustomizeSearchInput'; import Option from './option'; import KeyCodes from '../SelectOptionGroup/KeyCodes'; -import IconButton from '../IconButton'; -import { getLocale } from '../lang'; import './select-option-group.css'; @@ -184,19 +182,15 @@ class SelectOptionGroup extends Component { style={style} onMouseDown={this.onMouseDown} > -
- + { - return { e.stopPropagation(); e.nativeEvent.stopImmediatePropagation(); props.clearValue(); }}/>; - }, - }} + isClearable={true} + isLeftIcon={true} />
this.optionGroupContentRef = ref}> diff --git a/src/DTableSelect/index.js b/src/DTableSelect/index.js index 66483da8..f20e248c 100644 --- a/src/DTableSelect/index.js +++ b/src/DTableSelect/index.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Select from 'react-select'; -import { MenuSelectStyle, DropdownIndicator, MenuList, Option, processOptionsWithClear, createHandleChange } from './utils'; +import { MenuSelectStyle, DropdownIndicator, ClearIndicator, MenuList, Option, processOptionsWithClear, createHandleChange } from './utils'; export default class DTableSelect extends React.Component { @@ -53,10 +53,9 @@ export default class DTableSelect extends React.Component { return (