Skip to content

Commit

Permalink
fixup! Feat(web-react): Implement new HelperText component #DS-886
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelklibani committed Sep 11, 2023
1 parent f1d1d9e commit a54fde0
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 88 deletions.
21 changes: 11 additions & 10 deletions packages/web-react/src/components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import classNames from 'classnames';
import { useStyleProps } from '../../hooks';
import { SpiritCheckboxProps } from '../../types';
import { HelperText, ValidationText } from '../Field';
import useAriaDescribedBy from '../Field/useAriaDescribedBy';
import useAriaIds from '../Field/useAriaIds';
import { useCheckboxStyleProps } from './useCheckboxStyleProps';

/* We need an exception for components exported with forwardRef */
Expand All @@ -25,18 +25,13 @@ const _Checkbox = (props: SpiritCheckboxProps, ref: ForwardedRef<HTMLInputElemen
} = modifiedProps;
const { styleProps, props: otherProps } = useStyleProps(restProps);

const { validationTextId, helperTextId, joinedAriaDescribedBy } = useAriaDescribedBy({
id,
helperText,
validationText,
ariaDescribedBy,
});
const [ids, register] = useAriaIds(ariaDescribedBy);

return (
<label {...styleProps} htmlFor={id} className={classNames(classProps.root, styleProps.className)}>
<input
{...otherProps}
aria-describedby={joinedAriaDescribedBy}
aria-describedby={ids.join(' ')}
type="checkbox"
id={id}
className={classProps.input}
Expand All @@ -49,16 +44,22 @@ const _Checkbox = (props: SpiritCheckboxProps, ref: ForwardedRef<HTMLInputElemen
<span className={classProps.text}>
<span className={classProps.label}>{label}</span>
{helperText && (
<HelperText className={classProps.helperText} elementType="span" id={helperTextId}>
<HelperText
className={classProps.helperText}
elementType="span"
id={`${id}__helperText`}
registerAria={register}
>
{helperText}
</HelperText>
)}
{validationState && validationText && (
<ValidationText
className={classProps.validationText}
elementType="span"
id={validationTextId}
id={`${id}__validationText`}
validationText={validationText}
registerAria={register}
/>
)}
</span>
Expand Down
19 changes: 17 additions & 2 deletions packages/web-react/src/components/Field/HelperText.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
import React, { ElementType } from 'react';
import React, { ElementType, useEffect } from 'react';
import { RegisterType } from './useAriaIds';

interface Props {
children: React.ReactNode;
className?: string;
elementType?: ElementType;
id?: string;
registerAria?: RegisterType;
}

const defaultProps = {
className: undefined,
elementType: 'div',
id: undefined,
registerAria: undefined,
};

const HelperText = (props: Props) => {
const { children, className, elementType: ElementTag = 'div', id } = props;
const { children, className, elementType: ElementTag = 'div', id, registerAria } = props;

useEffect(() => {
if (!registerAria) return;

registerAria({ add: id });

return () => {
registerAria({ remove: id });
};

// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<ElementTag className={className} id={id}>
Expand Down
19 changes: 17 additions & 2 deletions packages/web-react/src/components/Field/ValidationText.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
import React, { ElementType } from 'react';
import React, { ElementType, useEffect } from 'react';
import { ValidationTextProp } from '../../types';
import { RegisterType } from './useAriaIds';

export interface ValidationTextProps extends ValidationTextProp {
className?: string;
elementType?: ElementType;
id?: string;
registerAria?: RegisterType;
}

const defaultProps = {
className: undefined,
elementType: 'div',
id: undefined,
registerAria: undefined,
};

export const ValidationText = (props: ValidationTextProps) => {
const { className, validationText, elementType: ElementTag = 'div', id } = props;
const { className, validationText, elementType: ElementTag = 'div', id, registerAria } = props;

useEffect(() => {
if (!registerAria) return;

registerAria({ add: id });

return () => {
registerAria({ remove: id });
};

// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return Array.isArray(validationText) ? (
<ul className={className} id={id}>
Expand Down
2 changes: 1 addition & 1 deletion packages/web-react/src/components/Field/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './ValidationText';
export * from './HelperText';
export * from './useAriaDescribedBy';
export * from './useAriaIds';
export { default as ValidationText } from './ValidationText';
export { default as HelperText } from './HelperText';
18 changes: 0 additions & 18 deletions packages/web-react/src/components/Field/useAriaDescribedBy.tsx

This file was deleted.

29 changes: 29 additions & 0 deletions packages/web-react/src/components/Field/useAriaIds.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useCallback, useState } from 'react';

export type RegisterParams = { add?: string; remove?: string };
export type RegisterType = (params: RegisterParams) => void;
export type UseAriaIdsHook = (otherAriaIds?: string) => [string[], RegisterType];

const useAriaIds: UseAriaIdsHook = (otherAriaIds) => {
const [ids, setIds] = useState<string[]>(otherAriaIds ? otherAriaIds.split(' ') : []);

const register = useCallback(function registerFn({ add, remove }: RegisterParams) {
setIds((prevIds) => {
let newIds = [...prevIds];

if (remove) {
newIds = newIds.filter((item) => item !== remove);
}

if (add) {
newIds = [...newIds, add];
}

return newIds;
});
}, []);

return [ids, register];
};

export default useAriaIds;
20 changes: 10 additions & 10 deletions packages/web-react/src/components/FieldGroup/FieldGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React from 'react';
import classNames from 'classnames';
import { SpiritFieldGroupProps } from '../../types';
import { useStyleProps } from '../../hooks';
import useAriaDescribedBy from '../Field/useAriaDescribedBy';
import HelperText from '../Field/HelperText';
import ValidationText from '../Field/ValidationText';
import useAriaIds from '../Field/useAriaIds';
import { useFieldGroupStyleProps } from './useFieldGroupStyleProps';

const FieldGroup = (props: SpiritFieldGroupProps) => {
Expand All @@ -26,18 +26,13 @@ const FieldGroup = (props: SpiritFieldGroupProps) => {
const { classProps } = useFieldGroupStyleProps({ isFluid, isRequired, validationState });
const { styleProps, props: transferProps } = useStyleProps(rest);

const { validationTextId, helperTextId, joinedAriaDescribedBy } = useAriaDescribedBy({
id,
helperText,
validationText,
ariaDescribedBy,
});
const [ids, register] = useAriaIds(ariaDescribedBy);

return (
<fieldset
{...transferProps}
{...styleProps}
aria-describedby={joinedAriaDescribedBy}
aria-describedby={ids.join(' ')}
className={classNames(classProps.root, styleProps.className)}
disabled={isDisabled}
>
Expand All @@ -49,12 +44,17 @@ const FieldGroup = (props: SpiritFieldGroupProps) => {
)}
<div className={classProps.fields}>{children}</div>
{helperText && (
<HelperText className={classProps.helperText} id={helperTextId}>
<HelperText className={classProps.helperText} id={`${id}__helperText`} registerAria={register}>
{helperText}
</HelperText>
)}
{validationState && validationText && (
<ValidationText className={classProps.validationText} id={validationTextId} validationText={validationText} />
<ValidationText
className={classProps.validationText}
id={`${id}__helperText`}
validationText={validationText}
registerAria={register}
/>
)}
</fieldset>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import classNames from 'classnames';
import { SpiritFileUploaderInputProps } from '../../types';
import { useStyleProps } from '../../hooks';
import { HelperText, ValidationText } from '../Field';
import { Icon } from '../Icon';
import useAriaIds from '../Field/useAriaIds';
import { DEFAULT_FILE_QUEUE_LIMIT, DEFAULT_FILE_SIZE_LIMIT } from './constants';
import { useFileUploaderStyleProps } from './useFileUploaderStyleProps';
import { useFileUploaderInput } from './useFileUploaderInput';
import { Icon } from '../Icon';
import useAriaDescribedBy from '../Field/useAriaDescribedBy';

const FileUploaderInput = (props: SpiritFileUploaderInputProps) => {
const {
Expand Down Expand Up @@ -66,12 +66,7 @@ const FileUploaderInput = (props: SpiritFileUploaderInputProps) => {
});
const { styleProps, props: transferProps } = useStyleProps(restProps);

const { validationTextId, helperTextId, joinedAriaDescribedBy } = useAriaDescribedBy({
id,
helperText,
validationText,
ariaDescribedBy,
});
const [ids, register] = useAriaIds(ariaDescribedBy);

return (
<div
Expand All @@ -87,7 +82,7 @@ const FileUploaderInput = (props: SpiritFileUploaderInputProps) => {
{label}
</label>
<input
aria-describedby={joinedAriaDescribedBy}
aria-describedby={ids.join(' ')}
type="file"
accept={accept}
id={id}
Expand All @@ -107,7 +102,7 @@ const FileUploaderInput = (props: SpiritFileUploaderInputProps) => {
<span className={classProps.input.dropLabel}>{labelText}</span>
</label>
{helperText && (
<HelperText className={classProps.input.helper} id={helperTextId}>
<HelperText className={classProps.input.helper} id={`${id}__helperText`} registerAria={register}>
{helperText}
</HelperText>
)}
Expand All @@ -116,8 +111,9 @@ const FileUploaderInput = (props: SpiritFileUploaderInputProps) => {
<ValidationText
className={classProps.input.validationText}
elementType="span"
id={validationTextId}
id={`${id}__validationText`}
validationText={validationText}
registerAria={register}
/>
)}
</div>
Expand Down
18 changes: 9 additions & 9 deletions packages/web-react/src/components/Radio/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import classNames from 'classnames';
import { useStyleProps } from '../../hooks';
import { SpiritRadioProps } from '../../types';
import { HelperText } from '../Field';
import useAriaDescribedBy from '../Field/useAriaDescribedBy';
import useAriaIds from '../Field/useAriaIds';
import { useRadioStyleProps } from './useRadioStyleProps';

/* We need an exception for components exported with forwardRef */
Expand All @@ -23,18 +23,13 @@ const _Radio = (props: SpiritRadioProps, ref: ForwardedRef<HTMLInputElement>): J
} = modifiedProps;
const { styleProps, props: otherProps } = useStyleProps(restProps);

const { helperTextId, joinedAriaDescribedBy } = useAriaDescribedBy({
id,
helperText,
validationText: undefined,
ariaDescribedBy,
});
const [ids, register] = useAriaIds(ariaDescribedBy);

return (
<label htmlFor={id} {...styleProps} className={classNames(classProps.root, styleProps.className)}>
<input
{...otherProps}
aria-describedby={joinedAriaDescribedBy}
aria-describedby={ids.join(' ')}
type="radio"
id={id}
className={classProps.input}
Expand All @@ -47,7 +42,12 @@ const _Radio = (props: SpiritRadioProps, ref: ForwardedRef<HTMLInputElement>): J
<span className={classProps.text}>
<span className={classProps.label}>{label}</span>
{helperText && (
<HelperText className={classProps.helperText} elementType="span" id={helperTextId}>
<HelperText
className={classProps.helperText}
elementType="span"
id={`${id}__helperText`}
registerAria={register}
>
{helperText}
</HelperText>
)}
Expand Down
20 changes: 10 additions & 10 deletions packages/web-react/src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { SpiritSelectProps } from '../../types';
import { useStyleProps } from '../../hooks';
import { HelperText, ValidationText } from '../Field';
import { Icon } from '../Icon';
import useAriaDescribedBy from '../Field/useAriaDescribedBy';
import useAriaIds from '../Field/useAriaIds';
import { useSelectStyleProps } from './useSelectStyleProps';

/* We need an exception for components exported with forwardRef */
Expand All @@ -28,12 +28,7 @@ const _Select = (props: SpiritSelectProps, ref: ForwardedRef<HTMLSelectElement>)
const { classProps } = useSelectStyleProps({ isDisabled, isFluid, isRequired, isLabelHidden, validationState });
const { styleProps, props: transferProps } = useStyleProps(restProps);

const { validationTextId, helperTextId, joinedAriaDescribedBy } = useAriaDescribedBy({
id,
helperText,
validationText,
ariaDescribedBy,
});
const [ids, register] = useAriaIds(ariaDescribedBy);

return (
<div {...styleProps} className={classNames(classProps.root, styleProps.className)}>
Expand All @@ -43,7 +38,7 @@ const _Select = (props: SpiritSelectProps, ref: ForwardedRef<HTMLSelectElement>)
<div className={classProps.container}>
<select
{...transferProps}
aria-describedby={joinedAriaDescribedBy}
aria-describedby={ids.join(' ')}
id={id}
className={classProps.input}
ref={ref}
Expand All @@ -57,12 +52,17 @@ const _Select = (props: SpiritSelectProps, ref: ForwardedRef<HTMLSelectElement>)
</div>
</div>
{helperText && (
<HelperText className={classProps.helperText} id={helperTextId}>
<HelperText className={classProps.helperText} id={`${id}__helperText`} registerAria={register}>
{helperText}
</HelperText>
)}
{validationState && validationText && (
<ValidationText className={classProps.validationText} id={validationTextId} validationText={validationText} />
<ValidationText
className={classProps.validationText}
id={`${id}__validationText`}
validationText={validationText}
registerAria={register}
/>
)}
</div>
);
Expand Down
Loading

0 comments on commit a54fde0

Please sign in to comment.