diff --git a/collections/forms/i18n/en.pot b/collections/forms/i18n/en.pot index 589f3f8baa..7451179c93 100644 --- a/collections/forms/i18n/en.pot +++ b/collections/forms/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-07-17T14:10:05.852Z\n" -"PO-Revision-Date: 2024-07-17T14:10:05.853Z\n" +"POT-Creation-Date: 2024-09-02T18:17:51.871Z\n" +"PO-Revision-Date: 2024-09-02T18:17:51.873Z\n" msgid "Upload file" msgstr "Upload file" diff --git a/components/chip/API.md b/components/chip/API.md index 5a687e7615..2d78cc9892 100644 --- a/components/chip/API.md +++ b/components/chip/API.md @@ -1,3 +1,21 @@ +### ChipGroup + +#### Usage + +**Note**: If possible, import the component from the main UI (`@dhis2/ui`) package. + +```js +import { ChipGroup } from '@dhis2-ui/chip' +``` + +#### Props + +|Name|Type|Default|Required|Description| +|---|---|---|---|---| +|children|node|||The Chip components to be rendered within the group| +|className|string|||Additional CSS class for the chip group| +|dataTest|string|`'dhis2-uicore-chipgroup'`||Data test id for testing purposes| + ### Chip #### Usage diff --git a/components/chip/src/chip-group/chip-group.js b/components/chip/src/chip-group/chip-group.js new file mode 100644 index 0000000000..d8b62458f3 --- /dev/null +++ b/components/chip/src/chip-group/chip-group.js @@ -0,0 +1,106 @@ +import { theme } from '@dhis2/ui-constants' +import PropTypes from 'prop-types' +import React, { useRef, useEffect, useState } from 'react' + +const ChipGroup = ({ className, dataTest, children }) => { + const chipContainer = useRef(null) + const [childrenToFocus, setChildrenToFocus] = useState([]) + + useEffect(() => { + if (chipContainer.current) { + const chips = + chipContainer.current.querySelectorAll('[role="option"]') + if (chips.length > 0) { + const childrenToFocus = Array.from(chips) + setChildrenToFocus(childrenToFocus) + } + } + }, [children]) + + const handleKeyDown = (event) => { + if (!childrenToFocus.length) { + return + } + + const currentFocus = document.activeElement + + const currentIndex = childrenToFocus.findIndex( + (element) => element === currentFocus + ) + + if (currentIndex === -1) { + return + } + + if (event.key === 'ArrowRight') { + event.preventDefault() + const nextIndex = (currentIndex + 1) % childrenToFocus.length + childrenToFocus[nextIndex].focus() + } + + if (event.key === 'ArrowLeft') { + event.preventDefault() + const prevIndex = + (currentIndex - 1 + childrenToFocus.length) % + childrenToFocus.length + childrenToFocus[prevIndex].focus() + } + + if (event.key === 'Backspace' || event.key === 'Delete') { + event.preventDefault() + event.stopPropagation() + const nextIndex = (currentIndex + 1) % childrenToFocus.length + childrenToFocus[nextIndex].focus() + } + } + + const handleFocus = (event) => { + if (!childrenToFocus.length) { + return + } + if (event.target === chipContainer.current) { + const selectedChild = childrenToFocus.find( + (child) => child.getAttribute('aria-selected') === 'true' + ) + if (!selectedChild && childrenToFocus[0]) { + childrenToFocus[0].focus() + } + selectedChild.focus() + } + } + + return ( +
+ {children} + +
+ ) +} + +ChipGroup.defaultProps = { + dataTest: 'dhis2-uicore-chipgroup', +} + +ChipGroup.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + dataTest: PropTypes.string, +} + +export { ChipGroup } diff --git a/components/chip/src/chip-group/index.js b/components/chip/src/chip-group/index.js new file mode 100644 index 0000000000..d94f294e98 --- /dev/null +++ b/components/chip/src/chip-group/index.js @@ -0,0 +1 @@ +export { ChipGroup } from './chip-group.js' diff --git a/components/chip/src/chip.js b/components/chip/src/chip.js deleted file mode 100644 index 221bf677cd..0000000000 --- a/components/chip/src/chip.js +++ /dev/null @@ -1,149 +0,0 @@ -import { colors, theme } from '@dhis2/ui-constants' -import cx from 'classnames' -import PropTypes from 'prop-types' -import React from 'react' -import { Content } from './content.js' -import { Icon } from './icon.js' -import { Remove } from './remove.js' - -const DEFAULT_INLINE_MARGIN = '4' - -const Chip = ({ - selected, - dense, - disabled, - dragging, - overflow, - className, - children, - onRemove, - onClick, - icon, - dataTest, - marginBottom, - marginLeft, - marginRight, - marginTop, - marginInlineStart, - marginInlineEnd, -}) => ( - { - if (!disabled && onClick) { - onClick({}, e) - } - }} - className={cx(className, { - selected, - dense, - disabled, - dragging, - })} - data-test={dataTest} - > - - {children} - - - - - -) - -Chip.defaultProps = { - dataTest: 'dhis2-uicore-chip', - marginBottom: 4, - marginTop: 4, -} - -Chip.propTypes = { - children: PropTypes.any, - className: PropTypes.string, - dataTest: PropTypes.string, - dense: PropTypes.bool, - disabled: PropTypes.bool, - dragging: PropTypes.bool, - icon: PropTypes.element, - /** `margin-bottom` value, applied in `px` */ - marginBottom: PropTypes.number, - /** `margin-inline-end` value, applied in `px` */ - marginInlineEnd: PropTypes.number, - /** `margin-inline-start` value, applied in `px` */ - marginInlineStart: PropTypes.number, - /** `margin-inline-start` value, applied in `px` */ - marginLeft: PropTypes.number, - /** `margin-inline-end` value, applied in `px` */ - marginRight: PropTypes.number, - /** `margin-top` value, applied in `px` */ - marginTop: PropTypes.number, - overflow: PropTypes.bool, - selected: PropTypes.bool, - onClick: PropTypes.func, - onRemove: PropTypes.func, -} - -export { Chip } diff --git a/components/chip/src/chip.e2e.stories.js b/components/chip/src/chip/chip.e2e.stories.js similarity index 100% rename from components/chip/src/chip.e2e.stories.js rename to components/chip/src/chip/chip.e2e.stories.js diff --git a/components/chip/src/chip/chip.js b/components/chip/src/chip/chip.js new file mode 100644 index 0000000000..a8eaef83f9 --- /dev/null +++ b/components/chip/src/chip/chip.js @@ -0,0 +1,178 @@ +import { colors, theme } from '@dhis2/ui-constants' +import cx from 'classnames' +import PropTypes from 'prop-types' +import React from 'react' +import { Content } from './content.js' +import { Icon } from './icon.js' +import { Remove } from './remove.js' + +const DEFAULT_INLINE_MARGIN = '4' + +const Chip = ({ + selected, + dense, + disabled, + dragging, + overflow, + className, + children, + onRemove, + onClick, + icon, + dataTest, + marginBottom, + marginLeft, + marginRight, + marginTop, + marginInlineStart, + marginInlineEnd, + component: Component, + href, + to, +}) => { + const handleKeyDown = (event) => { + if (!onRemove) { + return + } + if (event.key === 'Backspace' || event.key === 'Delete') { + onRemove({}, event) + } + } + + const chipProps = { + onClick: (e) => { + if (!disabled && onClick) { + onClick({}, e) + } + }, + onKeyDown: handleKeyDown, + className: cx('chip', className, { + selected, + dense, + disabled, + dragging, + }), + 'data-test': dataTest, + tabIndex: -1, + role: 'option', + 'aria-selected': selected ? 'true' : 'false', + 'aria-disabled': disabled ? 'true' : 'false', + } + + const content = ( + <> + + {children} + + + ) + + const chipElement = + Component === 'a' ? ( + + {content} + + ) : ( + {content} + ) + + return ( + <> + {chipElement} + + + ) +} + +Chip.defaultProps = { + dataTest: 'dhis2-uicore-chip', + marginBottom: 4, + marginTop: 4, +} + +Chip.propTypes = { + children: PropTypes.any, + className: PropTypes.string, + component: PropTypes.oneOf(['a', 'span']), + dataTest: PropTypes.string, + dense: PropTypes.bool, + disabled: PropTypes.bool, + dragging: PropTypes.bool, + href: PropTypes.string, + icon: PropTypes.element, + marginBottom: PropTypes.number, + marginInlineEnd: PropTypes.number, + marginInlineStart: PropTypes.number, + marginLeft: PropTypes.number, + marginRight: PropTypes.number, + marginTop: PropTypes.number, + overflow: PropTypes.bool, + selected: PropTypes.bool, + to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + onClick: PropTypes.func, + onRemove: PropTypes.func, +} + +export { Chip } diff --git a/components/chip/src/chip.prod.stories.js b/components/chip/src/chip/chip.prod.stories.js similarity index 100% rename from components/chip/src/chip.prod.stories.js rename to components/chip/src/chip/chip.prod.stories.js diff --git a/components/chip/src/content.js b/components/chip/src/chip/content.js similarity index 100% rename from components/chip/src/content.js rename to components/chip/src/chip/content.js diff --git a/components/chip/src/features/accepts_children.feature b/components/chip/src/chip/features/accepts_children.feature similarity index 100% rename from components/chip/src/features/accepts_children.feature rename to components/chip/src/chip/features/accepts_children.feature diff --git a/components/chip/src/features/accepts_children/index.js b/components/chip/src/chip/features/accepts_children/index.js similarity index 100% rename from components/chip/src/features/accepts_children/index.js rename to components/chip/src/chip/features/accepts_children/index.js diff --git a/components/chip/src/features/accepts_icon.feature b/components/chip/src/chip/features/accepts_icon.feature similarity index 100% rename from components/chip/src/features/accepts_icon.feature rename to components/chip/src/chip/features/accepts_icon.feature diff --git a/components/chip/src/features/accepts_icon/index.js b/components/chip/src/chip/features/accepts_icon/index.js similarity index 100% rename from components/chip/src/features/accepts_icon/index.js rename to components/chip/src/chip/features/accepts_icon/index.js diff --git a/components/chip/src/features/is_clickable.feature b/components/chip/src/chip/features/is_clickable.feature similarity index 100% rename from components/chip/src/features/is_clickable.feature rename to components/chip/src/chip/features/is_clickable.feature diff --git a/components/chip/src/features/is_clickable/index.js b/components/chip/src/chip/features/is_clickable/index.js similarity index 100% rename from components/chip/src/features/is_clickable/index.js rename to components/chip/src/chip/features/is_clickable/index.js diff --git a/components/chip/src/features/is_removable.feature b/components/chip/src/chip/features/is_removable.feature similarity index 100% rename from components/chip/src/features/is_removable.feature rename to components/chip/src/chip/features/is_removable.feature diff --git a/components/chip/src/features/is_removable/index.js b/components/chip/src/chip/features/is_removable/index.js similarity index 100% rename from components/chip/src/features/is_removable/index.js rename to components/chip/src/chip/features/is_removable/index.js diff --git a/components/chip/src/icon.js b/components/chip/src/chip/icon.js similarity index 100% rename from components/chip/src/icon.js rename to components/chip/src/chip/icon.js diff --git a/components/chip/src/chip/index.js b/components/chip/src/chip/index.js new file mode 100644 index 0000000000..8214f999ee --- /dev/null +++ b/components/chip/src/chip/index.js @@ -0,0 +1 @@ +export { Chip } from './chip.js' diff --git a/components/chip/src/remove.js b/components/chip/src/chip/remove.js similarity index 100% rename from components/chip/src/remove.js rename to components/chip/src/chip/remove.js diff --git a/components/chip/src/index.js b/components/chip/src/index.js index 8214f999ee..9d4b3062bd 100644 --- a/components/chip/src/index.js +++ b/components/chip/src/index.js @@ -1 +1,2 @@ -export { Chip } from './chip.js' +export { Chip } from './chip/index.js' +export { ChipGroup } from './chip-group/index.js'