From 25d83ec6b0633aba4dc800b3e769d7de66bb5d42 Mon Sep 17 00:00:00 2001 From: KANAMORI Yu Date: Tue, 10 Sep 2024 09:15:55 +0900 Subject: [PATCH] =?UTF-8?q?feat!:=20Dialog=20=E3=81=AE=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=82=92=E6=B6=88=E3=81=97=E3=80=81=E3=82=B9?= =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=AA=E3=83=B3=E3=82=B0=E3=82=92=E8=A6=8B?= =?UTF-8?q?=E7=9B=B4=E3=81=97=20(#4848)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: oti Co-authored-by: shingo.sasaki --- .../ActionDialog/ActionDialogContentInner.tsx | 107 ++++------- .../src/components/Dialog/Dialog.stories.tsx | 30 --- ...{useDialogInnerStyle.ts => DialogBody.tsx} | 68 +++---- .../components/Dialog/DialogContentInner.tsx | 101 ++++------ .../src/components/Dialog/DialogHeader.tsx | 45 +++++ .../src/components/Dialog/DialogOverlap.tsx | 29 +-- .../Dialog/DialogPositionProvider.tsx | 21 --- .../FormDialog/FormDialogContentInner.tsx | 164 +++++++---------- .../MessageDialogContentInner.tsx | 174 +++--------------- .../src/components/Dialog/ModelessDialog.tsx | 106 ++--------- .../src/components/Dialog/dialogHelper.ts | 37 ---- .../src/components/Dialog/dialogInnerStyle.ts | 13 ++ .../smarthr-ui/src/components/Dialog/types.ts | 10 +- 13 files changed, 276 insertions(+), 629 deletions(-) rename packages/smarthr-ui/src/components/Dialog/{useDialogInnerStyle.ts => DialogBody.tsx} (51%) create mode 100644 packages/smarthr-ui/src/components/Dialog/DialogHeader.tsx delete mode 100644 packages/smarthr-ui/src/components/Dialog/DialogPositionProvider.tsx delete mode 100644 packages/smarthr-ui/src/components/Dialog/dialogHelper.ts create mode 100644 packages/smarthr-ui/src/components/Dialog/dialogInnerStyle.ts diff --git a/packages/smarthr-ui/src/components/Dialog/ActionDialog/ActionDialogContentInner.tsx b/packages/smarthr-ui/src/components/Dialog/ActionDialog/ActionDialogContentInner.tsx index 7a672349c2..19a35c4a4f 100644 --- a/packages/smarthr-ui/src/components/Dialog/ActionDialog/ActionDialogContentInner.tsx +++ b/packages/smarthr-ui/src/components/Dialog/ActionDialog/ActionDialogContentInner.tsx @@ -1,61 +1,40 @@ -import React, { FC, PropsWithChildren, ReactNode, useCallback } from 'react' +import React, { type FC, type PropsWithChildren, type ReactNode, useCallback } from 'react' import { Button } from '../../Button' -import { Heading, HeadingTagTypes } from '../../Heading' import { Cluster, Stack } from '../../Layout' import { ResponseMessage } from '../../ResponseMessage' -import { Section } from '../../SectioningContent' -import { Text } from '../../Text' -import { useOffsetHeight } from '../dialogHelper' -import { type ContentBodyProps, useDialoginnerStyle } from '../useDialogInnerStyle' +import { DialogBody, type Props as DialogBodyProps } from '../DialogBody' +import { DialogHeader, type Props as DialogHeaderProps } from '../DialogHeader' +import { dialogContentInner } from '../dialogInnerStyle' import type { DecoratorsType, ResponseMessageType } from '../../../types' -export type BaseProps = PropsWithChildren<{ - /** - * ダイアログのタイトル - */ - title: ReactNode - /** - * ダイアログのサブタイトル - */ - subtitle?: ReactNode - /** - * @deprecated SectioningContent(Article, Aside, Nav, Section)でDialog全体をラップして、ダイアログタイトルのHeadingレベルを設定してください - */ - titleTag?: HeadingTagTypes - /** - * アクションボタンのラベル - */ - actionText: ReactNode - /** - * アクションボタンのスタイル - */ - actionTheme?: 'primary' | 'secondary' | 'danger' - /** - * アクションボタンをクリックした時に発火するコールバック関数 - * @param closeDialog - ダイアログを閉じる関数 - */ - onClickAction: (closeDialog: () => void) => void - /** - * アクションボタンを無効にするかどうか - */ - actionDisabled?: boolean - /** - * 閉じるボタンを無効にするかどうか - */ - closeDisabled?: boolean - /** ダイアログフッターの左端操作領域 */ - subActionArea?: ReactNode - /** コンポーネント内の文言を変更するための関数を設定 */ - decorators?: DecoratorsType<'closeButtonLabel'> -}> & - ContentBodyProps +export type BaseProps = PropsWithChildren< + DialogHeaderProps & + DialogBodyProps & { + /** アクションボタンのラベル */ + actionText: ReactNode + /** アクションボタンのスタイル */ + actionTheme?: 'primary' | 'secondary' | 'danger' + /** + * アクションボタンをクリックした時に発火するコールバック関数 + * @param closeDialog - ダイアログを閉じる関数 + */ + onClickAction: (closeDialog: () => void) => void + /** アクションボタンを無効にするかどうか */ + actionDisabled?: boolean + /** 閉じるボタンを無効にするかどうか */ + closeDisabled?: boolean + /** ダイアログフッターの左端操作領域 */ + subActionArea?: ReactNode + /** コンポーネント内の文言を変更するための関数を設定 */ + decorators?: DecoratorsType<'closeButtonLabel'> + } +> export type ActionDialogContentInnerProps = BaseProps & { onClickClose: () => void responseMessage?: ResponseMessageType - titleId: string } const CLOSE_BUTTON_LABEL = 'キャンセル' @@ -80,33 +59,21 @@ export const ActionDialogContentInner: FC = ({ const handleClickAction = useCallback(() => { onClickAction(onClickClose) }, [onClickAction, onClickClose]) - const { offsetHeight, titleRef, bottomRef } = useOffsetHeight() - const isRequestProcessing = responseMessage && responseMessage.status === 'processing' - const { titleAreaStyle, bodyStyleProps, actionAreaStyle, buttonAreaStyle, messageStyle } = - useDialoginnerStyle(offsetHeight, contentBgColor, contentPadding) + const { wrapper, actionArea, buttonArea, message } = dialogContentInner() return ( -
- {/* eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content */} - - - {subtitle && ( - - {subtitle} - - )} - - {title} - - - -
{children}
- + // eslint-disable-next-line smarthr/best-practice-for-layouts, smarthr/a11y-heading-in-sectioning-content + + + + {children} + + {subActionArea} - +
+ ) } diff --git a/packages/smarthr-ui/src/components/Dialog/Dialog.stories.tsx b/packages/smarthr-ui/src/components/Dialog/Dialog.stories.tsx index 5948113297..370381e5c4 100644 --- a/packages/smarthr-ui/src/components/Dialog/Dialog.stories.tsx +++ b/packages/smarthr-ui/src/components/Dialog/Dialog.stories.tsx @@ -459,30 +459,6 @@ export const WidthAndPosition: StoryFn = () => ( -
  • - - - - - -

    This Dialog is set to `top: 50px, left: 200px`.

    -
    -
    -
  • -
  • - - - - - -

    This Dialog is set to `right: 50px, bottom: 100px`.

    -
    -
    -
  • ) WidthAndPosition.parameters = { @@ -551,12 +527,6 @@ export const RegDialogOpenedDialogWidth: StoryFn = () => ( ) -export const RegDialogOpenedDialogPosition: StoryFn = () => ( - -

    {dummyText}

    -
    -) - export const RegOpenedForm: StoryFn = () => ( , 'contentBgColor'> & { + contentPadding?: Gap | { block?: Gap; inline?: Gap } + className?: string | undefined + } +> -const dialogContentInner = tv({ - slots: { - titleArea: ['smarthr-ui-Dialog-titleArea', 'shr-border-b-shorthand shr-px-1.5 shr-py-1'], - actionArea: ['smarthr-ui-Dialog-actionArea', 'shr-border-t-shorthand shr-px-1.5 shr-py-1'], - buttonArea: ['smarthr-ui-Dialog-buttonArea', 'shr-ms-auto'], - message: 'shr-text-right', - }, -}) const dialogBody = tv({ - base: ['smarthr-ui-Dialog-body', 'shr-overflow-auto'], + base: ['smarthr-ui-Dialog-body', 'shr-overflow-auto shr-flex-auto'], variants: { - contentPaddingBlock: { + paddingBlock: { 0: 'shr-py-0', 0.25: 'shr-py-0.25', 0.5: 'shr-py-0.5', @@ -38,7 +37,7 @@ const dialogBody = tv({ XXL: 'shr-py-3.5', X3L: 'shr-py-4', } as { [key in Gap]: string }, - contentPaddingInline: { + paddingInline: { 0: 'shr-px-0', 0.25: 'shr-px-0.25', 0.5: 'shr-px-0.5', @@ -80,34 +79,15 @@ const dialogBody = tv({ }, }) -export type ContentBodyProps = Pick, 'contentBgColor'> & { - contentPadding?: Gap | { block?: Gap; inline?: Gap } -} - -export const useDialoginnerStyle = ( - offsetHeight: number, - bgColor: ContentBodyProps['contentBgColor'], - padding: ContentBodyProps['contentPadding'] = 1.5, -) => - useMemo(() => { - const { titleArea, actionArea, buttonArea, message } = dialogContentInner() - const paddingBlock = padding instanceof Object ? padding.block : padding - const paddingInline = padding instanceof Object ? padding.inline : padding +export const DialogBody: React.FC = ({ + contentBgColor, + contentPadding = 1.5, + className, + ...rest +}) => { + const paddingBlock = contentPadding instanceof Object ? contentPadding.block : contentPadding + const paddingInline = contentPadding instanceof Object ? contentPadding.inline : contentPadding - return { - titleAreaStyle: titleArea(), - bodyStyleProps: { - className: dialogBody({ - contentBgColor: bgColor, - contentPaddingBlock: paddingBlock, - contentPaddingInline: paddingInline, - }), - style: { - maxHeight: `calc(100svh - ${offsetHeight}px)`, - }, - }, - actionAreaStyle: actionArea(), - buttonAreaStyle: buttonArea(), - messageStyle: message(), - } - }, [bgColor, offsetHeight, padding]) + 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 3d6e02f14c..005483cd9f 100644 --- a/packages/smarthr-ui/src/components/Dialog/DialogContentInner.tsx +++ b/packages/smarthr-ui/src/components/Dialog/DialogContentInner.tsx @@ -10,10 +10,8 @@ import React, { import { tv } from 'tailwind-variants' import { useHandleEscape } from '../../hooks/useHandleEscape' -import { useTheme } from '../../hooks/useTailwindTheme' import { DialogOverlap } from './DialogOverlap' -import { DialogPositionProvider } from './DialogPositionProvider' import { FocusTrap } from './FocusTrap' import { useBodyScrollLock } from './useBodyScrollLock' @@ -34,24 +32,9 @@ export type DialogContentInnerProps = PropsWithChildren<{ * ダイアログの幅 */ width?: string | number - /** - * ダイアログを開いたときの初期 top 位置 - */ - top?: number - /** - * ダイアログを開いたときの初期 right 位置 - */ - right?: number - /** - * ダイアログを開いたときの初期 bottom 位置 - */ - bottom?: number - /** - * ダイアログを開いたときの初期 left 位置 - */ - left?: number /** * ダイアログの `id` + * TODO 使われてなさそうなので確認 */ id?: string /** @@ -69,16 +52,15 @@ export type DialogContentInnerProps = PropsWithChildren<{ }> type ElementProps = Omit, keyof DialogContentInnerProps> -function exist(value: any) { - return value !== undefined && value !== null -} - const dialogContentInner = tv({ slots: { - layout: 'smarthr-ui-Dialog-wrapper shr-fixed shr-inset-0', - inner: - 'smarthr-ui-Dialog contrast-more:shr-border-highContrast shr-absolute shr-rounded-m shr-bg-white shr-shadow-layer-3 contrast-more:shr-border-shorthand', - background: 'smarthr-ui-Dialog-background shr-fixed shr-inset-0 shr-bg-scrim', + layout: ['smarthr-ui-Dialog-wrapper', 'shr-max-w-[calc(100dvw-theme(spacing.1))]'], + inner: [ + 'smarthr-ui-Dialog', + 'shr-relative shr-z-1 shr-rounded-m shr-bg-white shr-shadow-layer-3', + 'contrast-more:shr-border-highContrast contrast-more:shr-border-shorthand', + ], + background: ['smarthr-ui-Dialog-background', 'shr-absolute shr-inset-0 shr-bg-scrim'], }, }) @@ -90,46 +72,27 @@ export const DialogContentInner: FC = ({ isOpen, id, width, - top, - right, - bottom, - left, firstFocusTarget, ariaLabel, ariaLabelledby, children, className, - ...props + ...rest }) => { - const { spacing } = useTheme() - const { layoutStyle, innerStyleProps, backgroundStyle } = useMemo(() => { + const { layoutStyleProps, innerStyle, backgroundStyle } = useMemo(() => { const { layout, inner, background } = dialogContentInner() - const positionTop = exist(top) ? `${top}px` : 'auto' - const positionRight = exist(right) ? `${right}px` : 'auto' - const positionBottom = exist(bottom) ? `${bottom}px` : 'auto' - const positionLeft = exist(left) ? `${left}px` : 'auto' const actualWidth = typeof width === 'number' ? `${width}px` : width - const minimumMaxWidth = `calc(100vw - max(${left || 0}px, ${spacing[0.5]}) - max(${ - right || 0 - }px, ${spacing[0.5]}))` - const translateX = exist(right) || exist(left) ? '0' : 'calc((100vw - 100%) / 2)' - const translateY = exist(top) || exist(bottom) ? '0' : 'calc((100svh - 100%) / 2)' return { - layoutStyle: layout(), - innerStyleProps: { - className: inner({ className }), + layoutStyleProps: { + className: layout(), style: { - inset: `${positionTop} ${positionRight} ${positionBottom} ${positionLeft}`, - width: exist(actualWidth) ? actualWidth : undefined, - maxWidth: exist(actualWidth) - ? `min(${minimumMaxWidth}, ${actualWidth})` - : minimumMaxWidth, - transform: `translate(${translateX}, ${translateY})`, + width: actualWidth ?? undefined, }, }, + innerStyle: inner({ className }), backgroundStyle: background(), } - }, [bottom, className, left, right, spacing, top, width]) + }, [className, width]) const innerRef = useRef(null) useHandleEscape( @@ -151,24 +114,22 @@ export const DialogContentInner: FC = ({ useBodyScrollLock(isOpen) return ( - - -
    - {/* eslint-disable-next-line smarthr/a11y-delegate-element-has-role-presentation */} -
    -
    - {children} -
    + +
    + {/* eslint-disable-next-line smarthr/a11y-delegate-element-has-role-presentation */} +
    +
    + {children}
    - - +
    + ) } diff --git a/packages/smarthr-ui/src/components/Dialog/DialogHeader.tsx b/packages/smarthr-ui/src/components/Dialog/DialogHeader.tsx new file mode 100644 index 0000000000..61fa9a5732 --- /dev/null +++ b/packages/smarthr-ui/src/components/Dialog/DialogHeader.tsx @@ -0,0 +1,45 @@ +import React from 'react' +import { tv } from 'tailwind-variants' + +import { Heading, HeadingTagTypes } from '../Heading' +import { Stack } from '../Layout' +import { Text } from '../Text' + +export type Props = { + /** ダイアログタイトル */ + title: React.ReactNode + /** ダイアログサブタイトル */ + subtitle?: React.ReactNode + /** + * ダイアログヘッダーの HTML タグ + * @deprecated SectioningContent(Article, Aside, Nav, Section)でDialog全体をラップして、ダイアログタイトルのHeadingレベルを設定してください + */ + titleTag?: HeadingTagTypes + titleId: string +} + +const dialogHeader = tv({ + base: [ + 'smarthr-ui-Dialog-titleArea', + 'shr-border-b-shorthand shr-px-1.5 shr-py-1 shr-flex-[0_0_auto]', + ], +}) + +export const DialogHeader: React.FC = ({ title, subtitle, titleTag, titleId }) => { + const style = dialogHeader() + return ( + // eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content + + + {subtitle && ( + + {subtitle} + + )} + + {title} + + + + ) +} diff --git a/packages/smarthr-ui/src/components/Dialog/DialogOverlap.tsx b/packages/smarthr-ui/src/components/Dialog/DialogOverlap.tsx index 3fe7c3912c..39c192de21 100644 --- a/packages/smarthr-ui/src/components/Dialog/DialogOverlap.tsx +++ b/packages/smarthr-ui/src/components/Dialog/DialogOverlap.tsx @@ -1,23 +1,26 @@ import React, { - FC, - PropsWithChildren, - ReactNode, + type ComponentProps, + type FC, + type PropsWithChildren, + type ReactNode, useEffect, - useMemo, useRef, useState, } from 'react' import { CSSTransition } from 'react-transition-group' import { tv } from 'tailwind-variants' -type Props = PropsWithChildren<{ - isOpen: boolean -}> +import { Center } from '../Layout' + +type Props = PropsWithChildren< + { + isOpen: boolean + } & ComponentProps<'div'> +> const dialogOverlap = tv({ base: [ - 'shr-absolute', - 'shr-z-overlap-base', + 'shr-absolute shr-inset-0', '[&.shr-dialog-transition-enter]:shr-opacity-0', '[&.shr-dialog-transition-enter-active]:shr-transition-opacity', '[&.shr-dialog-transition-enter-active]:shr-duration-300', @@ -33,8 +36,8 @@ const dialogOverlap = tv({ ], }) -export const DialogOverlap: FC = ({ isOpen, children }) => { - const styles = useMemo(() => dialogOverlap(), []) +export const DialogOverlap: FC = ({ isOpen, className, children }) => { + const styles = dialogOverlap({ className }) const [childrenBuffer, setChildrenBuffer] = useState(null) const nodeRef = useRef(null) @@ -52,9 +55,9 @@ export const DialogOverlap: FC = ({ isOpen, children }) => { unmountOnExit nodeRef={nodeRef} > -
    +
    {isOpen ? children : childrenBuffer} -
    + ) } diff --git a/packages/smarthr-ui/src/components/Dialog/DialogPositionProvider.tsx b/packages/smarthr-ui/src/components/Dialog/DialogPositionProvider.tsx deleted file mode 100644 index 49c1b4ed60..0000000000 --- a/packages/smarthr-ui/src/components/Dialog/DialogPositionProvider.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, { createContext } from 'react' - -type PositionContextType = { - top?: number - bottom?: number -} - -export const PositionContext = createContext({}) - -export const DialogPositionProvider: React.FC< - PositionContextType & { children?: React.ReactNode } -> = ({ top, bottom, children }) => ( - - {children} - -) diff --git a/packages/smarthr-ui/src/components/Dialog/FormDialog/FormDialogContentInner.tsx b/packages/smarthr-ui/src/components/Dialog/FormDialog/FormDialogContentInner.tsx index 7b32292d8b..4d6398d687 100644 --- a/packages/smarthr-ui/src/components/Dialog/FormDialog/FormDialogContentInner.tsx +++ b/packages/smarthr-ui/src/components/Dialog/FormDialog/FormDialogContentInner.tsx @@ -1,61 +1,47 @@ -import React, { FC, FormEvent, PropsWithChildren, ReactNode, useCallback } from 'react' +import React, { + type FC, + type FormEvent, + type PropsWithChildren, + type ReactNode, + useCallback, +} from 'react' import { Button } from '../../Button' -import { Heading, HeadingTagTypes } from '../../Heading' import { Cluster, Stack } from '../../Layout' import { ResponseMessage } from '../../ResponseMessage' import { Section } from '../../SectioningContent' -import { Text } from '../../Text' -import { useOffsetHeight } from '../dialogHelper' -import { type ContentBodyProps, useDialoginnerStyle } from '../useDialogInnerStyle' +import { DialogBody, Props as DialogBodyProps } from '../DialogBody' +import { DialogHeader, type Props as DialogHeaderProps } from '../DialogHeader' +import { dialogContentInner } from '../dialogInnerStyle' import type { DecoratorsType, ResponseMessageType } from '../../../types' -export type BaseProps = PropsWithChildren<{ - /** - * ダイアログのタイトル - */ - title: ReactNode - /** - * ダイアログのサブタイトル - */ - subtitle?: ReactNode - /** - * @deprecated SectioningContent(Article, Aside, Nav, Section)でDialog全体をラップして、ダイアログタイトルのHeadingレベルを設定してください - */ - titleTag?: HeadingTagTypes - /** - * アクションボタンのラベル - */ - actionText: ReactNode - /** - * アクションボタンのスタイル - */ - actionTheme?: 'primary' | 'secondary' | 'danger' - /** - * アクションボタンをクリックした時に発火するコールバック関数 - * @param closeDialog - ダイアログを閉じる関数 - */ - onSubmit: (closeDialog: () => void, e: FormEvent) => void - /** - * アクションボタンを無効にするかどうか - */ - actionDisabled?: boolean - /** - * 閉じるボタンを無効にするかどうか - */ - closeDisabled?: boolean - /** ダイアログフッターの左端操作領域 */ - subActionArea?: ReactNode - /** コンポーネント内の文言を変更するための関数を設定 */ - decorators?: DecoratorsType<'closeButtonLabel'> -}> & - ContentBodyProps +export type BaseProps = PropsWithChildren< + DialogHeaderProps & + DialogBodyProps & { + /** アクションボタンのラベル */ + actionText: ReactNode + /** アクションボタンのスタイル */ + actionTheme?: 'primary' | 'secondary' | 'danger' + /** + * アクションボタンをクリックした時に発火するコールバック関数 + * @param closeDialog ダイアログを閉じる関数 + */ + onSubmit: (closeDialog: () => void, e: FormEvent) => void + /** アクションボタンを無効にするかどうか */ + actionDisabled?: boolean + /** 閉じるボタンを無効にするかどうか */ + closeDisabled?: boolean + /** ダイアログフッターの左端操作領域 */ + subActionArea?: ReactNode + /** コンポーネント内の文言を変更するための関数を設定 */ + decorators?: DecoratorsType<'closeButtonLabel'> + } +> export type FormDialogContentInnerProps = BaseProps & { onClickClose: () => void responseMessage?: ResponseMessageType - titleId: string } const CLOSE_BUTTON_LABEL = 'キャンセル' @@ -88,64 +74,50 @@ export const FormDialogContentInner: FC = ({ }, [onSubmit, onClickClose], ) - const { offsetHeight, titleRef, bottomRef } = useOffsetHeight() - const isRequestProcessing = responseMessage && responseMessage.status === 'processing' - const { titleAreaStyle, bodyStyleProps, actionAreaStyle, buttonAreaStyle, messageStyle } = - useDialoginnerStyle(offsetHeight, contentBgColor, contentPadding) + const { wrapper, actionArea, buttonArea, message } = dialogContentInner() return ( + // eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content
    - {/* eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content */} - - - {subtitle && ( - - {subtitle} - + {/* eslint-disable-next-line smarthr/best-practice-for-layouts */} + + + + {children} + + + + {subActionArea} + + + + + + {(responseMessage?.status === 'success' || responseMessage?.status === 'error') && ( +
    + + {responseMessage.text} + +
    )} - - {title} -
    -
    -
    {children}
    - - - {subActionArea} - - - - - - {(responseMessage?.status === 'success' || responseMessage?.status === 'error') && ( -
    - - {responseMessage.text} - -
    - )}
    diff --git a/packages/smarthr-ui/src/components/Dialog/MessageDialog/MessageDialogContentInner.tsx b/packages/smarthr-ui/src/components/Dialog/MessageDialog/MessageDialogContentInner.tsx index 9436828220..03b6797d0c 100644 --- a/packages/smarthr-ui/src/components/Dialog/MessageDialog/MessageDialogContentInner.tsx +++ b/packages/smarthr-ui/src/components/Dialog/MessageDialog/MessageDialogContentInner.tsx @@ -1,177 +1,53 @@ -import React, { FC, useMemo } from 'react' -import { tv } from 'tailwind-variants' +import React, { type FC } from 'react' import { Button } from '../../Button' -import { Heading, HeadingTagTypes } from '../../Heading' -import { Stack } from '../../Layout' -import { Section } from '../../SectioningContent' -import { Text } from '../../Text' -import { useOffsetHeight } from '../dialogHelper' -import { type ContentBodyProps } from '../useDialogInnerStyle' +import { Cluster, Stack } from '../../Layout' +import { DialogBody, Props as DialogBodyProps } from '../DialogBody' +import { DialogHeader, Props as DialogHeaderProps } from '../DialogHeader' +import { dialogContentInner } from '../dialogInnerStyle' -import type { DecoratorsType, Gap } from '../../../types' +import type { DecoratorsType } from '../../../types' -export type BaseProps = { - /** - * ダイアログのタイトル - */ - title: React.ReactNode - /** - * ダイアログのサブタイトル - */ - subtitle?: React.ReactNode - /** - * @deprecated SectioningContent(Article, Aside, Nav, Section)でDialog全体をラップして、ダイアログタイトルのHeadingレベルを設定してください - */ - titleTag?: HeadingTagTypes - /** - * ダイアログの説明 - */ - description: React.ReactNode - /** コンポーネント内の文言を変更するための関数を設定 */ - decorators?: DecoratorsType<'closeButtonLabel'> -} & ContentBodyProps +export type BaseProps = DialogHeaderProps & + DialogBodyProps & { + /** ダイアログの説明 */ + description: React.ReactNode + /** コンポーネント内の文言を変更するための関数を設定 */ + decorators?: DecoratorsType<'closeButtonLabel'> + } export type MessageDialogContentInnerProps = BaseProps & { onClickClose: () => void - titleId: string } const CLOSE_BUTTON_LABEL = '閉じる' -const messegeDialogContentInner = tv({ - slots: { - titleArea: - 'smarthr-ui-Dialog-titleArea shr-border-b-shorthand shr-my-[unset] shr-px-1.5 shr-py-1', - footer: - 'smarthr-ui-Dialog-buttonArea shr-border-t-shorthand shr-flex shr-justify-end shr-px-1.5 shr-py-1', - }, -}) -// FIXME: 他のダイアログと共通化したかったが別でやる -const dialogBody = tv({ - base: ['smarthr-ui-Dialog-description', 'shr-overflow-auto shr-text-base'], - variants: { - contentPaddingBlock: { - 0: 'shr-py-0', - 0.25: 'shr-py-0.25', - 0.5: 'shr-py-0.5', - 0.75: 'shr-py-0.75', - 1: 'shr-py-1', - 1.25: 'shr-py-1.25', - 1.5: 'shr-py-1.5', - 2: 'shr-py-2', - 2.5: 'shr-py-2.5', - 3: 'shr-py-3', - 3.5: 'shr-py-3.5', - 4: 'shr-py-4', - 8: 'shr-py-8', - X3S: 'shr-py-0.25', - XXS: 'shr-py-0.5', - XS: 'shr-py-1', - S: 'shr-py-1.5', - M: 'shr-py-2', - L: 'shr-py-2.5', - XL: 'shr-py-3', - XXL: 'shr-py-3.5', - X3L: 'shr-py-4', - } as { [key in Gap]: string }, - contentPaddingInline: { - 0: 'shr-px-0', - 0.25: 'shr-px-0.25', - 0.5: 'shr-px-0.5', - 0.75: 'shr-px-0.75', - 1: 'shr-px-1', - 1.25: 'shr-px-1.25', - 1.5: 'shr-px-1.5', - 2: 'shr-px-2', - 2.5: 'shr-px-2.5', - 3: 'shr-px-3', - 3.5: 'shr-px-3.5', - 4: 'shr-px-4', - 8: 'shr-px-8', - X3S: 'shr-px-0.25', - XXS: 'shr-px-0.5', - XS: 'shr-px-1', - S: 'shr-px-1.5', - M: 'shr-px-2', - L: 'shr-px-2.5', - XL: 'shr-px-3', - XXL: 'shr-px-3.5', - X3L: 'shr-px-4', - } as { [key in Gap]: string }, - contentBgColor: { - BACKGROUND: 'shr-bg-background', - COLUMN: 'shr-bg-column', - BASE_GREY: 'shr-bg-base-grey', - OVER_BACKGROUND: 'shr-bg-over-background', - HEAD: 'shr-bg-head', - BORDER: 'shr-bg-[theme(colors.grey.20)]', - ACTION_BACKGROUND: 'shr-bg-action-background', - WHITE: 'shr-bg-white', - GREY_5: 'shr-bg-[theme(colors.grey.5)]', - GREY_6: 'shr-bg-[theme(colors.grey.6)]', - GREY_7: 'shr-bg-[theme(colors.grey.7)]', - GREY_9: 'shr-bg-[theme(colors.grey.9)]', - GREY_20: 'shr-bg-[theme(colors.grey.20)]', - }, - }, -}) - export const MessageDialogContentInner: FC = ({ title, subtitle, titleTag, titleId, contentBgColor, - contentPadding = 1.5, + contentPadding, description, onClickClose, decorators, }) => { - const { offsetHeight, titleRef, bottomRef } = useOffsetHeight() - - const { titleAreaStyle, bodyStyleProps, footerStyle } = useMemo(() => { - const { titleArea, footer } = messegeDialogContentInner() - const paddingBlock = contentPadding instanceof Object ? contentPadding.block : contentPadding - const paddingInline = contentPadding instanceof Object ? contentPadding.inline : contentPadding - - return { - titleAreaStyle: titleArea(), - bodyStyleProps: { - style: { - maxHeight: `calc(100svh - ${offsetHeight}px)`, - }, - className: dialogBody({ - contentBgColor, - contentPaddingBlock: paddingBlock, - contentPaddingInline: paddingInline, - }), - }, - footerStyle: footer(), - } - }, [contentBgColor, contentPadding, offsetHeight]) + const { wrapper, actionArea } = dialogContentInner() return ( -
    - {/* eslint-disable-next-line smarthr/a11y-heading-in-sectioning-content */} - - - {subtitle && ( - - {subtitle} - - )} - - {title} - - - -
    {description}
    -
    + // eslint-disable-next-line smarthr/best-practice-for-layouts, smarthr/a11y-heading-in-sectioning-content + + + + {description} + + {/* eslint-disable-next-line smarthr/best-practice-for-layouts */} + -
    -
    + + ) } diff --git a/packages/smarthr-ui/src/components/Dialog/ModelessDialog.tsx b/packages/smarthr-ui/src/components/Dialog/ModelessDialog.tsx index 55c0132636..820d20ff40 100644 --- a/packages/smarthr-ui/src/components/Dialog/ModelessDialog.tsx +++ b/packages/smarthr-ui/src/components/Dialog/ModelessDialog.tsx @@ -19,13 +19,13 @@ import { useId } from '../../hooks/useId' import { useTheme } from '../../hooks/useTailwindTheme' import { Base, BaseElementProps } from '../Base' import { Button } from '../Button' -import { FaGripHorizontalIcon, FaTimesIcon } from '../Icon' +import { FaGripIcon, FaXmarkIcon } from '../Icon' +import { DialogBody, type Props as DialogBodyProps } from './DialogBody' import { DialogOverlap } from './DialogOverlap' -import { type ContentBodyProps } from './useDialogInnerStyle' import { useDialogPortal } from './useDialogPortal' -import type { DecoratorsType, Gap } from '../../types' +import type { DecoratorsType } from '../../types' type Props = PropsWithChildren<{ /** @@ -82,7 +82,7 @@ type Props = PropsWithChildren<{ dialogHandlerAriaValuetext?: (txt: string, data: DOMRect | undefined) => string } }> & - ContentBodyProps + DialogBodyProps const DIALOG_HANDLER_ARIA_LABEL = 'ダイアログの位置' const CLOSE_BUTTON_ICON_ALT = '閉じる' @@ -108,84 +108,12 @@ const modelessDialog = tv({ footerEl: 'smarthr-ui-ModelessDialog-footer shr-border-t-shorthand', }, }) -// FIXME: 他のダイアログと共通化したかったが別でやる -const dialogBody = tv({ - base: [ - 'smarthr-ui-ModelessDialog-content', - 'shr-flex-1 shr-overflow-auto shr-overscroll-contain', - ], - variants: { - contentPaddingBlock: { - 0: 'shr-py-0', - 0.25: 'shr-py-0.25', - 0.5: 'shr-py-0.5', - 0.75: 'shr-py-0.75', - 1: 'shr-py-1', - 1.25: 'shr-py-1.25', - 1.5: 'shr-py-1.5', - 2: 'shr-py-2', - 2.5: 'shr-py-2.5', - 3: 'shr-py-3', - 3.5: 'shr-py-3.5', - 4: 'shr-py-4', - 8: 'shr-py-8', - X3S: 'shr-py-0.25', - XXS: 'shr-py-0.5', - XS: 'shr-py-1', - S: 'shr-py-1.5', - M: 'shr-py-2', - L: 'shr-py-2.5', - XL: 'shr-py-3', - XXL: 'shr-py-3.5', - X3L: 'shr-py-4', - } as { [key in Gap]: string }, - contentPaddingInline: { - 0: 'shr-px-0', - 0.25: 'shr-px-0.25', - 0.5: 'shr-px-0.5', - 0.75: 'shr-px-0.75', - 1: 'shr-px-1', - 1.25: 'shr-px-1.25', - 1.5: 'shr-px-1.5', - 2: 'shr-px-2', - 2.5: 'shr-px-2.5', - 3: 'shr-px-3', - 3.5: 'shr-px-3.5', - 4: 'shr-px-4', - 8: 'shr-px-8', - X3S: 'shr-px-0.25', - XXS: 'shr-px-0.5', - XS: 'shr-px-1', - S: 'shr-px-1.5', - M: 'shr-px-2', - L: 'shr-px-2.5', - XL: 'shr-px-3', - XXL: 'shr-px-3.5', - X3L: 'shr-px-4', - } as { [key in Gap]: string }, - contentBgColor: { - BACKGROUND: 'shr-bg-background', - COLUMN: 'shr-bg-column', - BASE_GREY: 'shr-bg-base-grey', - OVER_BACKGROUND: 'shr-bg-over-background', - HEAD: 'shr-bg-head', - BORDER: 'shr-bg-[theme(colors.grey.20)]', - ACTION_BACKGROUND: 'shr-bg-action-background', - WHITE: 'shr-bg-white', - GREY_5: 'shr-bg-[theme(colors.grey.5)]', - GREY_6: 'shr-bg-[theme(colors.grey.6)]', - GREY_7: 'shr-bg-[theme(colors.grey.7)]', - GREY_9: 'shr-bg-[theme(colors.grey.9)]', - GREY_20: 'shr-bg-[theme(colors.grey.20)]', - }, - }, -}) export const ModelessDialog: FC = ({ header, children, contentBgColor, - contentPadding = 1.5, + contentPadding, footer, isOpen, onClickClose, @@ -213,13 +141,10 @@ export const ModelessDialog: FC = ({ titleStyle, dialogHandlerStyle, closeButtonLayoutStyle, - contentStyle, footerStyle, } = useMemo(() => { const { layout, box, headerEl, title, dialogHandler, closeButtonLayout, footerEl } = modelessDialog() - const paddingBlock = contentPadding instanceof Object ? contentPadding.block : contentPadding - const paddingInline = contentPadding instanceof Object ? contentPadding.inline : contentPadding return { layoutStyle: layout({ className }), boxStyle: box(), @@ -227,14 +152,9 @@ export const ModelessDialog: FC = ({ titleStyle: title(), dialogHandlerStyle: dialogHandler(), closeButtonLayoutStyle: closeButtonLayout(), - contentStyle: dialogBody({ - contentBgColor, - contentPaddingBlock: paddingBlock, - contentPaddingInline: paddingInline, - }), footerStyle: footerEl(), } - }, [className, contentBgColor, contentPadding]) + }, [className]) const boxStyleProps = useMemo(() => { const leftMargin = typeof left === 'number' ? `${left}px` : left const rightMargin = typeof right === 'number' ? `${right}px` : right @@ -388,7 +308,7 @@ export const ModelessDialog: FC = ({ ) return createPortal( - + setPosition({ x: data.x, y: data.y })} @@ -430,7 +350,7 @@ export const ModelessDialog: FC = ({ aria-valuetext={dialogHandlerAriaValuetext} onKeyDown={handleArrowKey} > - +
    {header} @@ -443,11 +363,17 @@ export const ModelessDialog: FC = ({ onClick={onClickClose} className="smarthr-ui-ModelessDialog-closeButton" > - +
    -
    {children}
    + + {children} + {footer &&
    {footer}
    }
    diff --git a/packages/smarthr-ui/src/components/Dialog/dialogHelper.ts b/packages/smarthr-ui/src/components/Dialog/dialogHelper.ts deleted file mode 100644 index 87693de5dc..0000000000 --- a/packages/smarthr-ui/src/components/Dialog/dialogHelper.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { RefObject, useContext, useEffect, useRef, useState } from 'react' - -import { useTheme } from '../../hooks/useTheme' - -import { PositionContext } from './DialogPositionProvider' - -type offsetHeightValues = { - offsetHeight: number - titleRef: RefObject - bottomRef: RefObject -} - -export const useOffsetHeight = (): offsetHeightValues => { - const theme = useTheme() - const baseSpace = theme.size.space.L - const { top, bottom } = useContext(PositionContext) - const titleRef = useRef(null) - const bottomRef = useRef(null) - const [offsetHeight, setOffsetHeight] = useState(0) - - useEffect(() => { - const topSpace = top ? top : baseSpace - const bottomSpace = bottom ? bottom : baseSpace - // delay scheduling to get the element's height - setTimeout(() => { - const titleHeight = titleRef.current ? titleRef.current.offsetHeight : 0 - const bottomHeight = bottomRef.current ? bottomRef.current.offsetHeight : 0 - setOffsetHeight(topSpace + bottomSpace + titleHeight + bottomHeight) - }, 0) - }, [top, bottom, baseSpace]) - - return { - offsetHeight, - titleRef, - bottomRef, - } -} diff --git a/packages/smarthr-ui/src/components/Dialog/dialogInnerStyle.ts b/packages/smarthr-ui/src/components/Dialog/dialogInnerStyle.ts new file mode 100644 index 0000000000..fcc85d22c3 --- /dev/null +++ b/packages/smarthr-ui/src/components/Dialog/dialogInnerStyle.ts @@ -0,0 +1,13 @@ +import { tv } from 'tailwind-variants' + +export const dialogContentInner = tv({ + slots: { + wrapper: 'shr-max-h-[calc(100dvh-theme(spacing.2))]', + actionArea: [ + 'smarthr-ui-Dialog-actionArea', + 'shr-border-t-shorthand shr-px-1.5 shr-py-1 shr-flex-[0_0_auto] shr-sticky shr-bottom-0 shr-z-1 shr-bg-white shr-rounded-b-m', + ], + buttonArea: ['smarthr-ui-Dialog-buttonArea', 'shr-ms-auto'], + message: 'shr-text-right', + }, +}) diff --git a/packages/smarthr-ui/src/components/Dialog/types.ts b/packages/smarthr-ui/src/components/Dialog/types.ts index f29a043bc6..499f9d3391 100644 --- a/packages/smarthr-ui/src/components/Dialog/types.ts +++ b/packages/smarthr-ui/src/components/Dialog/types.ts @@ -4,15 +4,7 @@ import { DialogContentInnerProps } from './DialogContentInner' type CommonProps = Pick< DialogContentInnerProps, - | 'width' - | 'top' - | 'right' - | 'bottom' - | 'left' - | 'id' - | 'firstFocusTarget' - | 'ariaLabel' - | 'ariaLabelledby' + 'width' | 'id' | 'firstFocusTarget' | 'ariaLabel' | 'ariaLabelledby' > type ControlledProps = Pick