Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/spindle-ui/bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"maxSize": "1.1 kB"
},
{
"path": "./dist/!(Icon|Toast|DropdownMenu|Pagination|Modal|SnackBar|StackNotificationManager)/*.mjs",
"path": "./dist/!(Icon|Toast|DropdownMenu|Pagination|Modal|SnackBar|StackNotificationManager|SegmentedControl)/*.mjs",
"maxSize": "1.1 kB"
},
{
Expand All @@ -36,6 +36,10 @@
"path": "./dist/StackNotificationManager/*.mjs",
"maxSize": "2.8 kB"
},
{
"path": "./dist/SegmentedControl/*.mjs",
"maxSize": "1.3 kB"
},
{
"path": "./dist/!(InlineNotification|Modal|SnackBar)/!(index).css",
"maxSize": "1.5 kB"
Expand Down
114 changes: 114 additions & 0 deletions packages/spindle-ui/src/SegmentedControl/SegmentedControl.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
.spui-SegmentedControl {
align-items: center;
background-color: var(--color-surface-tertiary);
border-radius: 19px;
box-sizing: border-box;
display: grid;
gap: 4px;
padding: 4px;
Comment thread
itsminadesu marked this conversation as resolved.
position: relative;
width: 100%;
}

.spui-SegmentedControl--large {
border-radius: 24px;
}

.spui-SegmentedControl--divider {
gap: 9px;
}

.spui-SegmentedControl-button {
Comment thread
itsminadesu marked this conversation as resolved.
background-color: transparent;
border: none;
border-radius: 15px;
box-sizing: border-box;
color: var(--color-text-medium-emphasis);
cursor: pointer;
height: 100%;
line-height: 1.3;
min-width: 0;
overflow-wrap: break-word;
padding: 4px;
position: relative;
transition: font-weight 0.35s ease, color 0.35s ease,
Comment thread
itsminadesu marked this conversation as resolved.
background-color 0.15s ease;
}

.spui-SegmentedControl-button[aria-checked='true'] {
color: var(--color-text-high-emphasis);
cursor: inherit;
font-weight: bold;
}

.spui-SegmentedControl--divider .spui-SegmentedControl-button::before {
background-color: var(--color-border-low-emphasis);
bottom: 50%;
content: '';
height: 20px;
left: -5px;
position: absolute;
top: 50%;
transform: translateY(-50%);
transition: background-color 0.35s ease;
width: 1px;
}

.spui-SegmentedControl--divider
.spui-SegmentedControl-button:first-of-type:before {
content: none;
}

.spui-SegmentedControl-button[aria-checked='true']::before,
.spui-SegmentedControl-button[aria-checked='true']
+ .spui-SegmentedControl-button[aria-checked='false']::before {
background-color: transparent;
Comment thread
tokimari marked this conversation as resolved.
}

.spui-SegmentedControl-button[aria-checked='false']:hover {
background-color: var(--color-surface-tertiary);
}

.spui-SegmentedControl-button:focus {
outline: 2px solid var(--color-focus-clarity);
outline-offset: 1px;
}

.spui-SegmentedControl-button[aria-checked='false']:focus {
background-color: var(--color-surface-secondary);
}

.spui-SegmentedControl-button:not(:focus-visible) {
outline: none;
}

.spui-SegmentedControl--medium .spui-SegmentedControl-button {
font-size: 0.8125em;
min-height: 30px;
}

.spui-SegmentedControl--large .spui-SegmentedControl-button {
border-radius: 20px;
font-size: 1em;
min-height: 40px;
}

.spui-SegmentedControl-indicator {
background-color: var(--color-surface-primary);
bottom: 4px;
/* TODO: replace color with color palette */
box-shadow: 0px 3.25px 7.75px rgba(8, 18, 26, 0.06);
content: '';
height: auto;
left: 0;
position: absolute;
top: 4px;
transition: transform 0.35s ease;
}

@media (prefers-reduced-motion: reduce) {
.spui-SegmentedControl-button,
.spui-SegmentedControl-indicator--ready {
transition: 0.1ms;
}
}
246 changes: 246 additions & 0 deletions packages/spindle-ui/src/SegmentedControl/SegmentedControl.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
import { Description, Meta, Story, Source } from '@storybook/addon-docs/blocks';
Comment thread
herablog marked this conversation as resolved.
import { SegmentedControl, SegmentedControlExample } from './SegmentedControl';
import { actions } from '@storybook/addon-actions';

# SegmentedControl

<Description>
SegmentedControlコンポーネントは、ページ内で機能やモードを切り替える際に利用します。
</Description>

<Description>
基本的にラジオボタンと同様の機能をもっており、単一の項目のみを選択できます。フォーム内での利用を想定しておらず、1つの機能やモードに対して複数の状態を切り替えることが特徴です。
</Description>

<Meta title="SegmentedControl" component={SegmentedControl} />

<Source
language="javascript"
code={`import { SegmentedControl } from '@openameba/spindle-ui'`}
/>

<Source
language="css"
code={`@import './node_modules/@openameba/spindle-ui/SegmentedControl/SegmentedControl.css'`}
/>

<Source
language="html"
code={`<link rel="stylesheet" href="https://unpkg.com/@openameba/spindle-ui/SegmentedControl/SegmentedControl.css">`}
/>

## 指定できるプロパティ

<Description>
- `options`(必須):
表示する項目について配列で指定してください。`id`は項目を識別するための値であり、配列内で一意である必要があります。`label`は画面に表示される値です。推奨する最大文字数は10文字です。
</Description>
<Description>
- `selectedId`(必須): optionsプロパティに指定した配列に含まれる id
のうち初期選択状態とする項目の id を指定します。selectedId
に一致する項目がない場合、先頭の項目が選択された状態になります。
</Description>
<Description>
- `onClick`(任意):
項目がクリックされたときに追加で行いたい処理がある場合は指定してください。第2引数の`id`には選択された項目のidが渡されます。
</Description>
<Description>
- `size`(任意):
SegmentedControlの大きさを指定します。デフォルトは`medium`で、その他にも`large`を指定できます。
</Description>

## Size

### Large

<Preview withSource="open">
<Story name="Large">
<SegmentedControl
selectedId={'small'}
options={[
{ id: 'small', label: '小' },
{ id: 'medium', label: '中' },
{ id: 'large', label: '大' },
]}
size="large"
{...actions('onClick')}
/>
</Story>
</Preview>

<Source
code={`
const options = [
{ id: 'small', label: '小' },
{ id: 'medium', label: '中' },
{ id: 'large', label: '大' },
];
const handleClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>, id: string) => {
console.log(id);
},
[],
);
return (
<SegmentedControl
selectedId="small"
options={options}
onClick={handleClick}
size="large"
/>
);
`}
/>

### Medium

<Preview withSource="open">
<Story name="Medium">
<SegmentedControl
selectedId={'small'}
options={[
{ id: 'small', label: '小' },
{ id: 'medium', label: '中' },
{ id: 'large', label: '大' },
]}
size="medium"
{...actions('onClick')}
/>
</Story>
</Preview>

<Source
code={`
const options = [
{ id: 'small', label: '小' },
{ id: 'medium', label: '中' },
{ id: 'large', label: '大' },
];
const handleClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>, id: string) => {
console.log(id);
},
[],
);
return (
<SegmentedControl
selectedId="small"
options={options}
onClick={handleClick}
size="medium"
/>
);
`}
/>

## Total

<Description>
optionsプロパティに指定された配列の項目数に依存します。なお、項目数が過剰に多い場合は、SegmentedControlコンポーネントの使用を非推奨とします。
</Description>

### Total 1

<Description>
項目数が1のケースも許容していますが、ユーザは何も操作できないことに注意してください。
</Description>

<Preview withSource="open">
<Story name="Total 1">
<SegmentedControl
selectedId={'all'}
options={[{ id: 'all', label: 'すべて' }]}
{...actions('onClick')}
/>
</Story>
</Preview>

### Total 2

<Description>
利用用途に応じて、Form/ToggleSwitch の利用も検討してください。
</Description>

<Preview withSource="open">
<Story name="Total 2">
<SegmentedControl
selectedId={'article'}
options={[
{ id: 'article', label: '記事ごとに見る' },
{ id: 'item', label: 'アイテムごとに見る' },
]}
{...actions('onClick')}
/>
</Story>
</Preview>

### Total 3

<Preview withSource="open">
<Story name="Total 3">
<SegmentedControl
selectedId={'follow'}
options={[
{ id: 'all', label: 'すべて' },
{ id: 'follow', label: 'フォロー' },
{ id: 'follower', label: 'フォロワー' },
]}
{...actions('onClick')}
/>
</Story>
</Preview>

### Total 4

<Preview withSource="open">
<Story name="Total 4">
<SegmentedControl
selectedId={'access'}
options={[
{ id: 'all', label: 'すべて' },
{ id: 'access', label: 'アクセス数' },
{ id: 'visitor', label: '訪問者数' },
{ id: 'good', label: 'いいね数' },
]}
{...actions('onClick')}
/>
</Story>
</Preview>

### Total 5

<Preview withSource="open">
<Story name="Total 5">
<SegmentedControl
selectedId={'6months'}
options={[
{ id: '7days', label: '7日間' },
{ id: '30days', label: '30日間' },
{ id: '3months', label: '3ヶ月間' },
{ id: '6months', label: '6ヶ月間' },
{ id: '12months', label: '1年間' },
]}
{...actions('onClick')}
/>
</Story>
</Preview>
Comment thread
itsminadesu marked this conversation as resolved.

## Wrapped Text

<Description>文字数や画面幅によっては改行されることがあります。</Description>

<Preview withSource="open">
<Story name="Wrapped Text">
<SegmentedControl
selectedId={'longText'}
options={[
{ id: 'all', label: 'すべて' },
{ id: 'access', label: 'アクセス数' },
{ id: 'longText', label: '文字数が多く折り返されるテキスト' },
Comment thread
itsminadesu marked this conversation as resolved.
{ id: 'visitor', label: '訪問者数' },
{ id: 'good', label: 'いいね数' },
]}
{...actions('onClick')}
/>
</Story>
</Preview>
Loading