Skip to content

Commit

Permalink
fix(selectorbar): handle components other than SelectorBarItem correc…
Browse files Browse the repository at this point in the history
…tly as children (#1534)
  • Loading branch information
alaa-yahia authored Jul 4, 2024
1 parent 7a27d3d commit 7c78ac7
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 168 deletions.
287 changes: 138 additions & 149 deletions components/selector-bar/src/selector-bar-item/selector-bar-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,158 +24,147 @@ const offsetModifier = {
* "multiple" selections, this would be more or less impossible to predict
* inside this component
*/
export const SelectorBarItem = React.forwardRef(
(
{
children,
className,
dataTest,
disabled,
displayOnly,
label,
noValueMessage,
open,
setOpen,
value,
onClearSelectionClick,
},
ref
) => {
let buttonRef = useRef()
if (ref) {
buttonRef = ref
}
const Icon = open ? IconChevronUp24 : IconChevronDown24

return (
<button
ref={buttonRef}
className={cx(
'selector-bar-item',
className,
!displayOnly ? 'openable' : ''
)}
disabled={disabled}
onClick={() => setOpen && setOpen(true)}
data-test={dataTest}
tabIndex={-1}
>
<span className="label">{label}</span>

{!disabled && (
<>
<span className="value">{value || noValueMessage}</span>
{value && onClearSelectionClick && (
<span
className="clear-icon"
onClick={(evt) => {
evt.stopPropagation()
onClearSelectionClick()
}}
data-test={`${dataTest}-clear-icon`}
export const SelectorBarItem = ({
children,
className,
dataTest,
disabled,
displayOnly,
label,
noValueMessage,
open,
setOpen,
value,
onClearSelectionClick,
}) => {
const buttonRef = useRef()
const Icon = open ? IconChevronUp24 : IconChevronDown24

return (
<button
ref={buttonRef}
className={cx(
'selector-bar-item',
className,
!displayOnly ? 'openable' : ''
)}
disabled={disabled}
onClick={() => setOpen && setOpen(true)}
data-test={dataTest}
>
<span className="label">{label}</span>

{!disabled && (
<>
<span className="value">{value || noValueMessage}</span>
{value && onClearSelectionClick && (
<span
className="clear-icon"
onClick={(evt) => {
evt.stopPropagation()
onClearSelectionClick()
}}
data-test={`${dataTest}-clear-icon`}
>
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M7 14C10.866 14 14 10.866 14 7C14 3.13401 10.866 0 7 0C3.13401 0 0 3.13401 0 7C0 10.866 3.13401 14 7 14ZM4.29289 4.29289C4.68342 3.90237 5.31658 3.90237 5.70711 4.29289L7 5.58579L8.29289 4.29289C8.68342 3.90237 9.31658 3.90237 9.70711 4.29289C10.0976 4.68342 10.0976 5.31658 9.70711 5.70711L8.41421 7L9.70711 8.29289C10.0976 8.68342 10.0976 9.31658 9.70711 9.70711C9.31658 10.0976 8.68342 10.0976 8.29289 9.70711L7 8.41421L5.70711 9.70711C5.31658 10.0976 4.68342 10.0976 4.29289 9.70711C3.90237 9.31658 3.90237 8.68342 4.29289 8.29289L5.58579 7L4.29289 5.70711C3.90237 5.31658 3.90237 4.68342 4.29289 4.29289Z" />
</svg>
</span>
)}
</>
)}

{!displayOnly && (
<span className="toggle-icon">
<Icon />
</span>
)}

{open && (
<Layer
onBackdropClick={(_, evt) => {
evt.stopPropagation()
setOpen(false)
}}
<path d="M7 14C10.866 14 14 10.866 14 7C14 3.13401 10.866 0 7 0C3.13401 0 0 3.13401 0 7C0 10.866 3.13401 14 7 14ZM4.29289 4.29289C4.68342 3.90237 5.31658 3.90237 5.70711 4.29289L7 5.58579L8.29289 4.29289C8.68342 3.90237 9.31658 3.90237 9.70711 4.29289C10.0976 4.68342 10.0976 5.31658 9.70711 5.70711L8.41421 7L9.70711 8.29289C10.0976 8.68342 10.0976 9.31658 9.70711 9.70711C9.31658 10.0976 8.68342 10.0976 8.29289 9.70711L7 8.41421L5.70711 9.70711C5.31658 10.0976 4.68342 10.0976 4.29289 9.70711C3.90237 9.31658 3.90237 8.68342 4.29289 8.29289L5.58579 7L4.29289 5.70711C3.90237 5.31658 3.90237 4.68342 4.29289 4.29289Z" />
</svg>
</span>
)}
</>
)}

{!displayOnly && (
<span className="toggle-icon">
<Icon />
</span>
)}

{open && (
<Layer
onBackdropClick={(_, evt) => {
evt.stopPropagation()
setOpen(false)
}}
>
<Popper
reference={buttonRef}
placement="bottom-start"
modifiers={[offsetModifier]}
>
<Popper
reference={buttonRef}
placement="bottom-start"
modifiers={[offsetModifier]}
>
<Card>{children}</Card>
</Popper>
</Layer>
)}

<style jsx>{`
.selector-bar-item {
display: flex;
background: none;
height: 40px;
align-items: center;
${
/*
* The arrow icon has ~4px empty space on the sides,
* that's why the padding on the right is only 8px.
*/ ''
}
padding-block: 0;
padding-inline-start: ${spacers.dp8};
padding-inline-end: ${spacers.dp12};
font-size: 14px;
line-height: 16px;
border: none;
box-shadow: 0px 0px 0px 1px ${colors.grey400};
}
.selector-bar-item.openable {
cursor: pointer;
}
.selector-bar-item:disabled {
cursor: not-allowed;
}
.label {
color: ${colors.grey600};
}
.value {
padding-inline-start: ${spacers.dp8};
<Card>{children}</Card>
</Popper>
</Layer>
)}

<style jsx>{`
.selector-bar-item {
display: flex;
background: none;
height: 40px;
align-items: center;
${
/*
* The arrow icon has ~4px empty space on the sides,
* that's why the padding on the right is only 8px.
*/ ''
}
.clear-icon {
display: flex;
align-items: center;
margin-inline-start: ${spacers.dp4};
padding: ${spacers.dp4};
cursor: pointer;
}
.clear-icon svg path {
fill: ${colors.grey500};
}
.clear-icon:hover svg path {
fill: ${colors.grey700};
}
.toggle-icon {
display: flex;
margin-inline-start: ${spacers.dp4};
height: 100%;
align-items: center;
}
`}</style>
</button>
)
}
)

SelectorBarItem.displayName = 'SelectorBarItem'
padding-block: 0;
padding-inline-start: ${spacers.dp8};
padding-inline-end: ${spacers.dp12};
font-size: 14px;
line-height: 16px;
border: none;
box-shadow: 0px 0px 0px 1px ${colors.grey400};
}
.selector-bar-item.openable {
cursor: pointer;
}
.selector-bar-item:disabled {
cursor: not-allowed;
}
.label {
color: ${colors.grey600};
}
.value {
padding-inline-start: ${spacers.dp8};
}
.clear-icon {
display: flex;
align-items: center;
margin-inline-start: ${spacers.dp4};
padding: ${spacers.dp4};
cursor: pointer;
}
.clear-icon svg path {
fill: ${colors.grey500};
}
.clear-icon:hover svg path {
fill: ${colors.grey700};
}
.toggle-icon {
display: flex;
margin-inline-start: ${spacers.dp4};
height: 100%;
align-items: center;
}
`}</style>
</button>
)
}

SelectorBarItem.defaultProps = {
dataTest: 'dhis2-ui-selectorbaritem',
Expand Down
53 changes: 34 additions & 19 deletions components/selector-bar/src/selector-bar/selector-bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Button } from '@dhis2-ui/button'
import { colors, spacers } from '@dhis2/ui-constants'
import cx from 'classnames'
import PropTypes from 'prop-types'
import React, { useRef, useMemo } from 'react'
import React, { useRef, useState, useEffect } from 'react'
import i18n from '../locales/index.js'

const ClearSelection = ({ disabled, onClick }) => {
Expand Down Expand Up @@ -38,25 +38,38 @@ export const SelectorBar = ({
additionalContent,
ariaLabel,
}) => {
const chipContainer = useRef(null)

const childrenRefs = useMemo(
() => React.Children.map(children, () => React.createRef()),
[children]
)
const container = useRef(null)

const [childrenToFocus, setChildrenToFocus] = useState([])

useEffect(() => {
if (container.current) {
const controlsDiv = container.current.querySelector('.controls')
if (controlsDiv) {
const childElements = Array.from(controlsDiv.children)
childElements.forEach((child) => {
child.tabIndex = -1
})
setChildrenToFocus(childElements)
}
}
}, [children])

const handleKeyDown = (event) => {
const currentFocus = document.activeElement

if (chipContainer.current && chipContainer.current === currentFocus) {
if (childrenRefs.length > 0 && childrenRefs[0].current) {
childrenRefs[0].current.focus()
if (container.current && container.current === currentFocus) {
if (childrenToFocus.length > 0 && childrenToFocus[0]) {
childrenToFocus[0].focus()
}
return
}
if (!childrenToFocus.length) {
return
}

const currentIndex = childrenRefs.findIndex(
(ref) => ref.current === currentFocus
const currentIndex = childrenToFocus.findIndex(
(element) => element === currentFocus
)

if (currentIndex === -1) {
Expand All @@ -65,15 +78,16 @@ export const SelectorBar = ({

if (event.key === 'ArrowRight') {
event.preventDefault()
const nextIndex = (currentIndex + 1) % childrenRefs.length
childrenRefs[nextIndex].current.focus()
const nextIndex = (currentIndex + 1) % childrenToFocus.length
childrenToFocus[nextIndex].focus()
}

if (event.key === 'ArrowLeft') {
event.preventDefault()
const prevIndex =
(currentIndex - 1 + childrenRefs.length) % childrenRefs.length
childrenRefs[prevIndex].current.focus()
(currentIndex - 1 + childrenToFocus.length) %
childrenToFocus.length
childrenToFocus[prevIndex].focus()
}
}

Expand All @@ -88,16 +102,17 @@ export const SelectorBar = ({
data-test={dataTest}
onKeyDown={handleKeyDown}
tabIndex={0}
ref={chipContainer}
ref={container}
role="toolbar"
aria-label={ariaLabel}
>
<div className="controls">
{React.Children.map(children, (child, index) =>
{/* {React.Children.map(children, (child, index) =>
React.cloneElement(child, {
ref: childrenRefs[index],
})
)}
)} */}
{children}
{onClearSelectionClick && (
<ClearSelection
disabled={disableClearSelections}
Expand Down

0 comments on commit 7c78ac7

Please sign in to comment.