Skip to content

Commit

Permalink
feat: search as you type for user/groups
Browse files Browse the repository at this point in the history
WIP
  • Loading branch information
edoardo committed Aug 18, 2021
1 parent 91f6737 commit 5fd447c
Show file tree
Hide file tree
Showing 11 changed files with 511 additions and 64 deletions.
8 changes: 6 additions & 2 deletions packages/widgets/src/SharingDialog/AccessSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,18 @@ export const AccessSelect = ({ access, onChange, showNoAccessOption }) => (
{Object.entries(accessStrings).map(
([value, strings]) =>
(value !== ACCESS_NONE || showNoAccessOption) && (
<SingleSelectOption label={strings.option} value={value} />
<SingleSelectOption
key={value}
label={strings.option}
value={value}
/>
)
)}
</SingleSelect>
)

AccessSelect.propTypes = {
access: PropTypes.object,
access: PropTypes.string,
showNoAccessOption: PropTypes.bool,
onChange: PropTypes.func,
}
110 changes: 110 additions & 0 deletions packages/widgets/src/SharingDialog/Autocomplete/Autocomplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React, { createRef, useState, useEffect } from 'react'
import propTypes from '@dhis2/prop-types'

import { Menu, MenuItem } from '@dhis2/ui-core'
import { InputField } from '../../'
import { MenuWrapper } from './MenuWrapper'

// Keycodes for the keypress event handlers
// XXX implement keyboard navigation in the Menu ?!
/*const ESCAPE_KEY = 27
const SPACE_KEY = 32
const UP_KEY = 38
const DOWN_KEY = 40
*/

// XXX pass this whole component or the one that renders the MenuItem
// from the app/parent to make it as flexible as possible
const SearchResults = ({ searchResults, onClick }) => (
<Menu>
{searchResults.map(searchResult => (
<MenuItem
key={searchResult.id}
label={searchResult.displayName}
value={searchResult.id}
onClick={onClick}
/>
))}
</Menu>
)

SearchResults.propTypes = {
searchResults: propTypes.array,
onClick: propTypes.func,
}

export const Autocomplete = ({
placeholder,
onChange,
onClose,
onSearch,
dataTest,
maxHeight,
inputWidth,
value,
searchResults,
}) => {
const inputRef = createRef()
const menuRef = createRef()

const [menuWidth, setMenuWidth] = useState('auto')

useEffect(() => {
if (inputRef.current) {
console.log('set menu width', inputRef.current.offsetWidth)
setMenuWidth(`${inputRef.current.offsetWidth}px`)
}
}, [])

// TODO debounce
const onInputChange = ({ value }) => {
onSearch(value)
}

const onSelect = ({ value }) => {
onChange(value)
}

return (
<div className="autocomplete-block" ref={menuRef}>
<div ref={inputRef}>
<InputField
placeholder={placeholder}
onChange={onInputChange}
value={value}
inputWidth={inputWidth}
/>
</div>
{Boolean(searchResults.length) && (
<MenuWrapper
onClick={onClose}
maxHeight={maxHeight}
menuRef={menuRef}
menuWidth={menuWidth}
dataTest={`${dataTest}-menu`}
>
<SearchResults
searchResults={searchResults}
onClick={onSelect}
/>
</MenuWrapper>
)}
</div>
)
}

Autocomplete.defaultProps = {
dataTest: 'dhis2-uicore-select',
}

Autocomplete.propTypes = {
dataTest: propTypes.string,
inputWidth: propTypes.number,
maxHeight: propTypes.string,
placeholder: propTypes.string,
searchResults: propTypes.array,
value: propTypes.string,
onChange: propTypes.func,
onClose: propTypes.func,
onSearch: propTypes.func,
}
114 changes: 114 additions & 0 deletions packages/widgets/src/SharingDialog/Autocomplete/InputWrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React from 'react'
import propTypes from '@dhis2/prop-types'
import cx from 'classnames'
import { ArrowDown } from './ArrowDown.js'
import { colors, theme, sharedPropTypes } from '@dhis2/ui-constants'

const InputWrapper = ({
dataTest,
onToggle,
children,
tabIndex,
error,
warning,
valid,
disabled,
dense,
className,
inputRef,
}) => {
const classNames = cx(className, 'root', {
error,
warning,
valid,
disabled,
dense,
})

return (
<div
className={classNames}
onClick={onToggle}
tabIndex={tabIndex}
ref={inputRef}
data-test={dataTest}
>
<div className="root-children">{children}</div>
<div className="root-right">
<ArrowDown />
</div>

<style jsx>{`
.root {
align-items: center;
background-color: white;
border-radius: 3px;
border: 1px solid ${colors.grey500};
box-sizing: border-box;
display: flex;
min-height: 40px;
padding: 6px 12px;
box-shadow: inset 0 1px 2px 0 rgba(48, 54, 60, 0.1);
}
.root:focus,
.root:active {
border-color: ${colors.teal400};
outline: 0;
}
.root.valid {
border-color: ${theme.valid};
}
.root.warning {
border-color: ${theme.warning};
}
.root.error {
border-color: ${theme.error};
}
.root.disabled {
background-color: ${colors.grey100};
border-color: ${colors.grey500};
color: ${theme.disabled};
cursor: not-allowed;
}
.root.dense {
padding: 4px 8px;
min-height: 32px;
}
.root-children {
flex-grow: 1;
}
.root-right {
margin-left: auto;
}
`}</style>
</div>
)
}

InputWrapper.defaultProps = {
tabIndex: '0',
}

InputWrapper.propTypes = {
dataTest: propTypes.string.isRequired,
inputRef: propTypes.object.isRequired,
tabIndex: propTypes.string.isRequired,
onToggle: propTypes.func.isRequired,
children: propTypes.element,
className: propTypes.string,
dense: propTypes.bool,
disabled: propTypes.bool,
error: sharedPropTypes.statusPropType,
valid: sharedPropTypes.statusPropType,
warning: sharedPropTypes.statusPropType,
}

export { InputWrapper }
57 changes: 57 additions & 0 deletions packages/widgets/src/SharingDialog/Autocomplete/MenuWrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react'
import { resolve } from 'styled-jsx/css'

import propTypes from '@dhis2/prop-types'

import { Card, Layer, Popper } from '@dhis2/ui-core'

const MenuWrapper = ({
children,
dataTest,
maxHeight,
menuWidth,
onClick,
menuRef,
}) => {
const { styles, className: cardClassName } = resolve`
height: auto;
max-height: ${maxHeight};
overflow: auto;
`
return (
<Layer onClick={onClick} transparent>
<Popper
reference={menuRef}
placement="bottom"
observeReferenceResize
>
<div data-test={`${dataTest}-menuwrapper`}>
<Card className={cardClassName}>{children}</Card>

{styles}

<style jsx>{`
div {
width: ${menuWidth};
}
`}</style>
</div>
</Popper>
</Layer>
)
}

MenuWrapper.defaultProps = {
maxHeight: '280px',
}

MenuWrapper.propTypes = {
dataTest: propTypes.string.isRequired,
menuRef: propTypes.object.isRequired,
menuWidth: propTypes.string.isRequired,
children: propTypes.node,
maxHeight: propTypes.string,
onClick: propTypes.func,
}

export { MenuWrapper }
Loading

0 comments on commit 5fd447c

Please sign in to comment.