Skip to content

Commit 531a222

Browse files
authored
Merge pull request #443 from mashmatrix/remove-id-for-label
Explicitly focus control element when label is clicked instead of using htmlFor
2 parents 7405e18 + 5369c30 commit 531a222

File tree

12 files changed

+75
-297
lines changed

12 files changed

+75
-297
lines changed

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
"classnames": "^2.3.1",
5656
"dayjs": "^1.11.2",
5757
"keycoder": "^1.1.1",
58-
"nanoid": "^3.3.4",
5958
"react-merge-refs": "^1.1.0",
6059
"react-relative-portal": "github:stomita/react-relative-portal#dist",
6160
"svg4everybody": "^2.1.9"
@@ -70,7 +69,7 @@
7069
"@salesforce-ux/design-system": "^2.22.2",
7170
"@storybook/addon-actions": "^6.5.16",
7271
"@storybook/addon-docs": "^6.5.16",
73-
"@storybook/addon-knobs": "^6.4.0",
72+
"@storybook/addon-controls": "^6.5.16",
7473
"@storybook/addon-storyshots": "^6.5.16",
7574
"@storybook/react": "^6.5.16",
7675
"@storybook/theming": "^6.5.16",

src/scripts/DateInput.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,7 @@ import { Datepicker, DatepickerProps } from './Datepicker';
1919
import { ComponentSettingsContext } from './ComponentSettings';
2020
import { AutoAlign, AutoAlignInjectedProps, AutoAlignProps } from './AutoAlign';
2121
import { isElInChildren } from './util';
22-
import {
23-
useControlledValue,
24-
useEventCallback,
25-
useFormElementId,
26-
useMergeRefs,
27-
} from './hooks';
22+
import { useControlledValue, useEventCallback, useMergeRefs } from './hooks';
2823
import { createFC } from './common';
2924

3025
/**
@@ -137,7 +132,7 @@ export type DateInputProps = {
137132
export const DateInput = createFC<DateInputProps, { isFormElement: boolean }>(
138133
(props) => {
139134
const {
140-
id: id_,
135+
id,
141136
opened: opened_,
142137
defaultOpened,
143138
value: value_,
@@ -165,7 +160,6 @@ export const DateInput = createFC<DateInputProps, { isFormElement: boolean }>(
165160
...rprops
166161
} = props;
167162

168-
const id = useFormElementId(id_, 'date-input');
169163
const [opened, setOpened] = useControlledValue(
170164
opened_,
171165
defaultOpened ?? false

src/scripts/FormElement.tsx

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import React, {
22
Ref,
3-
createContext,
43
useContext,
54
useMemo,
65
ReactNode,
76
CSSProperties,
7+
useRef,
8+
useCallback,
89
} from 'react';
910
import classnames from 'classnames';
1011
import { FieldSetColumnContext } from './FieldSet';
11-
import { useFormElementId } from './hooks';
1212
import { createFC } from './common';
1313

1414
/**
@@ -28,11 +28,6 @@ export type FormElementProps = {
2828
children?: ReactNode;
2929
};
3030

31-
/**
32-
*
33-
*/
34-
export const FormElementContext = createContext<{ id?: string }>({});
35-
3631
/**
3732
*
3833
*/
@@ -42,7 +37,7 @@ export const FormElement = createFC<
4237
>(
4338
(props) => {
4439
const {
45-
id: id_,
40+
id,
4641
className,
4742
cols = 1,
4843
elementRef,
@@ -54,7 +49,7 @@ export const FormElement = createFC<
5449
readOnly,
5550
} = props;
5651

57-
const id = useFormElementId(id_);
52+
const controlElRef = useRef<HTMLDivElement>(null);
5853

5954
const { totalCols } = useContext(FieldSetColumnContext);
6055

@@ -80,31 +75,39 @@ export const FormElement = createFC<
8075
className
8176
);
8277

83-
const formElemCtx = useMemo(() => ({ id }), [id]);
78+
const onClickLabel = useCallback(() => {
79+
if (controlElRef.current) {
80+
const inputEl = controlElRef.current.querySelector<HTMLElement>(
81+
'input,select,button'
82+
);
83+
inputEl?.focus();
84+
}
85+
}, []);
86+
8487
const emptyCtx = useMemo(() => ({}), []);
8588

8689
return (
87-
<FormElementContext.Provider value={formElemCtx}>
88-
<FieldSetColumnContext.Provider value={emptyCtx}>
89-
<div ref={elementRef} className={formElementClassNames}>
90-
{label ? (
91-
<label className='slds-form-element__label' htmlFor={id}>
92-
{label}
93-
{required ? (
94-
<abbr className='slds-required'>*</abbr>
95-
) : undefined}
96-
</label>
97-
) : null}
98-
<div className={formElementControlClassNames}>
99-
{children}
100-
{dropdown}
101-
{errorMessage ? (
102-
<span className='slds-form-element__help'>{errorMessage}</span>
103-
) : undefined}
104-
</div>
90+
<FieldSetColumnContext.Provider value={emptyCtx}>
91+
<div ref={elementRef} className={formElementClassNames}>
92+
{label ? (
93+
<label
94+
className='slds-form-element__label'
95+
htmlFor={id}
96+
onClick={id ? undefined : onClickLabel}
97+
>
98+
{label}
99+
{required ? <abbr className='slds-required'>*</abbr> : undefined}
100+
</label>
101+
) : null}
102+
<div ref={controlElRef} className={formElementControlClassNames}>
103+
{children}
104+
{dropdown}
105+
{errorMessage ? (
106+
<span className='slds-form-element__help'>{errorMessage}</span>
107+
) : undefined}
105108
</div>
106-
</FieldSetColumnContext.Provider>
107-
</FormElementContext.Provider>
109+
</div>
110+
</FieldSetColumnContext.Provider>
108111
);
109112
},
110113
{ isFormElement: true }

src/scripts/Input.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { FormElement, FormElementProps } from './FormElement';
1515
import { Text } from './Text';
1616
import { FieldSetColumnContext } from './FieldSet';
1717
import { registerStyle } from './util';
18-
import { useEventCallback, useFormElementId } from './hooks';
18+
import { useEventCallback } from './hooks';
1919
import { createFC } from './common';
2020

2121
/**
@@ -61,7 +61,7 @@ const InputIcon = ({
6161
icon
6262
) : typeof icon === 'string' ? (
6363
<Icon
64-
icon={icon}
64+
icon={typeof icon === 'string' ? icon : ''}
6565
className={classnames(
6666
'slds-input__icon',
6767
`slds-input__icon_${align}`,
@@ -103,7 +103,7 @@ export type InputProps = {
103103
export const Input = createFC<InputProps, { isFormElement: boolean }>(
104104
(props) => {
105105
const {
106-
id: id_,
106+
id,
107107
className,
108108
label,
109109
required,
@@ -150,7 +150,6 @@ export const Input = createFC<InputProps, { isFormElement: boolean }>(
150150
prevValueRef.current = e.target.value;
151151
});
152152

153-
const id = useFormElementId(id_, 'input');
154153
const { isFieldSetColumn } = useContext(FieldSetColumnContext);
155154
const inputClassNames = classnames(
156155
className,

src/scripts/Lookup.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,7 @@ import { DropdownButton } from './DropdownButton';
2424
import { DropdownMenuItem } from './DropdownMenu';
2525
import { isElInChildren, registerStyle } from './util';
2626
import { ComponentSettingsContext } from './ComponentSettings';
27-
import {
28-
useControlledValue,
29-
useEventCallback,
30-
useFormElementId,
31-
useMergeRefs,
32-
} from './hooks';
27+
import { useControlledValue, useEventCallback, useMergeRefs } from './hooks';
3328
import { createFC } from './common';
3429
import { Bivariant } from './typeUtils';
3530

@@ -602,7 +597,7 @@ export type LookupProps = {
602597
export const Lookup = createFC<LookupProps, { isFormElement: boolean }>(
603598
(props) => {
604599
const {
605-
id: id_,
600+
id,
606601
value: value_,
607602
defaultValue,
608603
selected: selected_,
@@ -639,8 +634,6 @@ export const Lookup = createFC<LookupProps, { isFormElement: boolean }>(
639634
...rprops
640635
} = props;
641636

642-
const id = useFormElementId(id_, 'lookup');
643-
644637
const [value, setValue] = useControlledValue<string | null>(
645638
value_,
646639
defaultValue ?? null

src/scripts/Picklist.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,7 @@ import {
1818
} from './DropdownMenu';
1919
import { isElInChildren } from './util';
2020
import { ComponentSettingsContext } from './ComponentSettings';
21-
import {
22-
useControlledValue,
23-
useEventCallback,
24-
useFormElementId,
25-
useMergeRefs,
26-
} from './hooks';
21+
import { useControlledValue, useEventCallback, useMergeRefs } from './hooks';
2722
import { createFC } from './common';
2823
import { Bivariant } from './typeUtils';
2924

@@ -88,7 +83,7 @@ export const Picklist: (<MultiSelect extends boolean | undefined>(
8883
(props) => {
8984
const {
9085
className,
91-
id: id_,
86+
id,
9287
value: value_,
9388
defaultValue,
9489
opened: opened_,
@@ -115,7 +110,6 @@ export const Picklist: (<MultiSelect extends boolean | undefined>(
115110
...rprops
116111
} = props;
117112

118-
const id = useFormElementId(id_, 'picklist');
119113
const values_: PicklistValue[] | undefined =
120114
typeof value_ === 'undefined'
121115
? undefined

src/scripts/Select.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import React, {
1010
import classnames from 'classnames';
1111
import { FormElement, FormElementProps } from './FormElement';
1212
import { FieldSetColumnContext } from './FieldSet';
13-
import { useEventCallback, useFormElementId } from './hooks';
13+
import { useEventCallback } from './hooks';
1414
import { createFC } from './common';
1515

1616
/**
@@ -32,7 +32,7 @@ export type SelectProps = {
3232
export const Select = createFC<SelectProps, { isFormElement: boolean }>(
3333
(props) => {
3434
const {
35-
id: id_,
35+
id,
3636
className,
3737
label,
3838
required,
@@ -45,7 +45,6 @@ export const Select = createFC<SelectProps, { isFormElement: boolean }>(
4545
onValueChange,
4646
...rprops
4747
} = props;
48-
const id = useFormElementId(id_, 'select');
4948
const { isFieldSetColumn } = useContext(FieldSetColumnContext);
5049
const prevValueRef = useRef<string>();
5150
const onChange = useEventCallback((e: ChangeEvent<HTMLSelectElement>) => {

src/scripts/Textarea.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import React, {
88
import classnames from 'classnames';
99
import { FormElement, FormElementProps } from './FormElement';
1010
import { FieldSetColumnContext } from './FieldSet';
11-
import { useEventCallback, useFormElementId } from './hooks';
11+
import { useEventCallback } from './hooks';
1212
import { createFC } from './common';
1313

1414
/**
@@ -30,7 +30,7 @@ export type TextareaProps = {
3030
export const Textarea = createFC<TextareaProps, { isFormElement: boolean }>(
3131
(props) => {
3232
const {
33-
id: id_,
33+
id,
3434
className,
3535
label,
3636
required,
@@ -48,7 +48,6 @@ export const Textarea = createFC<TextareaProps, { isFormElement: boolean }>(
4848
onValueChange?.(e.target.value, prevValueRef.current);
4949
prevValueRef.current = e.target.value;
5050
});
51-
const id = useFormElementId(id_, 'textarea');
5251
const { isFieldSetColumn } = useContext(FieldSetColumnContext);
5352
const taClassNames = classnames(className, 'slds-input');
5453
const textareaElem = (

src/scripts/Toggle.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { ChangeEvent, InputHTMLAttributes, Ref } from 'react';
22
import classnames from 'classnames';
33
import { FormElement, FormElementProps } from './FormElement';
4-
import { useEventCallback, useFormElementId } from './hooks';
4+
import { useEventCallback } from './hooks';
55
import { createFC } from './common';
66

77
/**
@@ -24,7 +24,7 @@ export type ToggleProps = {
2424
export const Toggle = createFC<ToggleProps, { isFormElement: boolean }>(
2525
(props) => {
2626
const {
27-
id: id_,
27+
id,
2828
className,
2929
label,
3030
required,
@@ -36,7 +36,6 @@ export const Toggle = createFC<ToggleProps, { isFormElement: boolean }>(
3636
onValueChange,
3737
...rprops
3838
} = props;
39-
const id = useFormElementId(id_, 'toggle');
4039
const onChange = useEventCallback((e: ChangeEvent<HTMLInputElement>) => {
4140
onChange_?.(e);
4241
onValueChange?.(e.target.checked);

src/scripts/hooks.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import {
22
Ref,
33
useCallback,
4-
useContext,
54
useLayoutEffect,
65
useMemo,
76
useRef,
87
useState,
98
} from 'react';
109
import mergeRefs from 'react-merge-refs';
11-
import { FormElementContext } from './FormElement';
12-
import { generateUniqueId } from './util';
1310

1411
/**
1512
*
@@ -23,18 +20,6 @@ export function useControlledValue<T>(value: T | undefined, defaultValue: T) {
2320
];
2421
}
2522

26-
/**
27-
*
28-
*/
29-
export function useFormElementId(
30-
propsId: string | undefined,
31-
prefix = 'form-element'
32-
) {
33-
const { id: formElemId } = useContext(FormElementContext);
34-
const [generatedId] = useState(`${prefix}-${generateUniqueId()}`);
35-
return propsId ?? formElemId ?? generatedId;
36-
}
37-
3823
/**
3924
*
4025
*/

0 commit comments

Comments
 (0)