Skip to content
Snippets Groups Projects
Commit e06c43d2 authored by Christos's avatar Christos
Browse files

Merge branch 'title-dropdown' into 'master'

new dropdown in progress

See merge request !514
parents e18d5011 67b1d0d7
No related branches found
No related tags found
1 merge request!514new dropdown in progress
Pipeline #55880 passed with stages
in 3 minutes
......@@ -46,7 +46,7 @@ const en = {
'Change to Subtitle': 'Switch to Subtitle',
Subtitle: 'Subtitle',
'Change to Title': 'Switch to Title',
'Title (H1)': 'Title (H1)',
'Title (H1)': 'Title',
'Change to Block Quote': 'Switch to Block Quote',
'Block Quote': 'Block Quote',
'Change to Extract Poetry': 'Switch to Extract Poetry',
......
......@@ -8,7 +8,7 @@ import checkLevelFromConfig from '../HeadingService/checkLevelFromConfig';
@injectable()
export default class Title extends Tools {
title = 'Change to Title';
label = 'Title (H1)';
label = 'Title';
name = 'Title';
get run() {
......
/* eslint-disable no-underscore-dangle */
import React, { useContext, useMemo, useState, useEffect } from 'react';
import React, {
useContext,
useMemo,
useState,
useEffect,
useRef,
createRef,
} from 'react';
import { isEmpty } from 'lodash';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
import { WaxContext, ReactDropDownStyles } from 'wax-prosemirror-core';
import Dropdown from 'react-dropdown';
import { v4 as uuidv4 } from 'uuid';
import { WaxContext, Icon, useOnClickOutside } from 'wax-prosemirror-core';
const Wrapper = styled.div`
${ReactDropDownStyles};
opacity: ${props => (props.disabled ? '0.4' : '1')};
display: flex;
`;
const DropdownStyled = styled(Dropdown)`
display: inline-flex;
cursor: not-allowed;
opacity: ${props => (props.select ? 1 : 0.4)};
pointer-events: ${props => (props.select ? 'default' : 'none')};
.Dropdown-control {
width: 170px;
border: none;
padding: 12px 26px 8px 10px;
&:hover {
box-shadow: none;
}
const ButtonWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
`;
const DropDownButton = styled.button`
background: #fff;
border: none;
color: #000;
cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
display: flex;
width: 160px;
span {
position: relative;
top: 2px;
}
`;
.Dropdown-arrow {
top: 17px;
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: 38px auto auto;
position: absolute;
width: 160px;
max-height: 180px;
overflow-y: auto;
z-index: 2;
option {
cursor: pointer;
padding: 8px 10px;
}
.Dropdown-menu {
width: 102%;
display: flex;
flex-direction: column;
align-items: flex-start;
.Dropdown-option {
width: 100%;
}
option:focus,
option:hover {
background: #f2f9fc;
outline: 2px solid #f2f9fc;
}
option:disabled {
opacity: 0.3;
cursor: not-allowed;
}
`;
// eslint-disable-next-line react/prop-types
const StyledIcon = styled(Icon)`
height: 18px;
width: 18px;
margin-left: auto;
`;
const BlockDropDownComponent = ({ view, tools }) => {
const { t, i18n } = useTranslation();
const context = useContext(WaxContext);
const {
activeView,
activeViewId,
pmViews: { main },
} = context;
const [label, setLabel] = useState(null);
const isEditable = main.props.editable(editable => {
return editable;
});
const translatedLabel = (translation, defaultLabel) => {
return !isEmpty(i18n) && i18n.exists(translation)
......@@ -59,7 +89,14 @@ const BlockDropDownComponent = ({ view, tools }) => {
: defaultLabel;
};
const [label, setLabel] = useState(null);
const dropDownOptions = [
{
label: translatedLabel(`Wax.BlockLevel.Title (H1)`, 'Title'),
value: '0',
item: tools[0],
},
{
label: translatedLabel(`Wax.BlockLevel.Heading 2`, 'Heading 2'),
value: '5',
......@@ -82,6 +119,21 @@ const BlockDropDownComponent = ({ view, tools }) => {
},
];
const itemRefs = useRef([]);
const wrapperRef = useRef();
const [isOpen, setIsOpen] = useState(false);
const isEditable = main.props.editable(editable => {
return editable;
});
const isDisabled = !isEditable;
useOnClickOutside(wrapperRef, () => setIsOpen(false));
useEffect(() => {
if (isDisabled) setIsOpen(false);
}, [isDisabled]);
useEffect(() => {
setLabel(translatedLabel('Wax.BlockLevel.Block Level', 'Heading styles'));
dropDownOptions.forEach(option => {
......@@ -101,25 +153,108 @@ const BlockDropDownComponent = ({ view, tools }) => {
t('Wax.BlockLevel.Paragraph'),
]);
const openCloseMenu = () => {
if (!isDisabled) setIsOpen(!isOpen);
if (isOpen)
setTimeout(() => {
activeView.focus();
});
};
const onKeyDown = (e, index) => {
e.preventDefault();
// arrow down
if (e.keyCode === 40) {
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.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
key={uuidv4()}
onChange={option => {
tools[option.value].run(main.state, main.dispatch);
}}
options={dropDownOptions}
placeholder={translatedLabel(
'Wax.BlockLevel.Block Level',
'Heading styles',
)}
select={isEditable}
value={label}
/>
<Wrapper disabled={isDisabled} ref={wrapperRef}>
<ButtonWrapper>
<DropDownButton
aria-controls="block-level-options"
aria-expanded={isOpen}
aria-haspopup
disabled={isDisabled}
onKeyDown={e => {
if (e.keyCode === 40) {
itemRefs.current[0].current.focus();
}
if (e.keyCode === 27) {
setIsOpen(false);
}
if (e.keyCode === 13 || e.keyCode === 32) {
setIsOpen(true);
}
}}
onMouseDown={openCloseMenu}
type="button"
>
<span>{label}</span>
<StyledIcon name="expand" />
</DropDownButton>
</ButtonWrapper>
<DropDownMenu
aria-label="Choose a block level action"
id="block-level-options"
isOpen={isOpen}
role="menu"
>
{dropDownOptions.map((option, index) => {
itemRefs.current[index] = itemRefs.current[index] || createRef();
return (
<option
disabled={
!tools[option.value].select(
main.state,
activeViewId,
activeView,
)
}
key={option.value}
onClick={() => {
tools[option.value].run(main.state, main.dispatch);
openCloseMenu();
}}
onKeyDown={e => onKeyDown(e, index)}
ref={itemRefs.current[index]}
role="menuitem"
tabIndex="-1"
>
{option.label}
</option>
);
})}
</DropDownMenu>
</Wrapper>
),
[label, isEditable, t('Wax.BlockLevel.Paragraph')],
[isDisabled, isOpen, label, t('Wax.BlockLevel.Paragraph')],
);
return MultipleDropDown;
......
import React, { useContext, useEffect, useState } from 'react';
import { WaxContext, DocumentHelpers, Icon } from 'wax-prosemirror-core';
import React, { useContext, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { th } from '@pubsweet/ui-toolkit';
import { WaxContext, DocumentHelpers, Icon } from 'wax-prosemirror-core';
import EditorComponent from '../../MultipleChoiceQuestionService/components/EditorComponent';
import FeedbackComponent from '../../MultipleChoiceQuestionService/components/FeedbackComponent';
import NumericalAnswerDropDownCompontent from './NumericalAnswerDropDownCompontent';
......@@ -24,10 +25,6 @@ const NumericalAnswerContainerTool = styled.div`
height: 33px;
display: flex;
flex-direction: row;
span:first-of-type {
position: relative;
top: 3px;
}
`;
const NumericalAnswerOption = styled.div`
......@@ -37,11 +34,41 @@ const NumericalAnswerOption = styled.div`
const ActionButton = styled.button`
background: transparent;
cursor: pointer;
margin-top: 16px;
border: none;
position: relative;
margin-left: auto;
bottom: 13px;
z-index: 999;
`;
const StyledIconContainer = styled.span`
float: right;
position: relative;
top: 3px;
`;
const StyledIconAction = styled(Icon)`
position: relative;
right: 4px;
cursor: pointer;
height: 24px;
width: 24px;
z-index: 999;
`;
const InfoMsg = styled.div`
color: #fff;
display: none;
user-select: none;
position: absolute;
width: 100%;
span {
background: ${th('colorPrimary')};
bottom: 35px;
border-radius: 4px;
float: right;
right: 162px;
padding: 4px;
position: relative;
}
`;
const StyledIconActionRemove = styled(Icon)`
......@@ -60,6 +87,8 @@ export default ({ node, view, getPos }) => {
const customProps = main.props.customValues;
const { testMode, showFeedBack } = customProps;
const infoMsgRef = useRef();
const [infoMsgIsOpen, setInfoMsgIsOpen] = useState(false);
const isEditable = main.props.editable(editable => {
return editable;
......@@ -88,6 +117,16 @@ export default ({ node, view, getPos }) => {
});
}, []);
const displayInfoMsg = () => {
if (infoMsgRef.current && !infoMsgIsOpen)
infoMsgRef.current.style.display = 'block';
if (infoMsgRef.current && infoMsgIsOpen)
infoMsgRef.current.style.display = 'none';
setInfoMsgIsOpen(!infoMsgIsOpen);
};
return (
<NumericalAnswerWrapper>
<div>
......@@ -101,6 +140,20 @@ export default ({ node, view, getPos }) => {
>
<StyledIconActionRemove name="deleteOutlinedQuestion" />
</ActionButton>
{options[node.attrs.id]?.numericalAnswer === 'preciseAnswer' && (
<StyledIconContainer
onClick={displayInfoMsg}
onKeyPress={() => {}}
role="button"
tabIndex={0}
>
<StyledIconAction name="help" />
</StyledIconContainer>
)}
<InfoMsg ref={infoMsgRef}>
<span>Separate answer variants with a semi colon</span>
</InfoMsg>
</NumericalAnswerContainerTool>
)}
</div>
......
......@@ -17,6 +17,7 @@ import {
const Wrapper = styled.div`
opacity: ${props => (props.disabled ? '0.4' : '1')};
z-index: 999;
`;
const DropDownButton = styled.button`
......@@ -33,7 +34,7 @@ const DropDownButton = styled.button`
span {
position: relative;
top: 12px;
top: 4px;
}
`;
......
......@@ -144,10 +144,7 @@ const PreciseAnswerComponent = ({ node, readOnly, testMode, showFeedBack }) => {
)}
{readOnly && showFeedBack && (
<ResultContainer>
<span>
Accepted Answer Range:{' '}
{`(Accepted Answers : ${precise.replaceAll(';', ' -')})`}
</span>
<span>{`(Accepted Answers : ${precise.replaceAll(';', ' -')})`}</span>
<span>
Answer:{' '}
<FinalResult isCorrect={isCorrect}>{preciseStudent}</FinalResult>
......
......@@ -15,6 +15,13 @@ import * as tablesFn from '../tableSrc';
const Wrapper = styled.div`
opacity: ${props => (props.disabled ? '0.4' : '1')};
display: flex;
`;
const ButtonWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
`;
const DropDownButton = styled.button`
......@@ -23,7 +30,6 @@ const DropDownButton = styled.button`
color: #000;
cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
display: flex;
position: relative;
width: 160px;
span {
......@@ -40,7 +46,7 @@ const DropDownMenu = styled.div`
border: 1px solid #ddd;
border-radius: 0.25rem;
box-shadow: 0 0.2rem 0.4rem rgb(0 0 0 / 10%);
margin: 10px auto auto;
margin: 29px auto auto;
position: absolute;
width: 170px;
max-height: 180px;
......@@ -250,32 +256,34 @@ const TableDropDown = ({ item }) => {
const TableDropDownComponent = useMemo(
() => (
<Wrapper disabled={isDisabled} ref={wrapperRef}>
<DropDownButton
aria-controls="table-options"
aria-expanded={isOpen}
aria-haspopup
disabled={isDisabled}
onKeyDown={e => {
if (e.keyCode === 40) {
itemRefs.current[0].current.focus();
}
if (e.keyCode === 27) {
setIsOpen(false);
}
if (e.keyCode === 13 || e.keyCode === 32) {
setIsOpen(true);
}
}}
onMouseDown={openCloseMenu}
type="button"
>
<span>
{!isEmpty(i18n) && i18n.exists('Wax.Tables.Table Options')
? t('Wax.Tables.Table Options')
: 'Table Options'}
</span>
<StyledIcon name="expand" />
</DropDownButton>
<ButtonWrapper>
<DropDownButton
aria-controls="table-options"
aria-expanded={isOpen}
aria-haspopup
disabled={isDisabled}
onKeyDown={e => {
if (e.keyCode === 40) {
itemRefs.current[0].current.focus();
}
if (e.keyCode === 27) {
setIsOpen(false);
}
if (e.keyCode === 13 || e.keyCode === 32) {
setIsOpen(true);
}
}}
onMouseDown={openCloseMenu}
type="button"
>
<span>
{!isEmpty(i18n) && i18n.exists('Wax.Tables.Table Options')
? t('Wax.Tables.Table Options')
: 'Table Options'}
</span>
<StyledIcon name="expand" />
</DropDownButton>
</ButtonWrapper>
<DropDownMenu
aria-label="Choose a table action"
id="table-options"
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment