Dialog

Dialogコンポーネントは、ユーザーに集中してほしい確認や操作を示します。

処理中
ドラッグして幅を変更
ドラッグして高さを変更
import { action } from '@storybook/addon-actions'
import { Story } from '@storybook/react'
import React, { ComponentProps, useRef, useState } from 'react'
import styled from 'styled-components'
import { Button } from '../Button'
import { CheckBox } from '../CheckBox'
import { DatePicker } from '../DatePicker'
import { Input } from '../Input'
import { Cluster, LineUp, Stack } from '../Layout'
import { RadioButton } from '../RadioButton'
import { Body, Cell, Head, Row, Table } from '../Table'
import {
ActionDialog,
ActionDialogContent,
ActionDialogWithTrigger,
Dialog,
DialogCloser,
DialogContent,
DialogTrigger,
DialogWrapper,
MessageDialog,
MessageDialogContent,
ModelessDialog,
} from '.'
export default {
title: 'Dialog(ダイアログ)/Dialog',
component: Dialog,
subcomponents: {
DialogContent,
DialogWrapper,
DialogTrigger,
DialogCloser,
MessageDialog,
MessageDialogContent,
ActionDialog,
ActionDialogContent,
ModelessDialog,
},
parameters: {
docs: {
source: {
type: 'code',
},
},
withTheming: true,
},
}
export const Default: Story = () => {
const [opened, setOpended] = useState<'default' | 'focus' | null>(null)
const [value, setValue] = useState('Apple')
const [date, setDate] = useState<Date | null>(null)
const onClickClose = () => setOpended(null)
const onChangeValue = (e: React.ChangeEvent<HTMLInputElement>) => setValue(e.currentTarget.name)
const inputRef = useRef<HTMLInputElement>(null)
return (
<TriggerList>
<li>
<Button
onClick={() => setOpended('default')}
aria-haspopup="dialog"
aria-controls="dialog-default"
data-test="dialog-trigger"
>
Dialog
</Button>
<Dialog
isOpen={opened === 'default'}
onClickOverlay={onClickClose}
onPressEscape={onClickClose}
id="dialog-default"
ariaLabel="Dialog"
data-test="dialog-content"
>
<Title>Dialog</Title>
<Description>
The value of isOpen must be managed by you, but you can customize content freely.
</Description>
<Content>
<DatePicker
name="dialog_datepicker"
value={date?.toDateString()}
formatDate={(_date) => (_date ? _date.toDateString() : '')}
onChangeDate={(_date) => setDate(_date)}
data-test="dialog-datepicker"
/>
</Content>
<RadioList>
<li>
<RadioButton name="Apple" checked={value === 'Apple'} onChange={onChangeValue}>
Apple
</RadioButton>
</li>
<li>
<RadioButton name="Orange" checked={value === 'Orange'} onChange={onChangeValue}>
Orange
</RadioButton>
</li>
<li>
<RadioButton name="Grape" checked={value === 'Grape'} onChange={onChangeValue}>
Grape
</RadioButton>
</li>
</RadioList>
<Footer>
<Button onClick={onClickClose} data-test="dialog-closer">
close
</Button>
</Footer>
</Dialog>
</li>
<li>
<Button
onClick={() => setOpended('focus')}
aria-haspopup="dialog"
aria-controls="dialog-focus"
data-test="dialog-focus-trigger"
>
特定の要素をフォーカス
</Button>
<Dialog
isOpen={opened === 'focus'}
onClickOverlay={onClickClose}
onPressEscape={onClickClose}
firstFocusTarget={inputRef}
id="dialog-focus"
ariaLabel="特定の要素をフォーカスするダイアログ"
>
<Title>特定の要素をフォーカスするダイアログ</Title>
<Content>
<Input ref={inputRef} name="input_focus_target" data-test="input-focus-target" />
</Content>
<Footer>
<Button onClick={onClickClose} data-test="dialog-closer">
close
</Button>
</Footer>
</Dialog>
</li>
</TriggerList>
)
}
const dummyText = (
<>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.
<br />
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.
<br />
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur.
<br />
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim
id est laborum.
</>
)
export const Message_Dialog: Story = () => {
const [isOpen, setIsOpen] = useState(false)
const onClickOpen = () => setIsOpen(true)
const onClickClose = () => setIsOpen(false)
return (
<>
<Button
onClick={onClickOpen}
aria-haspopup="dialog"
aria-controls="dialog-message"
data-test="dialog-trigger"
>
MessageDialog
</Button>
<MessageDialog
isOpen={isOpen}
title="MessageDialog"
subtitle="副題"
description={<p>{dummyText} </p>}
onClickClose={onClickClose}
onClickOverlay={onClickClose}
decorators={{ closeButtonLabel: (txt) => `close.(${txt})` }}
id="dialog-message"
data-test="dialog-content"
/>
</>
)
}
Message_Dialog.parameters = {
docs: {
description: {
story: '`MessageDialog` can show messages.',
},
},
}
export const Action_Dialog: Story = () => {
const [openedDialog, setOpenedDialog] = useState<'normal' | 'opened' | null>(null)
const [value, setValue] = React.useState('Apple')
const [responseMessage, setResponseMessage] =
useState<ComponentProps<typeof ActionDialog>['responseMessage']>()
const openedFocusRef = useRef<HTMLInputElement>(null)
const onClickClose = () => {
setOpenedDialog(null)
setResponseMessage(undefined)
}
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => setValue(e.currentTarget.name)
return (
<Cluster>
<Button
onClick={() => setOpenedDialog('normal')}
aria-haspopup="dialog"
aria-controls="dialog-action"
data-test="dialog-trigger"
>
ActionDialog
</Button>
<ActionDialog
isOpen={openedDialog === 'normal'}
title="ActionDialog"
subtitle="副題"
actionText="保存"
decorators={{ closeButtonLabel: (txt) => `cancel.(${txt})` }}
onClickAction={(closeDialog) => {
action('executed')()
setResponseMessage(undefined)
closeDialog()
}}
onClickClose={onClickClose}
responseMessage={responseMessage}
id="dialog-action"
data-test="dialog-content"
>
<RadioList>
<li>
<RadioButton name="Apple" checked={value === 'Apple'} onChange={onChange}>
Apple
</RadioButton>
</li>
<li>
<RadioButton name="Orange" checked={value === 'Orange'} onChange={onChange}>
Orange
</RadioButton>
</li>
<li>
<RadioButton name="Grape" checked={value === 'Grape'} onChange={onChange}>
Grape
</RadioButton>
</li>
</RadioList>
<Buttons>
<p>切り替えボタン:</p>
<Button
onClick={() =>
setResponseMessage({
status: 'success',
text: '保存しました。',
})
}
>
保存
</Button>
<Button
onClick={() =>
setResponseMessage({
status: 'error',
text: '何らかのエラーが発生しました。',
})
}
>
エラー
</Button>
<Button
onClick={() =>
setResponseMessage({
status: 'processing',
})
}
>
保存中
</Button>
</Buttons>
</ActionDialog>
<Button onClick={() => setOpenedDialog('opened')} data-test="opened-dialog-trigger">
開いた状態で DOM に投入
</Button>
{openedDialog === 'opened' && (
<ActionDialog
isOpen
title="開いた状態で投入されたダイアログ"
actionText="実行"
onClickAction={(closeDialog) => {
action('execute')()
closeDialog()
}}
onClickClose={onClickClose}
onClickOverlay={onClickClose}
decorators={{ closeButtonLabel: (txt) => `close.(${txt})` }}
firstFocusTarget={openedFocusRef}
data-test="opened-dialog"
>
<div style={{ padding: '2rem' }}>
<Stack align="flex-start">
<code>isOpen=true</code> の状態で DOM に投入した場合のダイアログ
<Input
ref={openedFocusRef}
name="opened_dialog_focus_target"
data-test="opened-dialog-focus-target"
/>
</Stack>
</div>
</ActionDialog>
)}
</Cluster>
)
}
const Buttons = styled.div`
margin-top: -2rem;
padding: 1rem 1.5rem;
> button + button {
margin-left: 0.5rem;
}
`
Action_Dialog.parameters = {
docs: {
description: {
story: '`ActionDialog` includes an action button that used for submitting, etc.',
},
},
}
export const Action_Dialog_With_Trigger: Story = () => {
return (
<>
<ActionDialogWithTrigger
trigger={<Button>open.</Button>}
title="ActionDialog With Trigger"
actionText="保存"
onClickAction={(close) => {
close()
}}
>
<Description>ActionDialog with Trigger.</Description>
</ActionDialogWithTrigger>
<ActionDialogWithTrigger
trigger={<Button disabled={true}>open.</Button>}
title="Disabled ActionDialog With Trigger"
actionText="保存"
onClickAction={(close) => {
close()
}}
>
<Description>ActionDialog with Trigger.</Description>
</ActionDialogWithTrigger>
</>
)
}
export const Uncontrolled: Story = () => {
return (
<TriggerList>
<li>
<DialogWrapper>
<DialogTrigger>
<Button
aria-haspopup="dialog"
aria-controls="dialog-uncontrolled"
data-test="dialog-trigger"
>
Dialog
</Button>
</DialogTrigger>
<DialogContent id="dialog-uncontrolled" data-test="dialog-content">
<Description>Uncontrolled Dialog.</Description>
<Content>
<DialogCloser>
<Button data-test="dialog-closer">Close</Button>
</DialogCloser>
</Content>
</DialogContent>
</DialogWrapper>
</li>
<li>
<DialogWrapper>
<DialogTrigger>
<Button
aria-haspopup="dialog"
aria-controls="dialog-uncontrolled-message"
data-test="message-dialog-trigger"
>
MessageDialog
</Button>
</DialogTrigger>
<MessageDialogContent
title="Uncontrolled Message Dialog"
description={<p>{dummyText} </p>}
id="dialog-uncontrolled-message"
data-test="message-dialog-content"
/>
</DialogWrapper>
</li>
<li>
<DialogWrapper>
<DialogTrigger>
<Button
aria-haspopup="dialog"
aria-controls="dialog-uncontrolled-action"
data-test="action-dialog-trigger"
>
ActionDialog
</Button>
</DialogTrigger>
<ActionDialogContent
title="Uncontrolled Action Dialog"
actionText="実行"
actionDisabled={false}
onClickAction={(closeDialog) => {
action('executed')()
closeDialog()
}}
id="dialog-uncontrolled-action"
data-test="action-dialog-content"
>
<Description>
The content of ActionDialogContent is freely implemented by the user as children.
<br />
So you need to prepare your own style.
<br />
When action is executed, you can specify when to close dialog. In this story, dialog
closes one second after clicking action
</Description>
</ActionDialogContent>
</DialogWrapper>
</li>
</TriggerList>
)
}
Uncontrolled.parameters = {
docs: {
description: {
story:
'Uncontrolled dialogs do not need to control its state of open / close, and they handle by themselves.',
},
},
}
export const WidthAndPosition: Story = () => {
return (
<TriggerList>
<li>
<DialogWrapper>
<DialogTrigger>
<Button aria-haspopup="dialog" aria-controls="dialog-width-1">
幅 400px
</Button>
</DialogTrigger>
<DialogContent width={400} id="dialog-width-1">
<Description>幅 400px のダイアログ</Description>
</DialogContent>
</DialogWrapper>
</li>
<li>
<DialogWrapper>
<DialogTrigger>
<Button aria-haspopup="dialog" aria-controls="dialog-width-2">
幅 80%
</Button>
</DialogTrigger>
<DialogContent width="80%" id="dialog-width-2">
<Description>幅 80% のダイアログ</Description>
</DialogContent>
</DialogWrapper>
</li>
<li>
<DialogWrapper>
<DialogTrigger>
<Button aria-haspopup="dialog" aria-controls="dialog-position-1">
top-left
</Button>
</DialogTrigger>
<DialogContent top={50} left={200} id="dialog-position-1">
<Description>This Dialog is set to `top: 50px, left: 200px`.</Description>
</DialogContent>
</DialogWrapper>
</li>
<li>
<DialogWrapper>
<DialogTrigger>
<Button aria-haspopup="dialog" aria-controls="dialog-position-2">
bottom-right
</Button>
</DialogTrigger>
<DialogContent right={50} bottom={100} id="dialog-position-2">
<Description>This Dialog is set to `right: 50px, bottom: 100px`.</Description>
</DialogContent>
</DialogWrapper>
</li>
</TriggerList>
)
}
WidthAndPosition.parameters = {
docs: {
description: {
story: 'The position of Dialog can be changed.',
},
},
}
export const WithScroll: Story = () => {
return (
<ScrollWrapper>
<BorderedWrapper>
We can confirm that there is no change in the width of the wrapper for this text before and
after opening a dialog.
</BorderedWrapper>
<DialogWrapper>
<DialogTrigger>
<Button aria-haspopup="dialog" aria-controls="dialog-with-scroll-1">
Open Dialog
</Button>
</DialogTrigger>
<DialogContent id="dialog-with-scroll-1">
<ContentWrapper>
<div>
The content behind the opened dialog is not scrollable.
<br />
Of course the content on the opened dialog is scrollable.
</div>
<br />
</ContentWrapper>
</DialogContent>
</DialogWrapper>
</ScrollWrapper>
)
}
WithScroll.parameters = { docs: { disable: true } }
const ScrollWrapper = styled.div`
height: 200vh;
margin: 1rem;
padding: 1rem;
`
const BorderedWrapper = styled.div`
margin: 1rem 0;
padding: 1rem;
border: solid 1px gray;
`
const ContentWrapper = styled.div`
width: 50vh;
height: 50vh;
overflow: auto;
padding: 1rem;
& > div {
margin-bottom: 100vh;
}
`
export const Modeless_Dialog: Story = () => {
const [isOpen1, setIsOpen1] = useState(true)
const [isOpen2, setIsOpen2] = useState(false)
return (
<TriggerList style={{ height: '200vh' }}>
<li>
<Button
onClick={() => setIsOpen1(!isOpen1)}
aria-haspopup="dialog"
aria-controls="modeless-dialog-1"
>
中央表示
</Button>
<ModelessDialog
isOpen={isOpen1}
header={<ModelessHeading>モードレスダイアログ(中央表示)</ModelessHeading>}
footer={<ModelessFooter>フッタ</ModelessFooter>}
onClickClose={() => setIsOpen1(false)}
onPressEscape={() => setIsOpen1(false)}
width="50%"
height="50%"
id="modeless-dialog-1"
decorators={{
closeButtonIconAlt: (txt) => `close.(${txt})`,
dialogHandlerAriaLabel: (txt) => `label.(${txt})`,
dialogHandlerAriaValuetext: (txt, data) =>
`valuetext.(${txt}: ${data?.left}, ${data?.top})`,
}}
>
<ModelessContent>
<Stack gap="S">
<ModelessContentPart>
<LineUp gap="S">
<RadioButton name="modeless_dialog_center_radio_1">ラジオボタン1</RadioButton>
<RadioButton name="modeless_dialog_center_radio_2">ラジオボタン2</RadioButton>
</LineUp>
</ModelessContentPart>
<ModelessContentPart>
<DatePicker name="modeless_dialog_center_datepicker" />
</ModelessContentPart>
<Table>
<Head>
<Row>
<Cell>
<CheckBox name="modeless_dialog_center_checkbox" />
</Cell>
<Cell>テーブル見出し1</Cell>
<Cell>テーブル見出し2</Cell>
<Cell>テーブル見出し3</Cell>
</Row>
</Head>
<Body>
{Array.from(Array(20).keys()).map((i) => (
<Row key={i}>
<Cell>
<CheckBox name={`modeless_dialog_center_checkbox_${i}`} />
</Cell>
<Cell>データ1-{i}</Cell>
<Cell>データ2-{i}</Cell>
<Cell>データ3-{i}</Cell>
</Row>
))}
</Body>
</Table>
</Stack>
</ModelessContent>
</ModelessDialog>
</li>
<li>
<Button
onClick={() => setIsOpen2(!isOpen2)}
data-test="dialog-trigger"
aria-haspopup="dialog"
aria-controls="modeless-dialog-2"
>
座標指定
</Button>
<ModelessDialog
isOpen={isOpen2}
header={<ModelessHeading>座標指定表示</ModelessHeading>}
onClickClose={() => setIsOpen2(false)}
onPressEscape={() => setIsOpen2(false)}
bottom={100}
right="10%"
id="modeless-dialog-2"
data-test="dialog"
>
<div style={{ margin: '1rem' }}>
bottom: 100px
<br /> right: 10%
</div>
</ModelessDialog>
</li>
</TriggerList>
)
}
export const RegDialogOpenedDialog: Story = () => {
return (
<Dialog isOpen>
<Description>{dummyText}</Description>
</Dialog>
)
}
export const RegDialogOpenedDialogWidth: Story = () => {
return (
<Dialog isOpen width={500}>
<Description>{dummyText}</Description>
</Dialog>
)
}
export const RegDialogOpenedDialogPosition: Story = () => {
return (
<Dialog isOpen top={20} right={40} bottom={60} left={80}>
<Description>{dummyText}</Description>
</Dialog>
)
}
export const RegOpendMessage: Story = () => {
return (
<MessageDialog
isOpen={true}
title="MessageDialog"
description={<p>{dummyText}</p>}
onClickClose={action('clicked close')}
/>
)
}
RegOpendMessage.parameters = { docs: { disable: true } }
export const RegOpendAction: Story = () => {
return (
<ActionDialog
isOpen={true}
title="ActionDialog"
actionText="保存"
onClickAction={action('clicked action')}
onClickClose={action('clicked close')}
>
<Description>
{dummyText}
{dummyText}
{dummyText}
{dummyText}
{dummyText}
{dummyText}
{dummyText}
{dummyText}
</Description>
<Content>
<label>
<input name="reg_opend_action_checkbox" type="checkbox" />
Agree
</label>
</Content>
</ActionDialog>
)
}
RegOpendAction.parameters = { docs: { disable: true } }
export const RegOpenedModeless: Story = () => {
return (
<ModelessDialog
isOpen
header={<ModelessHeading>モードレスダイアログ</ModelessHeading>}
footer={<ModelessFooter>フッタ</ModelessFooter>}
height={500}
width={600}
>
<div style={{ margin: '1rem' }}>
{dummyText}
{dummyText}
{dummyText}
{dummyText}
{dummyText}
</div>
</ModelessDialog>
)
}
export const Body以外のPortalParent: Story = () => {
const [isOpen, setIsOpen] = useState<'deault' | 'actiion' | 'message' | 'modeless'>()
const onClickOpen = (type: 'deault' | 'actiion' | 'message' | 'modeless') => setIsOpen(type)
const onClickClose = () => setIsOpen(undefined)
const portalParentRef = useRef<HTMLDivElement>(null)
return (
<div ref={portalParentRef}>
<Stack align="flex-start">
<Button
onClick={() => onClickOpen('deault')}
aria-haspopup="dialog"
aria-controls="portal-default"
data-test="dialog-trigger"
>
Dialog を開く
</Button>
<Button
onClick={() => onClickOpen('actiion')}
aria-haspopup="dialog"
aria-controls="portal-action"
>
ActionDialog を開く
</Button>
<Button
onClick={() => onClickOpen('message')}
aria-haspopup="dialog"
aria-controls="portal-message"
>
MessageDialog を開く
</Button>
<Button
onClick={() => onClickOpen('modeless')}
aria-haspopup="dialog"
aria-controls="portal-modeless"
>
ModelessDialog を開く
</Button>
</Stack>
<Dialog
isOpen={isOpen === 'deault'}
onClickOverlay={onClickClose}
onPressEscape={onClickClose}
id="portal-default"
ariaLabel="Dialog"
data-test="dialog-content"
portalParent={portalParentRef}
>
<Title>Dialog</Title>
<Content>
<p>Dialog を近接要素に生成しています。</p>
</Content>
<Footer>
<Button onClick={onClickClose} data-test="dialog-closer">
閉じる
</Button>
</Footer>
</Dialog>
<ActionDialog
isOpen={isOpen === 'actiion'}
title="ActionDialog"
actionText="保存"
onClickAction={(closeDialog) => {
action('executed')()
closeDialog()
}}
onClickClose={onClickClose}
id="portal-action"
portalParent={portalParentRef}
>
<Content>
<p>ActionDialog を近接要素に生成しています</p>
</Content>
</ActionDialog>
<MessageDialog
isOpen={isOpen === 'message'}
title="MessageDialog"
description={<p>MessageDialog を近接要素に生成しています</p>}
onClickClose={onClickClose}
onClickOverlay={onClickClose}
id="portal-message"
portalParent={portalParentRef}
/>
<ModelessDialog
isOpen={isOpen === 'modeless'}
header={<ModelessHeading>ModelessDialog</ModelessHeading>}
onClickClose={onClickClose}
onPressEscape={onClickClose}
id="portal-modeless"
portalParent={portalParentRef}
>
<Content>
<p>ModelessDialog を近接要素に生成しています。</p>
</Content>
</ModelessDialog>
</div>
)
}
const Title = styled.h2`
padding: 16px 24px;
margin: 0;
font-size: ${({ theme }) => theme.fontSize.L};
font-weight: normal;
line-height: 1;
border-bottom: ${({ theme }) => theme.border.shorthand};
`
const Description = styled.p`
margin: 16px 24px;
line-height: 1.5;
`
const Content = styled.div`
margin: 16px 24px;
line-height: 1.5;
`
const RadioList = styled.ul`
margin: 16px 24px;
padding: 0;
list-style: none;
`
const Footer = styled.div`
display: flex;
justify-content: flex-end;
padding: 16px 24px;
border-top: ${({ theme }) => theme.border.shorthand};
& > *:not(:first-child) {
margin-left: 16px;
}
`
const TriggerList = styled.ul`
margin: 0;
padding: 0;
& > li {
display: inline-block;
margin: 8px;
}
`
const ModelessHeading = styled.h2`
font-size: 1em;
margin: 0;
font-weight: normal;
`
const ModelessContent = styled.div`
margin: 1rem 0;
`
const ModelessContentPart = styled.div`
margin: 0 1rem;
`
const ModelessFooter = styled.div`
padding: 1rem;
`
処理中

使いどころ

ダイアログは、ユーザーの能動的なアクションを起点に、主に以下の目的で使います。

  • 別の情報を操作する必要がある場合
  • 作業の確認(参照・追加・編集・削除)を促す場合
  • メッセージや警告をユーザーに表示する場合

情報設計をしっかり行ない、ダイアログで表示したい内容が上記に挙げた目的に合うものか、メインコンテンツの画面で表示するよりも適切な整理なのかをよく考えましょう。

ダイアログの乱用は避ける

ダイアログの乱用は避けましょう。
ダイアログの表示中は、メインコンテンツの情報・状態は保持されますが、ユーザーは(それまでの操作を中断し)ダイアログでの操作に集中させられることになります。 また、以下も推奨されません。

  • ダイアログの上にダイアログを表示すること
  • 複数のダイアログを同時に表示すること

種類

ActionDialog

SmartHR UIではActionDialogです。
ActionDialogは、ユーザーに選択の確認や操作を求める場合に使用するモーダルダイアログです。Primaryボタンを使い強調します。

MessageDialog

SmartHR UIではMessageDialogです。
MessageDialogは、ユーザーの操作を中断させて、ユーザーへの警告や確認を明示的にしたい場合に使うモーダルダイアログです。

ユーザーの操作を中断させる必要がない場合は、InformationPanelCompactInformationPanelの使用を検討してください。

ModelessDialog

SmartHR UIではModelessDialogです。
ModelessDialogは、ユーザーに任意のタイミングや順序で、情報の確認や操作をさせたい場合に使います。

レイアウト

基準サイズ

ダイアログの横幅サイズの基準値は以下のとおりです。
サイズに意図がない場合は、下記の値から想定に近い値を選択してください。

デスクトップ、タブレット(TABLET

サイズ補足説明
S480pxDialogの最小値として使います。
M640px
L768px
MAXcalc(100% - 32px)Dialogの最大値として使います。

スマートフォン(SP

スマートフォンは表示領域が狭いため、サイズの最大値/最小値は同じとします。

サイズ補足説明
標準calc(100% - 32px)Dialogの最大値/最小値として使います。

表示位置

意図的な場合をのぞき、ダイアログは画面の天地中央(上下左右中央)に表示します。

モードレスダイアログ(ModelessDialog)では、操作の起点を示すために、ダイアログを開くボタンやリンクテキストの付近から表示することがあります。

ライティング

タイトルのつけ方

ダイアログは、コンポーネントの用途がそれぞれ明確にあり、タイトルのつけ方もそれぞれ異なります。
冗長な表記をなるべく避けてください。複数行になる長いタイトルは見直しましょう。

ActionDialog

基本的に、Headingの見出しの書き方と同様に、画面上のオブジェクトや操作を簡潔に体言止めで書きます。

MessageDialog

メッセージ確認を促す簡潔な文書を入れます。ユーザーに注意喚起を促す、お知らせをしたいなど、目的に合わせたタイトルを設定してください。

ModelessDialog

[WIP]

デザインパターン

サブタイトルの使い方

原則として、使いません。タイトルだけではオブジェクトや機能を特定できない場合にのみ使ってください。

ダイアログ外領域への操作

ダイアログ表示中における、ダイアログ外の領域に対する操作の基準は以下のとおりです。

操作モーダルダイアログ
(ActionDialog, MessageDialog)
モードレスダイアログ
(MessageDialog)
ダイアログ外のUIに対するインタラクションダイアログ外にスクリム(幕)を表示しているため、受け付けません。受け付けます。
ダイアログ外の領域のクリックMessageDialogなど、ダイアログ内の情報を保持する必要がない用途では、ダイアログを閉じる動作を許容します。モードレスダイアログは閉じす、ダイアログ外のUIに対するインタラクションとして受け付けます。

ダイアログを閉じる操作

ダイアログを閉じるアクションは、ダイアログの種類に応じて異なります。

「キャンセル」ボタン

ユーザーが、ダイアログの操作を明示的に中断するためのアクションボタンです。

主にActionDialogで使用し、ActionDialogのセカンダリアクションとして配置します。

「キャンセル」ボタンの配置

ダイアログ内の情報の保持状態(フォームの入力状態など)を初期化して、ダイアログを閉じる動作をします。
ダイアログ内の情報を途中保存していたり、自動保存している場合は、再表示時にもっとも近い保存時点まで復帰します。

「閉じる」ボタン

ユーザーが、ダイアログを閉じるためのアクションボタンです。

主にMessageDialogおよびModelessDialogで使用し、ダイアログの(唯一の)アクションとして配置します。
同じダイアログ内で、閉じる役割が重複する「キャンセル」ボタンと同時に配置されることはありません。

「閉じる」ボタンの配置

ダイアログ内にフォームなどを含む場合は、閉じる際に入力状態を保存し、再表示時には復帰することを推奨します。

ESCキー

ダイアログ表示中のキーボード操作として機能し、「キャンセル」および「閉じる」と同じ動作をします。

Props

Dialog props

NameRequiredTypeDescription
top-numberダイアログを開いたときの初期 top 位置
bottom-numberダイアログを開いたときの初期 bottom 位置
left-numberダイアログを開いたときの初期 left 位置
right-numberダイアログを開いたときの初期 right 位置
width-string, numberダイアログの幅
firstFocusTarget-RefObject<HTMLElement>ダイアログを開いた時にフォーカスする対象
ariaLabel-stringダイアログの `aria-label`
ariaLabelledby-stringダイアログの `aria-labelledby`
isOpentruefalse, trueダイアログを開いているかどうか
onPressEscape-() => voidエスケープキーを押下した時に発火するコールバック関数
onClickOverlay-() => voidオーバーレイをクリックした時に発火するコールバック関数
portalParent-HTMLElement, RefObject<HTMLElement>DOM 上でダイアログの要素を追加する親要素
childrentruestring, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalダイアログの内容

ActionDialog props

NameRequiredTypeDescription
childrentruestring, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalダイアログの内容
titletruestring, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalダイアログのタイトル
className-stringコンポーネントに適用するクラス名
decorators-DecoratorsType<"closeButtonLabel">コンポーネント内の文言を変更するための関数を設定
titleTag-"span", "h1", "h2", "h3", "h4", "h5", "h6", "legend"ダイアログタイトルの HTML タグ
onClickClosetrue() => void
subtitle-string, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalダイアログのサブタイトル
actionTexttruestring, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalアクションボタンのラベル
actionTheme-"primary", "secondary", "danger"アクションボタンのスタイル
onClickActiontrue(closeDialog: () => void) => voidアクションボタンをクリックした時に発火するコールバック関数 @param closeDialog - ダイアログを閉じる関数
actionDisabled-false, trueアクションボタンを無効にするかどうか
closeDisabled-false, true閉じるボタンを無効にするかどうか
responseMessage-{ status: "error" | "success"; text: ReactNode; }, { status: "processing"; }
top-numberダイアログを開いたときの初期 top 位置
bottom-numberダイアログを開いたときの初期 bottom 位置
left-numberダイアログを開いたときの初期 left 位置
right-numberダイアログを開いたときの初期 right 位置
width-string, numberダイアログの幅
firstFocusTarget-RefObject<HTMLElement>ダイアログを開いた時にフォーカスする対象
ariaLabel-stringダイアログの `aria-label`
ariaLabelledby-stringダイアログの `aria-labelledby`
isOpentruefalse, trueダイアログを開いているかどうか
onPressEscape-() => voidエスケープキーを押下した時に発火するコールバック関数
onClickOverlay-() => voidオーバーレイをクリックした時に発火するコールバック関数
portalParent-HTMLElement, RefObject<HTMLElement>DOM 上でダイアログの要素を追加する親要素

MessageDialog props

NameRequiredTypeDescription
titletruestring, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalダイアログのタイトル
decorators-DecoratorsType<"closeButtonLabel">コンポーネント内の文言を変更するための関数を設定
titleTag-"span", "h1", "h2", "h3", "h4", "h5", "h6", "legend"ダイアログタイトルの HTML タグ
descriptiontruestring, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalダイアログの説明
onClickClosetrue() => void
subtitle-string, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalダイアログのサブタイトル
top-numberダイアログを開いたときの初期 top 位置
bottom-numberダイアログを開いたときの初期 bottom 位置
left-numberダイアログを開いたときの初期 left 位置
right-numberダイアログを開いたときの初期 right 位置
width-string, numberダイアログの幅
firstFocusTarget-RefObject<HTMLElement>ダイアログを開いた時にフォーカスする対象
ariaLabel-stringダイアログの `aria-label`
ariaLabelledby-stringダイアログの `aria-labelledby`
isOpentruefalse, trueダイアログを開いているかどうか
onPressEscape-() => voidエスケープキーを押下した時に発火するコールバック関数
onClickOverlay-() => voidオーバーレイをクリックした時に発火するコールバック関数
portalParent-HTMLElement, RefObject<HTMLElement>DOM 上でダイアログの要素を追加する親要素

ModelessDialog props

NameRequiredTypeDescription
headertruestring, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalダイアログのヘッダ部分の内容
childrentruestring, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalダイアログのコンテンツ部分の内容
footer-string, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalダイアログのフッタ部分の内容
isOpentruefalse, trueダイアログが開かれているかどうかの真偽値
onClickClose-(e: MouseEvent<HTMLButtonElement, MouseEvent>) => void閉じるボタンを押下したときのハンドラ
onPressEscape-() => voidダイアログが開いている状態で Escape キーを押下したときのハンドラ
width-string, numberダイアログの幅
height-string, numberダイアログの高さ
top-string, numberダイアログを開いたときの初期 top 位置
left-string, numberダイアログを開いたときの初期 left 位置
right-string, numberダイアログを開いたときの初期 right 位置
bottom-string, numberダイアログを開いたときの初期 bottom 位置
portalParent-HTMLElement, RefObject<HTMLElement>ポータルの container となる DOM 要素を追加する親要素
decorators-DecoratorsType<"closeButtonIconAlt"> & { dialogHandlerAriaLabel?: (txt: string) => string; dialogHandlerAriaValuetext?: (txt: string, data: DOMRect) => string; }コンポーネント内の文言を変更するための関数を設定