-
Notifications
You must be signed in to change notification settings - Fork 142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: Tooltipの内部ロジックをリファクタリングする #5245
base: master
Are you sure you want to change the base?
Changes from all commits
d7f3973
396f558
6c22dda
d86ff41
e25f4dd
c4a8ed4
6c64f6f
9413ad8
9c025af
fe6a86a
e1a7afc
94c0328
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ import React, { | |
PropsWithChildren, | ||
ReactElement, | ||
ReactNode, | ||
memo, | ||
useCallback, | ||
useId, | ||
useMemo, | ||
|
@@ -25,6 +26,7 @@ import { TooltipPortal } from './TooltipPortal' | |
|
||
const subscribeFullscreenChange = (callback: () => void) => { | ||
window.addEventListener('fullscreenchange', callback) | ||
|
||
return () => { | ||
window.removeEventListener('fullscreenchange', callback) | ||
} | ||
|
@@ -52,7 +54,7 @@ type Props = PropsWithChildren<{ | |
}> | ||
type ElementProps = Omit<ComponentProps<'span'>, keyof Props | 'aria-describedby'> | ||
|
||
const tooltip = tv({ | ||
const classNameGenerator = tv({ | ||
base: [ | ||
'smarthr-ui-Tooltip', | ||
'shr-inline-block shr-max-w-full shr-align-bottom', | ||
|
@@ -70,7 +72,7 @@ export const Tooltip: FC<Props & ElementProps> = ({ | |
children, | ||
triggerType, | ||
multiLine, | ||
ellipsisOnly = false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 比較処理しかしておらず、booleanが型として必要な箇所もなかったため、undefinedのままでも問題になる可能性はありませんでした |
||
ellipsisOnly, | ||
horizontal = 'left', | ||
vertical = 'bottom', | ||
tabIndex = 0, | ||
|
@@ -99,53 +101,99 @@ export const Tooltip: FC<Props & ElementProps> = ({ | |
setPortalRoot(fullscreenElement ?? document.body) | ||
}, [fullscreenElement]) | ||
|
||
const getHandlerToShow = useCallback( | ||
<T,>(handler?: (e: T) => void) => | ||
(e: T) => { | ||
Comment on lines
-102
to
-104
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. もともとの方法では
これにより適切にメモ化されるようになり、基本的な使い方をしている限り、関数が再生成されないようになっています |
||
if (handler) { | ||
handler(e) | ||
} | ||
|
||
if (!ref.current) { | ||
const toShowAction = useCallback( | ||
(e: React.BaseSyntheticEvent) => { | ||
// Tooltipのtriggerの他の要素(Dropwdown menu buttonで開いたmenu contentとか)に移動されたらtooltipを表示しない | ||
if (!ref.current?.contains(e.target)) { | ||
return | ||
} | ||
|
||
if (ellipsisOnly) { | ||
const outerWidth = parseInt( | ||
window | ||
.getComputedStyle(ref.current.parentNode! as HTMLElement, null) | ||
.width.match(/\d+/)![0], | ||
10, | ||
) | ||
|
||
if (outerWidth < 0 || outerWidth > ref.current.clientWidth) { | ||
return | ||
} | ||
} | ||
|
||
if (ellipsisOnly) { | ||
const outerWidth = parseInt( | ||
window | ||
.getComputedStyle(ref.current.parentNode! as HTMLElement, null) | ||
.width.match(/\d+/)![0], | ||
10, | ||
) | ||
const wrapperWidth = ref.current.clientWidth | ||
const existsEllipsis = outerWidth >= 0 && outerWidth <= wrapperWidth | ||
|
||
if (!existsEllipsis) { | ||
return | ||
} | ||
} | ||
|
||
setRect(ref.current.getBoundingClientRect()) | ||
setIsVisible(true) | ||
}, | ||
setRect(ref.current.getBoundingClientRect()) | ||
setIsVisible(true) | ||
}, | ||
[ellipsisOnly], | ||
) | ||
const actualOnPointerEnter = useMemo( | ||
() => | ||
onPointerEnter | ||
? (e: React.PointerEvent<HTMLSpanElement>) => { | ||
onPointerEnter(e) | ||
toShowAction(e) | ||
} | ||
: toShowAction, | ||
[onPointerEnter, toShowAction], | ||
) | ||
const actualOnTouchStart = useMemo( | ||
() => | ||
onTouchStart | ||
? (e: React.TouchEvent<HTMLSpanElement>) => { | ||
onTouchStart(e) | ||
toShowAction(e) | ||
} | ||
: toShowAction, | ||
[onTouchStart, toShowAction], | ||
) | ||
const actualOnFocus = useMemo( | ||
() => | ||
onFocus | ||
? (e: React.FocusEvent<HTMLSpanElement>) => { | ||
onFocus(e) | ||
toShowAction(e) | ||
} | ||
: toShowAction, | ||
[onFocus, toShowAction], | ||
) | ||
|
||
const getHandlerToHide = useCallback( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. こちらも関数を返す関数になっていたため、onXxxx毎にmemo化する方法に変更しています |
||
<T,>(handler?: (e: T) => void) => | ||
(e: T) => { | ||
if (handler) { | ||
handler(e) | ||
} | ||
|
||
setIsVisible(false) | ||
}, | ||
[setIsVisible], | ||
const toCloseAction = useCallback(() => setIsVisible(false), []) | ||
const actualOnPointerLeave = useMemo( | ||
() => | ||
onPointerLeave | ||
? (e: React.PointerEvent<HTMLSpanElement>) => { | ||
onPointerLeave(e) | ||
toCloseAction() | ||
} | ||
: toCloseAction, | ||
[onPointerLeave, toCloseAction], | ||
) | ||
const actualOnTouchEnd = useMemo( | ||
() => | ||
onTouchEnd | ||
? (e: React.TouchEvent<HTMLSpanElement>) => { | ||
onTouchEnd(e) | ||
toCloseAction() | ||
} | ||
: toCloseAction, | ||
[onTouchEnd, toCloseAction], | ||
) | ||
const actualOnBlur = useMemo( | ||
() => | ||
onBlur | ||
? (e: React.FocusEvent<HTMLSpanElement>) => { | ||
onBlur(e) | ||
toCloseAction() | ||
} | ||
: toCloseAction, | ||
[onBlur, toCloseAction], | ||
) | ||
|
||
const hiddenText = useMemo(() => innerText(message), [message]) | ||
const isIcon = triggerType === 'icon' | ||
const styles = tooltip({ isIcon, className }) | ||
const actualClassName = useMemo( | ||
() => classNameGenerator({ isIcon, className }), | ||
[isIcon, className], | ||
) | ||
const isInnerTarget = ariaDescribedbyTarget === 'inner' | ||
const childrenWithProps = useMemo( | ||
() => | ||
|
@@ -159,16 +207,16 @@ export const Tooltip: FC<Props & ElementProps> = ({ | |
// eslint-disable-next-line jsx-a11y/no-static-element-interactions,smarthr/a11y-delegate-element-has-role-presentation | ||
<span | ||
{...props} | ||
aria-describedby={isInnerTarget ? undefined : messageId} | ||
ref={ref} | ||
onPointerEnter={getHandlerToShow(onPointerEnter)} | ||
onTouchStart={getHandlerToShow(onTouchStart)} | ||
onFocus={getHandlerToShow(onFocus)} | ||
onPointerLeave={getHandlerToHide(onPointerLeave)} | ||
onTouchEnd={getHandlerToHide(onTouchEnd)} | ||
onBlur={getHandlerToHide(onBlur)} | ||
tabIndex={tabIndex} | ||
className={styles} | ||
aria-describedby={isInnerTarget ? undefined : messageId} | ||
onPointerEnter={actualOnPointerEnter} | ||
onTouchStart={actualOnTouchStart} | ||
onFocus={actualOnFocus} | ||
onPointerLeave={actualOnPointerLeave} | ||
onTouchEnd={actualOnTouchEnd} | ||
onBlur={actualOnBlur} | ||
className={actualClassName} | ||
> | ||
{portalRoot && | ||
createPortal( | ||
|
@@ -185,9 +233,21 @@ export const Tooltip: FC<Props & ElementProps> = ({ | |
portalRoot, | ||
)} | ||
{childrenWithProps} | ||
<VisuallyHiddenText id={messageId} aria-hidden={!isVisible}> | ||
{hiddenText} | ||
</VisuallyHiddenText> | ||
<MemoizedVisuallyHiddenText id={messageId} visible={isVisible}> | ||
{message} | ||
</MemoizedVisuallyHiddenText> | ||
</span> | ||
) | ||
} | ||
|
||
const MemoizedVisuallyHiddenText = memo<PropsWithChildren<{ id: string; visible: boolean }>>( | ||
({ id, visible, children }) => { | ||
const hiddenText = useMemo(() => innerText(children), [children]) | ||
|
||
return ( | ||
<VisuallyHiddenText id={id} aria-hidden={!visible}> | ||
{hiddenText} | ||
</VisuallyHiddenText> | ||
) | ||
}, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
calcedはcalculatedってことですかね?ぱっと見読み取れなかった