diff --git a/src/components/forms/controls/CheckboxGroup/CheckboxGroup.tsx b/src/components/forms/controls/CheckboxGroup/CheckboxGroup.tsx index 4a86029c..fe8b0a48 100644 --- a/src/components/forms/controls/CheckboxGroup/CheckboxGroup.tsx +++ b/src/components/forms/controls/CheckboxGroup/CheckboxGroup.tsx @@ -135,6 +135,7 @@ export const CheckboxGroup = Object.assign( cl['bk-checkbox-group'], { [cl['bk-checkbox-group--horizontal']]: orientation === 'horizontal' }, { [cl['bk-checkbox-group--vertical']]: orientation === 'vertical' }, + propsRest.className, )} contentClassName={cl['bk-checkbox-group__content']} > diff --git a/src/components/forms/controls/DateTimePicker/DateTimePicker.tsx b/src/components/forms/controls/DateTimePicker/DateTimePicker.tsx index f9de88bb..865c94c5 100644 --- a/src/components/forms/controls/DateTimePicker/DateTimePicker.tsx +++ b/src/components/forms/controls/DateTimePicker/DateTimePicker.tsx @@ -16,6 +16,12 @@ export type DateTimePickerProps = Omit, 'onChange'> & { /** A Date object to hold the date and time. */ date: null | Date, + /** A Date object limit min selectable date. */ + minDate: null | Date, + + /** A Date object limit max selectable date. */ + maxDate: null | Date, + /** A callback function that is called when either the date or the time picker is changed. */ onChange: ((date: null | Date, event?: React.MouseEvent | React.KeyboardEvent) => void), @@ -32,6 +38,8 @@ export const DateTimePicker = (props: DateTimePickerProps) => { const { date, onChange, + minDate, + maxDate, dateFormat = 'MM/dd/yyyy', placeholderText = 'MM/DD/YYYY', ...propsRest @@ -65,6 +73,8 @@ export const DateTimePicker = (props: DateTimePickerProps) => { onChange={onChange} dateFormat={dateFormat} placeholderText={placeholderText} + {...(minDate ? { minDate: new Date(minDate) } : {})} + {...(maxDate ? { maxDate: new Date(maxDate) } : {})} /> /** Enable more precise tracking of the anchor, at the cost of performance. */ enablePreciseTracking?: undefined | boolean, + + /** For controlled open state. */ + open?: boolean, + + /** When controlled, callback to set state. */ + onOpenChange?: (isOpen: boolean) => void, + + /** (optional) Use an existing DOM node as the positioning anchor. */ + anchorRef?: React.RefObject, +}; + +export type DropdownRef = { + setIsOpen: (open: boolean) => void, + isOpen: boolean, + floatingEl: HTMLElement | null, }; + /** * Provider for a dropdown menu overlay with its trigger. */ export const DropdownMenuProvider = Object.assign( - (props: DropdownMenuProviderProps) => { + React.forwardRef((props, forwardRef) => { const { label, children, @@ -51,6 +67,9 @@ export const DropdownMenuProvider = Object.assign( items, placement = 'bottom', enablePreciseTracking, + open: controlledOpen, + onOpenChange: controlledOnOpenChange, + anchorRef, ...propsRest } = props; @@ -75,6 +94,19 @@ export const DropdownMenuProvider = Object.assign( fallbackAxisSideDirection: 'none', fallbackStrategy: 'initialPlacement', }, + floatingUiOptions: { + ...( + typeof controlledOpen !== 'undefined' ? + { + open: controlledOpen, + ...( + typeof controlledOnOpenChange !== 'undefined' ? + { onOpenChange: controlledOnOpenChange } : {} + ), + + } : {} + ), + }, floatingUiInteractions: context => [ useListNavigation(context, { listRef, @@ -83,7 +115,31 @@ export const DropdownMenuProvider = Object.assign( }), ], }); - + + // keep internal state in sync with the controlled prop + React.useEffect(() => { + if (controlledOpen !== undefined) { + setIsOpen(controlledOpen); + } + }, [controlledOpen, setIsOpen]); + + // Use external element as the reference, if provided + React.useLayoutEffect(() => { + if (anchorRef?.current) { + refs.setReference(anchorRef.current); + } + }, [anchorRef?.current, refs.setReference]); + + const dropdownRef = React.useMemo(() => ({ + isOpen, + setIsOpen, + get floatingEl() { + return refs.floating.current; + }, + }), [isOpen, setIsOpen, refs.floating]); + + React.useImperativeHandle(forwardRef, () => dropdownRef, [dropdownRef]); + const context: DropdownMenuContext = React.useMemo((): DropdownMenuContext => ({ optionProps: () => getItemProps(), selectedOption: selected?.optionKey ?? null, @@ -139,13 +195,13 @@ export const DropdownMenuProvider = Object.assign( ...propsRest, className: cx(propsRest.className), })} - ref={mergeRefs(refs.setFloating, propsRest.ref)} + ref={refs.setFloating} data-placement={placementEffective} > {items} ); - }, + }), { Action, Option }, ); diff --git a/src/components/tables/DataTable/DataTableEager.stories.tsx b/src/components/tables/DataTable/DataTableEager.stories.tsx index e734db85..54ee377f 100644 --- a/src/components/tables/DataTable/DataTableEager.stories.tsx +++ b/src/components/tables/DataTable/DataTableEager.stories.tsx @@ -13,6 +13,7 @@ import * as Filtering from './filtering/Filtering.ts'; import type { Fields, FilterQuery } from '../MultiSearch/filterQuery.ts'; import { Panel } from '../../containers/Panel/Panel.tsx'; +import * as MultiSearch from '../MultiSearch/MultiSearch.tsx'; import * as DataTablePlugins from './plugins/useRowSelectColumn.tsx'; import * as DataTableEager from './DataTableEager.tsx'; @@ -117,7 +118,7 @@ const DataTableEagerTemplate = (props: dataTeableEagerTemplateProps) => { plugins={[DataTablePlugins.useRowSelectColumn]} > - + ); @@ -139,6 +140,8 @@ const DataTableEagerWithFilterTemplate = (props: dataTeableEagerTemplateProps) = const filtered = Filtering.filterByQuery(fields, itemsAsRecord, filters); setFilteredItems(Object.values(filtered) as User[]); }, [filters, itemsAsRecord]); + + const query = React.useCallback((filters: FilterQuery) => { setFilters(filters); }, []); return ( @@ -149,7 +152,8 @@ const DataTableEagerWithFilterTemplate = (props: dataTeableEagerTemplateProps) = getRowId={(item: User) => item.id} plugins={[DataTablePlugins.useRowSelectColumn]} > - + + ); @@ -206,13 +210,13 @@ export const AsyncInitialization = { render: (args: dataTeableEagerTemplateProps) => , }; -// export const WithFilter = { -// args: { -// columns, -// items: generateData({ numItems: 45 }), -// }, -// render: (args: dataTeableEagerTemplateProps) => , -// }; +export const WithFilter = { + args: { + columns, + items: generateData({ numItems: 45 }), + }, + render: (args: dataTeableEagerTemplateProps) => , +}; const moreColumns = [ ...columns, @@ -223,6 +227,7 @@ const moreColumns = [ Cell: ({ value }: { value: string }) => value, disableSortBy: false, disableGlobalFilter: true, + className: 'user-table__column', }, { id: 'dummy_2', @@ -230,6 +235,7 @@ const moreColumns = [ Header: 'Email', disableSortBy: false, disableGlobalFilter: true, + className: 'user-table__column', }, { id: 'dummy_3', @@ -237,6 +243,7 @@ const moreColumns = [ Header: 'Company', disableSortBy: false, disableGlobalFilter: true, + className: 'user-table__column', }, { id: 'dummy_4', @@ -244,6 +251,7 @@ const moreColumns = [ Header: 'Company', disableSortBy: false, disableGlobalFilter: true, + className: 'user-table__column', }, { id: 'dummy_5', @@ -251,7 +259,57 @@ const moreColumns = [ Header: 'Company', disableSortBy: false, disableGlobalFilter: true, + className: 'user-table__column', + }, + { + id: 'dummy_6', + accessor: (user: User) => user.company, + Header: 'Company', + disableSortBy: false, + disableGlobalFilter: true, + className: 'user-table__column', + }, + { + id: 'dummy_7', + accessor: (user: User) => user.company, + Header: 'Company', + disableSortBy: false, + disableGlobalFilter: true, + className: 'user-table__column', }, + { + id: 'dummy_8', + accessor: (user: User) => user.company, + Header: 'Company', + disableSortBy: false, + disableGlobalFilter: true, + className: 'user-table__column', + }, + { + id: 'dummy_9', + accessor: (user: User) => user.company, + Header: 'Company', + disableSortBy: false, + disableGlobalFilter: true, + className: 'user-table__column', + }, + { + id: 'dummy_10', + accessor: (user: User) => user.company, + Header: 'Company', + disableSortBy: false, + disableGlobalFilter: true, + className: 'user-table__column', + }, + { + id: 'dummy_11', + accessor: (user: User) => user.company, + Header: 'Company', + disableSortBy: false, + disableGlobalFilter: true, + className: 'user-table__column', + }, + ]; // FIXME: example with horizontal scroll // export const WithScroll = { diff --git a/src/components/tables/DataTable/DataTableEager.tsx b/src/components/tables/DataTable/DataTableEager.tsx index 8692a25b..6795ecac 100644 --- a/src/components/tables/DataTable/DataTableEager.tsx +++ b/src/components/tables/DataTable/DataTableEager.tsx @@ -138,7 +138,7 @@ export const MultiSearch = (props: React.ComponentPropsWithoutRef, 'table'> & { +export type DataTableEagerProps = Omit, 'table' | 'status'> & { children?: React.ReactNode, className?: ClassNameArgument, footer?: React.ReactNode, diff --git a/src/components/tables/DataTable/table/DataTable.tsx b/src/components/tables/DataTable/table/DataTable.tsx index 67b73afa..0fe5afed 100644 --- a/src/components/tables/DataTable/table/DataTable.tsx +++ b/src/components/tables/DataTable/table/DataTable.tsx @@ -44,6 +44,8 @@ export const DataTable = (props: DataTableProps) => { const headerGroup: undefined | ReactTable.HeaderGroup = table.headerGroups[0]; if (!headerGroup) { return null; } + const { key, ...HeaderGroupPropsRest } = headerGroup.getHeaderGroupProps(); + return ( (props: DataTableProps) => { {columnGroups} - + {/*
{/ * Empty header for the selection checkbox column */} {headerGroup.headers.map((column: ReactTable.HeaderGroup) => { diff --git a/src/components/tables/MultiSearch/MultiSearch.scss b/src/components/tables/MultiSearch/MultiSearch.module.scss similarity index 54% rename from src/components/tables/MultiSearch/MultiSearch.scss rename to src/components/tables/MultiSearch/MultiSearch.module.scss index 3941173b..cfca7d29 100644 --- a/src/components/tables/MultiSearch/MultiSearch.scss +++ b/src/components/tables/MultiSearch/MultiSearch.module.scss @@ -2,8 +2,7 @@ |* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of |* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -@use '../../../style/variables.scss' as *; -@use '../../../components/overlays/dropdown/Dropdown.scss'; +@use '../../../styling/defs.scss' as bk; @layer baklava.components { .bk-search-input { @@ -11,25 +10,12 @@ position: relative; display: flex; flex: 1; - padding: $sizing-none; + border-block-end: 1px solid bk.$theme-form-rule-default; - background-color: $light-color-2; - border: 0.2rem solid $neutral-color-1; - border-radius: $sizing-2; - - &.bk-search-input--active { - background-color: $light-color-1; - border-color: $accent-color-light-2; - } - - &:hover:not(.bk-search-input--active) { - background-color: $light-color-1; - border-color: rgba($accent-color-light-2, 0.33); - outline: none; - } - - .bk-input { - .bk-input__input { + .bk-search-input__input{ + border: none; + inline-size: 100%; + input { background-color: transparent; border: none; } @@ -37,45 +23,32 @@ .bk-search-input__search-icon, .bk-search-input__search-key { - padding: 1rem; - padding-inline-end: $sizing-none; + margin-inline-end: bk.$spacing-1; } - .bk-search-input__search-icon { - inline-size: $sizing-m; - opacity: 0.5; - color: #8AA1B0; - } .bk-search-input__search-key { - font-size: $font-size-s; - font-weight: $font-weight-light; - line-height: $line-height-2; flex: 1 0 auto; } - - .bk-search-input__input { - inline-size: 100%; - } } .bk-multi-search__filters { display: flex; - margin-block-start: $sizing-s; + margin-block-start: bk.$spacing-3; .bk-multi-search__filters-wrapper { display: flex; flex-wrap: wrap; - gap: $sizing-s; + gap: bk.$spacing-3; .bk-multi-search__filter { .filter-operand { - margin-inline-start: $sizing-xxs; + margin-inline-start: bk.$spacing-1; } .filter-value { - margin-inline-start: $sizing-xxs; - font-weight: $font-weight-semibold; + margin-inline-start: bk.$spacing-1; + font-weight: bk.$font-weight-semibold; } } } @@ -83,10 +56,10 @@ .bk-multi-search__filter-actions { margin-inline-start: auto; flex-shrink: 0; - padding-inline-start: $sizing-s; + padding-inline-start: bk.$spacing-3; .clear-all { - color: $accent-color; + color: bk.$theme-text-link-default; &:hover { cursor: pointer; @@ -105,13 +78,14 @@ .bk-multi-search__alternatives { .bk-multi-search__alternatives-group { + padding: bk.$spacing-3; .bk-checkbox { - padding: $sizing-s; + padding: bk.$spacing-3; } } .bk-multi-search__alternatives-action { - padding-block-start: $sizing-s; + padding-block-start: bk.$spacing-3; display: flex; justify-content: center; } @@ -120,15 +94,15 @@ .bk-multi-search__date-time { .bk-multi-search__date-time-group { .bk-multi-search__date-time-label { - margin-block-end: $sizing-xs; - font-weight: $font-weight-semibold; + margin-block-end: bk.$spacing-2; + font-weight: bk.$font-weight-semibold; } - padding: $sizing-s; + padding: bk.$spacing-3; } .bk-multi-search__date-time-action { - padding-block-start: $sizing-s; + padding-block-start: bk.$spacing-3; display: flex; justify-content: center; } @@ -136,20 +110,20 @@ .bk-multi-search__suggested-keys { - .bk-multi-search__suggested-key-input .bk-input__input { - inline-size: auto; + .bk-multi-search__suggested-key-input { + padding: bk.$spacing-1 bk.$spacing-3; } } .bk-multi-search__error-msg, .bk-multi-search__dropdown-error-msg { - padding-block-start: $sizing-s; - color: $status-color-error; - max-inline-size: $sizing-6 * 10; + padding-block-start: bk.$spacing-3; + color: bk.$theme-form-text-error; + max-inline-size: calc(bk.$spacing-8 * 10); display: block; } .bk-multi-search__dropdown-error-msg { - padding: $sizing-s; + padding: bk.$spacing-3; } } diff --git a/src/components/tables/MultiSearch/MultiSearch.stories.tsx b/src/components/tables/MultiSearch/MultiSearch.stories.tsx index 55b87baf..1fc540ab 100644 --- a/src/components/tables/MultiSearch/MultiSearch.stories.tsx +++ b/src/components/tables/MultiSearch/MultiSearch.stories.tsx @@ -12,7 +12,7 @@ import * as MultiSearch from './MultiSearch.tsx'; export default { component: MultiSearch, - tags: [], //['autodocs'], + tags: ['autodocs'], parameters: { layout: 'centered', }, diff --git a/src/components/tables/MultiSearch/MultiSearch.tsx b/src/components/tables/MultiSearch/MultiSearch.tsx index 5a199a32..ee93c323 100644 --- a/src/components/tables/MultiSearch/MultiSearch.tsx +++ b/src/components/tables/MultiSearch/MultiSearch.tsx @@ -16,11 +16,8 @@ import { } from 'date-fns'; import * as React from 'react'; -import * as ReactDOM from 'react-dom'; import { classNames as cx, type ClassNameArgument, type ComponentProps } from '../../../util/componentUtil.ts'; -// import * as Popper from 'react-popper'; import { mergeRefs } from '../../../util/reactUtil.ts'; -import { useOutsideClickHandler } from '../../../util/hooks/useOutsideClickHandler.ts'; import { useFocus } from '../../../util/hooks/useFocus.ts'; import { Icon } from '../../graphics/Icon/Icon.tsx'; @@ -28,13 +25,12 @@ import { Tag } from '../../text/Tag/Tag.tsx'; import { Button } from '../../actions/Button/Button.tsx'; import { Input } from '../../forms/controls/Input/Input.tsx'; import { CheckboxGroup } from '../../forms/controls/CheckboxGroup/CheckboxGroup.tsx'; -// import * as Dropdown from '../../overlays/dropdown/Dropdown.tsx'; -import { DropdownMenu, DropdownMenuContext } from '../../overlays/DropdownMenu/DropdownMenu.tsx'; -// import { DateTimePicker } from '../../forms/datetime/DateTimePicker.tsx'; +import { DropdownMenuProvider, type DropdownRef } from '../../overlays/DropdownMenu/DropdownMenuProvider.tsx'; +import { DateTimePicker } from '../../forms/controls/DateTimePicker/DateTimePicker.tsx'; import * as FQ from './filterQuery.ts'; -//import './MultiSearch.scss'; +import cl from './MultiSearch.module.scss'; // Utilities @@ -137,7 +133,7 @@ const useFilters = (props: UseFiltersProps) => { if (fieldName && fields && typeof fields[fieldName]?.onAddFilter === 'function' && fieldQuery) { const field = fields[fieldName]; - // biome-ignore lint/style/noNonNullAssertion: + // biome-ignore lint/style/noNonNullAssertion: onAddFilter is guaranteed to exist by the guard above const updatedFilters = field.onAddFilter!(fieldQuery, filters); query?.(updatedFilters); } else if (fieldQuery) { @@ -185,7 +181,7 @@ export const Filters = (props: FiltersProps) => { let symbol = ':'; if (field && operatorSymbol && field.type === 'datetime') { - if (operatorSymbol === 'Range') { + if (operatorSymbol === 'range') { symbol = ''; } else { symbol = ` ${operatorSymbol}`; @@ -195,7 +191,7 @@ export const Filters = (props: FiltersProps) => { let operandLabel: { from: string, to: string } | string = ''; if (field && field.type === 'datetime') { - if (operatorSymbol === 'Range') { + if (operatorSymbol === 'range') { if (FQ.isRangeOperationValue(operand)) { const rangeOperand = operand as [number, number]; const startDateTime = dateFormat(rangeOperand[0] * 1000, 'MMMM do yyyy HH:mm'); @@ -215,13 +211,13 @@ export const Filters = (props: FiltersProps) => { <> {`${fieldNameLabel}${symbol}`} {typeof operandLabel === 'string' - ? {operandLabel} + ? {operandLabel} : ( <> - from - {operandLabel.from} - to - {operandLabel.to} + from + {operandLabel.from} + to + {operandLabel.to} ) } @@ -230,7 +226,7 @@ export const Filters = (props: FiltersProps) => { return ( { onRemoveFilter?.(index); }} content={content} /> @@ -264,16 +260,16 @@ export const Filters = (props: FiltersProps) => { return ( { onRemoveFilter?.(index); }} content={ fieldNameLabel - ? ( - <> - {`${fieldNameLabel}${symbol}`} - {operandLabel} - - ) : `${operandLabel}` + ? ( + <> + {`${fieldNameLabel}${symbol}`} + {operandLabel} + + ) : `${operandLabel}` } /> ); @@ -321,17 +317,17 @@ export const Filters = (props: FiltersProps) => { return ( { onRemoveFilter?.(index); }} content={ fieldNameLabel - ? ( - <> - {`${fieldNameLabel}${symbol}`} - {operandLabel} - - ) : `${operandLabel}` + ? ( + <> + {`${fieldNameLabel}${symbol}`} + {operandLabel} + + ) : `${operandLabel}` } /> ); @@ -339,12 +335,13 @@ export const Filters = (props: FiltersProps) => { const renderActions = () => { return filters.length > 0 && ( -
+
+ // biome-ignore lint/a11y/useSemanticElements: + // span used as clickable wrapper to keep custom layout & avoid button semantics role="button" tabIndex={0} - className="clear-all" + className={cx(cl['clear-all'])} onKeyDown={onRemoveAllFilters} onClick={onRemoveAllFilters} > @@ -359,8 +356,8 @@ export const Filters = (props: FiltersProps) => { } return ( -
-
+
+
{filters.map(renderFilter)}
{renderActions()} @@ -373,110 +370,43 @@ export const Filters = (props: FiltersProps) => { // Suggestions dropdown // -const SuggestionItem = DropdownMenu.Action; export type SuggestionProps = Omit, 'children'> & { - children: React.ReactNode | ((props: { close: () => void }) => React.ReactNode), - elementRef?: undefined | React.RefObject, // Helps to toggle multiple dropdowns on the same reference element + label: string, + items: React.ReactNode, + elementRef?: undefined | React.RefObject, // Helps to toggle multiple dropdowns on the same reference element active?: undefined | boolean, - withArrow?: undefined | boolean, - primary?: undefined | boolean, - secondary?: undefined | boolean, - basic?: undefined | boolean, - // popperOptions?: undefined | Dropdown.PopperOptions, onOutsideClick?: undefined | (() => void), - containerRef?: undefined | React.RefObject, }; -// export const Suggestions = (props: SuggestionProps) => { -// const { -// active = false, -// className = '', -// withArrow = false, -// primary = false, -// secondary = false, -// basic = false, -// children = '', -// elementRef, -// // popperOptions = {}, -// onOutsideClick, -// containerRef, -// } = props; - -// const [isActive, setIsActive] = React.useState(false); - -// const [referenceElement, setReferenceElement] = React.useState(elementRef?.current ?? null); -// const [popperElement, setPopperElement] = React.useState(null); -// const [arrowElement, setArrowElement] = React.useState(null); -// const popper = Popper.usePopper(referenceElement, popperElement, { -// modifiers: [ -// { name: 'arrow', options: { element: arrowElement } }, -// { name: 'preventOverflow', enabled: true }, -// ...(popperOptions.modifiers || []), -// ], -// placement: popperOptions.placement, -// }); - -// React.useEffect(() => { -// if (elementRef?.current) { -// setReferenceElement(elementRef?.current); -// } -// }, [elementRef]); - -// const onClose = () => { -// setIsActive(false); -// }; - -// const dropdownRef = { current: popperElement }; -// const toggleRef = { current: referenceElement }; -// useOutsideClickHandler([dropdownRef, toggleRef, ...(containerRef ? [containerRef] : [])], onOutsideClick ?? onClose); - -// const renderDropdownItems = (dropdownItems: React.ReactElement) => { -// const dropdownChildren = dropdownItems.type === React.Fragment -// ? dropdownItems.props.children -// : dropdownItems; - -// return React.Children.map(dropdownChildren, child => { -// const { onActivate: childOnActivate, onClose: childOnClose } = child.props; - -// return child.type !== SuggestionItem -// ? child -// : React.cloneElement(child, { -// onActivate: (value: string | number) => { childOnActivate(value); }, -// onClose: childOnClose ?? onClose, -// }); -// }); -// }; - -// const renderDropdown = () => { -// return ( -//
-//
    -// {typeof children === 'function' -// ? children({ close: onClose }) -// : renderDropdownItems(children as React.ReactElement) -// } -//
-// {withArrow &&
} -//
-// ); -// }; - -// return ( -// <> -// {(isActive || active) && ReactDOM.createPortal(renderDropdown(), document.body)} -// -// ); -// }; +export const Suggestions = (props: SuggestionProps) => { + const { + className, + active = false, + label = '', + items = '', + elementRef, + onOutsideClick, + } = props; + + const dropdownRef = React.useRef(null); + + const handleOpenChange = (open: boolean) => { + if (!open) onOutsideClick?.(); + }; + + return ( + } + /> + ); +}; export type SearchInputProps = ComponentProps & { fields: FQ.Fields, @@ -557,22 +487,23 @@ export const SearchInput = (props: SearchInputProps) => { return (
+ // biome-ignore lint/a11y/useSemanticElements: + // div used as clickable wrapper to keep custom layout & avoid button semantics role="button" tabIndex={0} - className={cx('bk-search-input', className, { 'bk-search-input--active': isFocused })} + className={cx(cl['bk-search-input'], className, { [cl['bk-search-input--active']]: isFocused })} onClick={onWrapperClick} onKeyDown={onWrapperKeyDown} > - + {field && - + {field.label}{operator}{subOperator} {key ? `${key} =` : ''} } , isActive?: boolean, fields?: FQ.Fields, - // popperOptions?: Dropdown.PopperOptions, onClick: (fieldName?: string) => void, onOutsideClick?: () => void, }; @@ -597,7 +527,6 @@ const FieldsDropdown = (props: FieldsDropdownProps) => { inputRef, isActive = false, fields, - // popperOptions, onClick, onOutsideClick, } = props; @@ -606,30 +535,30 @@ const FieldsDropdown = (props: FieldsDropdownProps) => { return null; } + const menuItems = Object.entries(fields || {}).map(([fieldName, { label }]) => ( + { onClick(fieldName); }} + /> + )); + return ( - <> - // - // {Object.entries(fields || {}).map(([fieldName, { label }]) => ( - // - // {label} - // - // ))} - // + ); }; type AlternativesDropdownProps = { inputRef?: React.RefObject, isActive?: boolean, - operators?: FQ.EnumFieldOperator[] | FQ.ArrayFieldOperator[], alternatives?: FQ.Alternatives, - // popperOptions?: Dropdown.PopperOptions, selectedOperator: FQ.Operator, onChange: (value: Primitive[]) => void, onOutsideClick?: () => void, @@ -640,37 +569,27 @@ const AlternativesDropdown = (props: AlternativesDropdownProps) => { const { inputRef, isActive = false, - operators, alternatives, - // popperOptions, onChange, onOutsideClick, selectedOperator, validator, } = props; + if (!alternatives) return null; + const [selectedAlternatives, setSelectedAlternatives] = React.useState>([]); const canSelectMultipleItems = ['$in', '$nin', '$any', '$all'].includes(selectedOperator); - const onOptionClick = (context: DropdownMenuContext) => { - if (typeof context.selectedOption !== 'undefined') { - onChange([context.selectedOption]); - } - }; - const ValidateSelection = () => { - let isValid = false; + let isValid = selectedAlternatives.length > 0; let message = ''; - if (selectedAlternatives.length > 0) { - isValid = true; - } - if (typeof validator === 'function' && selectedAlternatives.length > 0) { + if (validator && selectedAlternatives.length > 0) { const validatorResponse = validator({ buffer: selectedAlternatives }); isValid = validatorResponse.isValid; message = validatorResponse.message; } - return { isValid, message }; }; @@ -679,41 +598,32 @@ const AlternativesDropdown = (props: AlternativesDropdownProps) => { const onSelectionComplete = () => { onChange(selectedAlternatives); }; - - const onSelectionChange = (alternativeName: string, shouldBeChecked: boolean) => { - if (shouldBeChecked) { - setSelectedAlternatives([...selectedAlternatives, alternativeName]); - } else { - setSelectedAlternatives([...selectedAlternatives.filter(item => item !== alternativeName)]); - } - }; - const renderMultiSelectAlternatives = () => ( <> setSelectedAlternatives(Array.from(set))} + className={cx(cl['bk-multi-search__alternatives-group'])} > - {Object.entries(alternatives || {}).map(([alternativesName, { label }], index) => ( + {Object.entries(alternatives).map(([key, { label }]) => ( { onSelectionChange(alternativesName, event.target.checked); }} /> ))} {!arrayValidation.isValid && arrayValidation.message && ( - + {arrayValidation.message} )} -
+
@@ -721,37 +631,35 @@ const AlternativesDropdown = (props: AlternativesDropdownProps) => { ); - const renderAlternatives = () => ( - Object.entries(alternatives || {}).map(([alternativesName, { label }]) => ( - + Object.entries(alternatives).map(([key, { label }]) => ( + onChange([key])} /> - )) - ); + )); return ( - <> - // - // {canSelectMultipleItems ? renderMultiSelectAlternatives() : renderAlternatives()} - // + ); }; type DateTimeDropdownProps = { inputRef?: React.RefObject, isActive?: boolean, - // popperOptions?: Dropdown.PopperOptions, onChange: (value: number | [number, number]) => void, onOutsideClick?: () => void, maxDate?: Date | number | undefined, @@ -765,7 +673,6 @@ const DateTimeDropdown = (props: DateTimeDropdownProps) => { const { inputRef, isActive = false, - // popperOptions, onChange, onOutsideClick, maxDate, @@ -804,7 +711,7 @@ const DateTimeDropdown = (props: DateTimeDropdownProps) => { Array.isArray(date) && date.length === 2; - const initDateTime = (selectedDate: FQ.SelectedDate | undefined, range: 'start' | 'end') => { + const initDateTime = (selectedDate: FQ.SelectedDate | undefined | null, range: 'start' | 'end') => { const defaultDate = setDate(new Date(), { seconds: 0, milliseconds: 0 }); if (!selectedDate) { return defaultDate; @@ -832,7 +739,7 @@ const DateTimeDropdown = (props: DateTimeDropdownProps) => { const [startDateTime, setStartDateTime] = React.useState(initDateTime(selectedDate, 'start')); const [endDateTime, setEndDateTime] = React.useState(initDateTime(selectedDate, 'end')); - // biome-ignore lint/correctness/useExhaustiveDependencies: + // biome-ignore lint/correctness/useExhaustiveDependencies: selectedDate is the only variable React.useEffect(() => { if (isValidSelectedDate(selectedDate)) { const updatedDateTime = initDateTime(selectedDate, 'start'); @@ -912,38 +819,36 @@ const DateTimeDropdown = (props: DateTimeDropdownProps) => { const renderDateTimeRangePicker = () => ( <> -
-
Start Date
- {/* */} +
+
Start Date
+ setStartDateTime(initDateTime(date, 'start'))} + minDate={minDate ? new Date(minDate) : null} + maxDate={maxDate ? new Date(maxDate) : null} + />
-
-
End Date
- {/* */} +
+
End Date
+ setEndDateTime(initDateTime(date, 'end'))} + minDate={minDate ? new Date(minDate) : null} + maxDate={maxDate ? new Date(maxDate) : null} + />
{!dateTimeRangeValidation.isValid - && dateTimeRangeValidation.message - && ( - - {dateTimeRangeValidation.message} - - ) - } - -
+ && dateTimeRangeValidation.message + && ( + + {dateTimeRangeValidation.message} + + ) + } + +