diff --git a/packages/smarthr-ui/src/components/Dialog/DialogBody.tsx b/packages/smarthr-ui/src/components/Dialog/DialogBody.tsx index d52ccdc68e..e7bf152042 100644 --- a/packages/smarthr-ui/src/components/Dialog/DialogBody.tsx +++ b/packages/smarthr-ui/src/components/Dialog/DialogBody.tsx @@ -1,4 +1,4 @@ -import React, { type PropsWithChildren } from 'react' +import React, { type PropsWithChildren, useMemo } from 'react' import { VariantProps, tv } from 'tailwind-variants' import type { Gap } from '../../types' @@ -81,13 +81,25 @@ const dialogBody = tv({ export const DialogBody: React.FC = ({ contentBgColor, - contentPadding = 1.5, + contentPadding, className, ...rest }) => { - const paddingBlock = contentPadding instanceof Object ? contentPadding.block : contentPadding - const paddingInline = contentPadding instanceof Object ? contentPadding.inline : contentPadding + const actualPaddings = useMemo(() => { + const initialized = contentPadding === undefined ? 1.5 : contentPadding + + return initialized instanceof Object ? initialized : { block: initialized, inline: initialized } + }, [contentPadding]) + const style = useMemo( + () => + dialogBody({ + contentBgColor, + paddingBlock: actualPaddings.block, + paddingInline: actualPaddings.inline, + className, + }), + [actualPaddings.block, actualPaddings.inline, contentBgColor, className], + ) - const style = dialogBody({ contentBgColor, paddingBlock, paddingInline, className }) return
} diff --git a/packages/smarthr-ui/src/components/Dialog/DialogContentInner.tsx b/packages/smarthr-ui/src/components/Dialog/DialogContentInner.tsx index a668e6538e..bc78bcf259 100644 --- a/packages/smarthr-ui/src/components/Dialog/DialogContentInner.tsx +++ b/packages/smarthr-ui/src/components/Dialog/DialogContentInner.tsx @@ -1,14 +1,6 @@ 'use client' -import React, { - ComponentProps, - FC, - PropsWithChildren, - RefObject, - useCallback, - useMemo, - useRef, -} from 'react' +import React, { ComponentProps, FC, PropsWithChildren, RefObject, useMemo, useRef } from 'react' import { tv } from 'tailwind-variants' import { useHandleEscape } from '../../hooks/useHandleEscape' @@ -68,9 +60,7 @@ const dialogContentInner = tv({ export const DialogContentInner: FC = ({ onClickOverlay, - onPressEscape = () => { - /* noop */ - }, + onPressEscape, isOpen, id, width, @@ -81,44 +71,39 @@ export const DialogContentInner: FC = ({ className, ...rest }) => { - const { layoutStyleProps, innerStyle, backgroundStyle } = useMemo(() => { + const { layoutStyle, innerStyle, backgroundStyle } = useMemo(() => { const { layout, inner, background } = dialogContentInner() - const actualWidth = typeof width === 'number' ? `${width}px` : width + return { - layoutStyleProps: { - className: layout(), - style: { - width: actualWidth ?? undefined, - }, - }, + layoutStyle: layout(), innerStyle: inner({ className }), backgroundStyle: background(), } - }, [className, width]) + }, [className]) + const styleAttr = useMemo(() => { + const actualWidth = typeof width === 'number' ? `${width}px` : width + + if (!actualWidth) { + return undefined + } + + return { + width: actualWidth, + } + }, [width]) const innerRef = useRef(null) + useHandleEscape( - useCallback(() => { - if (!isOpen) { - return - } - onPressEscape() - }, [isOpen, onPressEscape]), + useMemo(() => (onPressEscape && isOpen ? onPressEscape : undefined), [isOpen, onPressEscape]), ) - const handleClickOverlay = useCallback(() => { - if (isOpen && onClickOverlay) { - onClickOverlay() - } - }, [isOpen, onClickOverlay]) - useBodyScrollLock(isOpen) return ( -
- {/* eslint-disable-next-line smarthr/a11y-delegate-element-has-role-presentation */} -
+
+
= ({ ) } + +const Overlay = React.memo< + Pick & { className: string } +>(({ onClickOverlay, isOpen, className }) => { + const handleClickOverlay = useMemo( + () => (onClickOverlay && isOpen ? onClickOverlay : undefined), + [isOpen, onClickOverlay], + ) + + // eslint-disable-next-line smarthr/a11y-delegate-element-has-role-presentation + return
+}) diff --git a/packages/smarthr-ui/src/components/Dialog/DialogHeader.tsx b/packages/smarthr-ui/src/components/Dialog/DialogHeader.tsx index 61fa9a5732..a6dac1155f 100644 --- a/packages/smarthr-ui/src/components/Dialog/DialogHeader.tsx +++ b/packages/smarthr-ui/src/components/Dialog/DialogHeader.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useMemo } from 'react' import { tv } from 'tailwind-variants' import { Heading, HeadingTagTypes } from '../Heading' @@ -25,8 +25,9 @@ const dialogHeader = tv({ ], }) -export const DialogHeader: React.FC = ({ title, subtitle, titleTag, titleId }) => { - const style = dialogHeader() +export const DialogHeader = React.memo(({ title, subtitle, titleTag, titleId }) => { + const style = useMemo(() => dialogHeader(), []) + return ( // eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content @@ -42,4 +43,4 @@ export const DialogHeader: React.FC = ({ title, subtitle, titleTag, title ) -} +}) diff --git a/packages/smarthr-ui/src/components/Dialog/DialogTrigger.tsx b/packages/smarthr-ui/src/components/Dialog/DialogTrigger.tsx index 9ecfb728c7..a1c57f82d0 100644 --- a/packages/smarthr-ui/src/components/Dialog/DialogTrigger.tsx +++ b/packages/smarthr-ui/src/components/Dialog/DialogTrigger.tsx @@ -6,6 +6,7 @@ import { DialogContext } from './DialogWrapper' export const DialogTrigger: React.FC = (props) => { const { onClickTrigger } = useContext(DialogContext) + return ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events,smarthr/a11y-delegate-element-has-role-presentation
diff --git a/packages/smarthr-ui/src/components/Dialog/DialogWrapper.tsx b/packages/smarthr-ui/src/components/Dialog/DialogWrapper.tsx index 7436374a7d..ddf497daf5 100644 --- a/packages/smarthr-ui/src/components/Dialog/DialogWrapper.tsx +++ b/packages/smarthr-ui/src/components/Dialog/DialogWrapper.tsx @@ -1,6 +1,6 @@ 'use client' -import React, { PropsWithChildren, createContext, useState } from 'react' +import React, { PropsWithChildren, createContext, useCallback, useState } from 'react' type DialogContextType = { onClickTrigger: () => void @@ -8,25 +8,25 @@ type DialogContextType = { active: boolean } +const noop = () => undefined export const DialogContext = createContext({ - onClickTrigger: () => { - /* noop */ - }, - onClickClose: () => { - /* noop */ - }, + onClickTrigger: noop, + onClickClose: noop, active: false, }) export const DialogWrapper: React.FC = (props) => { const [active, setActive] = useState(false) + const onClickTrigger = useCallback(() => setActive(true), []) + const onClickClose = useCallback(() => setActive(false), []) + return ( setActive(true), - onClickClose: () => setActive(false), + onClickTrigger, + onClickClose, active, }} /> diff --git a/packages/smarthr-ui/src/components/Dialog/ModelessDialog.tsx b/packages/smarthr-ui/src/components/Dialog/ModelessDialog.tsx index accdb8c481..c9fbb3f1c4 100644 --- a/packages/smarthr-ui/src/components/Dialog/ModelessDialog.tsx +++ b/packages/smarthr-ui/src/components/Dialog/ModelessDialog.tsx @@ -318,13 +318,22 @@ export const ModelessDialog: FC + onPressEscape + ? () => { + lastFocusElementRef.current?.focus() + onPressEscape() + } + : undefined, + [onPressEscape], + ) + useHandleEscape( - useCallback(() => { - if (isOpen && onPressEscape) { - lastFocusElementRef.current?.focus() - onPressEscape() - } - }, [isOpen, onPressEscape]), + useMemo( + () => (actualOnPressEscape && isOpen ? actualOnPressEscape : undefined), + [isOpen, actualOnPressEscape], + ), ) useEffect(() => { diff --git a/packages/smarthr-ui/src/hooks/useHandleEscape.ts b/packages/smarthr-ui/src/hooks/useHandleEscape.ts index 82da161f97..8802117844 100644 --- a/packages/smarthr-ui/src/hooks/useHandleEscape.ts +++ b/packages/smarthr-ui/src/hooks/useHandleEscape.ts @@ -1,18 +1,29 @@ -import { useCallback, useEffect } from 'react' +import { useEffect, useMemo } from 'react' -export const useHandleEscape = (cb: () => void) => { - const handleKeyPress = useCallback( - (e: KeyboardEvent) => { - // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key - // Esc is a IE/Edge specific value - if (e.key === 'Escape' || e.key === 'Esc') { - cb() - } - }, +const ESCAPE_KEY_REGEX = /^Esc(ape)?$/ + +export const useHandleEscape = (cb?: () => void) => { + const handleKeyPress = useMemo( + () => + cb + ? (e: KeyboardEvent) => { + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key + // Esc is a IE/Edge specific value + if (ESCAPE_KEY_REGEX.test(e.key)) { + cb() + } + } + : null, [cb], ) + useEffect(() => { + if (!handleKeyPress) { + return + } + document.addEventListener('keydown', handleKeyPress) + return () => document.removeEventListener('keydown', handleKeyPress) }, [handleKeyPress]) }