-
Notifications
You must be signed in to change notification settings - Fork 21
feat(spindle-ui): create SegmentedControl #731
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
packages/spindle-ui/src/SegmentedControl/SegmentedControl.css
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| position: relative; | ||
| width: 100%; | ||
| } | ||
|
|
||
| .spui-SegmentedControl--large { | ||
| border-radius: 24px; | ||
| } | ||
|
|
||
| .spui-SegmentedControl--divider { | ||
| gap: 9px; | ||
| } | ||
|
|
||
| .spui-SegmentedControl-button { | ||
|
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, | ||
|
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; | ||
|
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
246
packages/spindle-ui/src/SegmentedControl/SegmentedControl.stories.mdx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,246 @@ | ||
| import { Description, Meta, Story, Source } from '@storybook/addon-docs/blocks'; | ||
|
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> | ||
|
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: '文字数が多く折り返されるテキスト' }, | ||
|
itsminadesu marked this conversation as resolved.
|
||
| { id: 'visitor', label: '訪問者数' }, | ||
| { id: 'good', label: 'いいね数' }, | ||
| ]} | ||
| {...actions('onClick')} | ||
| /> | ||
| </Story> | ||
| </Preview> | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.