diff --git a/wax-prosemirror-services/src/MatchingService/components/TestModeDropDownComponent.js b/wax-prosemirror-services/src/MatchingService/components/TestModeDropDownComponent.js index b4696064bdbc47ee787a8372b224fbafdab3c0d2..34751cc9442d8462d9264bef11a6a0ab07567d83 100644 --- a/wax-prosemirror-services/src/MatchingService/components/TestModeDropDownComponent.js +++ b/wax-prosemirror-services/src/MatchingService/components/TestModeDropDownComponent.js @@ -76,7 +76,6 @@ const TestModeDropDownComponent = ({ getPos, node, view, uniqueId }) => { const context = useContext(WaxContext); const { pmViews: { main }, - activeView, } = context; let isDisabled = false; diff --git a/wax-prosemirror-services/src/MultipleDropDownService/components/MultipleDropDownComponent.js b/wax-prosemirror-services/src/MultipleDropDownService/components/MultipleDropDownComponent.js index 4a9d509450332a3ff0edcad3f5540a27d4846403..23489a76f4bda23222d4fc98d026d7e3802cb426 100644 --- a/wax-prosemirror-services/src/MultipleDropDownService/components/MultipleDropDownComponent.js +++ b/wax-prosemirror-services/src/MultipleDropDownService/components/MultipleDropDownComponent.js @@ -1,5 +1,6 @@ import React, { useContext, useEffect, useState } from 'react'; import { WaxContext, Icon } from 'wax-prosemirror-core'; +import { v4 as uuidv4 } from 'uuid'; import styled, { css } from 'styled-components'; import ReadOnlyDropDown from './ReadOnlyDropDown'; @@ -93,11 +94,5 @@ export default ({ node, getPos }) => { ); } - return ( - <ReadOnlyDropDown - getPos={getPos} - node={node} - options={node.attrs.options} - /> - ); + return <ReadOnlyDropDown getPos={getPos} node={node} uniqueId={uuidv4()} />; }; diff --git a/wax-prosemirror-services/src/MultipleDropDownService/components/ReadOnlyDropDown.js b/wax-prosemirror-services/src/MultipleDropDownService/components/ReadOnlyDropDown.js index d4d752e5bea9be36d52a08886643d5203104fc4d..2b7a2dd5c2dd19b5589c4f117c375a2c22132913 100644 --- a/wax-prosemirror-services/src/MultipleDropDownService/components/ReadOnlyDropDown.js +++ b/wax-prosemirror-services/src/MultipleDropDownService/components/ReadOnlyDropDown.js @@ -1,51 +1,83 @@ /* eslint-disable no-underscore-dangle */ -import React, { useContext, useEffect, useMemo, useState } from 'react'; +import React, { + useContext, + useMemo, + useEffect, + useState, + useRef, + createRef, +} from 'react'; import styled from 'styled-components'; import { WaxContext, DocumentHelpers, - ReactDropDownStyles, + Icon, + useOnClickOutside, } from 'wax-prosemirror-core'; -import Dropdown from 'react-dropdown'; -import { v4 as uuidv4 } from 'uuid'; const Wrapper = styled.div` display: inline-flex; - ${ReactDropDownStyles}; `; -const DropdownStyled = styled(Dropdown)` + +const DropDownButton = styled.button` + background: #fff; + border: 1px solid rgb(204, 204, 204); + color: #000; + cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')}; display: inline-flex; - cursor: not-allowed; - margin-left: auto; - opacity: ${props => (props.select ? 1 : 0.4)}; - pointer-events: ${props => (props.select ? 'default' : 'none')}; - .Dropdown-control { - // border: none; - padding: 8px 30px 8px 10px; - - &:hover { - box-shadow: none; - } + opacity: ${props => (props.disabled ? `0.4` : `1`)}; + padding: 8px 4px 4px 4px; + position: relative; + width: 165px; + + span { + position: relative; + top: 2px; } + &focus { + outline: 0; + } +`; + +const DropDownMenu = styled.div` + visibility: ${props => (props.isOpen ? 'visible' : 'hidden')}; + background: #fff; + display: flex; + flex-direction: column; + border: 1px solid #ddd; + border-radius: 0.25rem; + box-shadow: 0 0.2rem 0.4rem rgb(0 0 0 / 10%); + margin: 35px auto auto; + position: absolute; + width: 170px; + max-height: 150px; + overflow-y: auto; + z-index: 2; - .Dropdown-arrow { - top: 17px; + span { + cursor: pointer; + padding: 8px 10px; } - .Dropdown-menu { - width: 100%; - display: flex; - flex-direction: column; - align-items: flex-start; - .Dropdown-option { - width: 100%; - } + span:focus { + background: #f2f9fc; + outline: 2px solid #f2f9fc; } `; -const DropComponent = ({ getPos, node, options }) => { +const StyledIcon = styled(Icon)` + height: 18px; + width: 18px; + margin-left: auto; +`; + +const DropComponent = ({ getPos, node, uniqueId }) => { const [selectedOption, setSelectedOption] = useState(undefined); + const itemRefs = useRef([]); + const wrapperRef = useRef(); + const [isOpen, setIsOpen] = useState(false); + const context = useContext(WaxContext); const { pmViews: { main }, @@ -54,11 +86,15 @@ const DropComponent = ({ getPos, node, options }) => { const customProps = main.props.customValues; const { testMode } = customProps; + let isDisabled = false; + if (node.attrs.options.length === 0 || !testMode) isDisabled = true; + useEffect(() => { const currentOption = node.attrs.options.filter(option => { return option.value === node.attrs.correct; }); - if (!testMode && currentOption[0]) setSelectedOption(currentOption[0]); + if (!testMode && currentOption[0]) + setSelectedOption(currentOption[0].value); }, []); const onChange = option => { @@ -73,26 +109,111 @@ const DropComponent = ({ getPos, node, options }) => { } }); main.dispatch(tr); + openCloseMenu(); + setSelectedOption(option.value); + }; + + useOnClickOutside(wrapperRef, () => setIsOpen(false)); + + const onKeyDown = (e, index) => { + e.preventDefault(); + if (e.keyCode === 40) { + // arrow down + if (index === itemRefs.current.length - 1) { + itemRefs.current[0].current.focus(); + } else { + itemRefs.current[index + 1].current.focus(); + } + } + + // arrow up + if (e.keyCode === 38) { + if ( + index === 0 && + itemRefs.current[itemRefs.current.length - 1].current + ) { + itemRefs.current[itemRefs.current.length - 1].current.focus(); + } else { + itemRefs.current[index - 1].current.focus(); + } + } + + // enter + if (e.keyCode === 13) { + itemRefs.current[index].current.click(); + } + + // ESC + if (e.keyCode === 27) { + setIsOpen(false); + } }; - const MultipleDropDown = useMemo( - () => ( - <Wrapper key={uuidv4()}> - <DropdownStyled - disabled={!testMode} - key={uuidv4()} - onChange={option => onChange(option)} - options={options} - placeholder="Select option" - select - value={ - selectedOption === 'undedfined' ? 'Select Option' : selectedOption - } - /> + const openCloseMenu = () => { + if (!isDisabled) setIsOpen(!isOpen); + }; + + const MultipleDropDown = useMemo(() => { + let selectedValue; + if (selectedOption) { + selectedValue = node.attrs.options.filter(option => { + return option.value === selectedOption; + }); + } + return ( + <Wrapper disabled={isDisabled} ref={wrapperRef}> + <DropDownButton + aria-controls={uniqueId} + aria-expanded={isOpen} + aria-haspopup + disabled={isDisabled} + onKeyDown={e => { + if (e.keyCode === 40) { + if (!itemRefs.current[0].current) return; + itemRefs.current[0].current.focus(); + } + if (e.keyCode === 27) { + setIsOpen(false); + } + if (e.keyCode === 13 || e.keyCode === 32) { + setIsOpen(true); + } + }} + onMouseDown={openCloseMenu} + role="combobox" + type="button" + > + {selectedOption === null || !selectedOption + ? 'Select Option' + : selectedValue[0].label} + <StyledIcon name="expand" /> + </DropDownButton> + <DropDownMenu + aria-label="Choose an option" + id={uniqueId} + isOpen={isOpen} + role="listbox" + > + {node.attrs.options.map((option, index) => { + itemRefs.current[index] = itemRefs.current[index] || createRef(); + return ( + <span + aria-selected={option.value === selectedOption} + key={option.value} + onClick={() => onChange(option)} + onKeyDown={e => onKeyDown(e, index)} + ref={itemRefs.current[index]} + role="option" + tabIndex="-1" + > + {option.label} + </span> + ); + })} + </DropDownMenu> </Wrapper> - ), - [selectedOption], - ); + ); + }, [node.attrs.options, selectedOption, isOpen]); return MultipleDropDown; };