Skip to content

Commit 2649eae

Browse files
authored
Merge pull request #417 from mashmatrix/unify-interface
Unify interface of components
2 parents 86e7f59 + 17cbc7e commit 2649eae

23 files changed

+329
-225
lines changed

src/scripts/Button.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import React, { FC, ReactNode, ButtonHTMLAttributes, Ref, useRef } from 'react';
2-
import mergeRefs from 'react-merge-refs';
32
import classnames from 'classnames';
43
import { Icon } from './Icon';
54
import { Spinner } from './Spinner';
6-
import { useEventCallback } from './hooks';
5+
import { useEventCallback, useMergeRefs } from './hooks';
76

87
export type ButtonType =
98
| 'neutral'
@@ -126,9 +125,7 @@ export const Button: FC<ButtonProps> = (props) => {
126125
const iconMoreSize = iconMoreSize_ || adjoining ? 'x-small' : 'small';
127126
const inverse = inverse_ || /-?inverse$/.test(type || '');
128127
const buttonElRef = useRef<HTMLButtonElement | null>(null);
129-
const buttonRef = buttonRef_
130-
? mergeRefs([buttonElRef, buttonRef_])
131-
: buttonElRef;
128+
const buttonRef = useMergeRefs([buttonElRef, buttonRef_]);
132129

133130
const onClick = useEventCallback((e: React.MouseEvent<HTMLButtonElement>) => {
134131
if (buttonElRef.current !== null) {

src/scripts/Checkbox.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,31 @@ export type CheckboxProps = {
1515
value?: CheckboxValueType;
1616
checked?: boolean;
1717
defaultChecked?: boolean;
18-
checkboxRef?: Ref<HTMLLabelElement>;
18+
elementRef?: Ref<HTMLDivElement>;
19+
inputRef?: Ref<HTMLInputElement>;
1920
} & InputHTMLAttributes<HTMLInputElement>;
2021

2122
/**
2223
*
2324
*/
2425
export const Checkbox: FC<CheckboxProps> = (props) => {
25-
const { className, label, required, error, cols, checkboxRef, ...rprops } =
26-
props;
26+
const {
27+
type, // eslint-disable-line @typescript-eslint/no-unused-vars
28+
className,
29+
label,
30+
required,
31+
error,
32+
cols,
33+
elementRef,
34+
inputRef,
35+
...rprops
36+
} = props;
2737
const { grouped } = useContext(CheckboxGroupContext);
28-
const formElemProps = { required, error, cols };
38+
const formElemProps = { required, error, cols, elementRef };
2939
const checkClassNames = classnames(className, 'slds-checkbox');
3040
const check = (
31-
<label ref={checkboxRef} className={checkClassNames}>
32-
<input type='checkbox' {...rprops} />
41+
<label className={checkClassNames}>
42+
<input ref={inputRef} type='checkbox' {...rprops} />
3343
<span className='slds-checkbox_faux' />
3444
<span className='slds-form-element__label'>{label}</span>
3545
</label>

src/scripts/CheckboxGroup.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, {
22
createContext,
33
FieldsetHTMLAttributes,
4+
Ref,
45
useContext,
56
useMemo,
67
useRef,
@@ -30,6 +31,7 @@ export type CheckboxGroupProps = {
3031
error?: FormElementProps['error'];
3132
name?: string;
3233
cols?: number;
34+
elementRef?: Ref<HTMLFieldSetElement>;
3335
onValueChange?: (values: CheckboxValueType[]) => void;
3436
} & FieldsetHTMLAttributes<HTMLFieldSetElement>;
3537

@@ -48,6 +50,7 @@ export const CheckboxGroup = createFC<
4850
style,
4951
required,
5052
error,
53+
elementRef,
5154
onValueChange,
5255
onChange: onChange_,
5356
children,
@@ -101,6 +104,7 @@ export const CheckboxGroup = createFC<
101104

102105
return (
103106
<fieldset
107+
ref={elementRef}
104108
className={grpClassNames}
105109
style={grpStyles}
106110
{...rprops}

src/scripts/DateInput.tsx

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import React, {
66
Ref,
77
FC,
88
useRef,
9-
useMemo,
109
useState,
1110
useEffect,
1211
useContext,
@@ -17,15 +16,15 @@ import { Button } from './Button';
1716
import { FormElement } from './FormElement';
1817
import { Input, InputProps } from './Input';
1918
import { Datepicker, DatepickerProps } from './Datepicker';
20-
import { isElInChildren } from './util';
2119
import { ComponentSettingsContext } from './ComponentSettings';
22-
import mergeRefs from 'react-merge-refs';
20+
import { AutoAlign, AutoAlignInjectedProps, AutoAlignProps } from './AutoAlign';
21+
import { isElInChildren } from './util';
2322
import {
2423
useControlledValue,
2524
useEventCallback,
2625
useFormElementId,
26+
useMergeRefs,
2727
} from './hooks';
28-
import { AutoAlign, AutoAlignInjectedProps, AutoAlignProps } from './AutoAlign';
2928
import { createFC } from './common';
3029

3130
/**
@@ -56,32 +55,24 @@ const DatepickerDropdownInner: FC<
5655
minDate,
5756
maxDate,
5857
extensionRenderer,
59-
elementRef,
58+
elementRef: elementRef_,
6059
autoAlignContentRef,
6160
onSelect,
6261
onBlur,
6362
onClose,
6463
} = props;
6564
const elRef = useRef<HTMLDivElement | null>(null);
65+
const elementRef = useMergeRefs([elRef, autoAlignContentRef, elementRef_]);
6666
const [vertAlign, align] = alignment;
6767
const datepickerClassNames = classnames(
6868
className,
6969
'slds-dropdown',
7070
align ? `slds-dropdown_${align}` : undefined,
7171
vertAlign ? `slds-dropdown_${vertAlign}` : undefined
7272
);
73-
const mergedRef = useMemo(
74-
() =>
75-
mergeRefs([
76-
elRef,
77-
autoAlignContentRef,
78-
...(elementRef ? [elementRef] : []),
79-
]),
80-
[elementRef, autoAlignContentRef]
81-
);
8273
return (
8374
<Datepicker
84-
elementRef={mergedRef}
75+
elementRef={elementRef}
8576
className={datepickerClassNames}
8677
selectedDate={dateValue}
8778
autoFocus
@@ -126,6 +117,8 @@ export type DateInputProps = {
126117
minDate?: string;
127118
maxDate?: string;
128119
menuAlign?: 'left' | 'right';
120+
elementRef?: Ref<HTMLDivElement>;
121+
datepickerRef?: Ref<HTMLDivElement>;
129122
onBlur?: () => void;
130123
onValueChange?: (value: string | null, prevValue: string | null) => void;
131124
onComplete?: () => void;
@@ -154,6 +147,9 @@ export const DateInput = createFC<DateInputProps, { isFormElement: boolean }>(
154147
minDate,
155148
maxDate,
156149
extensionRenderer,
150+
elementRef: elementRef_,
151+
inputRef: inputRef_,
152+
datepickerRef: datepickerRef_,
157153
onChange,
158154
onValueChange,
159155
onKeyDown,
@@ -179,9 +175,12 @@ export const DateInput = createFC<DateInputProps, { isFormElement: boolean }>(
179175
? mvalue.format(inputValueFormat)
180176
: '';
181177

182-
const nodeRef = useRef<HTMLDivElement | null>(null);
183-
const datepickerElRef = useRef<HTMLDivElement | null>(null);
178+
const elRef = useRef<HTMLDivElement | null>(null);
179+
const elementRef = useMergeRefs([elRef, elementRef_]);
184180
const inputElRef = useRef<HTMLInputElement | null>(null);
181+
const inputRef = useMergeRefs([inputElRef, inputRef_]);
182+
const datepickerElRef = useRef<HTMLDivElement | null>(null);
183+
const datepickerRef = useMergeRefs([datepickerElRef, datepickerRef_]);
185184

186185
const { getActiveElement } = useContext(ComponentSettingsContext);
187186

@@ -204,7 +203,7 @@ export const DateInput = createFC<DateInputProps, { isFormElement: boolean }>(
204203
const isFocusedInComponent = useEventCallback(() => {
205204
const targetEl = getActiveElement();
206205
return (
207-
isElInChildren(nodeRef.current, targetEl) ||
206+
isElInChildren(elRef.current, targetEl) ||
208207
isElInChildren(datepickerElRef.current, targetEl)
209208
);
210209
});
@@ -314,13 +313,13 @@ export const DateInput = createFC<DateInputProps, { isFormElement: boolean }>(
314313
}
315314
});
316315

317-
const formElemProps = { id, cols, label, required, error };
316+
const formElemProps = { id, cols, label, required, error, elementRef };
318317
return (
319-
<FormElement formElementRef={nodeRef} {...formElemProps}>
318+
<FormElement {...formElemProps}>
320319
<div className={classnames(className, 'slds-dropdown-trigger')}>
321320
<div className='slds-input-has-icon slds-input-has-icon_right'>
322321
<Input
323-
inputRef={inputElRef}
322+
inputRef={inputRef}
324323
{...rprops}
325324
id={id}
326325
value={inputValue}
@@ -341,7 +340,7 @@ export const DateInput = createFC<DateInputProps, { isFormElement: boolean }>(
341340
{opened ? (
342341
<DatepickerDropdown
343342
portalClassName={className}
344-
elementRef={datepickerElRef}
343+
elementRef={datepickerRef}
345344
dateValue={
346345
mvalue.isValid() ? mvalue.format('YYYY-MM-DD') : undefined
347346
}

src/scripts/Datepicker.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ import { Button } from './Button';
1919
import { Select, Option } from './Select';
2020
import { getToday, isElInChildren } from './util';
2121
import { ComponentSettingsContext } from './ComponentSettings';
22-
import mergeRefs from 'react-merge-refs';
23-
import { useEventCallback } from './hooks';
22+
import { useEventCallback, useMergeRefs } from './hooks';
2423

2524
/**
2625
*
@@ -333,13 +332,13 @@ const DatepickerMonth = forwardRef(
333332
*/
334333
export const Datepicker: FC<DatepickerProps> = (props) => {
335334
const {
336-
elementRef,
337335
autoFocus,
338336
className,
339337
selectedDate,
340338
minDate,
341339
maxDate,
342340
extensionRenderer: ExtensionRenderer,
341+
elementRef: elementRef_,
343342
onSelect,
344343
onBlur: onBlur_,
345344
onClose,
@@ -349,14 +348,10 @@ export const Datepicker: FC<DatepickerProps> = (props) => {
349348
const [targetDate, setTargetDate] = useState<string | undefined>(
350349
selectedDate
351350
);
352-
const nodeElRef = useRef<HTMLDivElement | null>(null);
351+
const elRef = useRef<HTMLDivElement | null>(null);
352+
const elementRef = useMergeRefs([elRef, elementRef_]);
353353
const monthElRef = useRef<HTMLTableElement | null>(null);
354354

355-
const mergedNodeElRef = useMemo(
356-
() => (elementRef ? mergeRefs([nodeElRef, elementRef]) : nodeElRef),
357-
[elementRef]
358-
);
359-
360355
const onFocusDate = useEventCallback((date: string | undefined) => {
361356
const el = monthElRef.current;
362357
if (!el || !date) {
@@ -373,7 +368,7 @@ export const Datepicker: FC<DatepickerProps> = (props) => {
373368
const { getActiveElement } = useContext(ComponentSettingsContext);
374369

375370
const isFocusedInComponent = useEventCallback(() => {
376-
const nodeEl = nodeElRef.current;
371+
const nodeEl = elRef.current;
377372
const targetEl = getActiveElement();
378373
return isElInChildren(nodeEl, targetEl);
379374
});
@@ -484,7 +479,7 @@ export const Datepicker: FC<DatepickerProps> = (props) => {
484479
<div
485480
{...rprops}
486481
className={datepickerClassNames}
487-
ref={mergedNodeElRef}
482+
ref={elementRef}
488483
tabIndex={-1}
489484
aria-hidden={false}
490485
onBlur={onBlur}

src/scripts/DropdownButton.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import React, {
66
useRef,
77
useContext,
88
useEffect,
9+
Ref,
910
} from 'react';
1011
import classnames from 'classnames';
1112
import { Button, ButtonProps } from './Button';
1213
import { DropdownMenu } from './DropdownMenu';
1314
import { registerStyle, isElInChildren } from './util';
1415
import { ComponentSettingsContext } from './ComponentSettings';
1516
import { ButtonGroupContext } from './ButtonGroup';
16-
import { useControlledValue, useEventCallback } from './hooks';
17+
import { useControlledValue, useEventCallback, useMergeRefs } from './hooks';
1718

1819
export type DropdownMenuAlign = 'left' | 'right';
1920
export type DropdownMenuSize = 'small' | 'medium' | 'large';
@@ -75,6 +76,7 @@ export type DropdownButtonProps = {
7576
nubbinTop?: boolean;
7677
hoverPopup?: boolean;
7778
menuStyle?: CSSProperties;
79+
dropdownRef?: Ref<HTMLDivElement>;
7880
onClick?: (e: SyntheticEvent<HTMLButtonElement>) => void;
7981
onBlur?: () => void;
8082
onMenuSelect?: (eventKey: EventKey) => void;
@@ -98,6 +100,8 @@ export const DropdownButton = (props: DropdownButtonProps) => {
98100
label,
99101
children,
100102
style,
103+
buttonRef: buttonRef_,
104+
dropdownRef: dropdownRef_,
101105
onBlur: onBlur_,
102106
onClick: onClick_,
103107
onMenuSelect: onMenuSelect_,
@@ -107,9 +111,11 @@ export const DropdownButton = (props: DropdownButtonProps) => {
107111
const { getActiveElement } = useContext(ComponentSettingsContext);
108112
const { grouped, isFirstInGroup, isLastInGroup } =
109113
useContext(ButtonGroupContext) ?? {};
110-
const rootElRef = useRef<HTMLDivElement | null>(null);
111-
const triggerElRef = useRef<HTMLButtonElement | null>(null);
114+
const elRef = useRef<HTMLDivElement | null>(null);
115+
const buttonElRef = useRef<HTMLButtonElement | null>(null);
116+
const buttonRef = useMergeRefs([buttonElRef, buttonRef_]);
112117
const dropdownElRef = useRef<HTMLDivElement | null>(null);
118+
const dropdownRef = useMergeRefs([dropdownElRef, dropdownRef_]);
113119

114120
const [opened, setOpened] = useControlledValue(
115121
opened_,
@@ -119,11 +125,7 @@ export const DropdownButton = (props: DropdownButtonProps) => {
119125
setTimeout(() => {
120126
const targetEl = getActiveElement();
121127
if (
122-
!isFocusedInComponent(
123-
targetEl,
124-
rootElRef.current,
125-
dropdownElRef.current
126-
)
128+
!isFocusedInComponent(targetEl, elRef.current, dropdownElRef.current)
127129
) {
128130
setOpened(false);
129131
onBlur_?.();
@@ -165,15 +167,15 @@ export const DropdownButton = (props: DropdownButtonProps) => {
165167
const onMenuSelect = useEventCallback((eventKey: EventKey) => {
166168
if (!hoverPopup) {
167169
setTimeout(() => {
168-
triggerElRef.current?.focus();
170+
buttonElRef.current?.focus();
169171
setOpened(false);
170172
}, 10);
171173
}
172174
onMenuSelect_?.(eventKey);
173175
});
174176

175177
const onMenuClose = useEventCallback(() => {
176-
triggerElRef.current?.focus();
178+
buttonElRef.current?.focus();
177179
setOpened(false);
178180
});
179181

@@ -196,7 +198,7 @@ export const DropdownButton = (props: DropdownButtonProps) => {
196198
}}
197199
{...rprops}
198200
aria-haspopup
199-
buttonRef={triggerElRef}
201+
buttonRef={buttonRef}
200202
onClick={onTriggerClick}
201203
onKeyDown={onKeyDown}
202204
onBlur={onBlur}
@@ -210,7 +212,7 @@ export const DropdownButton = (props: DropdownButtonProps) => {
210212
const noneStyle = { display: 'none' };
211213

212214
return (
213-
<div className={dropdownClassNames} style={style} ref={rootElRef}>
215+
<div className={dropdownClassNames} style={style} ref={elRef}>
214216
{grouped ? (
215217
<div className='slds-button-group'>
216218
{isFirstInGroup ? null : (
@@ -232,7 +234,7 @@ export const DropdownButton = (props: DropdownButtonProps) => {
232234
size={menuSize}
233235
nubbinTop={nubbinTop}
234236
hoverPopup={hoverPopup}
235-
dropdownMenuRef={dropdownElRef}
237+
elementRef={dropdownRef}
236238
onMenuSelect={onMenuSelect}
237239
onMenuClose={onMenuClose}
238240
onBlur={onBlur}

0 commit comments

Comments
 (0)