Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*feat CheckableSelectField adornments #556

Merged
merged 1 commit into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 13 additions & 1 deletion packages/ui/__stories__/CheckableSelectField.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import React, { useState } from 'react'
import { logger } from '@hazelcast/services'
import { Meta, Story } from '@storybook/react'
import { Form, Formik } from 'formik'
import { AlertTriangle, Info } from 'react-feather'

import { CheckableSelectField, CheckableSelectProps } from '../src/Select/CheckableSelectField'
import { SelectFieldOption } from '../src/Select/helpers'
import { CheckableSelectFieldFormik } from '../src/Select/CheckableSelectFieldFormik'
import { LONG_ONE_WORD_TEXT, LONG_MULTIPLE_WORD_TEXT } from './constants'
import { Icon } from '../src/Icon'

const options: SelectFieldOption<string>[] = [
{ value: 'darth_vader', label: 'Darth Vader' },
Expand Down Expand Up @@ -146,7 +148,6 @@ export const CustomSearch = Template.bind({})
CustomSearch.args = {
value: [],
options,
filterOptionsLabel: 'Custom negative search',
defaultOpen: true,
filterOptions: (candidate, input) => {
if (!input) {
Expand All @@ -156,3 +157,14 @@ CustomSearch.args = {
return !candidate.label.toLowerCase().includes(input)
},
}

export const WithSearchInputAdornments = Template.bind({})
WithSearchInputAdornments.args = {
value: [],
options,
defaultOpen: true,
searchInputProps: {
endAdornment: <Icon icon={AlertTriangle} />,
startAdornment: <Icon icon={Info} />,
},
}
48 changes: 28 additions & 20 deletions packages/ui/__tests__/Select/CheckableSelectField.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React from 'react'
import { useUID } from 'react-uid'
import { mountAndCheckA11Y, simulateChange } from '@hazelcast/test-helpers'
import { mountAndCheckA11Y } from '@hazelcast/test-helpers'
import { act } from 'react-dom/test-utils'
import { mount } from 'enzyme'

import { CheckableSelectField } from '../../src/Select/CheckableSelectField'
import { Label } from '../../src/Label'
import { Error } from '../../src/Error'
import { SelectFieldOption } from '../../src/Select/helpers'

import styles from '../src/SelectField.module.scss'
import { mount } from 'enzyme'

jest.mock('react-uid')

Expand Down Expand Up @@ -315,7 +315,7 @@ describe('CheckableSelectField', () => {
id += 1
return id.toString()
})
const wrapper = await mountAndCheckA11Y(
await mountAndCheckA11Y(
<CheckableSelectField
defaultOpen
name={selectName}
Expand All @@ -330,25 +330,33 @@ describe('CheckableSelectField', () => {
/>,
)

const searchInput = wrapper.findDataTestFirst('test-search').find('input')

expect(searchInput).toBeTruthy()
act(() => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
simulateChange(searchInput, 'Luke')
})

expect(filterOptions).toHaveBeenCalledTimes(0)

act(() => {
wrapper.findDataTestFirst('test-toggle-custom-search').find('input').simulate('change')
})
expect(filterOptions).toHaveBeenCalled()
})

act(() => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
simulateChange(searchInput, 'Luke2')
it('Renders Adornments', async () => {
let id = 0
const onChange = jest.fn()
useUIDMock.mockImplementation(() => {
id += 1
return id.toString()
})
const wrapper = await mountAndCheckA11Y(
<CheckableSelectField
defaultOpen
name={selectName}
label={selectLabel}
options={options}
value={[]}
id={'21313123'}
onChange={onChange}
data-test="test"
endAdornment={<div data-test={'endAdornment'} />}
startAdornment={<div data-test={'startAdornment'} />}
noOptionsMessage="There are no options"
/>,
)

expect(filterOptions).toHaveBeenCalled()
expect(wrapper.findDataTestFirst('endAdornment')).toBeTruthy()
expect(wrapper.findDataTestFirst('startAdornment')).toBeTruthy()
})
})
4 changes: 2 additions & 2 deletions packages/ui/src/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ export const Popover: FC<PopoverProps> = (props) => {

const handleFocusIn = useCallback(
(e: FocusEvent) => {
if (popperElement && !popperElement.contains(e.target as Node)) {
if (popperElement && !popperElement.contains(e.target as Node) && !anchorElement?.contains(e.target as Node)) {
onClose()
}
},
[onClose, popperElement],
[onClose, popperElement, anchorElement],
)
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
Expand Down
30 changes: 9 additions & 21 deletions packages/ui/src/Select/CheckableSelectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import { Link } from '../Link'
import { SelectFieldOption } from './helpers'
import { TextField } from '../TextField'
import { HelpProps } from '../Help'
import { Tooltip } from '../Tooltip'
import { Checkbox } from '../Checkbox'
import { useOpenCloseState } from '../hooks'
import { TruncatedText } from '../TruncatedText'

Expand Down Expand Up @@ -42,7 +40,10 @@ export type CheckableSelectFieldExtraProps<V> = {
noOptionsMessage?: string
defaultOpen?: boolean
id?: string
filterOptionsLabel?: string
searchInputProps?: {
endAdornment?: ReactNode
startAdornment?: ReactNode
}
filterOptions?: (candidate: SelectFieldOption<V>, input: string) => boolean
}

Expand Down Expand Up @@ -82,11 +83,10 @@ export const CheckableSelectField = <V extends string | number = number>(props:
noOptionsMessage = 'No options',
id: rootId,
filterOptions,
filterOptionsLabel,
searchInputProps = {},
} = props
const id = useUID()
const { isOpen, toggle, close } = useOpenCloseState(defaultOpen)
const { isOpen: isCustomSearchEnabled, toggle: toggleCustomSearch } = useOpenCloseState(false)
const [searchValue, setSearchValue] = useState('')
const [forceUpdateToken, setForceUpdateToken] = useState(1)
const [anchorElement, setAnchorElement] = useState<HTMLElement | null>(null)
Expand All @@ -105,13 +105,13 @@ export const CheckableSelectField = <V extends string | number = number>(props:
const value = searchValue.toLowerCase()

return options.filter((option) => {
if (isCustomSearchEnabled && filterOptions) {
if (filterOptions) {
return filterOptions(option, value)
}

return option.label.toLowerCase().includes(value)
})
}, [options, searchValue, isCustomSearchEnabled, filterOptions])
}, [options, searchValue, filterOptions])

const getValueLabel = () => {
if (placeholderMode === 'permanent') {
Expand Down Expand Up @@ -164,6 +164,7 @@ export const CheckableSelectField = <V extends string | number = number>(props:
>
<div className={styles.dropdown} data-test={`${dataTest}-dropdown`}>
<div className={styles.search}>
{searchInputProps.startAdornment}
<TextField
size={size}
iconSize="medium"
Expand All @@ -176,20 +177,7 @@ export const CheckableSelectField = <V extends string | number = number>(props:
disabled={disabled}
placeholder={placeholder}
/>
{filterOptions && (
<Tooltip content={filterOptionsLabel} zIndex={21}>
{(tooltipRef) => (
<Checkbox
ref={tooltipRef}
aria-label="Custom search checkbox"
checked={isCustomSearchEnabled}
name="toggle custom search"
onChange={toggleCustomSearch}
data-test={`${dataTest}-toggle-custom-search`}
/>
)}
</Tooltip>
)}
{searchInputProps.endAdornment}
</div>
<div className={styles.options}>
{filteredOptions.length > 0 ? (
Expand Down
Loading