Skip to content

Commit 40879e5

Browse files
authored
Feat/indication for mandatory field (#2775)
* Added mandatory star indication to the text field. Passing showMandatoryIndication to a required text field now shows a star next to the label.
1 parent d22f94c commit 40879e5

File tree

7 files changed

+47
-6
lines changed

7 files changed

+47
-6
lines changed

src/incubator/TextField/FieldContext.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ const FieldContext = createContext<FieldContextType>({
1010
disabled: false,
1111
readonly: false,
1212
validateField: _.noop,
13-
checkValidity: () => true
13+
checkValidity: () => true,
14+
isMandatory: false
1415
});
1516

1617
export default FieldContext;

src/incubator/TextField/Label.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {ColorType, LabelProps, ValidationMessagePosition} from './types';
66
import {getColorByState} from './Presenter';
77
import FieldContext from './FieldContext';
88

9-
109
const DEFAULT_LABEL_COLOR: ColorType = {
1110
default: Colors.$textDefault,
1211
readonly: Colors.$textNeutral
@@ -19,6 +18,7 @@ const Label = ({
1918
labelProps,
2019
validationMessagePosition,
2120
floatingPlaceholder,
21+
showMandatoryIndication,
2222
testID
2323
}: LabelProps) => {
2424
const context = useContext(FieldContext);
@@ -28,6 +28,7 @@ const Label = ({
2828
const style = useMemo(() => {
2929
return [styles.label, labelStyle, floatingPlaceholder && styles.dummyPlaceholder];
3030
}, [labelStyle, floatingPlaceholder]);
31+
const shouldRenderIndication = context.isMandatory && showMandatoryIndication;
3132

3233
if ((label || floatingPlaceholder) && !forceHidingLabel) {
3334
return (
@@ -38,7 +39,7 @@ const Label = ({
3839
recorderTag={'unmask'}
3940
{...labelProps}
4041
>
41-
{label}
42+
{shouldRenderIndication ? label?.concat('*') : label}
4243
</Text>
4344
);
4445
}

src/incubator/TextField/__tests__/index.spec.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,33 @@ describe('TextField', () => {
8383
const validationMessageElement = renderTree.queryByText(props.validationMessage);
8484
expect(validationMessageElement).toBe(null);
8585
});
86+
87+
describe('Mandatory Indication', () => {
88+
it('Should show mandatory star indication - 1', async () => {
89+
const renderTree = render(<TestCase testID={'field'} validate={'required'} label={'Label'} showMandatoryIndication/>);
90+
const label = renderTree.getByTestId('field.label');
91+
const text = label.children[0];
92+
expect(text).toEqual('Label*');
93+
});
94+
it('Should show mandatory star indication - 2', () => {
95+
const renderTree = render(<TestCase testID={'field'} validate={['email', 'required']} label={'Label'} showMandatoryIndication/>);
96+
const label = renderTree.getByTestId('field.label');
97+
const text = label.children[0];
98+
expect(text).toEqual('Label*');
99+
});
100+
it('Should not show mandatory star indication - 1', () => {
101+
const renderTree = render(<TestCase testID={'field'} validate={['email', 'required']} label={'Label'}/>);
102+
const label = renderTree.getByTestId('field.label');
103+
const text = label.children[0];
104+
expect(text).not.toEqual('Label*');
105+
});
106+
it('Should not show mandatory star indication - 2', () => {
107+
const renderTree = render(<TestCase testID={'field'} validate={['email']} label={'Label'} showMandatoryIndication/>);
108+
const label = renderTree.getByTestId('field.label');
109+
const text = label.children[0];
110+
expect(text).not.toEqual('Label*');
111+
});
112+
});
86113
});
87114

88115
describe('defaultValue', () => {

src/incubator/TextField/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ const TextField = (props: InternalTextFieldProps) => {
7878
children,
7979
centered,
8080
readonly = false,
81+
showMandatoryIndication,
8182
...others
8283
} = usePreset(props);
8384
const {ref: leadingAccessoryRef, measurements: leadingAccessoryMeasurements} = useMeasure();
@@ -135,6 +136,7 @@ const TextField = (props: InternalTextFieldProps) => {
135136
floatingPlaceholder={floatingPlaceholder}
136137
validationMessagePosition={validationMessagePosition}
137138
testID={`${props.testID}.label`}
139+
showMandatoryIndication={showMandatoryIndication}
138140
/>
139141
{validationMessagePosition === ValidationMessagePosition.TOP && (
140142
<ValidationMessage

src/incubator/TextField/textField.api.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@
115115
"type": "boolean",
116116
"description": "A UI preset for read only state"
117117
},
118-
{"name": "recorderTag", "type": "'mask' | 'unmask'", "description": "Recorder Tag"}
118+
{"name": "recorderTag", "type": "'mask' | 'unmask'", "description": "Recorder Tag"},
119+
{"name": "showMandatoryIndication", "type": "boolean", "description": "Whether to show a mandatory field indication"}
119120
],
120121
"snippet": [
121122
"<TextField",

src/incubator/TextField/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export interface LabelProps {
7474
validationMessagePosition?: ValidationMessagePositionType;
7575
floatingPlaceholder?: boolean;
7676
testID?: string;
77+
showMandatoryIndication?: boolean;
7778
}
7879

7980
export interface FloatingPlaceholderProps {
@@ -251,6 +252,10 @@ export type TextFieldProps = MarginModifiers &
251252
* Set an alignment fit for inline behavior (when rendered inside a row container)
252253
*/
253254
inline?: boolean;
255+
/**
256+
* Whether to show a mandatory field indication.
257+
*/
258+
showMandatoryIndication?: boolean;
254259
};
255260

256261
export type InternalTextFieldProps = PropsWithChildren<
@@ -267,6 +272,7 @@ export type FieldContextType = {
267272
readonly: boolean;
268273
validateField: () => void;
269274
checkValidity: () => boolean;
275+
isMandatory: boolean;
270276
};
271277

272278
export interface TextFieldMethods {

src/incubator/TextField/useFieldState.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export default function useFieldState({
2020
const [isFocused, setIsFocused] = useState(false);
2121
const [isValid, setIsValid] = useState<boolean | undefined>(undefined);
2222
const [failingValidatorIndex, setFailingValidatorIndex] = useState<number | undefined>(undefined);
23+
const isMandatory = useMemo(() => ((typeof validate === 'string' && validate === 'required') || (Array.isArray(validate) && validate.includes('required'))), [validate]);
24+
2325

2426
useEffect(() => {
2527
if (Constants.isWeb && !props.value && props.defaultValue && props.defaultValue !== value) {
@@ -110,9 +112,10 @@ export default function useFieldState({
110112
hasValue: !_.isEmpty(value),
111113
isValid: validationMessage && !validate ? false : isValid ?? true,
112114
isFocused,
113-
failingValidatorIndex
115+
failingValidatorIndex,
116+
isMandatory
114117
};
115-
}, [value, isFocused, isValid, failingValidatorIndex, validationMessage, validate]);
118+
}, [value, isFocused, isValid, failingValidatorIndex, validationMessage, validate, isMandatory]);
116119

117120
return {
118121
onFocus,

0 commit comments

Comments
 (0)