Skip to content

Commit

Permalink
feat(Input): make label prop optional & add accessibilityLabel pr…
Browse files Browse the repository at this point in the history
…op (#1472)
  • Loading branch information
snitin315 authored Jul 28, 2023
1 parent 9f214f3 commit 5bddbe0
Show file tree
Hide file tree
Showing 13 changed files with 314 additions and 18 deletions.
13 changes: 13 additions & 0 deletions .changeset/nervous-chicken-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@razorpay/blade": minor
---

feat(Input): make `label` prop optional & add `accessibilityLabel` prop to `TextInput`, `TextArea`, `PasswordInput`, `SelectInput`, and `OTPInput` components

#### Key Updates

- **Optional `label` Prop**: We understand that not all use cases require a label for the Input components. Therefore, we have made the label prop optional, providing you with the freedom to choose whether to display a label or not, depending on your specific application requirements.

- **Introducing `accessibilityLabel`:** Recognizing the significance of accessibility in modern applications, we have added the `accessibilityLabel` prop to the Input components. This prop enables developers to assign a descriptive label for the input field, making it more user-friendly for individuals using assistive technologies or screen readers.

- **Enhanced User Guidance:** To maintain usability, we have implemented a requirement that either the `label` or `accessibilityLabel` prop must be provided. This ensures that users will always have clear guidance when interacting with Inputs, promoting a seamless user experience.
4 changes: 2 additions & 2 deletions packages/blade/src/components/Form/FormLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type CommonProps = {
position?: 'top' | 'left';
necessityIndicator?: 'required' | 'optional' | 'none';
accessibilityText?: string;
children: string;
children: string | undefined;
id?: string;
contrast?: ColorContrastTypes;
};
Expand All @@ -37,7 +37,7 @@ export type FormInputLabelProps = {
/**
* Label to be shown for the input field
*/
label: string;
label?: string;
/**
* Desktop only prop. Default value on mobile will be `top`
*/
Expand Down
35 changes: 33 additions & 2 deletions packages/blade/src/components/Input/BaseInput/BaseInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type CommonAutoCompleteSuggestionTypes =

type WebAutoCompleteSuggestionType = CommonAutoCompleteSuggestionTypes | 'on';

export type BaseInputProps = FormInputLabelProps &
type BaseInputCommonProps = FormInputLabelProps &
FormInputValidationProps & {
/**
* Determines if it needs to be rendered as input, textarea or button
Expand Down Expand Up @@ -270,6 +270,37 @@ export type BaseInputProps = FormInputLabelProps &
}> &
StyledPropsBlade;

/*
Mandatory accessibilityLabel prop when label is not provided
*/
type BaseInputPropsWithA11yLabel = {
/**
* Label to be shown for the input field
*/
label?: undefined;
/**
* Accessibility label for the input
*/
accessibilityLabel: string;
};

/*
Optional accessibilityLabel prop when label is provided
*/
type BaseInputPropsWithLabel = {
/**
* Label to be shown for the input field
*/
label: string;
/**
* Accessibility label for the input
*/
accessibilityLabel?: string;
};

export type BaseInputProps = (BaseInputPropsWithA11yLabel | BaseInputPropsWithLabel) &
BaseInputCommonProps;

const autoCompleteSuggestionTypeValues = [
'none',
'on',
Expand Down Expand Up @@ -731,7 +762,7 @@ export const BaseInput = React.forwardRef<HTMLInputElement, BaseInputProps>(
</BaseBox>
{/* the magic number 136 is basically max-width of label i.e 120 and then right margin i.e 16 which is the spacing between label and input field */}
{!hideFormHint && (
<BaseBox marginLeft={makeSize(isLabelLeftPositioned ? 136 : 0)}>
<BaseBox marginLeft={makeSize(isLabelLeftPositioned && !hideLabelText ? 136 : 0)}>
<BaseBox
display="flex"
flexDirection="row"
Expand Down
13 changes: 13 additions & 0 deletions packages/blade/src/components/Input/OTPInput/OTPInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ export default {
category: propsCategory.LABEL_PROPS,
},
},
accessibilityLabel: {
table: {
category: propsCategory.LABEL_PROPS,
},
},
labelPosition: {
table: {
category: propsCategory.LABEL_PROPS,
Expand Down Expand Up @@ -193,6 +198,14 @@ OTPInputHelpText.args = {
helpText: 'Add a message here',
};

export const OTPInputWithoutLabel = OTPInputTemplate.bind({});
OTPInputWithoutLabel.storyName = 'OTPInput without Label';
OTPInputWithoutLabel.args = {
label: undefined,
accessibilityLabel: 'Enter OTP',
helpText: 'Add a message here',
};

export const OTPInputMasked = OTPInputTemplate.bind({});
OTPInputMasked.storyName = 'OTPInput with Masked input';
OTPInputMasked.args = {
Expand Down
46 changes: 41 additions & 5 deletions packages/blade/src/components/Input/OTPInput/OTPInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ type FormInputOnEventWithIndex = ({
inputIndex: number;
}) => void;

export type OTPInputProps = Pick<
export type OTPInputCommonProps = Pick<
BaseInputProps,
| 'label'
| 'accessibilityLabel'
| 'labelPosition'
| 'validationState'
| 'helpText'
Expand Down Expand Up @@ -82,6 +83,36 @@ export type OTPInputProps = Pick<
onBlur?: FormInputOnEventWithIndex;
} & StyledPropsBlade;

/*
Mandatory accessibilityLabel prop when label is not provided
*/
type OTPInputPropsWithA11yLabel = {
/**
* Label to be shown for the input field
*/
label?: undefined;
/**
* Accessibility label for the input
*/
accessibilityLabel: string;
};

/*
Optional accessibilityLabel prop when label is provided
*/
type OTPInputPropsWithLabel = {
/**
* Label to be shown for the input field
*/
label: string;
/**
* Accessibility label for the input
*/
accessibilityLabel?: string;
};

type OTPInputProps = (OTPInputPropsWithA11yLabel | OTPInputPropsWithLabel) & OTPInputCommonProps;

const isReactNative = getPlatformType() === 'react-native';

/**
Expand Down Expand Up @@ -111,6 +142,7 @@ const OTPInput = ({
keyboardReturnKeyType,
keyboardType = 'decimal',
label,
accessibilityLabel,
labelPosition,
name,
onChange,
Expand Down Expand Up @@ -295,7 +327,9 @@ const OTPInput = ({
<BaseInput
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autoFocus && index === 0}
accessibilityLabel={`${index === 0 ? label : ''} character ${index + 1}`}
accessibilityLabel={`${index === 0 ? label || accessibilityLabel : ''} character ${
index + 1
}`}
label={label}
hideLabelText={true}
id={`${inputId}-${index}`}
Expand Down Expand Up @@ -341,9 +375,11 @@ const OTPInput = ({
alignItems={isLabelLeftPositioned ? 'center' : undefined}
position="relative"
>
<FormLabel as="label" position={labelPosition} htmlFor={inputId}>
{label}
</FormLabel>
{Boolean(label) && (
<FormLabel as="label" position={labelPosition} htmlFor={inputId}>
{label}
</FormLabel>
)}
<BaseBox display="flex" flexDirection="row">
{getHiddenInput()}
{getOTPInputFields()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ LabelAtLeft.parameters = {
},
};

export const PasswordInputWithoutLabel = PasswordInputTemplate.bind({});
PasswordInputWithoutLabel.args = {
label: undefined,
accessibilityLabel: 'Password',
};

export const Disabled = PasswordInputTemplate.bind({});
Disabled.args = {
isDisabled: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ type PasswordInputExtraProps = {
>;
};

type PasswordInputProps = Pick<
type PasswordInputCommonProps = Pick<
BaseInputProps,
| 'label'
| 'accessibilityLabel'
| 'labelPosition'
| 'maxCharacters'
| 'validationState'
Expand All @@ -74,9 +75,41 @@ type PasswordInputProps = Pick<
PasswordInputExtraProps &
StyledPropsBlade;

/*
Mandatory accessibilityLabel prop when label is not provided
*/
type PasswordInputPropsWithA11yLabel = {
/**
* Label to be shown for the input field
*/
label?: undefined;
/**
* Accessibility label for the input
*/
accessibilityLabel: string;
};

/*
Optional accessibilityLabel prop when label is provided
*/
type PasswordInputPropsWithLabel = {
/**
* Label to be shown for the input field
*/
label: string;
/**
* Accessibility label for the input
*/
accessibilityLabel?: string;
};

type PasswordInputProps = (PasswordInputPropsWithA11yLabel | PasswordInputPropsWithLabel) &
PasswordInputCommonProps;

const _PasswordInput: React.ForwardRefRenderFunction<BladeElementRef, PasswordInputProps> = (
{
label,
accessibilityLabel,
labelPosition = 'top',
showRevealButton = true,
maxCharacters,
Expand Down Expand Up @@ -111,7 +144,7 @@ const _PasswordInput: React.ForwardRefRenderFunction<BladeElementRef, PasswordIn
const isRevealedAndEnabled = isRevealed && isEnabled;

const toggleIsRevealed = (): void => setIsRevealed((revealed) => !revealed);
const accessibilityLabel = isRevealedAndEnabled ? 'Hide password' : 'Show password';
const iconAccessibilityLabel = isRevealedAndEnabled ? 'Hide password' : 'Show password';
const type = isRevealedAndEnabled ? 'text' : 'password';

const revealButtonIcon = isRevealedAndEnabled ? EyeOffIcon : EyeIcon;
Expand All @@ -121,7 +154,7 @@ const _PasswordInput: React.ForwardRefRenderFunction<BladeElementRef, PasswordIn
size="medium"
icon={revealButtonIcon}
onClick={toggleIsRevealed}
accessibilityLabel={accessibilityLabel}
accessibilityLabel={iconAccessibilityLabel}
/>
) : null;

Expand All @@ -137,7 +170,9 @@ const _PasswordInput: React.ForwardRefRenderFunction<BladeElementRef, PasswordIn
ref={inputRef as React.Ref<HTMLInputElement>}
componentName={MetaConstants.PasswordInput}
id="password-field"
label={label}
label={label as string}
accessibilityLabel={accessibilityLabel}
hideLabelText={!Boolean(label)}
labelPosition={labelPosition}
type={type}
interactionElement={revealButton}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ export default {
category: propsCategory.LABEL_PROPS,
},
},
accessibilityLabel: {
table: {
category: propsCategory.LABEL_PROPS,
},
},
labelPosition: {
table: {
category: propsCategory.LABEL_PROPS,
Expand Down Expand Up @@ -233,3 +238,10 @@ const SelectInputTemplate: ComponentStory<typeof SelectInput> = ({ icon, ...args
export const Default = SelectInputTemplate.bind({});
// Need to do this because of storybook's weird naming convention, More details here: https://storybook.js.org/docs/react/writing-stories/naming-components-and-hierarchy#single-story-hoisting
Default.storyName = 'SelectInput';

export const SelectInputWithoutLabel = SelectInputTemplate.bind({});
SelectInputWithoutLabel.args = {
label: undefined,
accessibilityLabel: 'City',
};
SelectInputWithoutLabel.storyName = 'SelectInput without Label';
35 changes: 34 additions & 1 deletion packages/blade/src/components/Input/SelectInput/SelectInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import { useBladeInnerRef } from '~utils/useBladeInnerRef';
import { MetaConstants } from '~utils/metaAttribute';
import { assignWithoutSideEffects } from '~utils/assignWithoutSideEffects';

type SelectInputProps = Pick<
type SelectInputCommonProps = Pick<
BaseInputProps,
| 'label'
| 'accessibilityLabel'
| 'labelPosition'
| 'necessityIndicator'
| 'validationState'
Expand Down Expand Up @@ -51,6 +52,37 @@ type SelectInputProps = Pick<
onChange?: ({ name, values }: { name?: string; values: string[] }) => void;
};

/*
Mandatory accessibilityLabel prop when label is not provided
*/
type SelectInputPropsWithA11yLabel = {
/**
* Label to be shown for the input field
*/
label?: undefined;
/**
* Accessibility label for the input
*/
accessibilityLabel: string;
};

/*
Optional accessibilityLabel prop when label is provided
*/
type SelectInputPropsWithLabel = {
/**
* Label to be shown for the input field
*/
label: string;
/**
* Accessibility label for the input
*/
accessibilityLabel?: string;
};

type SelectInputProps = (SelectInputPropsWithA11yLabel | SelectInputPropsWithLabel) &
SelectInputCommonProps;

const _SelectInput = (
props: SelectInputProps,
ref: React.ForwardedRef<BladeElementRef>,
Expand Down Expand Up @@ -196,6 +228,7 @@ const _SelectInput = (
<BaseInput
{...baseInputProps}
as="button"
label={props.label as string}
hideLabelText={props.label?.length === 0}
componentName={MetaConstants.SelectInput}
ref={!isReactNative() ? (triggererRef as React.MutableRefObject<HTMLInputElement>) : null}
Expand Down
Loading

0 comments on commit 5bddbe0

Please sign in to comment.