Fix a11y issues

This commit is contained in:
bulby97 2021-08-23 17:30:56 +02:00
parent 9a042610ea
commit 3c9e92a2c4
14 changed files with 182 additions and 238 deletions

View File

@ -18,7 +18,7 @@ const Wrapper = styled.div`
left: -10px;
width: 6px;
height: 6px;
border-radius: 20px;
border-radius: ${20 / 16}rem;;
background: ${disabled ? theme.colors.neutral100 : theme.colors.primary600};
}
`}

View File

@ -7,7 +7,7 @@ import styled from 'styled-components';
import ConditionsSelect from '../ConditionsSelect';
const RowWrapper = styled(Row)`
height: 52px;
height: ${52 / 16}rem;
`;
const ActionRow = ({
@ -23,9 +23,9 @@ const ActionRow = ({
const { formatMessage } = useIntl();
return (
<RowWrapper background={isGrey ? 'neutral100' : 'neutral0'}>
<RowWrapper as="li" background={isGrey ? 'neutral100' : 'neutral0'}>
<Row paddingLeft={6} style={{ width: 180 }}>
<TableLabel textColor="neutral500">
<TableLabel textColor="neutral600">
{formatMessage({
id: 'Settings.permissions.conditions.can',
defaultMessage: 'Can',
@ -47,7 +47,7 @@ const ActionRow = ({
defaultMessage: label,
})}
</TableLabel>
<TableLabel textColor="neutral500">
<TableLabel textColor="neutral600">
&nbsp;
{formatMessage({
id: 'Settings.permissions.conditions.when',

View File

@ -1,39 +1,25 @@
import { After } from '@strapi/icons';
import {
Box,
Breadcrumbs,
Button,
Crumb,
H2,
ModalFooter,
ModalHeader,
ModalLayout,
Stack,
Text,
TextButton,
Divider,
} from '@strapi/parts';
import { cloneDeep, get, groupBy, set, upperFirst } from 'lodash';
import PropTypes from 'prop-types';
import React, { useMemo, useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import styled from 'styled-components';
import { useIntl } from 'react-intl';
import { usePermissionsDataManager } from '../../../hooks';
import updateValues from '../Permissions/utils/updateValues';
import ActionRow from './ActionRow';
import createDefaultConditionsForm from './utils/createDefaultConditionsForm';
// ! Something needs to be done in the DS parts to avoid doing this
const Icon = styled(Box)`
svg {
width: 6px;
}
* {
fill: ${({ theme }) => theme.colors.neutral300};
}
`;
const Separator = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.main.colors.brightGrey};
`;
const ConditionsModal = ({
actions,
headerBreadCrumbs,
@ -45,11 +31,6 @@ const ConditionsModal = ({
const { formatMessage } = useIntl();
const { availableConditions, modifiedData, onChangeConditions } = usePermissionsDataManager();
const translatedHeaders = headerBreadCrumbs.map(headerTrad => ({
key: headerTrad,
element: <FormattedMessage id={headerTrad} defaultMessage={upperFirst(headerTrad)} />,
}));
const arrayOfOptionsGroupedByCategory = useMemo(() => {
return Object.entries(groupBy(availableConditions, 'category'));
}, [availableConditions]);
@ -113,22 +94,18 @@ const ConditionsModal = ({
return (
<ModalLayout onClose={onClosed}>
<ModalHeader>
<Stack horizontal size={3}>
{translatedHeaders.map(({ key, element }, index) => {
const shouldDisplayChevron = index < translatedHeaders.length - 1;
return (
<React.Fragment key={key}>
<TextButton textColor="neutral800">{element}</TextButton>
{shouldDisplayChevron && (
<Icon>
<After />
</Icon>
)}
</React.Fragment>
);
})}
</Stack>
<Breadcrumbs label={headerBreadCrumbs.join(', ')}>
{headerBreadCrumbs.map(label => (
<Crumb key={label}>
{upperFirst(
formatMessage({
id: label,
defaultMessage: label,
})
)}
</Crumb>
))}
</Breadcrumbs>
</ModalHeader>
<Box padding={8}>
<Stack size={6}>
@ -138,7 +115,8 @@ const ConditionsModal = ({
defaultMessage: 'Define conditions',
})}
</H2>
<Separator />
{/* ! Need to force margin here - Remove this when Divider is updated in parts */}
<Divider style={{ marginTop: `${24 / 16}rem` }} />
<Box>
{actionsToDisplay.length === 0 && (
<Text>
@ -149,23 +127,25 @@ const ConditionsModal = ({
})}
</Text>
)}
{actionsToDisplay.map(({ actionId, label, pathToConditionsObject }, index) => {
const name = pathToConditionsObject.join('..');
<ul>
{actionsToDisplay.map(({ actionId, label, pathToConditionsObject }, index) => {
const name = pathToConditionsObject.join('..');
return (
<ActionRow
key={actionId}
arrayOfOptionsGroupedByCategory={arrayOfOptionsGroupedByCategory}
label={label}
isFormDisabled={isFormDisabled}
isGrey={index % 2 === 0}
name={name}
onCategoryChange={handleCategoryChange}
onChange={handleChange}
value={get(state, name, {})}
/>
);
})}
return (
<ActionRow
key={actionId}
arrayOfOptionsGroupedByCategory={arrayOfOptionsGroupedByCategory}
label={label}
isFormDisabled={isFormDisabled}
isGrey={index % 2 === 0}
name={name}
onCategoryChange={handleCategoryChange}
onChange={handleChange}
value={get(state, name, {})}
/>
);
})}
</ul>
</Box>
</Stack>
</Box>

View File

@ -34,7 +34,7 @@ const activeRowStyle = (theme, isActive, isClicked) => `
const Wrapper = styled.div`
display: flex;
align-items: center;
height: 52px;
height: ${52 / 16}rem;
background-color: ${({ isGrey, theme }) =>
isGrey ? theme.colors.neutral100 : theme.colors.neutral0};
border: 1px solid transparent;
@ -45,6 +45,10 @@ const Wrapper = styled.div`
&:hover {
${({ theme, isActive }) => activeRowStyle(theme, isActive)}
}
&:focus-within {
${({ theme, isActive }) => activeRowStyle(theme, isActive)}
}
`;
const Cell = styled(Row)`
@ -81,7 +85,7 @@ const Collapse = ({
onClickToggle,
pathToData,
}) => {
const [modalState, setModalState] = useState({ isOpen: false, isMounted: false });
const [isModalOpen, setModalOpen] = useState(false);
const {
modifiedData,
onChangeParentCheckbox,
@ -89,11 +93,11 @@ const Collapse = ({
} = usePermissionsDataManager();
const handleToggleModalIsOpen = () => {
setModalState(prevState => ({ isMounted: true, isOpen: !prevState.isOpen }));
setModalOpen(s => !s);
};
const handleModalClose = () => {
setModalState(prevState => ({ ...prevState, isMounted: false }));
setModalOpen(false);
};
// This corresponds to the data related to the CT left checkbox
@ -133,6 +137,7 @@ const Collapse = ({
onClick={onClickToggle}
someChecked={hasSomeActionsSelected}
value={hasAllActionsSelected}
isActive={isActive}
>
<Chevron paddingLeft={2}>{isActive ? <Up /> : <Down />}</Chevron>
</RowLabelWithCheckbox>
@ -159,6 +164,7 @@ const Collapse = ({
<Checkbox
disabled={isFormDisabled || IS_DISABLED}
name={checkboxName}
aria-label={checkboxName}
// Keep same signature as packages/core/admin/admin/src/components/Roles/Permissions/index.js l.91
onValueChange={value =>
onChangeParentCheckbox({
@ -202,16 +208,14 @@ const Collapse = ({
hasConditions={doesConditionButtonHasConditions}
/>
</Box>
{modalState.isMounted && (
<ConditionsModal
headerBreadCrumbs={[label, 'app.components.LeftMenuLinkContainer.settings']}
actions={checkboxesActions}
isOpen={modalState.isOpen}
isFormDisabled={isFormDisabled}
onClosed={handleModalClose}
onToggle={handleToggleModalIsOpen}
/>
)}
<ConditionsModal
headerBreadCrumbs={[label, 'app.components.LeftMenuLinkContainer.settings']}
actions={checkboxesActions}
isOpen={isModalOpen}
isFormDisabled={isFormDisabled}
onClosed={handleModalClose}
onToggle={handleToggleModalIsOpen}
/>
</Wrapper>
);
};

View File

@ -27,7 +27,7 @@ const Cell = styled(Box)`
const Chevron = styled(Box)`
display: none;
svg {
width: 11px;
width: ${11 / 16}rem;
}
* {
fill: ${({ theme }) => theme.colors.primary600};
@ -39,7 +39,7 @@ const Wrapper = styled(Row)`
flex: 1;
background: ${({ theme, isOdd }) => theme.colors[isOdd ? 'neutral100' : 'neutral0']};
${Chevron} {
width: 13px;
width: ${13 / 16}rem;
}
${({ isCollapsable, theme }) =>
isCollapsable &&
@ -118,6 +118,7 @@ const ActionRow = ({
label={label}
someChecked={hasSomeActionsSelected}
value={hasAllActionsSelected}
isActive={isActive}
>
{required && <RequiredSign />}
<Chevron paddingLeft={2}>{isActive ? <Up /> : <Down />}</Chevron>
@ -144,6 +145,7 @@ const ActionRow = ({
<Checkbox
disabled={isFormDisabled || IS_DISABLED}
name={checkboxName.join('..')}
aria-label={checkboxName.join('..')}
// Keep same signature as packages/core/admin/admin/src/components/Roles/Permissions/index.js l.91
onValueChange={value =>
onChangeSimpleCheckbox({

View File

@ -1,29 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { Row, Box } from '@strapi/parts';
import { Row, TableLabel } from '@strapi/parts';
import styled from 'styled-components';
import { cellWidth, firstRowWidth } from '../../../Permissions/utils/constants';
// Those styles are very specific.
// so it is not a big problem to use custom paddings and widths.
const HeaderLabel = styled.div`
justify-content: center;
display: flex;
const HeaderLabel = styled(Row)`
width: ${cellWidth};
`;
const PropertyLabelWrapper = styled.div`
const PropertyLabelWrapper = styled(Row)`
width: ${firstRowWidth};
display: flex;
align-items: center;
padding-left: ${({ theme }) => theme.spaces[6]};
height: 52px;
`;
const Label = styled(Box)`
font-size: ${11 / 16}rem;
text-transform: uppercase;
color: ${({ theme }) => theme.colors.neutral500};
font-weight: bold;
height: ${52 / 16}rem;
`;
const Header = ({ headers, label }) => {
@ -38,8 +25,8 @@ const Header = ({ headers, label }) => {
return (
<Row>
<PropertyLabelWrapper>
<Label>{translatedLabel}</Label>
<PropertyLabelWrapper alignItems="center" paddingLeft={6}>
<TableLabel textColor="neutral500">{translatedLabel}</TableLabel>
</PropertyLabelWrapper>
{headers.map(header => {
if (!header.isActionRelatedToCurrentProperty) {
@ -47,13 +34,13 @@ const Header = ({ headers, label }) => {
}
return (
<HeaderLabel key={header.label}>
<Label>
<HeaderLabel justifyContent="center" key={header.label}>
<TableLabel textColor="neutral500">
{formatMessage({
id: `Settings.roles.form.permissions.${header.label.toLowerCase()}`,
defaultMessage: header.label,
})}
</Label>
</TableLabel>
</HeaderLabel>
);
})}

View File

@ -16,7 +16,6 @@ const Wrapper = styled.div`
return `none`;
}};
border-radius: 0px 0px 2px 2px;
`;
Wrapper.defaultProps = {

View File

@ -1,4 +1,4 @@
import { Checkbox, Stack, TableLabel } from '@strapi/parts';
import { Checkbox, Stack, TableLabel, Box } from '@strapi/parts';
import IS_DISABLED from 'ee_else_ce/components/Roles/GlobalActions/utils/constants';
import { get } from 'lodash';
import PropTypes from 'prop-types';
@ -15,21 +15,6 @@ const CenteredStack = styled(Stack)`
width: ${cellWidth};
`;
const Wrapper = styled.div`
padding-left: ${firstRowWidth};
padding-bottom: ${({ theme }) => theme.spaces[4]};
padding-top: ${({ theme }) => theme.spaces[6]};
${({ disabled, theme }) =>
`
input[type='checkbox'] {
&:after {
color: ${!disabled ? theme.main.colors.mediumBlue : theme.main.colors.grey};
}
}
cursor: initial;
`}
`;
const GlobalActions = ({ actions, isFormDisabled, kind }) => {
const { formatMessage } = useIntl();
const { modifiedData, onChangeCollectionTypeGlobalActionCheckbox } = usePermissionsDataManager();
@ -43,7 +28,7 @@ const GlobalActions = ({ actions, isFormDisabled, kind }) => {
}, [modifiedData, displayedActions, kind]);
return (
<Wrapper disabled={isFormDisabled}>
<Box paddingBottom={4} paddingTop={6} style={{ paddingLeft: firstRowWidth }}>
<Stack horizontal>
{displayedActions.map(({ label, actionId }) => {
return (
@ -60,6 +45,7 @@ const GlobalActions = ({ actions, isFormDisabled, kind }) => {
onChangeCollectionTypeGlobalActionCheckbox(kind, actionId, value);
}}
name={actionId}
aria-label={actionId}
value={get(checkboxesState, [actionId, 'hasAllActionsSelected'], false)}
indeterminate={get(checkboxesState, [actionId, 'hasSomeActionsSelected'], false)}
/>
@ -67,7 +53,7 @@ const GlobalActions = ({ actions, isFormDisabled, kind }) => {
);
})}
</Stack>
</Wrapper>
</Box>
);
};

View File

@ -1,2 +1,2 @@
export const cellWidth = '120px';
export const firstRowWidth = '200px';
export const cellWidth = `${120 / 16}rem`;
export const firstRowWidth = `${200 / 16}rem`;

View File

@ -26,18 +26,18 @@ const CheckboxWrapper = styled.div`
&:before {
content: '';
position: absolute;
top: -4px;
left: -8px;
width: 6px;
height: 6px;
border-radius: 20px;
top: ${-4 / 16}rem;
left: ${-8 / 16}rem;
width: ${6 / 16}rem;
height: ${6 / 16}rem;
border-radius: ${20 / 16}rem;
background: ${disabled ? theme.colors.neutral100 : theme.colors.primary600};
}
`}
`;
const SubCategory = ({ categoryName, isFormDisabled, subCategoryName, actions, pathToData }) => {
const [modalState, setModalState] = useState({ isOpen: false, isMounted: false });
const [isModalOpen, setModalOpen] = useState(false);
const {
modifiedData,
onChangeParentCheckbox,
@ -57,13 +57,12 @@ const SubCategory = ({ categoryName, isFormDisabled, subCategoryName, actions, p
const { hasAllActionsSelected, hasSomeActionsSelected } = getCheckboxState(dataWithoutCondition);
const handleToggleModalIsOpen = () => {
setModalState(prevState => ({ isMounted: true, isOpen: !prevState.isOpen }));
setModalOpen(s => !s);
};
const handleModalClose = () => {
setModalState(prevState => ({ ...prevState, isMounted: false }));
setModalOpen(false);
};
// We need to format the actions so it matches the shape of the ConditionsModal actions props
const formattedActions = formatActions(actions, modifiedData, pathToData);
const doesButtonHasCondition = getConditionsButtonState(get(modifiedData, [...pathToData], {}));
@ -79,6 +78,7 @@ const SubCategory = ({ categoryName, isFormDisabled, subCategoryName, actions, p
<Box paddingLeft={4}>
<Checkbox
name={pathToData.join('..')}
aria-label={pathToData.join('..')}
disabled={isFormDisabled || IS_DISABLED}
// Keep same signature as packages/core/admin/admin/src/components/Roles/Permissions/index.js l.91
onValueChange={value =>
@ -131,16 +131,14 @@ const SubCategory = ({ categoryName, isFormDisabled, subCategoryName, actions, p
/>
</Row>
</Box>
{modalState.isMounted && (
<ConditionsModal
headerBreadCrumbs={[categoryName, subCategoryName]}
actions={formattedActions}
isOpen={modalState.isOpen}
isFormDisabled={isFormDisabled}
onClosed={handleModalClose}
onToggle={handleToggleModalIsOpen}
/>
)}
<ConditionsModal
headerBreadCrumbs={[categoryName, subCategoryName]}
actions={formattedActions}
isOpen={isModalOpen}
isFormDisabled={isFormDisabled}
onClosed={handleModalClose}
onToggle={handleToggleModalIsOpen}
/>
</>
);
};

View File

@ -1,4 +1,4 @@
import { Box, Checkbox, Text } from '@strapi/parts';
import { Row, Checkbox, Text } from '@strapi/parts';
import upperFirst from 'lodash/upperFirst';
import PropTypes from 'prop-types';
import React, { memo } from 'react';
@ -6,24 +6,6 @@ import styled from 'styled-components';
import CollapseLabel from '../CollapseLabel';
import { firstRowWidth } from '../Permissions/utils/constants';
const Wrapper = styled(Box)`
display: flex;
align-items: center;
width: ${firstRowWidth};
padding-left: ${({ theme }) => theme.spaces[6]};
${({ disabled, theme }) =>
disabled &&
`
input[type='checkbox'] {
cursor: not-allowed;
&:after {
color: ${theme.main.colors.grey};
}
}
`};
`;
// ! REMOVE THIS WHEN DS IS UPDATED WITH ELLIPSIS PROP
const StyledText = styled(Text)`
white-space: nowrap;
@ -34,6 +16,7 @@ const StyledText = styled(Text)`
const RowLabelWithCheckbox = ({
children,
isCollapsable,
isActive,
isFormDisabled,
label,
onChange,
@ -43,9 +26,10 @@ const RowLabelWithCheckbox = ({
value,
}) => {
return (
<Wrapper disabled={isFormDisabled}>
<Row alignItems="center" paddingLeft={6} style={{ width: firstRowWidth }}>
<Checkbox
name={checkboxName}
aria-label={checkboxName}
disabled={isFormDisabled}
// Keep same signature as packages/core/admin/admin/src/components/Roles/Permissions/index.js l.91
onValueChange={value =>
@ -64,6 +48,7 @@ const RowLabelWithCheckbox = ({
isCollapsable={isCollapsable}
{...(isCollapsable && {
onClick,
'aria-expanded': isActive,
onKeyDown: ({ key }) => (key === 'Enter' || key === ' ') && onClick(),
tabIndex: 0,
role: 'button',
@ -72,7 +57,7 @@ const RowLabelWithCheckbox = ({
<StyledText>{upperFirst(label)}</StyledText>
{children}
</CollapseLabel>
</Wrapper>
</Row>
);
};
@ -95,6 +80,7 @@ RowLabelWithCheckbox.propTypes = {
onClick: PropTypes.func.isRequired,
someChecked: PropTypes.bool,
value: PropTypes.bool,
isActive: PropTypes.bool.isRequired,
};
export default memo(RowLabelWithCheckbox);

View File

@ -132,6 +132,7 @@ const Login = ({ onSubmit, schema, children }) => {
handleChange({ target: { value: checked, name: 'rememberMe' } });
}}
value={values.rememberMe}
aria-label="rememberMe"
name="rememberMe"
>
{formatMessage({

View File

@ -280,6 +280,7 @@ const Register = ({ fieldsToDisable, noSignin, onSubmit, schema }) => {
}}
value={values.news}
name="news"
aria-label="news"
>
{formatMessage(
{

View File

@ -4,6 +4,7 @@ import {
useNotification,
useOverlayBlocker,
useTracking,
Form,
} from '@strapi/helper-plugin';
import {
Box,
@ -124,8 +125,8 @@ const CreatePage = () => {
validationSchema={schema}
validateOnChange={false}
>
{({ handleSubmit, values, errors, handleReset, handleChange, handleBlur }) => (
<form onSubmit={handleSubmit}>
{({ handleSubmit, values, errors, handleReset, handleChange }) => (
<Form noValidate>
<>
<HeaderLayout
id="title"
@ -162,87 +163,86 @@ const CreatePage = () => {
as="h1"
/>
<ContentLayout>
<Box background="neutral0" padding={6} shadow="filterShadow" hasRadius>
<Stack size={4}>
<Row justifyContent="space-between">
<Box>
<Stack size={6}>
<Box background="neutral0" padding={6} shadow="filterShadow" hasRadius>
<Stack size={4}>
<Row justifyContent="space-between">
<Box>
<Text highlighted>
{formatMessage({
id: 'Settings.roles.form.title',
defaultMessage: 'Details',
})}
</Text>
<Box>
<Text highlighted>
{formatMessage({
id: 'Settings.roles.form.title',
defaultMessage: 'Details',
})}
</Text>
</Box>
<Box>
<Text textColor="neutral600" small>
{formatMessage({
id: 'Settings.roles.form.description',
defaultMessage: 'Name and description of the role',
})}
</Text>
</Box>
</Box>
<Box>
<Text textColor="neutral500" small>
{formatMessage({
id: 'Settings.roles.form.description',
defaultMessage: 'Name and description of the role',
<UsersRoleNumber>
{formatMessage(
{
id: 'Settings.roles.form.button.users-with-role',
defaultMessage:
'{number, plural, =0 {# users} one {# user} other {# users}} with this role',
},
{ number: 0 }
)}
</UsersRoleNumber>
</Row>
<Grid gap={4}>
<GridItem col={6}>
<TextInput
name="name"
error={errors.name && formatMessage({ id: errors.name })}
label={formatMessage({
id: 'Settings.roles.form.input.name',
defaultMessage: 'Name',
})}
</Text>
</Box>
</Box>
<UsersRoleNumber>
{formatMessage(
{
id: 'Settings.roles.form.button.users-with-role',
defaultMessage:
'{number, plural, =0 {# users} one {# user} other {# users}} with this role',
},
{ number: 0 }
)}
</UsersRoleNumber>
</Row>
<Grid gap={4}>
<GridItem col={6}>
<TextInput
name="name"
error={errors.name && formatMessage({ id: errors.name })}
label={formatMessage({
id: 'Settings.roles.form.input.name',
defaultMessage: 'Name',
})}
onChange={handleChange}
onBlur={handleBlur}
value={values.name}
/>
</GridItem>
<GridItem col={6}>
<Textarea
label={formatMessage({
id: 'Settings.roles.form.input.description',
defaultMessage: 'Description',
})}
name="description"
error={errors.name && formatMessage({ id: errors.name })}
onChange={handleChange}
onBlur={handleBlur}
>
{values.description}
</Textarea>
</GridItem>
</Grid>
</Stack>
</Box>
{!isLayoutLoading && !isRoleLoading ? (
<Box paddingTop={6} paddingBottom={6}>
<Permissions
isFormDisabled={false}
ref={permissionsRef}
permissions={rolePermissions}
layout={permissionsLayout}
/>
onChange={handleChange}
value={values.name}
/>
</GridItem>
<GridItem col={6}>
<Textarea
label={formatMessage({
id: 'Settings.roles.form.input.description',
defaultMessage: 'Description',
})}
name="description"
error={errors.name && formatMessage({ id: errors.name })}
onChange={handleChange}
>
{values.description}
</Textarea>
</GridItem>
</Grid>
</Stack>
</Box>
) : (
// ! TODO : Switch to the DS component when done
<Row alignItems="center" justifyContent="center" padding={11}>
<Loader />
</Row>
)}
{!isLayoutLoading && !isRoleLoading ? (
<Box shadow="filterShadow" hasRadius>
<Permissions
isFormDisabled={false}
ref={permissionsRef}
permissions={rolePermissions}
layout={permissionsLayout}
/>
</Box>
) : (
<Row alignItems="center" justifyContent="center" padding={11}>
<Loader />
</Row>
)}
</Stack>
</ContentLayout>
</>
</form>
</Form>
)}
</Formik>
</Main>