Skip to content

Commit 057f26a

Browse files
committed
refactor(ui): use FC instead of useFocusVisible
1 parent fe8756b commit 057f26a

File tree

9 files changed

+385
-351
lines changed

9 files changed

+385
-351
lines changed
Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
1-
import type { DCloneHTMLElement } from '../utils/types';
1+
import type { DCloneHTMLElement } from '../../utils/types';
22

33
import { isUndefined } from 'lodash';
4-
import { useRef, useState } from 'react';
4+
import { useRef } from 'react';
55

66
import { useAsync, useEvent, useRefExtra } from '@react-devui/hooks';
77

8-
import { cloneHTMLElement } from '../utils';
8+
import { cloneHTMLElement } from '../../utils';
99

1010
const FOCUS_KEY = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'Enter', 'Space'];
1111

12-
export function useFocusVisible(disabled = false): [boolean, DCloneHTMLElement] {
12+
export interface DFocusVisibleProps {
13+
children: (props: { render: DCloneHTMLElement }) => JSX.Element | null;
14+
dDisabled?: boolean;
15+
onFocusVisibleChange: (focus: boolean) => void;
16+
}
17+
18+
export function DFocusVisible(props: DFocusVisibleProps): JSX.Element | null {
19+
const { children, dDisabled = false, onFocusVisibleChange } = props;
20+
21+
//#region Ref
1322
const windowRef = useRefExtra(() => window);
23+
//#endregion
1424

1525
const dataRef = useRef<{
1626
clearTid?: () => void;
1727
}>({});
1828

19-
const [focusVisible, setFocusVisible] = useState(false);
20-
2129
const async = useAsync();
2230
useEvent(
2331
windowRef,
@@ -29,34 +37,33 @@ export function useFocusVisible(disabled = false): [boolean, DCloneHTMLElement]
2937
}, 20);
3038
},
3139
{},
32-
disabled
40+
dDisabled
3341
);
3442

35-
return [
36-
focusVisible,
37-
(el) =>
38-
disabled
43+
return children({
44+
render: (el) =>
45+
dDisabled
3946
? el
4047
: cloneHTMLElement(el, {
4148
onFocus: (e) => {
4249
el.props.onFocus?.(e);
4350

4451
if (!isUndefined(dataRef.current.clearTid)) {
45-
setFocusVisible(true);
52+
onFocusVisibleChange(true);
4653
}
4754
},
4855
onBlur: (e) => {
4956
el.props.onBlur?.(e);
5057

51-
setFocusVisible(false);
58+
onFocusVisibleChange(false);
5259
},
5360
onKeyDown: (e) => {
5461
el.props.onKeyDown?.(e);
5562

5663
if (FOCUS_KEY.includes(e.code)) {
57-
setFocusVisible(true);
64+
onFocusVisibleChange(true);
5865
}
5966
},
6067
}),
61-
];
68+
});
6269
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './FocusVisible';

packages/ui/src/components/_selectbox/Selectbox.tsx

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import { useForkRef, useRefExtra, useResize } from '@react-devui/hooks';
99
import { CloseCircleFilled, DownOutlined, LoadingOutlined, SearchOutlined } from '@react-devui/icons';
1010
import { checkNodeExist, getClassName } from '@react-devui/utils';
1111

12-
import { useMaxIndex, useFocusVisible } from '../../hooks';
12+
import { useMaxIndex } from '../../hooks';
1313
import { cloneHTMLElement } from '../../utils';
1414
import { DBaseDesign } from '../_base-design';
1515
import { DBaseInput } from '../_base-input';
16+
import { DFocusVisible } from '../_focus-visible';
1617
import { useGlobalScroll, useLayout, usePrefixConfig, useTranslation } from '../root';
1718

1819
export interface DSelectboxProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
@@ -94,12 +95,6 @@ export function DSelectbox(props: DSelectboxProps): JSX.Element | null {
9495

9596
const [isFocus, setIsFocus] = useState(false);
9697

97-
const [focusVisible, renderFocusVisible] = useFocusVisible();
98-
useEffect(() => {
99-
onFocusVisibleChange(focusVisible);
100-
// eslint-disable-next-line react-hooks/exhaustive-deps
101-
}, [focusVisible]);
102-
10398
const inputable = dSearchable && dVisible;
10499
const clearable = dClearable && dContent && !dVisible && !dLoading && !dDisabled;
105100

@@ -172,36 +167,40 @@ export function DSelectbox(props: DSelectboxProps): JSX.Element | null {
172167
}}
173168
>
174169
<div className={`${prefix}__container`} title={dContentTitle}>
175-
<DBaseInput dFormControl={dFormControl} dLabelFor>
176-
{({ render: renderBaseInput }) => {
177-
return renderFocusVisible(
178-
dInputRender(
179-
renderBaseInput(
180-
<input
181-
ref={combineInputRef}
182-
className={`${prefix}__search`}
183-
style={{
184-
opacity: inputable ? undefined : 0,
185-
zIndex: inputable ? undefined : -1,
186-
}}
187-
type="text"
188-
autoComplete="off"
189-
disabled={dDisabled}
190-
role="combobox"
191-
aria-haspopup="listbox"
192-
aria-expanded={dVisible}
193-
onFocus={() => {
194-
setIsFocus(true);
195-
}}
196-
onBlur={() => {
197-
setIsFocus(false);
198-
}}
199-
/>
200-
)
201-
)
202-
);
203-
}}
204-
</DBaseInput>
170+
<DFocusVisible onFocusVisibleChange={onFocusVisibleChange}>
171+
{({ render: renderFocusVisible }) => (
172+
<DBaseInput dFormControl={dFormControl} dLabelFor>
173+
{({ render: renderBaseInput }) => {
174+
return dInputRender(
175+
renderFocusVisible(
176+
renderBaseInput(
177+
<input
178+
ref={combineInputRef}
179+
className={`${prefix}__search`}
180+
style={{
181+
opacity: inputable ? undefined : 0,
182+
zIndex: inputable ? undefined : -1,
183+
}}
184+
type="text"
185+
autoComplete="off"
186+
disabled={dDisabled}
187+
role="combobox"
188+
aria-haspopup="listbox"
189+
aria-expanded={dVisible}
190+
onFocus={() => {
191+
setIsFocus(true);
192+
}}
193+
onBlur={() => {
194+
setIsFocus(false);
195+
}}
196+
/>
197+
)
198+
)
199+
);
200+
}}
201+
</DBaseInput>
202+
)}
203+
</DFocusVisible>
205204
{!inputable &&
206205
(dContent ? (
207206
<div className={`${prefix}__content`}>{dContent}</div>

packages/ui/src/components/auto-complete/AutoComplete.tsx

Lines changed: 78 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import { useEvent, useEventCallback, useId, useRefExtra, useResize } from '@reac
99
import { LoadingOutlined } from '@react-devui/icons';
1010
import { findNested, getClassName, getOriginalSize, getVerticalSidePosition } from '@react-devui/utils';
1111

12-
import { useMaxIndex, useDValue, useFocusVisible } from '../../hooks';
12+
import { useMaxIndex, useDValue } from '../../hooks';
1313
import { cloneHTMLElement, registerComponentMate, TTANSITION_DURING_POPUP, WINDOW_SPACE } from '../../utils';
14+
import { DFocusVisible } from '../_focus-visible';
1415
import { DComboboxKeyboard } from '../_keyboard';
1516
import { DTransition } from '../_transition';
1617
import { DInput } from '../input';
@@ -88,7 +89,7 @@ function AutoComplete<T extends DAutoCompleteItem>(
8889

8990
const [visible, changeVisible] = useDValue<boolean>(false, dVisible, onVisibleChange);
9091

91-
const [focusVisible, renderFocusVisible] = useFocusVisible();
92+
const [focusVisible, setFocusVisible] = useState(false);
9293

9394
const maxZIndex = useMaxIndex(visible);
9495

@@ -177,77 +178,81 @@ function AutoComplete<T extends DAutoCompleteItem>(
177178

178179
return (
179180
<>
180-
<DComboboxKeyboard
181-
dVisible={visible}
182-
dEditable
183-
dHasSub={false}
184-
onVisibleChange={changeVisible}
185-
onFocusChange={(key) => {
186-
switch (key) {
187-
case 'next':
188-
changeFocusItem(vsRef.current?.scrollByStep(1));
189-
break;
190-
191-
case 'prev':
192-
changeFocusItem(vsRef.current?.scrollByStep(-1));
193-
break;
194-
195-
case 'first':
196-
changeFocusItem(vsRef.current?.scrollToStart());
197-
break;
198-
199-
case 'last':
200-
changeFocusItem(vsRef.current?.scrollToEnd());
201-
break;
202-
203-
default:
204-
break;
205-
}
206-
}}
207-
>
208-
{({ render: renderComboboxKeyboard }) => {
209-
const renderInput = (el: React.ReactElement<React.HTMLAttributes<HTMLElement>>, props?: React.HTMLAttributes<HTMLElement>) =>
210-
renderFocusVisible(
211-
renderComboboxKeyboard(
212-
cloneHTMLElement(el, {
213-
...props,
214-
role: 'combobox',
215-
'aria-autocomplete': 'list',
216-
'aria-expanded': visible,
217-
'aria-controls': listId,
218-
onBlur: (e) => {
219-
el.props.onBlur?.(e);
220-
221-
changeVisible(false);
222-
},
223-
onClick: (e) => {
224-
el.props.onClick?.(e);
225-
226-
changeVisible(true);
227-
},
228-
onKeyDown: (e) => {
229-
el.props.onKeyDown?.(e);
230-
231-
if (e.code === 'Enter' && visible && focusItem) {
232-
changeVisible(false);
233-
onItemClick?.(focusItem.value, focusItem);
234-
}
235-
},
236-
})
237-
)
238-
);
239-
240-
const boxSelector = { ['data-auto-complete-box' as string]: uniqueId };
241-
if (child.type === DInput) {
242-
return React.cloneElement<DInputProps>(child, {
243-
...boxSelector,
244-
dInputRender: renderInput,
245-
});
246-
} else {
247-
return renderInput(child, boxSelector);
248-
}
249-
}}
250-
</DComboboxKeyboard>
181+
<DFocusVisible onFocusVisibleChange={setFocusVisible}>
182+
{({ render: renderFocusVisible }) => (
183+
<DComboboxKeyboard
184+
dVisible={visible}
185+
dEditable
186+
dHasSub={false}
187+
onVisibleChange={changeVisible}
188+
onFocusChange={(key) => {
189+
switch (key) {
190+
case 'next':
191+
changeFocusItem(vsRef.current?.scrollByStep(1));
192+
break;
193+
194+
case 'prev':
195+
changeFocusItem(vsRef.current?.scrollByStep(-1));
196+
break;
197+
198+
case 'first':
199+
changeFocusItem(vsRef.current?.scrollToStart());
200+
break;
201+
202+
case 'last':
203+
changeFocusItem(vsRef.current?.scrollToEnd());
204+
break;
205+
206+
default:
207+
break;
208+
}
209+
}}
210+
>
211+
{({ render: renderComboboxKeyboard }) => {
212+
const renderInput = (el: React.ReactElement<React.HTMLAttributes<HTMLElement>>, props?: React.HTMLAttributes<HTMLElement>) =>
213+
renderFocusVisible(
214+
renderComboboxKeyboard(
215+
cloneHTMLElement(el, {
216+
...props,
217+
role: 'combobox',
218+
'aria-autocomplete': 'list',
219+
'aria-expanded': visible,
220+
'aria-controls': listId,
221+
onBlur: (e) => {
222+
el.props.onBlur?.(e);
223+
224+
changeVisible(false);
225+
},
226+
onClick: (e) => {
227+
el.props.onClick?.(e);
228+
229+
changeVisible(true);
230+
},
231+
onKeyDown: (e) => {
232+
el.props.onKeyDown?.(e);
233+
234+
if (e.code === 'Enter' && visible && focusItem) {
235+
changeVisible(false);
236+
onItemClick?.(focusItem.value, focusItem);
237+
}
238+
},
239+
})
240+
)
241+
);
242+
243+
const boxSelector = { ['data-auto-complete-box' as string]: uniqueId };
244+
if (child.type === DInput) {
245+
return React.cloneElement<DInputProps>(child, {
246+
...boxSelector,
247+
dInputRender: renderInput,
248+
});
249+
} else {
250+
return renderInput(child, boxSelector);
251+
}
252+
}}
253+
</DComboboxKeyboard>
254+
)}
255+
</DFocusVisible>
251256
{containerRef.current &&
252257
ReactDOM.createPortal(
253258
<DTransition

0 commit comments

Comments
 (0)