From 8f6f948babb65cf6c00cb73dc8fe5c27924b840b Mon Sep 17 00:00:00 2001 From: "Yang Wooseong (Andrew)" Date: Thu, 20 Jun 2024 12:58:06 +0900 Subject: [PATCH] Add `ToggleButton` and `ToggleButtonGroup` component (#2276) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Self Checklist - [x] I wrote a PR title in **English** and added an appropriate **label** to the PR. - [x] I wrote the commit message in **English** and to follow [**the Conventional Commits specification**](https://www.conventionalcommits.org/en/v1.0.0/). - [x] I [added the **changeset**](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md) about the changes that needed to be released. (or didn't have to) - [x] I wrote or updated **documentation** related to the changes. (or didn't have to) - [x] I wrote or updated **tests** related to the changes. (or didn't have to) - [x] I tested the changes in various browsers. (or didn't have to) - Windows: Chrome, Edge, (Optional) Firefox - macOS: Chrome, Edge, Safari, (Optional) Firefox ## Related Issue - Fixes #2271 - Fixes #2277 ## Summary - ToggleButton와 ToggleButtonGroup컴포넌트를 구현합니다. radix-ui/primitive를 사용해서 구현했고, uncontrolled/controlled모두 지원하도록 했습니다. ## Details - ~~uncontrolled로도 사용할 수 있게 하기 위해 defaultSelected를 옵셔널로 받을 수 있게 했습니다. 다만 텍스트의 볼드 유무가 selected에 따라 바뀌다 보니, defaultSelected를 라딕스 컴포넌트에 바로 넘기지 못하고 isSelected 상태를 내부에 한번 더 선언하고 defaultSelected 와 동기화 했습니다.~~ -> ToggleButtonGroup을 구현하다보니 selected, defaultSelected 속성이 아니라 value로 선택되었는지를 판단해야해서 이렇게 하면 안될 것 같네요. Text의 bold속성을 주지 않고 css 에서 직접 font-weight을 주는 것으로 변경했습니다. - toggle 할 때 bold가 on/off되면서 너비가 약간 달라져서 레이아웃이 살짝 shift되는 현상이 있었습니다. 안보이는 상태로 렌더링되는 bold상태의 텍스트를 기준으로 너비를 정해서 이런 현상을 막았습니다. - shape 속성은 본래 ToggleButton에만 있는 속성이지만 개발의 편의성을 위해 ToggleButtonGroup에 넣어줘서 한번만 써도 되도록 했습니다. Form에서도 비슷한 접근을 하고 있어서 일관성을 해치지 않을 것 같습니다. ### Breaking change? (Yes/No) - No ## References - https://www.radix-ui.com/primitives/docs/components/toggle-group#api-reference - https://www.radix-ui.com/primitives/docs/components/toggle - [디자인 (internal)](https://www.figma.com/design/fPXP9zfjZU9NyARnhTWL6o/Input?node-id=426-1255&t=XUHjD3u6gWmZOgqj-0) - [스펙 (internal)](https://www.notion.so/channelio/Toggle-Button-1efbe7a0b118456dac4b6d2bdd36b633) --- .changeset/forty-turkeys-joke.md | 5 + .changeset/honest-fans-tease.md | 5 + packages/bezier-react/package.json | 2 + .../AlphaButton/AlphaButton.stories.tsx | 2 +- .../AlphaFloatingButton.stories.tsx | 2 +- .../AlphaFloatingIconButton.stories.tsx | 2 +- .../AlphaIconButton.stories.tsx | 2 +- .../AlphaToggleButton.stories.tsx | 27 ++++ .../ToggleButton.module.scss | 127 ++++++++++++++++++ .../AlphaToggleButton/ToggleButton.tsx | 117 ++++++++++++++++ .../AlphaToggleButton/ToggleButton.types.ts | 76 +++++++++++ .../AlphaToggleButton/ToggleButtonContext.ts | 9 ++ .../src/components/AlphaToggleButton/index.ts | 2 + .../AlphaToggleButtonGroup.stories.tsx | 73 ++++++++++ .../ToggleButtonGroup.module.scss | 12 ++ .../ToggleButtonGroup.tsx | 92 +++++++++++++ .../ToggleButtonGroup.types.ts | 66 +++++++++ .../AlphaToggleButtonGroup/index.ts | 5 + .../src/components/Banner/Banner.stories.tsx | 2 +- .../src/components/Button/Button.stories.tsx | 2 +- .../ButtonGroup/ButtonGroup.stories.tsx | 2 +- .../src/components/Icon/Icon.stories.tsx | 2 +- .../ProgressBar/ProgressBar.stories.tsx | 2 +- packages/bezier-react/src/index.ts | 2 + yarn.lock | 6 +- 25 files changed, 633 insertions(+), 11 deletions(-) create mode 100644 .changeset/forty-turkeys-joke.md create mode 100644 .changeset/honest-fans-tease.md create mode 100644 packages/bezier-react/src/components/AlphaToggleButton/AlphaToggleButton.stories.tsx create mode 100644 packages/bezier-react/src/components/AlphaToggleButton/ToggleButton.module.scss create mode 100644 packages/bezier-react/src/components/AlphaToggleButton/ToggleButton.tsx create mode 100644 packages/bezier-react/src/components/AlphaToggleButton/ToggleButton.types.ts create mode 100644 packages/bezier-react/src/components/AlphaToggleButton/ToggleButtonContext.ts create mode 100644 packages/bezier-react/src/components/AlphaToggleButton/index.ts create mode 100644 packages/bezier-react/src/components/AlphaToggleButtonGroup/AlphaToggleButtonGroup.stories.tsx create mode 100644 packages/bezier-react/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.module.scss create mode 100644 packages/bezier-react/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.tsx create mode 100644 packages/bezier-react/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.types.ts create mode 100644 packages/bezier-react/src/components/AlphaToggleButtonGroup/index.ts diff --git a/.changeset/forty-turkeys-joke.md b/.changeset/forty-turkeys-joke.md new file mode 100644 index 0000000000..9d25fbc945 --- /dev/null +++ b/.changeset/forty-turkeys-joke.md @@ -0,0 +1,5 @@ +--- +'@channel.io/bezier-react': patch +--- + +Add `AlphaToggleButton` component. diff --git a/.changeset/honest-fans-tease.md b/.changeset/honest-fans-tease.md new file mode 100644 index 0000000000..57f82ddb7c --- /dev/null +++ b/.changeset/honest-fans-tease.md @@ -0,0 +1,5 @@ +--- +'@channel.io/bezier-react': patch +--- + +Add `AlphaToggleButtonGroup` component. diff --git a/packages/bezier-react/package.json b/packages/bezier-react/package.json index c5a3eb2d65..19a82f20b8 100644 --- a/packages/bezier-react/package.json +++ b/packages/bezier-react/package.json @@ -136,6 +136,8 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-toggle": "^1.0.3", + "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-toolbar": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-visually-hidden": "^1.0.3", diff --git a/packages/bezier-react/src/components/AlphaButton/AlphaButton.stories.tsx b/packages/bezier-react/src/components/AlphaButton/AlphaButton.stories.tsx index 35ecb49bdb..31fc9cc27b 100644 --- a/packages/bezier-react/src/components/AlphaButton/AlphaButton.stories.tsx +++ b/packages/bezier-react/src/components/AlphaButton/AlphaButton.stories.tsx @@ -14,7 +14,7 @@ const meta: Meta = { } export default meta -export const Playground: StoryObj = { +export const Primary: StoryObj = { args: { text: 'Invite', disabled: false, diff --git a/packages/bezier-react/src/components/AlphaFloatingButton/AlphaFloatingButton.stories.tsx b/packages/bezier-react/src/components/AlphaFloatingButton/AlphaFloatingButton.stories.tsx index bcffdcd5dd..4e391c729b 100644 --- a/packages/bezier-react/src/components/AlphaFloatingButton/AlphaFloatingButton.stories.tsx +++ b/packages/bezier-react/src/components/AlphaFloatingButton/AlphaFloatingButton.stories.tsx @@ -14,7 +14,7 @@ const meta: Meta = { } export default meta -export const Playground: StoryObj = { +export const Primary: StoryObj = { args: { text: 'Invite', disabled: false, diff --git a/packages/bezier-react/src/components/AlphaFloatingIconButton/AlphaFloatingIconButton.stories.tsx b/packages/bezier-react/src/components/AlphaFloatingIconButton/AlphaFloatingIconButton.stories.tsx index f4a167370a..2c4520a37b 100644 --- a/packages/bezier-react/src/components/AlphaFloatingIconButton/AlphaFloatingIconButton.stories.tsx +++ b/packages/bezier-react/src/components/AlphaFloatingIconButton/AlphaFloatingIconButton.stories.tsx @@ -14,7 +14,7 @@ const meta: Meta = { } export default meta -export const Playground: StoryObj = { +export const Primary: StoryObj = { args: { disabled: false, active: false, diff --git a/packages/bezier-react/src/components/AlphaIconButton/AlphaIconButton.stories.tsx b/packages/bezier-react/src/components/AlphaIconButton/AlphaIconButton.stories.tsx index 4c2eb1b525..c27398d12a 100644 --- a/packages/bezier-react/src/components/AlphaIconButton/AlphaIconButton.stories.tsx +++ b/packages/bezier-react/src/components/AlphaIconButton/AlphaIconButton.stories.tsx @@ -14,7 +14,7 @@ const meta: Meta = { } export default meta -export const Playground: StoryObj = { +export const Primary: StoryObj = { args: { disabled: false, active: false, diff --git a/packages/bezier-react/src/components/AlphaToggleButton/AlphaToggleButton.stories.tsx b/packages/bezier-react/src/components/AlphaToggleButton/AlphaToggleButton.stories.tsx new file mode 100644 index 0000000000..25a775ac07 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaToggleButton/AlphaToggleButton.stories.tsx @@ -0,0 +1,27 @@ +import { ArrowRightIcon, GiftIcon } from '@channel.io/bezier-icons' +import { type Meta, type StoryObj } from '@storybook/react' + +import { AlphaToggleButton } from '~/src/components/AlphaToggleButton' + +const meta = { + component: AlphaToggleButton, + argTypes: { + onClick: { action: 'onClick' }, + }, +} satisfies Meta + +export default meta + +export const Primary = { + args: { + text: 'Invite', + selected: false, + loading: false, + prefixContent: GiftIcon, + suffixContent: ArrowRightIcon, + size: 'm', + shape: 'capsule', + value: 'invite', + variant: 'primary', + }, +} satisfies StoryObj diff --git a/packages/bezier-react/src/components/AlphaToggleButton/ToggleButton.module.scss b/packages/bezier-react/src/components/AlphaToggleButton/ToggleButton.module.scss new file mode 100644 index 0000000000..98566c2e36 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaToggleButton/ToggleButton.module.scss @@ -0,0 +1,127 @@ +@use '../../styles/mixins/dimension'; +@use 'sass:map'; + +@import '../Icon/Icon.module'; + +.Button { + position: relative; + + box-sizing: border-box; + max-width: 200px; + + color: var(--txt-black-darkest); + + transition: background-color var(--transition-s); + + /* dimension, variant, and shape */ + &:where(.size-m) { + height: 36px; + padding: 8px 12px; + + & :where(.ButtonText) { + padding: 1px 4px; + } + } + + &:where(.variant-primary) { + color: var(--alpha-color-fg-black-darkest); + background-color: var(--alpha-color-bg-grey-lightest); + box-shadow: var(--alpha-shadow-input-default); + } + + &:where(.variant-secondary) { + background-color: var(--alpha-color-bg-black-lightest); + } + + &:where(.shape-rectangle) { + border-radius: var(--alpha-dimension-10); + } + + &:where(.shape-capsule) { + border-radius: 9999px; + } + + /* visual effect on interaction */ + &:where(:hover) { + &:where(.variant-primary) { + background-color: var(--alpha-color-bg-grey-lighter); + } + + &:where(.variant-secondary) { + background-color: var(--alpha-color-bg-black-lighter); + } + + &:where([data-state='off']) { + & :is(.ButtonIcon) { + color: var(--txt-black-darker); + } + } + } + + &:where([data-state='on']) { + color: var(--alpha-color-fg-blue-normal); + + &:where(.variant-primary) { + background-color: var(--alpha-color-bg-blue-lightest); + box-shadow: var(--alpha-shadow-input-default); + box-shadow: 0 0 0 1px var(--alpha-color-primary-bg-normal) inset; + } + + &:where(.variant-secondary) { + background-color: var(--alpha-color-primary-bg-lighter); + } + + & :where(.ButtonText) { + font-weight: var(--font-weight-700); + } + } + + &:where(:focus-visible) { + box-shadow: var(--alpha-shadow-input-active); + } + + /* internal components */ + &:where([data-state='off']) :where(.ButtonIcon) { + color: var(--alpha-color-fg-black-dark); + } + + & :where(.ButtonContent) { + display: flex; + align-items: center; + justify-content: center; + + &:where(.loading) { + visibility: hidden; + } + } + + & :where(.ButtonLoader) { + position: absolute; + inset: 0; + + display: flex; + align-items: center; + justify-content: center; + + &:where(.size-s) { + & :is(.Spinner) { + @include dimension.square(#{map.get($size-map, 's')}px); + } + } + } + + /* NOTE: this fixes container width when bold toggles */ + & :where(.ButtonText)::after { + content: attr(data-text); + + overflow: hidden; + display: block; + + height: 0; + + font-weight: var(--font-weight-700); + color: transparent; + + visibility: hidden; + } +} diff --git a/packages/bezier-react/src/components/AlphaToggleButton/ToggleButton.tsx b/packages/bezier-react/src/components/AlphaToggleButton/ToggleButton.tsx new file mode 100644 index 0000000000..c8b48e2199 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaToggleButton/ToggleButton.tsx @@ -0,0 +1,117 @@ +import React, { forwardRef } from 'react' + +import { isBezierIcon } from '@channel.io/bezier-icons' +import * as TogglePrimitive from '@radix-ui/react-toggle' +import classNames from 'classnames' + +import { AlphaSpinner } from '~/src/components/AlphaSpinner' +import { useToggleButtonContext } from '~/src/components/AlphaToggleButton/ToggleButtonContext' +import { BaseButton } from '~/src/components/BaseButton' +import { Icon, type IconSize } from '~/src/components/Icon' +import { Text } from '~/src/components/Text' + +import { type ToggleButtonProps } from './ToggleButton.types' + +import styles from './ToggleButton.module.scss' + +function SideContent({ + size, + content, +}: { + size: IconSize + content?: ToggleButtonProps['prefixContent'] +}) { + return isBezierIcon(content) ? ( + + ) : ( + content + ) +} + +export const ToggleButton = forwardRef( + function ToggleButton( + { + as = BaseButton, + text, + prefixContent, + suffixContent, + variant = 'primary', + shape: shapeProps, + size = 'm', + className, + loading, + onSelectedChange, + ...rest + }, + forwardedRef + ) { + const { shape: shapeContext } = useToggleButtonContext() + const shape = shapeProps ?? shapeContext ?? 'capsule' + const Comp = as as typeof BaseButton + const ICON_SIZE = 's' + + return ( + + +
+ + + {/* TODO: use AlphaText later, add typo */} + + {text} + + + +
+ + {loading && ( +
+ +
+ )} +
+
+ ) + } +) diff --git a/packages/bezier-react/src/components/AlphaToggleButton/ToggleButton.types.ts b/packages/bezier-react/src/components/AlphaToggleButton/ToggleButton.types.ts new file mode 100644 index 0000000000..055ce20a31 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaToggleButton/ToggleButton.types.ts @@ -0,0 +1,76 @@ +import { type ReactNode } from 'react' + +import { type BezierIcon } from '@channel.io/bezier-icons' + +import { + type BezierComponentProps, + type DisableProps, + type PolymorphicProps, + type SizeProps, +} from '~/src/types/props' + +type ToggleButtonSize = 'm' + +interface ToggleButtonOwnProps { + /** + * The text content in the button. + */ + text: string + + /** + * If `loading` is true, spinner will be shown, replacing the content. + * @default false + */ + loading?: boolean + + /** + * Props that shows whether the button is selected. + * @default false + */ + selected?: boolean + + /** + * The selected state of the button when it is initially rendered. + * Use when you **do not need to control** its selected state. + */ + defaultSelected?: boolean + + /** + * The value to associate with the button when selected in a `ToggleButtonGroup` component. + */ + value: string + + /** + * Types of visual styles for button. + * @default 'primary' + */ + variant?: 'primary' | 'secondary' + + /** + * Shape of button. + * @default 'capsule' + */ + shape?: 'capsule' | 'rectangle' + + /** + * Icon on the left. + */ + prefixContent?: BezierIcon | ReactNode + + /** + * Icon on the right. + */ + suffixContent?: BezierIcon | ReactNode + + /** + * Callback function to be called when the button toggles. + */ + onSelectedChange?: (selected: boolean) => void +} + +export interface ToggleButtonProps + extends Omit, keyof ToggleButtonOwnProps>, + PolymorphicProps, + SizeProps, + DisableProps, + ToggleButtonOwnProps {} diff --git a/packages/bezier-react/src/components/AlphaToggleButton/ToggleButtonContext.ts b/packages/bezier-react/src/components/AlphaToggleButton/ToggleButtonContext.ts new file mode 100644 index 0000000000..58e57f6a72 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaToggleButton/ToggleButtonContext.ts @@ -0,0 +1,9 @@ +import { createContext } from '~/src/utils/react' + +import { type ToggleButtonProps } from '~/src/components/AlphaToggleButton/ToggleButton.types' + +const [ToggleButtonProvider, useToggleButtonContext] = createContext< + Pick +>({ shape: undefined }) + +export { ToggleButtonProvider, useToggleButtonContext } diff --git a/packages/bezier-react/src/components/AlphaToggleButton/index.ts b/packages/bezier-react/src/components/AlphaToggleButton/index.ts new file mode 100644 index 0000000000..55fb886e13 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaToggleButton/index.ts @@ -0,0 +1,2 @@ +export { ToggleButton as AlphaToggleButton } from './ToggleButton' +export type { ToggleButtonProps as AlphaToggleButtonProps } from './ToggleButton.types' diff --git a/packages/bezier-react/src/components/AlphaToggleButtonGroup/AlphaToggleButtonGroup.stories.tsx b/packages/bezier-react/src/components/AlphaToggleButtonGroup/AlphaToggleButtonGroup.stories.tsx new file mode 100644 index 0000000000..a506f8f4b1 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaToggleButtonGroup/AlphaToggleButtonGroup.stories.tsx @@ -0,0 +1,73 @@ +import React, { useEffect, useMemo, useState } from 'react' + +import { type Meta, type StoryFn, type StoryObj } from '@storybook/react' + +import { ToggleButton } from '~/src/components/AlphaToggleButton/ToggleButton' +import { + type ToggleButtonMultipleGroupProps, + type ToggleButtonSingleGroupProps, +} from '~/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.types' +import { Box } from '~/src/components/Box' + +import { ToggleButtonGroup } from './ToggleButtonGroup' + +const meta = { + component: ToggleButtonGroup, +} satisfies Meta + +const Template: StoryFn< + ToggleButtonMultipleGroupProps | ToggleButtonSingleGroupProps +> = ({ type, ...props }) => { + const initialValue = useMemo( + () => (type === 'single' ? 'react' : ['react']), + [type] + ) + + const [value, setValue] = useState(initialValue) + + useEffect( + function initializeValue() { + setValue(initialValue) + }, + [initialValue] + ) + + return ( + + {/* @ts-ignore */} + + + + + + + ) +} + +export const Primary = { + render: Template, + args: { + type: 'single', + shape: 'rectangle', + fullWidth: false, + }, +} satisfies StoryObj + +export default meta diff --git a/packages/bezier-react/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.module.scss b/packages/bezier-react/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.module.scss new file mode 100644 index 0000000000..5dccc81a9b --- /dev/null +++ b/packages/bezier-react/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.module.scss @@ -0,0 +1,12 @@ +.ToggleButtonGroup { + display: flex; + gap: 6px; + justify-content: center; + + /* stylelint-disable-next-line selector-class-pattern */ + &:where(.fullWidth) { + & :is(.item) { + flex-grow: 1; + } + } +} diff --git a/packages/bezier-react/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.tsx b/packages/bezier-react/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.tsx new file mode 100644 index 0000000000..62529781e4 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.tsx @@ -0,0 +1,92 @@ +import React, { forwardRef, useMemo } from 'react' + +import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group' +import classNames from 'classnames' + +import { type ToggleButtonProps } from '~/src/components/AlphaToggleButton/ToggleButton.types' +import { ToggleButtonProvider } from '~/src/components/AlphaToggleButton/ToggleButtonContext' +import { + type ToggleButtonMultipleGroupProps, + type ToggleButtonSingleGroupProps, +} from '~/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.types' + +import styles from './ToggleButtonGroup.module.scss' + +/** + * `ToggleButtonGroup` is a group of two-state buttons that can be toggled. + * Notice that it keeps at least one button selected. + * @example + * + * ```tsx + * + * + * + * + * + * ``` + */ +export const ToggleButtonGroup = forwardRef< + HTMLDivElement, + ToggleButtonMultipleGroupProps | ToggleButtonSingleGroupProps +>(function ToggleButtonGroup(props, forwardedRef) { + const { children, shape, className, fullWidth, onValueChange, ...rest } = + props + + const handleValueChange = (value: string | string[]) => { + if (!props.onValueChange || !value.length) { + return + } + + if (props.type === 'single' && typeof value === 'string') { + props.onValueChange(value) + } else if (props.type === 'multiple' && Array.isArray(value)) { + props.onValueChange(value) + } + } + + const ToggleButtons = React.Children.map(children, (toggleButton) => { + if (!React.isValidElement(toggleButton)) { + return null + } + + return ( + + {toggleButton} + + ) + }) + + return ( + + ({ shape }), [shape])}> + {ToggleButtons} + + + ) +}) diff --git a/packages/bezier-react/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.types.ts b/packages/bezier-react/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.types.ts new file mode 100644 index 0000000000..1390055a17 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaToggleButtonGroup/ToggleButtonGroup.types.ts @@ -0,0 +1,66 @@ +import { type PropsWithChildren } from 'react' + +import { + type BezierComponentProps, + type PolymorphicProps, +} from '~/src/types/props' + +import { type ToggleButtonProps } from '~/src/components/AlphaToggleButton/ToggleButton.types' + +type Value = T extends 'single' ? string : string[] + +interface ToggleButtonGroupOwnProps { + /** + * Shape of `ToggleButtonComponent` rendered as children. + * @default 'capsule' + */ + shape?: ToggleButtonProps['shape'] + + /** + * If true, the button group will take up the full width of its container. + * @default false + */ + fullWidth?: boolean + + /** + * Determines whether a single or multiple buttons can be selected at a time. + * @default 'single' + */ + type: T + + /** + * The controlled value of the selected button. + */ + value?: Value + + /** + * The reading direction of the button group. + * @default 'ltr' + */ + dir?: 'ltr' | 'rtl' + + /** + * Event handler called when the selected state of an button changes. + */ + onValueChange?: T extends 'single' + ? (value: string) => void + : (value: string[]) => void +} + +export interface ToggleButtonSingleGroupProps + extends Omit< + BezierComponentProps<'div'>, + keyof ToggleButtonGroupOwnProps | 'defaultValue' + >, + PropsWithChildren, + PolymorphicProps, + ToggleButtonGroupOwnProps<'single'> {} + +export interface ToggleButtonMultipleGroupProps + extends Omit< + BezierComponentProps<'div'>, + keyof ToggleButtonGroupOwnProps | 'defaultValue' + >, + PropsWithChildren, + PolymorphicProps, + ToggleButtonGroupOwnProps<'multiple'> {} diff --git a/packages/bezier-react/src/components/AlphaToggleButtonGroup/index.ts b/packages/bezier-react/src/components/AlphaToggleButtonGroup/index.ts new file mode 100644 index 0000000000..a79e5046c6 --- /dev/null +++ b/packages/bezier-react/src/components/AlphaToggleButtonGroup/index.ts @@ -0,0 +1,5 @@ +export { ToggleButtonGroup } from './ToggleButtonGroup' +export type { + ToggleButtonMultipleGroupProps, + ToggleButtonSingleGroupProps, +} from './ToggleButtonGroup.types' diff --git a/packages/bezier-react/src/components/Banner/Banner.stories.tsx b/packages/bezier-react/src/components/Banner/Banner.stories.tsx index 4ce47437db..977639aa43 100644 --- a/packages/bezier-react/src/components/Banner/Banner.stories.tsx +++ b/packages/bezier-react/src/components/Banner/Banner.stories.tsx @@ -48,7 +48,7 @@ const meta: Meta = { } export default meta -export const Playground: StoryObj = { +export const Primary: StoryObj = { args: { variant: 'default', icon: LightbulbIcon, diff --git a/packages/bezier-react/src/components/Button/Button.stories.tsx b/packages/bezier-react/src/components/Button/Button.stories.tsx index 8c86038d75..e691d7c026 100644 --- a/packages/bezier-react/src/components/Button/Button.stories.tsx +++ b/packages/bezier-react/src/components/Button/Button.stories.tsx @@ -56,7 +56,7 @@ const meta: Meta = { } export default meta -export const Playground: StoryObj = { +export const Primary: StoryObj = { args: { text: 'Invite', disabled: false, diff --git a/packages/bezier-react/src/components/ButtonGroup/ButtonGroup.stories.tsx b/packages/bezier-react/src/components/ButtonGroup/ButtonGroup.stories.tsx index b3551c91bb..5d735428f2 100644 --- a/packages/bezier-react/src/components/ButtonGroup/ButtonGroup.stories.tsx +++ b/packages/bezier-react/src/components/ButtonGroup/ButtonGroup.stories.tsx @@ -42,7 +42,7 @@ const Template: StoryFn = (props) => ( ) -export const Playground: StoryObj = { +export const Primary: StoryObj = { render: Template, args: { diff --git a/packages/bezier-react/src/components/Icon/Icon.stories.tsx b/packages/bezier-react/src/components/Icon/Icon.stories.tsx index d1ac67a1f4..90f52fb311 100644 --- a/packages/bezier-react/src/components/Icon/Icon.stories.tsx +++ b/packages/bezier-react/src/components/Icon/Icon.stories.tsx @@ -51,7 +51,7 @@ const meta: Meta = { } export default meta -export const Playground: StoryObj = { +export const Primary: StoryObj = { args: { source: ChannelIcon, size: 'm', diff --git a/packages/bezier-react/src/components/ProgressBar/ProgressBar.stories.tsx b/packages/bezier-react/src/components/ProgressBar/ProgressBar.stories.tsx index 96ad5d5fb6..0f574477db 100644 --- a/packages/bezier-react/src/components/ProgressBar/ProgressBar.stories.tsx +++ b/packages/bezier-react/src/components/ProgressBar/ProgressBar.stories.tsx @@ -50,7 +50,7 @@ const meta: Meta = { } export default meta -export const Playground: StoryObj = { +export const Primary: StoryObj = { render: (props) => , args: { size: 'm', diff --git a/packages/bezier-react/src/index.ts b/packages/bezier-react/src/index.ts index acc1807223..894a901005 100644 --- a/packages/bezier-react/src/index.ts +++ b/packages/bezier-react/src/index.ts @@ -13,6 +13,8 @@ export * from '~/src/components/AlphaFloatingButton' export * from '~/src/components/AlphaFloatingIconButton' export * from '~/src/components/AlphaIconButton' export * from '~/src/components/AlphaSpinner' +export * from '~/src/components/AlphaToggleButton' +export * from '~/src/components/AlphaToggleButtonGroup' export * from '~/src/components/AlphaTooltipPrimitive' export * from '~/src/components/AppProvider' export * from '~/src/components/AutoFocus' diff --git a/yarn.lock b/yarn.lock index 6dec032a26..2a10b7ef39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2348,6 +2348,8 @@ __metadata: "@radix-ui/react-slot": "npm:^1.0.2" "@radix-ui/react-switch": "npm:^1.0.3" "@radix-ui/react-tabs": "npm:^1.0.4" + "@radix-ui/react-toggle": "npm:^1.0.3" + "@radix-ui/react-toggle-group": "npm:^1.0.4" "@radix-ui/react-toolbar": "npm:^1.0.4" "@radix-ui/react-tooltip": "npm:^1.0.7" "@radix-ui/react-visually-hidden": "npm:^1.0.3" @@ -4897,7 +4899,7 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-toggle-group@npm:1.0.4": +"@radix-ui/react-toggle-group@npm:1.0.4, @radix-ui/react-toggle-group@npm:^1.0.4": version: 1.0.4 resolution: "@radix-ui/react-toggle-group@npm:1.0.4" dependencies: @@ -4923,7 +4925,7 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-toggle@npm:1.0.3": +"@radix-ui/react-toggle@npm:1.0.3, @radix-ui/react-toggle@npm:^1.0.3": version: 1.0.3 resolution: "@radix-ui/react-toggle@npm:1.0.3" dependencies: