Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions packages/gamut/src/ConnectedForm/ConnectedFormGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export function ConnectedFormGroup<T extends ConnectedField>({
id,
label,
name,
labelSize,
spacing = 'fit',
isSoloField,
infotip,
Expand All @@ -82,7 +81,6 @@ export function ConnectedFormGroup<T extends ConnectedField>({
infotip={infotip}
isSoloField={isSoloField}
required={!!validation?.required}
size={labelSize}
>
{label}
</FormGroupLabel>
Expand All @@ -103,31 +101,36 @@ export function ConnectedFormGroup<T extends ConnectedField>({
name={name}
/>
{children}
{showError && (
<FormError
aria-live={isFirstError ? 'assertive' : 'off'}
id={errorId}
role={isFirstError ? 'alert' : 'status'}
variant={errorType}
>
{/*
* For screen readers to read new content, role="alert" and/or
* aria-live wrapper elements must be present *before* content is
* added. Thus, we need to render the FormError span always,
* regardless of whether or not there is an error.
*/}
<FormError
aria-live={isFirstError ? 'assertive' : 'off'}
id={errorId}
role={isFirstError ? 'alert' : 'status'}
variant={errorType}
>
{showError && (
<Markdown
inline
overrides={{
a: {
allowedAttributes: ['href', 'target'],
component: ErrorAnchor,
processNode: (
node: unknown,
props: { onClick?: () => void }
) => <ErrorAnchor {...props} />,
processNode: (_: unknown, props: { onClick?: () => void }) => (
<ErrorAnchor {...props} />
),
},
}}
skipDefaultOverrides={{ a: true }}
spacing="none"
text={textError}
/>
</FormError>
)}
)}
</FormError>
</FormGroup>
);
}
49 changes: 43 additions & 6 deletions packages/gamut/src/Form/elements/FormGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { variant } from '@codecademy/gamut-styles';
import { css, variant } from '@codecademy/gamut-styles';
import { StyleProps } from '@codecademy/variance';
import styled from '@emotion/styled';
import { ComponentProps } from 'react';
import * as React from 'react';

import { Anchor } from '../../Anchor';
import { Box } from '../../Box';
import { Markdown } from '../../Markdown';
import { BaseInputProps } from '../types';
import { FormError } from './FormError';
import { FormGroupDescription } from './FormGroupDescription';
Expand All @@ -20,6 +22,8 @@ export interface FormGroupProps
error?: string;
description?: string;
labelSize?: 'small' | 'large';
isFirstError?: boolean;
errorType?: 'initial' | 'absolute';
}

const formGroupSpacing = variant({
Expand Down Expand Up @@ -60,6 +64,12 @@ const FormGroupContainer: React.FC<
return <StyledFormGroupContainer mb={mb} pb={pb} {...rest} />;
};

const ErrorAnchor = styled(Anchor)(
css({
color: 'feedback-error',
})
);

export const FormGroup: React.FC<FormGroupProps> = ({
children,
className,
Expand All @@ -72,6 +82,8 @@ export const FormGroup: React.FC<FormGroupProps> = ({
labelSize,
required,
isSoloField,
isFirstError,
errorType,
...rest
}) => {
const labelComponent = label ? (
Expand All @@ -98,11 +110,36 @@ export const FormGroup: React.FC<FormGroupProps> = ({
{labelComponent}
{descriptionComponent}
{children}
{error && (
<FormError aria-live="polite" role="alert">
{error}
</FormError>
)}
{/*
* For screen readers to read new content, role="alert" and/or
* aria-live wrapper elements must be present *before* content is
* added. Thus, we need to render the FormError span always,
* regardless of whether or not there is an error.
*/}
<FormError
aria-live={isFirstError ? 'assertive' : 'off'}
role={isFirstError ? 'alert' : 'status'}
variant={errorType}
>
{error && (
<Markdown
inline
overrides={{
a: {
allowedAttributes: ['href', 'target'],
component: ErrorAnchor,
processNode: (
node: unknown,
props: { onClick?: () => void }
) => <ErrorAnchor {...props} />,
},
}}
skipDefaultOverrides={{ a: true }}
spacing="none"
text={error}
/>
)}
</FormError>
</FormGroupContainer>
);
};
43 changes: 8 additions & 35 deletions packages/gamut/src/GridForm/GridFormInputGroup/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { css } from '@codecademy/gamut-styles';
import styled from '@emotion/styled';
import * as React from 'react';
import { UseFormReturn } from 'react-hook-form';

import { Anchor } from '../../Anchor';
import { FormError, FormGroup, FormGroupLabel } from '../../Form';
import { FormGroup, FormGroupLabel } from '../../Form';
import { HiddenText } from '../../HiddenText';
import { Column } from '../../Layout';
import { Markdown } from '../../Markdown';
import {
GridFormField,
GridFormHiddenField,
Expand All @@ -23,11 +19,7 @@ import { GridFormSweetContainerInput } from './GridFormSweetContainerInput';
import { GridFormTextArea } from './GridFormTextArea';
import { GridFormTextInput } from './GridFormTextInput';

const ErrorAnchor = styled(Anchor)(
css({
color: 'feedback-error',
})
);


export type GridFormInputGroupProps = {
error?: string;
Expand Down Expand Up @@ -157,33 +149,14 @@ export const GridFormInputGroup: React.FC<GridFormInputGroupProps> = ({

return (
<Column rowspan={field?.rowspan ?? 1} size={field?.size}>
<FormGroup spacing={isTightCheckbox ? 'tight' : 'padded'}>
<FormGroup
error={errorMessage}
errorType={isTightCheckbox ? 'initial' : 'absolute'}
isFirstError={isFirstError}
spacing={isTightCheckbox ? 'tight' : 'padded'}
>
{field.hideLabel ? <HiddenText>{label}</HiddenText> : label}
{getInput()}
{errorMessage && (
<FormError
aria-live={isFirstError ? 'assertive' : 'off'}
role={isFirstError ? 'alert' : 'status'}
variant={isTightCheckbox ? 'initial' : 'absolute'}
>
<Markdown
inline
overrides={{
a: {
allowedAttributes: ['href', 'target'],
component: ErrorAnchor,
processNode: (
node: unknown,
props: { onClick?: () => void }
) => <ErrorAnchor {...props} />,
},
}}
skipDefaultOverrides={{ a: true }}
spacing="none"
text={errorMessage}
/>
</FormError>
)}
</FormGroup>
</Column>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/gamut/src/GridForm/__tests__/GridForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ describe('GridForm', () => {

// there should only be a single "assertive" error from the form submission
expect(view.getAllByRole('alert').length).toBe(1);
expect(view.getAllByRole('status').length).toBe(1);
expect(view.getAllByRole('status').length).toBe(2);
});

describe('when "onSubmit" validation is selected', () => {
Expand Down
Loading