Button

Buttonコンポーネントは、ユーザーが情報の保存や検索などの操作をできるようにします。

処理中
ドラッグして幅を変更
ドラッグして高さを変更
import { action } from '@storybook/addon-actions'
import { Story } from '@storybook/react'
import React, { useState } from 'react'
import styled, { css } from 'styled-components'
import { FaPlusCircleIcon, FaPlusIcon, FaPlusSquareIcon } from '../Icon'
import { Cluster, Stack } from '../Layout'
import { AnchorButton, Button } from '.'
export default {
title: 'Buttons(ボタン)/Button',
component: Button,
subcomponents: {
AnchorButton,
},
}
type ButtonProps = React.ComponentProps<typeof Button>
type AnchorButtonProps = React.ComponentProps<typeof AnchorButton>
export const _Button: Story = () => {
return (
<List>
<dt>Default</dt>
<dd>
<Stack>
<Cluster>
<Button variant="primary" onClick={action('clicked')}>
ボタン
</Button>
<Button variant="primary" prefix={<FaPlusIcon />} onClick={action('clicked')}>
ボタン
</Button>
<Button variant="primary" suffix={<FaPlusSquareIcon />} onClick={action('clicked')}>
ボタン
</Button>
<Button variant="primary" square onClick={action('clicked')}>
<FaPlusCircleIcon alt="プラスボタン" />
</Button>
</Cluster>
<Cluster>
<Button variant="primary" disabled onClick={action('clicked')}>
ボタン
</Button>
<Button variant="primary" disabled prefix={<FaPlusIcon />} onClick={action('clicked')}>
ボタン
</Button>
<Button
variant="primary"
disabled
suffix={<FaPlusSquareIcon />}
onClick={action('clicked')}
>
ボタン
</Button>
<Button variant="primary" disabled square onClick={action('clicked')}>
<FaPlusCircleIcon alt="プラスボタン" />
</Button>
</Cluster>
<Cluster>
<Button variant="primary" loading onClick={action('clicked')}>
ボタン
</Button>
<Button variant="primary" loading prefix={<FaPlusIcon />} onClick={action('clicked')}>
ボタン
</Button>
<Button
variant="primary"
loading
suffix={<FaPlusSquareIcon />}
onClick={action('clicked')}
>
ボタン
</Button>
<Button variant="primary" loading square onClick={action('clicked')}>
<FaPlusCircleIcon alt="プラスボタン" />
</Button>
</Cluster>
</Stack>
</dd>
<dt>Variants</dt>
<dd>
<Stack>
<Cluster>
<Button variant="primary" onClick={action('clicked')}>
ボタン
</Button>
<Button variant="primary" disabled onClick={action('clicked')}>
ボタン
</Button>
</Cluster>
<Cluster>
<Button variant="secondary" onClick={action('clicked')}>
ボタン
</Button>
<Button variant="secondary" disabled onClick={action('clicked')}>
ボタン
</Button>
</Cluster>
<Cluster>
<Button variant="danger" onClick={action('clicked')}>
ボタン
</Button>
<Button variant="danger" disabled onClick={action('clicked')}>
ボタン
</Button>
</Cluster>
<DarkBackground>
<Cluster>
<Button variant="skeleton" onClick={action('clicked')}>
ボタン
</Button>
<Button variant="skeleton" disabled onClick={action('clicked')}>
ボタン
</Button>
</Cluster>
</DarkBackground>
<Cluster>
<Button variant="text" onClick={action('clicked')}>
ボタン
</Button>
<Button variant="text" disabled onClick={action('clicked')}>
ボタン
</Button>
</Cluster>
</Stack>
</dd>
<dt>Small</dt>
<dd>
<Stack>
<Cluster>
<Button variant="primary" size="s" onClick={action('clicked')}>
ボタン
</Button>
</Cluster>
<Cluster>
<Button variant="primary" disabled size="s" onClick={action('clicked')}>
ボタン
</Button>
</Cluster>
<Cluster>
<Button variant="primary" disabled size="s" onClick={action('clicked')} loading>
ボタン
</Button>
<Button variant="primary" square disabled size="s" onClick={action('clicked')} loading>
ボタン
</Button>
</Cluster>
</Stack>
</dd>
<dt>Wide</dt>
<dd>
<Stack>
<Button variant="primary" wide onClick={action('clicked')}>
ボタン
</Button>
<Button variant="primary" disabled wide onClick={action('clicked')}>
ボタン
</Button>
</Stack>
</dd>
<dt>Extending Style</dt>
<dd>
<ExtendingButton variant="primary" onClick={action('clicked')}>
width: 300px
</ExtendingButton>
</dd>
</List>
)
}
_Button.parameters = {
controls: { hideNoControlsWarning: true },
}
export const _ButtonControl: Story = (args: ButtonProps) => {
return (
<Wrapper>
<Button {...args} onClick={action('clicked')}>
{args.children}
</Button>
</Wrapper>
)
}
_ButtonControl.argTypes = {
children: { control: 'text', defaultValue: 'ボタン' },
prefix: { control: 'text' },
suffix: { control: 'text' },
}
export const _ButtonAnchor: Story = () => {
return (
<List>
<dt>Default</dt>
<dd>
<Stack>
<Cluster>
<AnchorButton href="#" variant="primary" onClick={action('clicked')}>
ボタン
</AnchorButton>
<AnchorButton
href="#"
variant="primary"
prefix={<FaPlusIcon />}
onClick={action('clicked')}
>
ボタン
</AnchorButton>
<AnchorButton
href="#"
variant="primary"
suffix={<FaPlusSquareIcon />}
onClick={action('clicked')}
>
ボタン
</AnchorButton>
<AnchorButton href="#" variant="primary" square onClick={action('clicked')}>
<FaPlusCircleIcon alt="プラスボタン" />
</AnchorButton>
</Cluster>
<Cluster>
<AnchorButton variant="primary" onClick={action('clicked')}>
ボタン
</AnchorButton>
<AnchorButton variant="primary" prefix={<FaPlusIcon />} onClick={action('clicked')}>
ボタン
</AnchorButton>
<AnchorButton
variant="primary"
suffix={<FaPlusSquareIcon />}
onClick={action('clicked')}
>
ボタン
</AnchorButton>
<AnchorButton variant="primary" square onClick={action('clicked')}>
<FaPlusCircleIcon alt="プラスボタン" />
</AnchorButton>
</Cluster>
</Stack>
</dd>
<dt>Variants</dt>
<dd>
<Stack>
<Cluster>
<AnchorButton href="#" variant="primary" onClick={action('clicked')}>
ボタン
</AnchorButton>
<AnchorButton variant="primary" onClick={action('clicked')}>
ボタン
</AnchorButton>
</Cluster>
<Cluster>
<AnchorButton href="#" variant="secondary" onClick={action('clicked')}>
ボタン
</AnchorButton>
<AnchorButton variant="secondary" onClick={action('clicked')}>
ボタン
</AnchorButton>
</Cluster>
<Cluster>
<AnchorButton href="#" variant="danger" onClick={action('clicked')}>
ボタン
</AnchorButton>
<AnchorButton variant="danger" onClick={action('clicked')}>
ボタン
</AnchorButton>
</Cluster>
<DarkBackground>
<Cluster>
<AnchorButton href="#" variant="skeleton" onClick={action('clicked')}>
ボタン
</AnchorButton>
<AnchorButton variant="skeleton" onClick={action('clicked')}>
ボタン
</AnchorButton>
</Cluster>
</DarkBackground>
<Cluster>
<AnchorButton href="#" variant="text" onClick={action('clicked')}>
ボタン
</AnchorButton>
<AnchorButton variant="text" onClick={action('clicked')}>
ボタン
</AnchorButton>
</Cluster>
</Stack>
</dd>
<dt>Small</dt>
<dd>
<Stack>
<Cluster>
<AnchorButton href="#" variant="primary" size="s" onClick={action('clicked')}>
ボタン
</AnchorButton>
</Cluster>
<Cluster>
<AnchorButton variant="primary" size="s" onClick={action('clicked')}>
ボタン
</AnchorButton>
</Cluster>
</Stack>
</dd>
<dt>Wide</dt>
<dd>
<Stack>
<AnchorButton href="#" variant="primary" wide onClick={action('clicked')}>
ボタン
</AnchorButton>
<AnchorButton variant="primary" wide onClick={action('clicked')}>
ボタン
</AnchorButton>
</Stack>
</dd>
<dt>Extending Style</dt>
<dd>
<ExtendingAnchorButton href="#" variant="primary" onClick={action('clicked')}>
width: 300px
</ExtendingAnchorButton>
</dd>
</List>
)
}
_ButtonAnchor.parameters = {
controls: { hideNoControlsWarning: true },
}
export const _ButtonAnchorControl: Story = (args: AnchorButtonProps) => {
return (
<Wrapper>
<AnchorButton {...args} href="#" onClick={action('clicked')}>
{args.children}
</AnchorButton>
</Wrapper>
)
}
_ButtonAnchorControl.argTypes = {
children: { control: 'text', defaultValue: 'ボタン' },
prefix: { control: 'text' },
suffix: { control: 'text' },
}
export const WithLoading: Story = (args: ButtonProps) => {
const [loading, setLoading] = useState(false)
const handleClick = () => {
setLoading(true)
setTimeout(() => setLoading(false), 1000)
}
return (
<Button {...args} prefix={<FaPlusCircleIcon />} loading={loading} onClick={handleClick}>
読み込み
</Button>
)
}
const Wrapper = styled.div`
padding: 24px;
`
const List = styled.dl`
margin: 0;
padding: 1rem;
dt {
margin: 0 0 1rem;
}
dd {
margin: 0 0 1rem;
}
`
const DarkBackground = styled.div`
padding: 1rem;
background-color: #5c5c5c;
color: #fff;
`
const extendingStyle = css`
width: 300px;
`
const ExtendingButton = styled(Button)`
${extendingStyle}
`
const ExtendingAnchorButton = styled(AnchorButton)`
${extendingStyle}
`
処理中

種類

ボタンの優先度や役割に合わせた4種類のコンポーネントを総称して、Buttonコンポーネントとして定義しています。

Primary

画面の主要なアクションを促すボタンです。

1つの画面に複数のPrimaryボタンを使わないでください。複数のPrimaryボタンがあるとユーザーは何をすればいいかわからなくなります。

Secondary

画面でのアクションを促す、Primaryボタンに対する副次的なボタンとして使います。
SmartHR UI では特に種類の指定をしない場合はSecondaryになります。

画面内にボタンが多すぎる場合、ユーザーは次に何をすればいいのか戸惑うかもしれません。その場合はボタンを減らしたり、画面を簡略化したり、複数の画面に分けることを検討しましょう。

Danger

破壊的なアクション(データの永久消去など)の実行前に、ユーザーに一歩立ち止まって考えてもらいたいときに使います。

  • Dangerボタンが頻出すると効果がありません。
  • ユーザーにこのアクションを実行させる場合は、必ず確認を求めるステップを追加してください。(例:削除ダイアログ
  • すべてのユーザーが色を見たり、理解できるわけではないため、警告を促す赤色(DANGER)に頼らないでください。ボタン配置のコンテキストやラベルテキストは重要です。

Text

Secondaryボタンよりもさらに優先度が低いアクションのボタンとして使います。

また、ドロップダウンメニューとして使用したり、アイコンボタンとして使用するなど、ボタンではあるもののボタンの装飾を必要としない場合に使います。

AnchorButton

リンク(a要素)をボタンのように見せるためのコンポーネントですが、どうしようもないときだけ使います。 アクションボタンとして表現したい場合は、素直にButtonを使いましょう。

レイアウト

サイズやアイコンの有無で、コンポーネント内のレイアウトにバリエーションがあります。
サイズ小かつアイコン付き(左)」のように組み合わせることもできます。

ボタンサイズ

2種類の大きさのボタンを使用できます。 SmartHR UIでは、サイズ(sizeprops)で指定できます。

種類表示例説明
通常
ボタン
通常のサイズです。
ボタン
レイアウトの都合上、どうしようもない場合に使うサイズです。

アイコンの有無

アイコン付きボタン

ラベルテキストの左右にアイコンを配置できます。
SmartHR UIでは、プレフィックス(prefixprops)およびサフィックス(suffixprops)にIconコンポーネントを指定することで表現できます。

ただし、左右のアイコンを同時に指定しないでください。アイコン付き(右)を優先し、アイコンを右に表示する場合は左のアイコンは設定しないようにしましょう。

種類表示例アイコンの役割
アイコン付き(左)
項目を追加
ダウンロード
ボタンのアクション(機能)を表すために使います。
同じアクションのボタンには、同じアイコンを採用してください。
アイコン付き(右)
一括操作
絞り込み
ドロップダウンのように、ボタン押下後に表示するUIを想起させるために使います。
(例:ドロップダウンメニューボタンDropdownFilterDropdown

アイコンボタン

アイコンのみのボタンです。ラベルテキストを表示するレイアウト上の余裕がない場合などに使います。
ラベルテキストがないため、アイコンには必ず代替テキストを含めてください。

状態

マウスオーバー

マウスポインタでボタンに触れている状態(hover状態)のスタイルです。 マウスオーバーを認知できるように、標準時の背景色にOVERLAYを重ねて表現します。

無効(disabled)

ボタンの操作ができない状態を表現したスタイルです。
無効状態のボタンはコントラストが低くユーザーを混乱させるため、できるだけ使わないようにしてください。

ユーザーはなぜ無効になっているのかわからないことがあります。「権限による表示制御」のデザインパターンを参考にして、無効状態の理由を表示したり、無効ではなくボタン自体を非表示にすることを検討してください。

ライティング

ラベルの書き方

ラベルは、そのボタンが担うアクションを体言止めで書きます。

  • ユーザーが入力したフォームを「送信」
  • タスクやオブジェクトを「追加」
  • 次のプロセスに進む「次へ」

「権限を追加」「申請を取り消し」など、対象のオブジェクト名などを明記しないとアクションが伝わりにくい場合は「を」で接続します。

ラベルの判断基準は以下のとおりです。

ボタンやテキストリンクなど、ユーザーのアクションを促すテキストですか?そうでない場合は{オブジェクト}の{操作名}と書きます。クリックしたら{操作名}の処理が実行されますか?(最終アクションですか?)そうでない場合は{オブジェクト}の{操作名}と書きます。テキストに対象オブジェクト名を明記しないと伝わりにくい場合は{オブジェクト名}を{操作名}と書きます。そうでない場合は{操作名}と書きます。

その他

type="button" について

SmartHR UIのButtonには意図しないsubmitを防ぐために、type="button"がついています。 そのため、form submitを使いたい場合は、必ずtype="submit"を明記してください。

Props

NameRequiredTypeDescription
size-"default", "s"ボタンの大きさ
children-string, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalボタン内に表示する内容
className-stringコンポーネントに適用するクラス名
disabledDetail-{ icon?: FunctionComponent<{}>; message: ReactNode; }無効な理由
prefix-string, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalボタン内の先頭に表示する内容。 通常は、アイコンを表示するために用いる。
suffix-string, number, false, true, ReactElement<any, string | JSXElementConstructor<any>>, ReactFragment, ReactPortalボタン内の末尾に表示する内容。 通常は、アイコンを表示するために用いる。
square-false, true`true` のとき、ボタンを正方形にする。
wide-false, true`true` のとき、ボタンの `width` を 100% にする。
variant-"text", "primary", "secondary", "danger", "skeleton"ボタンのスタイルの種類
loading-false, true処理が走ってるかどうか

参考文献