From 0fd56cd77760c6fbbe28749f29fbcf11de33e3b9 Mon Sep 17 00:00:00 2001 From: Damien Pellier Date: Mon, 1 Dec 2025 14:15:16 +0100 Subject: [PATCH] feat(buttonGroup): implement component --- .../accessibility-test/src/app/App.tsx | 14 ++ .../button-group/.storybook/main.ts | 26 +++ .../button-group/.storybook/manager.ts | 10 + .../button-group/.storybook/preview.ts | 9 + .../button-group/jest-puppeteer.config.ts | 17 ++ .../components/button-group/jest.config.ts | 26 +++ .../src/components/button-group/modules.d.ts | 2 + .../src/components/button-group/package.json | 23 ++ .../button-group-item/ButtonGroupItem.tsx | 53 +++++ .../buttonGroupItem.module.scss | 51 +++++ .../components/button-group/ButtonGroup.tsx | 43 ++++ .../button-group/buttonGroup.module.scss | 6 + .../src/constants/button-group-size.ts | 7 + .../src/contexts/useButtonGroup.tsx | 71 ++++++ .../button-group/src/dev.module.css | 3 + .../button-group/src/dev.stories.tsx | 104 +++++++++ .../src/components/button-group/src/index.ts | 4 + .../tests/rendering/button-group.e2e.ts | 14 ++ .../tests/rendering/button-group.stories.tsx | 14 ++ .../src/components/button-group/tsconfig.json | 5 + .../src/components/button-group/typedoc.json | 17 ++ packages/ods-react/src/components/index.ts | 2 + .../components/button-group/anatomy-tech.png | Bin 0 -> 5124 bytes .../components/button-group/anatomy.png | Bin 0 -> 5544 bytes .../ThemeGeneratorPreview.tsx | 3 + packages/storybook/src/constants/meta.ts | 1 + .../button-group/button-group.stories.tsx | 212 ++++++++++++++++++ .../components/button-group/documentation.mdx | 115 ++++++++++ .../button-group/technical-information.mdx | 55 +++++ .../storybook/stories/components/gallery.mdx | 2 + .../templates/storybook/documentation.mdx.hbs | 2 +- ...prefix-join prefix name }}.stories.tsx.hbs | 2 +- yarn.lock | 6 + 33 files changed, 917 insertions(+), 2 deletions(-) create mode 100644 packages/ods-react/src/components/button-group/.storybook/main.ts create mode 100644 packages/ods-react/src/components/button-group/.storybook/manager.ts create mode 100644 packages/ods-react/src/components/button-group/.storybook/preview.ts create mode 100644 packages/ods-react/src/components/button-group/jest-puppeteer.config.ts create mode 100644 packages/ods-react/src/components/button-group/jest.config.ts create mode 100644 packages/ods-react/src/components/button-group/modules.d.ts create mode 100644 packages/ods-react/src/components/button-group/package.json create mode 100644 packages/ods-react/src/components/button-group/src/components/button-group-item/ButtonGroupItem.tsx create mode 100644 packages/ods-react/src/components/button-group/src/components/button-group-item/buttonGroupItem.module.scss create mode 100644 packages/ods-react/src/components/button-group/src/components/button-group/ButtonGroup.tsx create mode 100644 packages/ods-react/src/components/button-group/src/components/button-group/buttonGroup.module.scss create mode 100644 packages/ods-react/src/components/button-group/src/constants/button-group-size.ts create mode 100644 packages/ods-react/src/components/button-group/src/contexts/useButtonGroup.tsx create mode 100644 packages/ods-react/src/components/button-group/src/dev.module.css create mode 100644 packages/ods-react/src/components/button-group/src/dev.stories.tsx create mode 100644 packages/ods-react/src/components/button-group/src/index.ts create mode 100644 packages/ods-react/src/components/button-group/tests/rendering/button-group.e2e.ts create mode 100644 packages/ods-react/src/components/button-group/tests/rendering/button-group.stories.tsx create mode 100644 packages/ods-react/src/components/button-group/tsconfig.json create mode 100644 packages/ods-react/src/components/button-group/typedoc.json create mode 100644 packages/storybook/assets/components/button-group/anatomy-tech.png create mode 100644 packages/storybook/assets/components/button-group/anatomy.png create mode 100644 packages/storybook/stories/components/button-group/button-group.stories.tsx create mode 100644 packages/storybook/stories/components/button-group/documentation.mdx create mode 100644 packages/storybook/stories/components/button-group/technical-information.mdx diff --git a/packages/examples/accessibility-test/src/app/App.tsx b/packages/examples/accessibility-test/src/app/App.tsx index ffb25281e9..05e862eb4d 100644 --- a/packages/examples/accessibility-test/src/app/App.tsx +++ b/packages/examples/accessibility-test/src/app/App.tsx @@ -4,6 +4,7 @@ import { Badge, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button, + ButtonGroup, ButtonGroupItem, Card, Checkbox, CheckboxControl, CheckboxLabel, Clipboard, ClipboardControl, ClipboardTrigger, @@ -249,6 +250,19 @@ function App(): ReactElement { +
+

Button Group

+ + + Hourly + Daily + Monthly + + Custom + + +
+

Card

diff --git a/packages/ods-react/src/components/button-group/.storybook/main.ts b/packages/ods-react/src/components/button-group/.storybook/main.ts new file mode 100644 index 0000000000..c8ecf218d5 --- /dev/null +++ b/packages/ods-react/src/components/button-group/.storybook/main.ts @@ -0,0 +1,26 @@ +import { StorybookConfig } from '@storybook/react-vite'; + +const config: StorybookConfig = { + core: { + disableTelemetry: true, + disableWhatsNewNotifications: true, + }, + docs: { + autodocs: false, + }, + framework: '@storybook/react-vite', + previewHead: (head) => ` + ${head} + + `, + stories: [ + '../src/dev.stories.tsx', + '../tests/**/*.stories.tsx', + ], +}; + +export default config; diff --git a/packages/ods-react/src/components/button-group/.storybook/manager.ts b/packages/ods-react/src/components/button-group/.storybook/manager.ts new file mode 100644 index 0000000000..7007ab574d --- /dev/null +++ b/packages/ods-react/src/components/button-group/.storybook/manager.ts @@ -0,0 +1,10 @@ +import { addons } from '@storybook/manager-api'; + +addons.register('custom-panel', (api) => { + api.togglePanel(false); +}); + +addons.setConfig({ + enableShortcuts: false, + showToolbar: true, +}); diff --git a/packages/ods-react/src/components/button-group/.storybook/preview.ts b/packages/ods-react/src/components/button-group/.storybook/preview.ts new file mode 100644 index 0000000000..604373b1c1 --- /dev/null +++ b/packages/ods-react/src/components/button-group/.storybook/preview.ts @@ -0,0 +1,9 @@ +import { type Preview } from '@storybook/react'; +import '@ovhcloud/ods-themes/default/css'; +import '@ovhcloud/ods-themes/default/fonts'; + +const preview: Preview = { + parameters: {}, +}; + +export default preview; diff --git a/packages/ods-react/src/components/button-group/jest-puppeteer.config.ts b/packages/ods-react/src/components/button-group/jest-puppeteer.config.ts new file mode 100644 index 0000000000..73ec1b96e5 --- /dev/null +++ b/packages/ods-react/src/components/button-group/jest-puppeteer.config.ts @@ -0,0 +1,17 @@ +const isCI = !!process.env.CI; + +export default { + launch: { + headless: isCI, + slowMo: isCI ? 0 : 300, + product: 'chrome', + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + "--disable-dev-shm-usage", + "--disable-accelerated-2d-canvas", + "--disable-gpu", + '--font-render-hinting=none', + ], + }, +}; diff --git a/packages/ods-react/src/components/button-group/jest.config.ts b/packages/ods-react/src/components/button-group/jest.config.ts new file mode 100644 index 0000000000..0b172199c1 --- /dev/null +++ b/packages/ods-react/src/components/button-group/jest.config.ts @@ -0,0 +1,26 @@ +const baseOption = { + collectCoverage: false, + testPathIgnorePatterns: [ + 'node_modules/', + 'dist/', + ], + testRegex: 'tests\\/.*\\.spec\\.(ts|tsx)$', + transform: { + '\\.(ts|tsx)$': 'ts-jest', + }, + verbose: true, +}; + +export default !!process.env.E2E ? + { + ...baseOption, + preset: 'jest-puppeteer', + testRegex: 'tests\\/.*\\.e2e\\.ts$', + testTimeout: 60000, + } : { + ...baseOption, + transform: { + ...baseOption.transform, + '\\.scss$': 'jest-transform-stub', + } + }; diff --git a/packages/ods-react/src/components/button-group/modules.d.ts b/packages/ods-react/src/components/button-group/modules.d.ts new file mode 100644 index 0000000000..875203d567 --- /dev/null +++ b/packages/ods-react/src/components/button-group/modules.d.ts @@ -0,0 +1,2 @@ +declare module '*.css'; +declare module '*.scss'; diff --git a/packages/ods-react/src/components/button-group/package.json b/packages/ods-react/src/components/button-group/package.json new file mode 100644 index 0000000000..72a0e461d4 --- /dev/null +++ b/packages/ods-react/src/components/button-group/package.json @@ -0,0 +1,23 @@ +{ + "name": "@ovhcloud/ods-react-button-group", + "version": "19.3.0", + "private": true, + "description": "ODS React ButtonGroup component", + "type": "module", + "main": "dist/index.js", + "scripts": { + "clean": "rimraf documentation node_modules", + "doc": "npm run clean && npm run doc:ts && npm run doc:css", + "doc:css": "sass src/components:documentation --no-source-map --pkg-importer=node && node ../../../scripts/generate-component-token-list.js", + "doc:ts": "typedoc", + "lint:a11y": "eslint --config ../../../../../.eslintrc-a11y 'src/**/*.{js,ts,tsx}' --ignore-pattern '*.stories.tsx'", + "lint:scss": "stylelint --aei 'src/components/**/*.scss'", + "lint:ts": "eslint '{src,tests}/**/*.{js,ts,tsx}' --ignore-pattern '*.stories.tsx'", + "start": "npm run start:storybook", + "start:storybook": "storybook dev -p 3000 --no-open", + "test:e2e": "E2E=true start-server-and-test 'npm run start:storybook' 3000 'jest -i --detectOpenHandles'", + "test:e2e:ci": "CI=true npm run test:e2e", + "test:spec": "jest --passWithNoTests", + "test:spec:ci": "npm run test:spec" + } +} diff --git a/packages/ods-react/src/components/button-group/src/components/button-group-item/ButtonGroupItem.tsx b/packages/ods-react/src/components/button-group/src/components/button-group-item/ButtonGroupItem.tsx new file mode 100644 index 0000000000..42bf1852fc --- /dev/null +++ b/packages/ods-react/src/components/button-group/src/components/button-group-item/ButtonGroupItem.tsx @@ -0,0 +1,53 @@ +import { ToggleGroup, useToggleGroupContext } from '@ark-ui/react/toggle-group'; +import classNames from 'classnames'; +import { type ComponentPropsWithRef, type FC, type JSX, forwardRef } from 'react'; +import { BUTTON_COLOR, BUTTON_VARIANT, Button } from '../../../../button/src'; +import { useButtonGroup } from '../../contexts/useButtonGroup'; +import style from './buttonGroupItem.module.scss'; + +interface ButtonGroupItemProp extends ComponentPropsWithRef<'button'> { + /** + * Whether the component is disabled. + */ + disabled?: boolean; + /** + * The value of the item. + */ + value: string, +} + +const ButtonGroupItem: FC = forwardRef(({ + children, + className, + disabled, + value, + ...props +}, ref): JSX.Element => { + const { value: selection } = useToggleGroupContext(); + const { size } = useButtonGroup(); + + return ( + + + + ); +}); + +ButtonGroupItem.displayName = 'ButtonGroupItem'; + +export { + ButtonGroupItem, + type ButtonGroupItemProp, +}; diff --git a/packages/ods-react/src/components/button-group/src/components/button-group-item/buttonGroupItem.module.scss b/packages/ods-react/src/components/button-group/src/components/button-group-item/buttonGroupItem.module.scss new file mode 100644 index 0000000000..a1f7ec7397 --- /dev/null +++ b/packages/ods-react/src/components/button-group/src/components/button-group-item/buttonGroupItem.module.scss @@ -0,0 +1,51 @@ +$ods-button-group-item-z-index: 1; + +@layer ods-molecules { + .button-group-item { + --ods-button-group-item-background-color-checked-disabled: var(--ods-color-neutral-500); + + z-index: $ods-button-group-item-z-index; + + &:first-child { + margin-inline-start: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + &:not(:first-child) { + margin-inline-start: -1px; + + &:not(:last-child) { + border-radius: 0; + } + } + + &:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + &:hover, + &[data-focus], + &[data-state="on"] { + z-index: $ods-button-group-item-z-index + 1; + } + + &[data-state="on"] { + &:not([data-disabled]) { + border-color: var(--ods-theme-background-color-selected); + background-color: var(--ods-theme-background-color-selected); + } + + &[data-disabled] { + border-color: var(--ods-button-group-item-background-color-checked-disabled); + background-color: var(--ods-button-group-item-background-color-checked-disabled); + color: var(--ods-button-text-color-primary); + } + } + + &[data-disabled] { + z-index: $ods-button-group-item-z-index - 1; + } + } +} diff --git a/packages/ods-react/src/components/button-group/src/components/button-group/ButtonGroup.tsx b/packages/ods-react/src/components/button-group/src/components/button-group/ButtonGroup.tsx new file mode 100644 index 0000000000..6b90971925 --- /dev/null +++ b/packages/ods-react/src/components/button-group/src/components/button-group/ButtonGroup.tsx @@ -0,0 +1,43 @@ +import { ToggleGroup } from '@ark-ui/react/toggle-group'; +import classNames from 'classnames'; +import { type FC, type JSX, forwardRef } from 'react'; +import { ButtonGroupProvider, type ButtonGroupRootProp } from '../../contexts/useButtonGroup'; +import style from './buttonGroup.module.scss'; + +interface ButtonGroupProp extends ButtonGroupRootProp {} + +const ButtonGroup: FC = forwardRef(({ + children, + className, + defaultValue, + disabled, + multiple, + onValueChange, + size, + value, + ...props +}, ref): JSX.Element => { + return ( + + + { children } + + + ); +}); + +ButtonGroup.displayName = 'ButtonGroup'; + +export { + ButtonGroup, + type ButtonGroupProp, +}; diff --git a/packages/ods-react/src/components/button-group/src/components/button-group/buttonGroup.module.scss b/packages/ods-react/src/components/button-group/src/components/button-group/buttonGroup.module.scss new file mode 100644 index 0000000000..c78db1dba9 --- /dev/null +++ b/packages/ods-react/src/components/button-group/src/components/button-group/buttonGroup.module.scss @@ -0,0 +1,6 @@ +@layer ods-molecules { + .button-group { + display: flex; + flex-direction: row; + } +} diff --git a/packages/ods-react/src/components/button-group/src/constants/button-group-size.ts b/packages/ods-react/src/components/button-group/src/constants/button-group-size.ts new file mode 100644 index 0000000000..98ffff2f63 --- /dev/null +++ b/packages/ods-react/src/components/button-group/src/constants/button-group-size.ts @@ -0,0 +1,7 @@ +import { BUTTON_SIZE, BUTTON_SIZES, type ButtonSize } from '../../../button/src'; + +export { + BUTTON_SIZE as BUTTON_GROUP_SIZE, + BUTTON_SIZES as BUTTON_GROUP_SIZES, + type ButtonSize as ButtonGroupSize, +}; diff --git a/packages/ods-react/src/components/button-group/src/contexts/useButtonGroup.tsx b/packages/ods-react/src/components/button-group/src/contexts/useButtonGroup.tsx new file mode 100644 index 0000000000..482ab64d1b --- /dev/null +++ b/packages/ods-react/src/components/button-group/src/contexts/useButtonGroup.tsx @@ -0,0 +1,71 @@ +import { type ComponentPropsWithRef, type JSX, type ReactNode, createContext, useContext } from 'react'; +import { type ButtonGroupSize } from '../constants/button-group-size'; + +interface ButtonGroupValueChangeDetail { + value: string[], +} + +type ButtonGroupRootProp = ComponentPropsWithRef<'div'> & { + /** + * The initial value of the selected items. Use when you don't need to control the value of the component. + */ + defaultValue?: string[]; + /** + * Whether the component is disabled. + */ + disabled?: boolean; + /** + * Whether multiple items can be selected at the same time. + */ + multiple?: boolean; + /** + * Callback fired when the selection changes. + */ + onValueChange?: (detail: ButtonGroupValueChangeDetail) => void; + /** + * The size preset to use. + */ + size?: ButtonGroupSize, + /** + * The controlled value of the selected items. + */ + value?: string[]; +}; + +interface ButtonGroupProviderProp extends ButtonGroupRootProp { + children: ReactNode; +} + +type ButtonGroupContextType = Omit; + +const ButtonGroupContext = createContext(undefined); + +const ButtonGroupProvider = ({ + children, + size, +}: ButtonGroupProviderProp): JSX.Element => { + return ( + + { children } + + ); +}; + +function useButtonGroup(): ButtonGroupContextType { + const context = useContext(ButtonGroupContext); + + if (!context) { + throw new Error('useButtonGroup must be used within a ButtonGroupProvider'); + } + + return context; +} + +export { + ButtonGroupProvider, + type ButtonGroupRootProp, + type ButtonGroupValueChangeDetail, + useButtonGroup, +}; diff --git a/packages/ods-react/src/components/button-group/src/dev.module.css b/packages/ods-react/src/components/button-group/src/dev.module.css new file mode 100644 index 0000000000..db5f5fab83 --- /dev/null +++ b/packages/ods-react/src/components/button-group/src/dev.module.css @@ -0,0 +1,3 @@ +.custom-button-group { + +} diff --git a/packages/ods-react/src/components/button-group/src/dev.stories.tsx b/packages/ods-react/src/components/button-group/src/dev.stories.tsx new file mode 100644 index 0000000000..faa12ff73d --- /dev/null +++ b/packages/ods-react/src/components/button-group/src/dev.stories.tsx @@ -0,0 +1,104 @@ +import { useState } from 'react'; +import { BUTTON_GROUP_SIZE, ButtonGroup, ButtonGroupItem } from '.'; + +export default { + component: ButtonGroup, + title: 'ButtonGroup dev', +}; + +export const Controlled = () => { + const [values, setValues] = useState([]); + + return ( + <> + setValues(value) } + value={ values }> + Button 1 + Button 2 + Button 3 + + + + + ); +}; + +export const Default = () => ( + + Button 1 + Button 2 + Button 3 + +); + +export const DefaultValue = () => ( + + Button 1 + Button 2 + Button 3 + +); + +export const Disabled = () => ( + <> +

Whole component disabled

+ + Button 1 + Button 2 + Button 3 + + +

Specific item disabled

+ + Button 1 + Button 2 + Button 3 + Button 4 + + +

Selected item disabled

+ + Button 1 + Button 2 + Button 3 + Button 4 + + +); + +export const Multiple = () => ( + + Button 1 + Button 2 + Button 3 + Button 4 + +); + +export const Sizes = () => ( + <> +

MD

+ + Button 1 + Button 2 + Button 3 + + +

SM

+ + Button 1 + Button 2 + Button 3 + + +

XS

+ + Button 1 + Button 2 + Button 3 + + +); diff --git a/packages/ods-react/src/components/button-group/src/index.ts b/packages/ods-react/src/components/button-group/src/index.ts new file mode 100644 index 0000000000..1b59778ec5 --- /dev/null +++ b/packages/ods-react/src/components/button-group/src/index.ts @@ -0,0 +1,4 @@ +export { ButtonGroup, type ButtonGroupProp } from './components/button-group/ButtonGroup'; +export { ButtonGroupItem, type ButtonGroupItemProp } from './components/button-group-item/ButtonGroupItem'; +export { BUTTON_GROUP_SIZE, BUTTON_GROUP_SIZES, type ButtonGroupSize } from './constants/button-group-size'; +export { type ButtonGroupValueChangeDetail } from './contexts/useButtonGroup'; diff --git a/packages/ods-react/src/components/button-group/tests/rendering/button-group.e2e.ts b/packages/ods-react/src/components/button-group/tests/rendering/button-group.e2e.ts new file mode 100644 index 0000000000..a11ad8b8a2 --- /dev/null +++ b/packages/ods-react/src/components/button-group/tests/rendering/button-group.e2e.ts @@ -0,0 +1,14 @@ +import 'jest-puppeteer'; +import { gotoStory } from '../../../../helpers/test'; + +describe('ButtonGroup rendering', () => { + it('should render the component', async() => { + await gotoStory(page, 'rendering/render'); + + expect(await page.waitForSelector('[data-ods="button-group"]')).not.toBeNull(); + + const items = await page.$$('[data-ods="button-group-item"]'); + + expect(items.length).toBe(3); + }); +}); diff --git a/packages/ods-react/src/components/button-group/tests/rendering/button-group.stories.tsx b/packages/ods-react/src/components/button-group/tests/rendering/button-group.stories.tsx new file mode 100644 index 0000000000..0dbc694c07 --- /dev/null +++ b/packages/ods-react/src/components/button-group/tests/rendering/button-group.stories.tsx @@ -0,0 +1,14 @@ +import { ButtonGroup, ButtonGroupItem } from '../../src'; + +export default { + component: ButtonGroup, + title: 'Tests rendering', +}; + +export const render = () => ( + + Button 1 + Button 2 + Button 3 + +); diff --git a/packages/ods-react/src/components/button-group/tsconfig.json b/packages/ods-react/src/components/button-group/tsconfig.json new file mode 100644 index 0000000000..e201f26bdd --- /dev/null +++ b/packages/ods-react/src/components/button-group/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../../tsconfig.json", + "include": ["modules.d.ts", "src", "tests"], + "exclude": [".storybook", "node_modules"] +} diff --git a/packages/ods-react/src/components/button-group/typedoc.json b/packages/ods-react/src/components/button-group/typedoc.json new file mode 100644 index 0000000000..d7e0a9c0c2 --- /dev/null +++ b/packages/ods-react/src/components/button-group/typedoc.json @@ -0,0 +1,17 @@ +{ + "disableGit": true, + "disableSources": true, + "entryPoints": ["src/index.ts"], + "excludeExternals": true, + "excludeInternal": true, + "excludePrivate": true, + "excludeProtected": true, + "outputs": [ + { + "name": "json", + "path": "./documentation/button-group.json" + } + ], + "sort": ["source-order"], + "tsconfig":"tsconfig.json" +} diff --git a/packages/ods-react/src/components/index.ts b/packages/ods-react/src/components/index.ts index 07c81106d9..681f7041b0 100644 --- a/packages/ods-react/src/components/index.ts +++ b/packages/ods-react/src/components/index.ts @@ -42,3 +42,5 @@ export * from './tree-view/src'; export * from './meter/src'; export * from './toaster/src'; export * from './kbd/src'; + +export * from './button-group/src'; \ No newline at end of file diff --git a/packages/storybook/assets/components/button-group/anatomy-tech.png b/packages/storybook/assets/components/button-group/anatomy-tech.png new file mode 100644 index 0000000000000000000000000000000000000000..2cd80a44818b30528d5c7a0aa0fcf5cc7eb29b88 GIT binary patch literal 5124 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 literal 0 HcmV?d00001 diff --git a/packages/storybook/assets/components/button-group/anatomy.png b/packages/storybook/assets/components/button-group/anatomy.png new file mode 100644 index 0000000000000000000000000000000000000000..c98bc16f90871334ce752cb9e1b3892adb923b26 GIT binary patch literal 5544 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 PunHLHnA|M6?i}@R!}A#0 literal 0 HcmV?d00001 diff --git a/packages/storybook/src/components/themeGenerator/themeGeneratorPreview/ThemeGeneratorPreview.tsx b/packages/storybook/src/components/themeGenerator/themeGeneratorPreview/ThemeGeneratorPreview.tsx index 96250098d9..b20720c4c2 100644 --- a/packages/storybook/src/components/themeGenerator/themeGeneratorPreview/ThemeGeneratorPreview.tsx +++ b/packages/storybook/src/components/themeGenerator/themeGeneratorPreview/ThemeGeneratorPreview.tsx @@ -4,6 +4,7 @@ import * as AccordionStories from '../../../../stories/components/accordion/acco import * as BadgeStories from '../../../../stories/components/badge/badge.stories'; import * as BreadcrumbStories from '../../../../stories/components/breadcrumb/breadcrumb.stories'; import * as ButtonStories from '../../../../stories/components/button/button.stories'; +import * as ButtonGroupStories from '../../../../stories/components/button-group/button-group.stories'; import * as CardStories from '../../../../stories/components/card/card.stories'; import * as CheckboxStories from '../../../../stories/components/checkbox/checkbox.stories'; import * as ClipboardStories from '../../../../stories/components/clipboard/clipboard.stories'; @@ -52,6 +53,7 @@ const THEME_STORY_MODULES = { Badge: BadgeStories, Breadcrumb: BreadcrumbStories, Button: ButtonStories, + ButtonGroup: ButtonGroupStories, Card: CardStories, Checkbox: CheckboxStories, Clipboard: ClipboardStories, @@ -110,6 +112,7 @@ const THEME_PREVIEW_COMPONENTS: ThemePreviewItem[] = [ { key: 'Badge', kind: REACT_COMPONENTS_TITLE.badge, label: 'Badge' }, { key: 'Breadcrumb', kind: REACT_COMPONENTS_TITLE.breadcrumb, label: 'Breadcrumb' }, { key: 'Button', kind: REACT_COMPONENTS_TITLE.button, label: 'Button' }, + { key: 'ButtonGroup', kind: REACT_COMPONENTS_TITLE.buttonGroup, label: 'Button Group' }, { key: 'Card', kind: REACT_COMPONENTS_TITLE.card, label: 'Card' }, { key: 'Checkbox', kind: REACT_COMPONENTS_TITLE.checkbox, label: 'Checkbox' }, { key: 'Clipboard', kind: REACT_COMPONENTS_TITLE.clipboard, label: 'Clipboard' }, diff --git a/packages/storybook/src/constants/meta.ts b/packages/storybook/src/constants/meta.ts index e2b0973542..f957c48ee4 100644 --- a/packages/storybook/src/constants/meta.ts +++ b/packages/storybook/src/constants/meta.ts @@ -34,6 +34,7 @@ enum REACT_COMPONENTS_TITLE { badge = `${SECTION.reactComponents}/Badge`, breadcrumb = `${SECTION.reactComponents}/Breadcrumb`, button = `${SECTION.reactComponents}/Button`, + buttonGroup = `${SECTION.reactComponents}/Button Group`, card = `${SECTION.reactComponents}/Card`, cart = `${SECTION.reactComponents}/Cart`, checkbox = `${SECTION.reactComponents}/Checkbox`, diff --git a/packages/storybook/stories/components/button-group/button-group.stories.tsx b/packages/storybook/stories/components/button-group/button-group.stories.tsx new file mode 100644 index 0000000000..ff6e30e90c --- /dev/null +++ b/packages/storybook/stories/components/button-group/button-group.stories.tsx @@ -0,0 +1,212 @@ +import { type Meta, type StoryObj } from '@storybook/react'; +import React, { useState } from 'react'; +import { BUTTON_GROUP_SIZE, BUTTON_GROUP_SIZES, ButtonGroup, ButtonGroupItem, type ButtonGroupProp } from '../../../../ods-react/src/components/button-group/src'; +import { ICON_NAME, Icon } from '../../../../ods-react/src/components/icon/src'; +import { CONTROL_CATEGORY } from '../../../src/constants/controls'; +import { excludeFromDemoControls, orderControls } from '../../../src/helpers/controls'; +import { staticSourceRenderConfig } from '../../../src/helpers/source'; + +type Story = StoryObj; + +const meta: Meta = { + argTypes: excludeFromDemoControls(['defaultValue', 'onValueChange', 'value']), + component: ButtonGroup, + subcomponents: { ButtonGroupItem }, + tags: ['new'], + title: 'React Components/Button Group', +}; + +export default meta; + +export const Demo: Story = { + render: (arg) => ( + + Option 1 + Option 2 + Option 3 + + ), + argTypes: orderControls({ + disabled: { + table: { + category: CONTROL_CATEGORY.general, + }, + control: 'boolean', + }, + multiple: { + table: { + category: CONTROL_CATEGORY.general, + }, + control: 'boolean', + }, + size: { + table: { + category: CONTROL_CATEGORY.design, + type: { summary: 'BUTTON_GROUP_SIZE' } + }, + control: { type: 'select' }, + options: BUTTON_GROUP_SIZES, + }, + }), +}; + +export const Controlled: Story = { + globals: { + imports: `import { ICON_NAME, ButtonGroup, ButtonGroupItem, Icon } from '@ovhcloud/ods-react'; +import { useState } from 'react';`, + }, + parameters: { + docs: { + source: { ...staticSourceRenderConfig() }, + }, + }, + tags: ['!dev'], + render: ({}) => { + const [values, setValues] = useState(['hourly']); + + return ( + setValues(value) } + value={ values }> + Hourly + Daily + Monthly + + Custom + + + ); + }, +}; + +export const Default: Story = { + globals: { + imports: `import { ICON_NAME, ButtonGroup, ButtonGroupItem, Icon } from '@ovhcloud/ods-react';`, + }, + tags: ['!dev'], + render: ({}) => ( + + Hourly + Daily + Monthly + + Custom + + + ), +}; + +export const Disabled: Story = { + globals: { + imports: `import { ICON_NAME, ButtonGroup, ButtonGroupItem, Icon } from '@ovhcloud/ods-react';`, + }, + tags: ['!dev'], + render: ({}) => ( + + Hourly + Daily + Monthly + + Custom + + + ), +}; + +export const DisabledItem: Story = { + globals: { + imports: `import { ICON_NAME, ButtonGroup, ButtonGroupItem, Icon } from '@ovhcloud/ods-react';`, + }, + tags: ['!dev'], + render: ({}) => ( + + Hourly + Daily + Monthly + + Custom + + + ), +}; + +export const Multiple: Story = { + globals: { + imports: `import { ButtonGroup, ButtonGroupItem } from '@ovhcloud/ods-react';`, + }, + tags: ['!dev'], + render: ({}) => ( + + Option 1 + Option 2 + Option 3 + Option 4 + + ), +}; + +export const Overview: Story = { + tags: ['!dev'], + parameters: { + layout: 'centered', + }, + render: ({}) => ( + + Hourly + Daily + Monthly + + Custom + + + ), +}; + +export const Size: Story = { + globals: { + imports: `import { BUTTON_GROUP_SIZE, ButtonGroup, ButtonGroupItem } from '@ovhcloud/ods-react';`, + }, + tags: ['!dev'], + render: ({}) => ( + <> +

MD

+ + Option 1 + Option 2 + Option 3 + Option 4 + + +

SM

+ + Option 1 + Option 2 + Option 3 + Option 4 + + +

XS

+ + Option 1 + Option 2 + Option 3 + Option 4 + + + ), +}; + +export const ThemeGenerator: Story = { + parameters: { + layout: 'fullscreen', + }, + tags: ['!dev'], + render: ({}) => ( + + Option 1 + Option 2 + Option 3 + Option 4 + + ), +}; diff --git a/packages/storybook/stories/components/button-group/documentation.mdx b/packages/storybook/stories/components/button-group/documentation.mdx new file mode 100644 index 0000000000..ada1ce44e1 --- /dev/null +++ b/packages/storybook/stories/components/button-group/documentation.mdx @@ -0,0 +1,115 @@ +import { Kbd } from '@ovhcloud/ods-react'; +import { Meta } from '@storybook/blocks'; +import * as ButtonGroupStories from './button-group.stories'; +import { Anatomy } from '../../../src/components/anatomy/Anatomy'; +import { Banner } from '../../../src/components/banner/Banner'; +import { Canvas } from '../../../src/components/canvas/Canvas'; +import { BestPractices } from '../../../src/components/bestPractices/BestPractices'; +import { Heading } from '../../../src/components/heading/Heading'; +import { IdentityCard } from '../../../src/components/identityCard/IdentityCard'; +import { StorybookLink } from '../../../src/components/storybookLink/StorybookLink'; +import { REACT_COMPONENTS_TITLE, STORY } from '../../../src/constants/meta'; + + + + + + + + + + + The **Button Group** component arranges multiple related button elements into a single, cohesive container. + It enables users to make selections from a set of related options, either allowing one or multiple active states depending on configuration. + + + + +Use a **Button Group** when multiple buttons represent a set of related options or actions. + +It is commonly used for: +- Toggle sets for text formatting or styling. +- Filtering or grouping options within a toolbar. +- Segmented controls in compact layouts. + + + + + + + + + +1. **ButtonGroup** +2. **Inactive buttons** +3. **Active buttons** + + + +**Button Group** buttons can be focused and triggered. +**Button Group** or its buttons can be disabled. + + + +Only one button in the group can be selected at a time. + +Selecting a new button automatically deselects the previously selected one. + + + +Several buttons can be selected simultaneously. + +Each button toggles independently between selected and unselected states. + + + +It may be allowed that no button is selected at all, when the user can clear all selections (e.g., "No filter applied"). + + + + + +When the **Button Group** receives focus, it is set on the first button. + +Disabled buttons are skipped in the focus order and cannot be activated. + +Focus remains within the group when navigating between items using arrow keys. + + + +Pressing Tab moves focus to the first button in the group. + +Pressing Shift + Tab moves focus to the previous focusable element outside the **Button Group**. + +Pressing Arrow Right moves focus to the next button in the group. + +Pressing Arrow Left moves focus to the previous item in the group. + +Pressing Home (or fn + Arrow Left) moves focus to the first button. + +Pressing End (or fn + Arrow Right) moves focus to the last button. + +Pressing Space or Enter activates or deactivates the focused button, updating the selection immediately. + + + +The **Button Group** component handles by itself the accessibility requirements regarding the control grouping. + +Though you need to ensure that each of your items follows the +Button Accessibility Best Practices. diff --git a/packages/storybook/stories/components/button-group/technical-information.mdx b/packages/storybook/stories/components/button-group/technical-information.mdx new file mode 100644 index 0000000000..875197c3e5 --- /dev/null +++ b/packages/storybook/stories/components/button-group/technical-information.mdx @@ -0,0 +1,55 @@ +import { Meta } from '@storybook/blocks'; +import cssVariable from '../../../../ods-react/src/components/button-group/documentation/cssVariable.json'; +import specificationsButtonGroup from '../../../../ods-react/src/components/button-group/documentation/button-group.json'; +import { Anatomy } from '../../../src/components/anatomy/Anatomy'; +import { Banner } from '../../../src/components/banner/Banner'; +import { Canvas } from '../../../src/components/canvas/Canvas'; +import { Heading } from '../../../src/components/heading/Heading'; +import { TechnicalSpecification } from '../../../src/components/technicalSpecification/TechnicalSpecification'; +import * as ButtonGroupStories from './button-group.stories'; + + + + + + + + + + + + + +1. **ButtonGroup** +2. **ButtonGroupItem** + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/storybook/stories/components/gallery.mdx b/packages/storybook/stories/components/gallery.mdx index 3c2186dd16..4228997172 100644 --- a/packages/storybook/stories/components/gallery.mdx +++ b/packages/storybook/stories/components/gallery.mdx @@ -3,6 +3,7 @@ import { Overview as Accordion } from './accordion/accordion.stories'; import { Overview as Badge } from './badge/badge.stories'; import { Overview as Breadcrumb } from './breadcrumb/breadcrumb.stories'; import { Overview as Button } from './button/button.stories'; +import { Overview as ButtonGroup } from './button-group/button-group.stories'; import { Overview as Card } from './card/card.stories'; import { Overview as Checkbox } from './checkbox/checkbox.stories'; import { Overview as Clipboard } from './clipboard/clipboard.stories'; @@ -61,6 +62,7 @@ import { REACT_COMPONENTS_TITLE } from '../../src/constants/meta'; { kind: REACT_COMPONENTS_TITLE.badge, name: 'Badge', story: Badge }, { kind: REACT_COMPONENTS_TITLE.breadcrumb, name: 'Breadcrumb', story: Breadcrumb }, { kind: REACT_COMPONENTS_TITLE.button, name: 'Button', story: Button }, + { kind: REACT_COMPONENTS_TITLE.buttonGroup, name: 'Button Group', story: ButtonGroup }, { kind: REACT_COMPONENTS_TITLE.card, name: 'Card', story: Card }, { kind: REACT_COMPONENTS_TITLE.checkbox, name: 'Checkbox', story: Checkbox }, { kind: REACT_COMPONENTS_TITLE.clipboard, name: 'Clipboard', story: Clipboard }, diff --git a/scripts/component-generator/templates/storybook/documentation.mdx.hbs b/scripts/component-generator/templates/storybook/documentation.mdx.hbs index d7e87f9b51..9139793a07 100644 --- a/scripts/component-generator/templates/storybook/documentation.mdx.hbs +++ b/scripts/component-generator/templates/storybook/documentation.mdx.hbs @@ -15,7 +15,7 @@ import { REACT_COMPONENTS_TITLE, STORY } from '../../../src/constants/meta'; _**{{> ComponentName }}** TODO some text about the component_ - ComponentName }}Stories.Overview } sourceState='none' /> + ComponentName }}Stories.Overview } sourceState="none" /> diff --git a/scripts/component-generator/templates/storybook/{{ prefix-join prefix name }}.stories.tsx.hbs b/scripts/component-generator/templates/storybook/{{ prefix-join prefix name }}.stories.tsx.hbs index bf67d55773..a89d2b18fb 100644 --- a/scripts/component-generator/templates/storybook/{{ prefix-join prefix name }}.stories.tsx.hbs +++ b/scripts/component-generator/templates/storybook/{{ prefix-join prefix name }}.stories.tsx.hbs @@ -9,7 +9,7 @@ type Story = StoryObj<{{> ComponentName }}Prop>; const meta: Meta<{{> ComponentName }}Prop> = { component: {{> ComponentName }}, // subcomponents: { {{> ComponentName }}Xxx }, // Uncomment if sub components, otherwise remove - tags={ ['new'] } + tags: ['new'], title: 'React Components/{{> ComponentName }}', }; diff --git a/yarn.lock b/yarn.lock index a7dd603a69..682c882935 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4723,6 +4723,12 @@ __metadata: languageName: unknown linkType: soft +"@ovhcloud/ods-react-button-group@workspace:packages/ods-react/src/components/button-group": + version: 0.0.0-use.local + resolution: "@ovhcloud/ods-react-button-group@workspace:packages/ods-react/src/components/button-group" + languageName: unknown + linkType: soft + "@ovhcloud/ods-react-button@workspace:packages/ods-react/src/components/button": version: 0.0.0-use.local resolution: "@ovhcloud/ods-react-button@workspace:packages/ods-react/src/components/button"