implements accessibility measures and finishes Link richText element

This commit is contained in:
James
2020-10-03 13:04:15 -04:00
parent 0764f34c64
commit febe213e80
16 changed files with 176 additions and 50 deletions

View File

@@ -79,7 +79,7 @@ const Button = (props) => {
function handleClick(event) {
if (type !== 'submit' && onClick) event.preventDefault();
if (onClick) onClick();
if (onClick) onClick(event);
}
const buttonProps = {

View File

@@ -92,6 +92,15 @@
&--style-none {
padding: 0;
margin: 0;
border-radius: 0;
&:focus {
opacity: .8;
}
&:active {
opacity: .7;
}
}
&--round {
@@ -155,6 +164,8 @@
@include color-svg($color-dark-gray);
background: $color-light-gray;
}
outline: none;
}
&:active {

View File

@@ -82,6 +82,15 @@
padding: base(.125) 0;
display: flex;
text-decoration: none;
&:focus {
box-shadow: none;
font-weight: 600;
}
&:active {
font-weight: normal;
}
}
nav {

View File

@@ -12,12 +12,12 @@ const baseClass = 'popup';
const Popup = (props) => {
const {
render, align, size, color, button, buttonType, children, showOnHover, horizontalAlign,
render, align, size, color, button, buttonType, children, showOnHover, horizontalAlign, initActive,
} = props;
const buttonRef = useRef(null);
const contentRef = useRef(null);
const [active, setActive] = useState(false);
const [active, setActive] = useState(initActive);
const [verticalAlign, setVerticalAlign] = useState('top');
const [forceHorizontalAlign, setForceHorizontalAlign] = useState(null);
@@ -143,6 +143,7 @@ Popup.defaultProps = {
button: undefined,
showOnHover: false,
horizontalAlign: 'left',
initActive: false,
};
Popup.propTypes = {
@@ -155,6 +156,7 @@ Popup.propTypes = {
buttonType: PropTypes.oneOf(['default', 'custom']),
button: PropTypes.node,
showOnHover: PropTypes.bool,
initActive: PropTypes.bool,
};
export default Popup;

View File

@@ -25,7 +25,8 @@
}
&__scroll {
padding: base(1);
padding: $baseline;
padding-right: calc(var(--scrollbar-width) + #{$baseline});
overflow-y: auto;
width: calc(100% + var(--scrollbar-width));
white-space: nowrap;

View File

@@ -2,6 +2,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import Tooltip from '../../elements/Tooltip';
import './index.scss';
const Error = (props) => {
const { showError, message } = props;

View File

@@ -1,14 +1,13 @@
@import '../../../scss/styles';
.field-error {
.tooltip {
left: auto;
right: base(.5);
transform: none;
background-color: $error;
.error-message {
left: auto;
right: base(.5);
transform: none;
background-color: $color-red;
span {
border-top-color: $error;
}
span {
border-top-color: $color-red;
}
}

View File

@@ -14,15 +14,12 @@ import elementTypes from './elements';
import toggleLeaf from './leaves/toggle';
import hotkeys from './hotkeys';
import enablePlugins from './enablePlugins';
import defaultValue from '../../../../../fields/richText/defaultValue';
import mergeCustomFunctions from './mergeCustomFunctions';
import './index.scss';
const defaultValue = [{
children: [{ text: '' }],
}];
const defaultElements = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'link'];
const defaultLeaves = ['bold', 'italic', 'underline', 'strikethrough', 'code'];

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { Fragment, useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { ReactEditor, useSlate } from 'slate-react';
import { Transforms } from 'slate';
@@ -8,8 +8,8 @@ import LinkIcon from '../../../../../icons/Link';
import Portal from '../../../../../utilities/Portal';
import Popup from '../../../../../elements/Popup';
import Button from '../../../../../elements/Button';
import Chevron from '../../../../../icons/Chevron';
import Check from '../../../../../icons/Check';
import Error from '../../../../Error';
import './index.scss';
@@ -20,6 +20,7 @@ const Link = ({ attributes, children, element }) => {
const linkRef = useRef();
const [left, setLeft] = useState(0);
const [top, setTop] = useState(0);
const [error, setError] = useState(false);
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const [url, setURL] = useState(element.url);
@@ -62,32 +63,67 @@ const Link = ({ attributes, children, element }) => {
}}
>
<Popup
initOpen
initActive={url === undefined}
className={`${baseClass}__popup`}
buttonType="custom"
button={<span className={`${baseClass}__button`} />}
size="small"
color="dark"
horizontalAlign="center"
>
<div className={`${baseClass}__url-wrap`}>
<input
value={url}
className={`${baseClass}__url`}
placeholder="Enter a URL"
onChange={(e) => setURL(e.target.value)}
/>
<Chevron />
</div>
<Button
className={`${baseClass}__new-tab`}
buttonStyle="none"
onClick={() => setNewTab(!newTab)}
>
<Check />
Open link in new tab
</Button>
</Popup>
render={({ close }) => (
<Fragment>
<div className={`${baseClass}__url-wrap`}>
<input
value={url || ''}
className={`${baseClass}__url`}
placeholder="Enter a URL"
onChange={(e) => {
const { value } = e.target;
if (value && error) {
setError(false);
}
setURL(value);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
close();
}
}}
/>
<Button
className={`${baseClass}__confirm`}
buttonStyle="none"
icon="chevron"
onClick={(e) => {
e.preventDefault();
if (url) {
close();
} else {
setError(true);
}
}}
/>
{error && (
<Error
showError={error}
message="Please enter a valid URL."
/>
)}
</div>
<Button
className={[`${baseClass}__new-tab`, newTab && `${baseClass}__new-tab--checked`].filter(Boolean).join(' ')}
buttonStyle="none"
onClick={() => setNewTab(!newTab)}
>
<Check />
Open link in new tab
</Button>
</Fragment>
)}
/>
</div>
</Portal>
{children}

View File

@@ -9,6 +9,10 @@
position: absolute;
z-index: $z-modal;
cursor: pointer;
.tooltip {
bottom: 80%;
}
}
.rich-text-link__button {
@@ -19,7 +23,59 @@
left: 0;
}
.rich-text-link__url-wrap {
position: relative;
width: 100%;
margin-bottom: base(.5);
}
.rich-text-link__confirm {
position: absolute;
right: base(.5);
top: 50%;
transform: translateY(-50%);
svg {
@include color-svg(white);
transform: rotate(-90deg);
}
}
.rich-text-link__url {
@include formInput;
width: base(12);
min-width: base(12);
width: 100%;
background: rgba($color-background-gray, .1);
border: 0;
color: white;
}
.rich-text-link__new-tab {
svg {
@include color-svg(white);
background-color: rgba(white, .1);
margin-right: base(.5);
}
path {
opacity: 0;
}
&:hover {
path {
opacity: .2;
}
}
&--checked {
path {
opacity: 1;
}
&:hover {
path {
opacity: .8;
}
}
}
}

View File

@@ -13,8 +13,10 @@
}
&__editor {
min-height: 150px;
min-height: base(10);
padding: base(.5);
max-height: base(20);
overflow-y: scroll;
p,
h1,

View File

@@ -93,6 +93,16 @@ ul, ol {
a {
color: $color-dark-gray;
&:focus {
opacity: .8;
outline: none;
}
&:active {
opacity: .7;
outline: none;
}
}
svg {

View File

@@ -42,6 +42,7 @@ $color-background-gray : #F3F3F3;
$color-red : #ff6f76;
$color-yellow : #FDFFA4;
$color-green : #B2FFD6;
$color-purple : #F3DDF3;
//////////////////////////////
// STYLES

View File

@@ -0,0 +1,3 @@
export default [{
children: [{ text: '' }],
}];

View File

@@ -1,3 +1,5 @@
const defaultRichTextValue = require('./richText/defaultValue');
const defaultMessage = 'This field is required.';
const optionsToValidatorMap = {
@@ -92,15 +94,9 @@ const optionsToValidatorMap = {
return true;
},
richText: (value, options) => {
//! Need better way to share an empty text node
//! it is used here and in field-types/RichText
const emptyRichTextNode = [{
children: [{ text: '' }],
}];
if (options.required) {
const blankSlateJSNode = JSON.stringify(emptyRichTextNode);
if (value && JSON.stringify(value) !== blankSlateJSNode) return true;
const stringifiedDefaultValue = JSON.stringify(defaultRichTextValue);
if (value && JSON.stringify(value) !== stringifiedDefaultValue) return true;
return 'This field is required.';
}