implements accessibility measures and finishes Link richText element
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'];
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
}
|
||||
|
||||
&__editor {
|
||||
min-height: 150px;
|
||||
min-height: base(10);
|
||||
padding: base(.5);
|
||||
max-height: base(20);
|
||||
overflow-y: scroll;
|
||||
|
||||
p,
|
||||
h1,
|
||||
|
||||
@@ -93,6 +93,16 @@ ul, ol {
|
||||
|
||||
a {
|
||||
color: $color-dark-gray;
|
||||
|
||||
&:focus {
|
||||
opacity: .8;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: .7;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
|
||||
@@ -42,6 +42,7 @@ $color-background-gray : #F3F3F3;
|
||||
$color-red : #ff6f76;
|
||||
$color-yellow : #FDFFA4;
|
||||
$color-green : #B2FFD6;
|
||||
$color-purple : #F3DDF3;
|
||||
|
||||
//////////////////////////////
|
||||
// STYLES
|
||||
|
||||
3
src/fields/richText/defaultValue.js
Normal file
3
src/fields/richText/defaultValue.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export default [{
|
||||
children: [{ text: '' }],
|
||||
}];
|
||||
@@ -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.';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user