From 2b5c86e59f16d697433e3302e78d623a177a061e Mon Sep 17 00:00:00 2001 From: Mike Perrotti Date: Mon, 25 Jul 2022 16:03:37 -0400 Subject: [PATCH] SegmentedControl variant prop (#2164) * renders a tooltip for icon-only segmented control buttons * implements responsive variant prop * adds tests * minor story tweaks * refactor useMatchMedia * adds useMatchMedia tests, fixes useMatchMedia bugs, updates SegmentedControl tests * removes aria attributes from Storybook controls * rm irrelevant 'TODO' comments * adds changeset * adds helpful comments * fixes button font-size in Safari * updates snapshots * addresses PR feedback * Update docs/content/SegmentedControl.mdx Co-authored-by: Cole Bemis * Update docs/content/SegmentedControl.mdx Co-authored-by: Cole Bemis * Update .changeset/pretty-students-judge.md Co-authored-by: Josep Martins * bumps @primer/primitives to version with segmented control variables * corrects storybook knobs to match current API * rm 'wide' key from 'variant' prop in props table * fix bad merge in SegmentedControl * adds more context to a11y issues with the tooltip implementation * adds changeset Co-authored-by: Cole Bemis Co-authored-by: Josep Martins --- .changeset/pretty-students-judge.md | 5 + docs/content/SegmentedControl.mdx | 12 +- package-lock.json | 24 ++- package.json | 1 + .../SegmentedControl.test.tsx | 137 ++++++++++++- src/SegmentedControl/SegmentedControl.tsx | 190 ++++++++++++++++-- .../SegmentedControlButton.tsx | 7 +- .../SegmentedControlIconButton.tsx | 41 +++- .../SegmentedControl.test.tsx.snap | 3 + src/SegmentedControl/examples.stories.tsx | 128 ++++++++++-- src/SegmentedControl/fixtures.stories.tsx | 3 +- .../getSegmentedControlStyles.ts | 61 +++--- src/__tests__/hooks/useMatchMedia.test.tsx | 55 +++++ src/hooks/useMatchMedia.ts | 107 ++++++++++ src/utils/types/ViewportRangeKeys.ts | 1 + 15 files changed, 684 insertions(+), 91 deletions(-) create mode 100644 .changeset/pretty-students-judge.md create mode 100644 src/__tests__/hooks/useMatchMedia.test.tsx create mode 100644 src/hooks/useMatchMedia.ts create mode 100644 src/utils/types/ViewportRangeKeys.ts diff --git a/.changeset/pretty-students-judge.md b/.changeset/pretty-students-judge.md new file mode 100644 index 00000000000..eb858eb30c8 --- /dev/null +++ b/.changeset/pretty-students-judge.md @@ -0,0 +1,5 @@ +--- +'@primer/react': minor +--- + +Adds support for a responsive 'variant' prop to the SegmentedControl component diff --git a/docs/content/SegmentedControl.mdx b/docs/content/SegmentedControl.mdx index 65b2b220701..55084f64f74 100644 --- a/docs/content/SegmentedControl.mdx +++ b/docs/content/SegmentedControl.mdx @@ -43,7 +43,7 @@ description: Use a segmented control to let users select an option from a short ### With labels hidden on smaller viewports ```jsx live drafts - + Preview @@ -55,7 +55,7 @@ description: Use a segmented control to let users select an option from a short ### Convert to a dropdown on smaller viewports ```jsx live drafts - + Preview @@ -161,11 +161,11 @@ description: Use a segmented control to let users select an option from a short /> diff --git a/package-lock.json b/package-lock.json index 4b2f0ec4a2a..a6c699015eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@primer/react", - "version": "35.4.0", + "version": "35.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@primer/react", - "version": "35.4.0", + "version": "35.5.0", "license": "MIT", "dependencies": { "@primer/behaviors": "^1.1.1", @@ -93,6 +93,7 @@ "husky": "7.0.4", "jest": "27.4.5", "jest-axe": "5.0.1", + "jest-matchmedia-mock": "1.1.0", "jest-styled-components": "6.3.4", "jscodeshift": "0.13.0", "lint-staged": "12.1.2", @@ -22890,6 +22891,18 @@ "node": ">=8" } }, + "node_modules/jest-matchmedia-mock": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jest-matchmedia-mock/-/jest-matchmedia-mock-1.1.0.tgz", + "integrity": "sha512-REnJRsOSCMpGAlkxmvVTqEBpregyFVi9MPEH3N83W1yLKzDdNehtCkcdDZduXq74PLtfI+11NyM4zKCK5ynV9g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "jest": ">=13" + } + }, "node_modules/jest-message-util": { "version": "27.4.2", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.2.tgz", @@ -52103,6 +52116,13 @@ } } }, + "jest-matchmedia-mock": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jest-matchmedia-mock/-/jest-matchmedia-mock-1.1.0.tgz", + "integrity": "sha512-REnJRsOSCMpGAlkxmvVTqEBpregyFVi9MPEH3N83W1yLKzDdNehtCkcdDZduXq74PLtfI+11NyM4zKCK5ynV9g==", + "dev": true, + "requires": {} + }, "jest-message-util": { "version": "27.4.2", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.2.tgz", diff --git a/package.json b/package.json index d2c68cc7e4c..11b02d75c3c 100644 --- a/package.json +++ b/package.json @@ -163,6 +163,7 @@ "jest": "27.4.5", "jest-axe": "5.0.1", "jest-styled-components": "6.3.4", + "jest-matchmedia-mock": "1.1.0", "jscodeshift": "0.13.0", "lint-staged": "12.1.2", "lodash.isempty": "4.4.0", diff --git a/src/SegmentedControl/SegmentedControl.test.tsx b/src/SegmentedControl/SegmentedControl.test.tsx index 6cdfe9fc4ac..cc41d98004f 100644 --- a/src/SegmentedControl/SegmentedControl.test.tsx +++ b/src/SegmentedControl/SegmentedControl.test.tsx @@ -1,10 +1,15 @@ import React from 'react' import '@testing-library/jest-dom/extend-expect' -import {fireEvent, render} from '@testing-library/react' +import MatchMediaMock from 'jest-matchmedia-mock' +import {render, fireEvent, waitFor} from '@testing-library/react' import {EyeIcon, FileCodeIcon, PeopleIcon} from '@primer/octicons-react' import userEvent from '@testing-library/user-event' import {behavesAsComponent, checkExports, checkStoriesForAxeViolations} from '../utils/testing' import {SegmentedControl} from '.' // TODO: update import when we move this to the global index +import theme from '../theme' +import {BaseStyles, SSRProvider, ThemeProvider} from '..' +import {act} from 'react-test-renderer' +import {viewportRanges} from '../hooks/useMatchMedia' const segmentData = [ {label: 'Preview', id: 'preview', iconLabel: 'EyeIcon', icon: () => }, @@ -12,12 +17,19 @@ const segmentData = [ {label: 'Blame', id: 'blame', iconLabel: 'PeopleIcon', icon: () => } ] -// TODO: improve test coverage +let matchMedia: MatchMediaMock + describe('SegmentedControl', () => { const mockWarningFn = jest.fn() beforeAll(() => { jest.spyOn(global.console, 'warn').mockImplementation(mockWarningFn) + matchMedia = new MatchMediaMock() + }) + + afterAll(() => { + jest.clearAllMocks() + matchMedia.clear() }) behavesAsComponent({ @@ -54,6 +66,47 @@ describe('SegmentedControl', () => { expect(selectedButton?.getAttribute('aria-current')).toBe('true') }) + it('renders the dropdown variant', () => { + act(() => { + matchMedia.useMediaQuery(viewportRanges.narrow) + }) + + const {getByText} = render( + + {segmentData.map(({label}, index) => ( + + {label} + + ))} + + ) + const button = getByText(segmentData[1].label) + + expect(button).toBeInTheDocument() + expect(button.closest('button')?.getAttribute('aria-haspopup')).toBe('true') + }) + + it('renders the hideLabels variant', () => { + act(() => { + matchMedia.useMediaQuery(viewportRanges.narrow) + }) + + const {getByLabelText} = render( + + {segmentData.map(({label, icon}, index) => ( + + {label} + + ))} + + ) + + for (const datum of segmentData) { + const labelledButton = getByLabelText(datum.label) + expect(labelledButton).toBeDefined() + } + }) + it('renders the first segment as selected if no child has the `selected` prop passed', () => { const {getByText} = render( @@ -190,6 +243,83 @@ describe('SegmentedControl', () => { expect(document.activeElement?.id).toEqual(initialFocusButtonNode.id) }) + it('calls onChange with index of clicked segment button when using the dropdown variant', async () => { + act(() => { + matchMedia.useMediaQuery(viewportRanges.narrow) + }) + const handleChange = jest.fn() + const component = render( + + + + + {segmentData.map(({label}, index) => ( + + {label} + + ))} + + + + + ) + const button = component.getByText(segmentData[0].label) + + fireEvent.click(button) + expect(handleChange).not.toHaveBeenCalled() + const menuItems = await waitFor(() => component.getAllByRole('menuitemradio')) + fireEvent.click(menuItems[1]) + + expect(handleChange).toHaveBeenCalledWith(1) + }) + + it('calls segment button onClick if it is passed when using the dropdown variant', async () => { + act(() => { + matchMedia.useMediaQuery(viewportRanges.narrow) + }) + const handleClick = jest.fn() + const component = render( + + + + + {segmentData.map(({label}, index) => ( + + {label} + + ))} + + + + + ) + const button = component.getByText(segmentData[0].label) + + fireEvent.click(button) + expect(handleClick).not.toHaveBeenCalled() + const menuItems = await waitFor(() => component.getAllByRole('menuitemradio')) + fireEvent.click(menuItems[1]) + + expect(handleClick).toHaveBeenCalled() + }) + + it('warns users if they try to use the hideLabels variant without a leadingIcon', () => { + act(() => { + matchMedia.useMediaQuery(viewportRanges.narrow) + }) + const consoleSpy = jest.spyOn(global.console, 'warn') + render( + + {segmentData.map(({label}, index) => ( + + {label} + + ))} + + ) + expect(consoleSpy).toHaveBeenCalled() + }) + it('should warn the user if they neglect to specify a label for the segmented control', () => { render( @@ -205,5 +335,6 @@ describe('SegmentedControl', () => { }) }) -checkStoriesForAxeViolations('examples', '../SegmentedControl/') +// TODO: uncomment these tests after we fix a11y for the Tooltip component +// checkStoriesForAxeViolations('examples', '../SegmentedControl/') checkStoriesForAxeViolations('fixtures', '../SegmentedControl/') diff --git a/src/SegmentedControl/SegmentedControl.tsx b/src/SegmentedControl/SegmentedControl.tsx index 80fc745364e..df44db70f32 100644 --- a/src/SegmentedControl/SegmentedControl.tsx +++ b/src/SegmentedControl/SegmentedControl.tsx @@ -1,10 +1,14 @@ import React, {RefObject, useRef} from 'react' import Button, {SegmentedControlButtonProps} from './SegmentedControlButton' import SegmentedControlIconButton, {SegmentedControlIconButtonProps} from './SegmentedControlIconButton' -import {Box, useTheme} from '..' +import {ActionList, ActionMenu, Box, useTheme} from '..' import {merge, SxProp} from '../sx' +import useMatchMedia from '../hooks/useMatchMedia' +import {ViewportRangeKeys} from '../utils/types/ViewportRangeKeys' import {FocusKeys, FocusZoneHookSettings, useFocusZone} from '../hooks/useFocusZone' +type WidthOnlyViewportRangeKeys = Exclude + type SegmentedControlProps = { 'aria-label'?: string 'aria-labelledby'?: string @@ -12,7 +16,9 @@ type SegmentedControlProps = { /** Whether the control fills the width of its parent */ fullWidth?: boolean /** The handler that gets called when a segment is selected */ - onChange?: (selectedIndex: number) => void // TODO: consider making onChange required if we force this component to be controlled + onChange?: (selectedIndex: number) => void + /** Configure alternative ways to render the control when it gets rendered in tight spaces */ + variant?: 'default' | Partial> } & SxProp const getSegmentedControlStyles = (props?: SegmentedControlProps) => ({ @@ -28,7 +34,6 @@ const getSegmentedControlStyles = (props?: SegmentedControlProps) => ({ height: '32px' // TODO: use primitive `primer.control.medium.size` when it is available }) -// TODO: implement `variant` prop for responsive behavior const Root: React.FC = ({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, @@ -36,16 +41,48 @@ const Root: React.FC = ({ fullWidth, onChange, sx: sxProp = {}, + variant, ...rest }) => { const segmentedControlContainerRef = useRef(null) const {theme} = useTheme() - const selectedChildren = React.Children.toArray(children).map( + const mediaQueryMatches = useMatchMedia(Object.keys(variant || {}) as WidthOnlyViewportRangeKeys[]) + const mediaQueryMatchesKeys = mediaQueryMatches + ? (Object.keys(mediaQueryMatches) as WidthOnlyViewportRangeKeys[]).filter( + viewportRangeKey => typeof mediaQueryMatches === 'object' && mediaQueryMatches[viewportRangeKey] + ) + : [] + + const selectedSegments = React.Children.toArray(children).map( child => React.isValidElement(child) && child.props.selected ) - const hasSelectedButton = selectedChildren.some(isSelected => isSelected) - const selectedIndex = hasSelectedButton ? selectedChildren.indexOf(true) : 0 + const hasSelectedButton = selectedSegments.some(isSelected => isSelected) + const selectedIndex = hasSelectedButton ? selectedSegments.indexOf(true) : 0 + const selectedChild = React.isValidElement( + React.Children.toArray(children)[selectedIndex] + ) + ? React.Children.toArray(children)[selectedIndex] + : undefined + const getChildIcon = (childArg: React.ReactNode) => { + if ( + React.isValidElement(childArg) && + childArg.type === Button && + childArg.props.leadingIcon + ) { + return childArg.props.leadingIcon + } + + return React.isValidElement(childArg) ? childArg.props.icon : null + } + const getChildText = (childArg: React.ReactNode) => { + if (React.isValidElement(childArg) && childArg.type === Button) { + return childArg.props.children + } + + return React.isValidElement(childArg) ? childArg.props['aria-label'] : null + } + const sx = merge( getSegmentedControlStyles({ fullWidth @@ -79,7 +116,75 @@ const Root: React.FC = ({ ) } - return ( + // Since we can have multiple media query matches for `variant` (e.g.: 'regular' and 'wide'), + // we need to pick which variant we actually show. + const getVariantToRender = () => { + // If no variant was passed, return 'default' + if (!variant || variant === 'default') { + return 'default' + } + + // Prioritize viewport range keys that override the 'regular' range in order of + // priorty from lowest to highest + // Orientation keys beat 'wide' because they are more specific. + const viewportRangeKeysByPriority: ViewportRangeKeys[] = ['wide', 'portrait', 'landscape'] + + // Filter the viewport range keys to only include those that: + // - are in the priority list + // - have a variant set + const variantPriorityKeys = mediaQueryMatchesKeys.filter(key => { + return viewportRangeKeysByPriority.includes(key) && variant[key] + }) + + // If we have to pick from multiple variants and one or more of them overrides 'regular', + // use the last key from the filtered list. + if (mediaQueryMatchesKeys.length > 1 && variantPriorityKeys.length) { + return variant[variantPriorityKeys[variantPriorityKeys.length - 1]] + } + + // Otherwise, use the variant for the first matching media query + return typeof mediaQueryMatches === 'object' && variant[mediaQueryMatchesKeys[0]] + } + + return getVariantToRender() === 'dropdown' ? ( + // Render the 'dropdown' variant of the SegmentedControlButton or SegmentedControlIconButton + + {getChildText(selectedChild)} + + + {React.Children.map(children, (child, index) => { + const ChildIcon = getChildIcon(child) + // Not a valid child element - skip rendering + if (!React.isValidElement(child)) { + return null + } + + return ( + | React.KeyboardEvent) => { + onChange(index) + // TODO: figure out a way around the typecasting + child.props.onClick && child.props.onClick(event as React.MouseEvent) + } + : // TODO: figure out a way around the typecasting + (child.props.onClick as ( + event: React.MouseEvent | React.KeyboardEvent + ) => void) + } + > + {ChildIcon && } {getChildText(child)} + + ) + })} + + + + ) : ( + // Render a segmented control = ({ ref={segmentedControlContainerRef as RefObject} {...rest} > - {React.Children.map(children, (child, i) => { - if (React.isValidElement(child)) { - return React.cloneElement(child, { - onClick: onChange - ? (e: React.MouseEvent) => { - onChange(i) - child.props.onClick && child.props.onClick(e) + {React.Children.map(children, (child, index) => { + // Not a valid child element - skip rendering child + if (!React.isValidElement(child)) { + return null + } + const sharedChildProps = { + onClick: onChange + ? (event: React.MouseEvent) => { + onChange(index) + child.props.onClick && child.props.onClick(event) + } + : child.props.onClick, + selected: index === selectedIndex, + sx: { + '--separator-color': + index === selectedIndex || index === selectedIndex - 1 ? 'transparent' : theme?.colors.border.default + } as React.CSSProperties + } + + // Render the 'hideLabels' variant of the SegmentedControlButton + if ( + getVariantToRender() === 'hideLabels' && + React.isValidElement(child) && + child.type === Button + ) { + const { + 'aria-label': childAriaLabel, + leadingIcon, + children: childPropsChildren, + ...restChildProps + } = child.props + if (!leadingIcon) { + // eslint-disable-next-line no-console + console.warn('A `leadingIcon` prop is required when hiding visible labels') + } else { + return ( + + ) + } } + + // Render the children as-is and add the shared child props + return React.cloneElement(child, sharedChildProps) })} ) @@ -111,6 +255,10 @@ const Root: React.FC = ({ Root.displayName = 'SegmentedControl' +Root.defaultProps = { + variant: 'default' +} + export const SegmentedControl = Object.assign(Root, { Button, IconButton: SegmentedControlIconButton diff --git a/src/SegmentedControl/SegmentedControlButton.tsx b/src/SegmentedControl/SegmentedControlButton.tsx index 7aacd5cb4d4..727fed82c03 100644 --- a/src/SegmentedControl/SegmentedControlButton.tsx +++ b/src/SegmentedControl/SegmentedControlButton.tsx @@ -3,16 +3,17 @@ import {IconProps} from '@primer/octicons-react' import styled from 'styled-components' import {Box} from '..' import sx, {merge, SxProp} from '../sx' -import getSegmentedControlButtonStyles from './getSegmentedControlStyles' +import {getSegmentedControlButtonStyles} from './getSegmentedControlStyles' export type SegmentedControlButtonProps = { - children?: string + /** The visible label rendered in the button */ + children: string /** Whether the segment is selected */ selected?: boolean /** The leading icon comes before item label */ leadingIcon?: React.FunctionComponent } & SxProp & - HTMLAttributes + HTMLAttributes const SegmentedControlButtonStyled = styled.button` ${sx}; diff --git a/src/SegmentedControl/SegmentedControlIconButton.tsx b/src/SegmentedControl/SegmentedControlIconButton.tsx index 3f6f9f0f075..84377525231 100644 --- a/src/SegmentedControl/SegmentedControlIconButton.tsx +++ b/src/SegmentedControl/SegmentedControlIconButton.tsx @@ -2,7 +2,12 @@ import React, {HTMLAttributes} from 'react' import {IconProps} from '@primer/octicons-react' import styled from 'styled-components' import sx, {merge, SxProp} from '../sx' -import getSegmentedControlButtonStyles from './getSegmentedControlStyles' +import { + borderedSegment, + getSegmentedControlButtonStyles, + directChildLayoutAdjustments +} from './getSegmentedControlStyles' +import Tooltip from '../Tooltip' export type SegmentedControlIconButtonProps = { 'aria-label': string @@ -11,16 +16,20 @@ export type SegmentedControlIconButtonProps = { /** Whether the segment is selected */ selected?: boolean } & SxProp & - HTMLAttributes + HTMLAttributes const SegmentedControlIconButtonStyled = styled.button` ${sx}; ` -// TODO: get tooltips working: -// - by default, the tooltip shows the `ariaLabel` content -// - allow users to pass custom tooltip text +// TODO: update this component to be accessible when we update the Tooltip component +// - we wouldn't render tooltip content inside a pseudoelement +// - users can pass custom tooltip text in addition to `ariaLabel` +// +// See Slack thread: https://github.slack.com/archives/C02NUUQ9C30/p1656444474509599 +// export const SegmentedControlIconButton: React.FC = ({ + 'aria-label': ariaLabel, icon: Icon, selected, sx: sxProp = {}, @@ -29,11 +38,23 @@ export const SegmentedControlIconButton: React.FC - - - - + + + + + + + ) } diff --git a/src/SegmentedControl/__snapshots__/SegmentedControl.test.tsx.snap b/src/SegmentedControl/__snapshots__/SegmentedControl.test.tsx.snap index 8760386920a..637c3c928bd 100644 --- a/src/SegmentedControl/__snapshots__/SegmentedControl.test.tsx.snap +++ b/src/SegmentedControl/__snapshots__/SegmentedControl.test.tsx.snap @@ -29,6 +29,7 @@ exports[`SegmentedControl renders consistently 1`] = ` -ms-flex-positive: 1; flex-grow: 1; font-family: inherit; + font-size: 14px; font-weight: 600; margin-top: -1px; margin-bottom: -1px; @@ -119,6 +120,7 @@ exports[`SegmentedControl renders consistently 1`] = ` -ms-flex-positive: 1; flex-grow: 1; font-family: inherit; + font-size: 14px; font-weight: 400; margin-top: -1px; margin-bottom: -1px; @@ -217,6 +219,7 @@ exports[`SegmentedControl renders consistently 1`] = ` -ms-flex-positive: 1; flex-grow: 1; font-family: inherit; + font-size: 14px; font-weight: 400; margin-top: -1px; margin-bottom: -1px; diff --git a/src/SegmentedControl/examples.stories.tsx b/src/SegmentedControl/examples.stories.tsx index 43b5cc10b40..163d1ae71f3 100644 --- a/src/SegmentedControl/examples.stories.tsx +++ b/src/SegmentedControl/examples.stories.tsx @@ -2,13 +2,51 @@ import React, {useState} from 'react' import {Meta} from '@storybook/react' import {BaseStyles, ThemeProvider} from '..' -import {ComponentProps} from '../utils/types' import {SegmentedControl} from '.' import {EyeIcon, FileCodeIcon, PeopleIcon} from '@primer/octicons-react' +import Box from '../Box' -type Args = ComponentProps +type ResponsiveVariantOptions = 'dropdown' | 'hideLabels' | 'default' +type Args = { + fullWidth?: boolean + variantAtNarrow: ResponsiveVariantOptions + variantAtNarrowLandscape: ResponsiveVariantOptions + variantAtRegular: ResponsiveVariantOptions + variantAtWide: ResponsiveVariantOptions + variantAtPortrait: ResponsiveVariantOptions + variantAtLandscape: ResponsiveVariantOptions +} + +const excludedControlKeys = [ + 'aria-label', + 'onChange', + 'sx', + 'variant', + 'aria-label', + 'aria-labelledby', + 'aria-describedby' +] -const excludedControlKeys = ['aria-label', 'onChange', 'sx'] +const variantOptions = ['dropdown', 'hideLabels', 'default'] + +const parseVarientFromArgs = (args: Args) => { + const { + variantAtNarrow, + variantAtNarrowLandscape, + variantAtRegular, + variantAtWide, + variantAtPortrait, + variantAtLandscape + } = args + return { + narrow: variantAtNarrow, + narrowLandscape: variantAtNarrowLandscape, + regular: variantAtRegular, + wide: variantAtWide, + portrait: variantAtPortrait, + landscape: variantAtLandscape + } +} export default { title: 'SegmentedControl/examples', @@ -20,10 +58,52 @@ export default { type: 'boolean' } }, - loading: { - defaultValue: false, + variantAtNarrow: { + name: 'variant.narrow', + defaultValue: 'default', control: { - type: 'boolean' + type: 'radio', + options: variantOptions + } + }, + variantAtNarrowLandscape: { + name: 'variant.narrowLandscape', + defaultValue: 'default', + control: { + type: 'radio', + options: variantOptions + } + }, + variantAtRegular: { + name: 'variant.regular', + defaultValue: 'default', + control: { + type: 'radio', + options: variantOptions + } + }, + variantAtWide: { + name: 'variant.wide', + defaultValue: 'default', + control: { + type: 'radio', + options: variantOptions + } + }, + variantAtPortrait: { + name: 'variant.portrait', + defaultValue: 'default', + control: { + type: 'radio', + options: variantOptions + } + }, + variantAtLandscape: { + name: 'variant.Landscape', + defaultValue: 'default', + control: { + type: 'radio', + options: variantOptions } } }, @@ -42,7 +122,7 @@ export default { } as Meta export const Default = (args: Args) => ( - + Preview Raw Blame @@ -56,7 +136,7 @@ export const Controlled = (args: Args) => { } return ( - + Preview Raw Blame @@ -65,19 +145,27 @@ export const Controlled = (args: Args) => { } export const WithIconsAndLabels = (args: Args) => ( - - - Preview - - Raw - Blame - + // padding needed to show Tooltip + // there is a separate initiative to change Tooltip to get positioned with useAnchoredPosition + + + + Preview + + Raw + Blame + + ) export const IconsOnly = (args: Args) => ( - - - - - + // padding needed to show Tooltip + // there is a separate initiative to change Tooltip to get positioned with useAnchoredPosition + + + + + + + ) diff --git a/src/SegmentedControl/fixtures.stories.tsx b/src/SegmentedControl/fixtures.stories.tsx index 888a8d36b20..5dce12899c9 100644 --- a/src/SegmentedControl/fixtures.stories.tsx +++ b/src/SegmentedControl/fixtures.stories.tsx @@ -20,8 +20,7 @@ export default { ] } as Meta -// TODO: make it possible to use FormControl -// - FormControl.Label needs to accept `id` prop +// TODO: make it possible to use FormControl as a wrapper for SegmentedControl // - FormControl.Label needs to accept a prop that lets it render an element that isn't a `