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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions docs/src/pages/components/segmented-control.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,21 @@ import PropsTable from 'components/PropsTable'

A segmented control is a set of two or more button segments.
Within the control, all segments are equal in width.

A segmented control is often used to switch between views of some data.
Use a segmented control only when the options are predefined and are at most 4 options.

## Design guidelines

By default the segmented control has a height of `32px` (the same as a button).
It is possible to change this to any height and the text style and spacing will adjust.
You should however keep things on the `8px` grid or in some cases the `4px` grid.
You should only need the following recommended heights.

### Recommended heights
### Sizes

* 24px
* 32px — default height
* 36px
* 40px
* `'small'` (24px)
* `'medium'` (32px — default height)
* `'large'` (40px)

## Basic

Expand Down Expand Up @@ -48,7 +47,7 @@ You should only need the following recommended heights.

## Small SegmentedControl example

The `SegmentedControl` will automatically chose the text style to match whatever height is passed.
The `SegmentedControl` will automatically chose the text style to match whatever size is passed.

```jsx
<Component
Expand All @@ -61,7 +60,7 @@ The `SegmentedControl` will automatically chose the text style to match whatever
<SegmentedControl
name="switch"
width={80}
height={24}
size="small"
options={state.options}
value={state.value}
onChange={value => setState({ value })}
Expand All @@ -70,4 +69,4 @@ The `SegmentedControl` will automatically chose the text style to match whatever
</Component>
```

<PropsTable of="SegmentedControl" />
<PropsTable of="SegmentedControl" />
11 changes: 11 additions & 0 deletions docs/src/pages/get-started/v6-migration-guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ that you're seeing that we might have missed!
- [No more `isSolid` prop on `<Badge />` or `<Pill />` or `<Avatar />`](#is-solid)
- [No more `<BackButton />` component](#back-button)
- [No more `marginTop="default"` on `<Paragraph />` or `<Heading />`](#no-more-default-margin-top)
- [`<SegmentedControl />`](#segmented-control)

#### No more `css` prop {#css-prop}

Expand Down Expand Up @@ -89,3 +90,13 @@ margin and applying that as standard in your codebase.
- <Heading marginTop="default"> ... </Heading>
+ <Heading marginTop={majorScale(3)}> ... </Heading>
```

#### `<SegmentedControl />` {#segmented-control}

We're deprecating the `SegmentedControl` in the next major version of Evergreen as we've seen it cause confusion for when to use it instead of other form controls or navigation components.

The internals have been slightly modified to make use of the new low-level `Group` component that implements the [WAI-ARIA Group role](https://www.w3.org/TR/wai-aria-1.1/#group).

This means that each option is represented as `Button`. If you were extracting values from form submission that included this component you may have changes to make.

Additionally, we've unified the sizing API so SegmentedControl also accepts a `size` prop just like Button, TextInput, Group, etc.
6 changes: 5 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1307,16 +1307,20 @@ export interface SearchTableHeaderCellOwnProps extends TableHeaderCellOwnProps {
export type SearchTableHeaderCellProps = PolymorphicBoxProps<'div', SearchTableHeaderCellOwnProps>
export declare const SearchTableHeaderCell: BoxComponent<SearchTableHeaderCellOwnProps, 'div'>

/** @deprecated This component will be removed in the next major version of Evergreen */
export interface SegmentedControlOwnProps {
options: Array<{ label: string, value: NonNullable<SegmentedControlOwnProps['value']> }>
value?: number | string | boolean
defaultValue?: number | string | boolean
onChange: (value: NonNullable<SegmentedControlOwnProps['value']>) => void
name?: string
height?: number
size?: 'small' | 'medium' | 'large'
}

/** @deprecated This component will be removed in the next major version of Evergreen */
export type SegmentedControlProps = PolymorphicBoxProps<'div', SegmentedControlOwnProps>

/** @deprecated This component will be removed in the next major version of Evergreen */
export declare const SegmentedControl: BoxComponent<SegmentedControlOwnProps, 'div'>

export interface SelectOwnProps {
Expand Down
59 changes: 31 additions & 28 deletions src/group/src/Group.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { memo } from 'react'
import React, { memo, forwardRef } from 'react'
import cx from 'classnames'
import PropTypes from 'prop-types'
import Box from 'ui-box'
Expand All @@ -19,38 +19,41 @@ const internalStyles = {
* Accessible `Group` component to identify a set of inputs/elements. Implements the WAI-ARIA Group Role
* @see {@link https://www.w3.org/TR/wai-aria-1.1/#group}
*/
const Group = memo(function Group(props) {
const { children, className, size, ...restProps } = props
const Group = memo(
forwardRef(function Group(props, ref) {
const { children, className, size, ...restProps } = props

const { className: themedClassName, ...styleProps } = useStyleConfig(
'Group',
{ size },
pseudoSelectors,
internalStyles
)
const { className: themedClassName, ...styleProps } = useStyleConfig(
'Group',
{ size },
pseudoSelectors,
internalStyles
)

const enhancedChildren = React.Children.map(children, child => {
if (!React.isValidElement(child)) {
return child
}
const enhancedChildren = React.Children.map(children, child => {
if (!React.isValidElement(child)) {
return child
}

return React.cloneElement(child, {
// Prefer more granularly defined props if present
size: child.props.size || size
return React.cloneElement(child, {
// Prefer more granularly defined props if present
size: child.props.size || size
})
})
})

return (
<Box
className={cx(className, themedClassName)}
role="group"
{...styleProps}
{...restProps}
>
{enhancedChildren}
</Box>
)
})
return (
<Box
className={cx(className, themedClassName)}
role="group"
ref={ref}
{...styleProps}
{...restProps}
>
{enhancedChildren}
</Box>
)
})
)

Group.propTypes = {
children: PropTypes.node.isRequired,
Expand Down
83 changes: 45 additions & 38 deletions src/segmented-control/src/SegmentedControl.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
import React, { memo, forwardRef, useState, useEffect } from 'react'
import React, {
memo,
forwardRef,
useState,
useEffect,
useCallback
} from 'react'
import PropTypes from 'prop-types'
import Box, { spacing, position, layout, dimensions } from 'ui-box'
import { spacing, position, layout, dimensions } from 'ui-box'
import { Button } from '../../buttons'
import { Group } from '../../group'
import { useId } from '../../hooks'
import safeInvoke from '../../lib/safe-invoke'
import { minorScale } from '../../scales'
import { useTheme } from '../../theme'
import SegmentedControlRadio from './SegmentedControlRadio'
import warning from '../../lib/warning'

const SegmentedControl = memo(
forwardRef(function SegmentedControl(props, ref) {
const {
defaultValue,
disabled,
height = 32,
height,
name,
onChange,
options,
size,
value,
...rest
} = props

const groupName = useId('SegmentedControl')

const {
tokens: { colors }
} = useTheme()
if (process.env.NODE_ENV !== 'production') {
warning(
true,
'<SegmentedControl> is deprecated and will be removed in the next major verison of Evergreen. Prefer Tabs for navigational elements, or form components / button groups for other use cases.'
)
}

const isControlled = () => {
return typeof value !== 'undefined' && value !== null
Expand All @@ -48,43 +58,40 @@ const SegmentedControl = memo(
}
}, [value])

const handleChange = newValue => {
// Save a render cycle when it's a controlled input
if (!isControlled()) {
setActiveValue(newValue)
}
const handleChange = useCallback(
event => {
event.preventDefault()
const newValue = event.target.value

safeInvoke(onChange, newValue)
}
// Save a render cycle when it's a controlled input
if (!isControlled()) {
setActiveValue(newValue)
}

safeInvoke(onChange, newValue)
},
[onChange]
)

return (
<Box
display="flex"
boxShadow={`inset 0 0 0 1px ${colors.gray400}`}
backgroundColor="white"
borderRadius={minorScale(1)}
marginRight={-1}
height={height}
ref={ref}
{...rest}
>
<Group ref={ref} display="flex" {...rest}>
{options.map((option, index) => (
<SegmentedControlRadio
<Button
key={option.value}
id={groupName + index}
name={name || groupName}
label={option.label}
value={String(option.value)}
height={height - minorScale(2)}
checked={activeValue === option.value}
onChange={handleChange.bind(null, option.value)}
appearance="default"
isFirstItem={index === 0}
isLastItem={index === options.length - 1}
disabled={disabled}
/>
size={size}
height={height}
isActive={activeValue === String(option.value)}
onClick={handleChange}
flex="1"
>
{option.label}
</Button>
))}
</Box>
</Group>
)
})
)
Expand Down Expand Up @@ -141,9 +148,9 @@ SegmentedControl.propTypes = {
name: PropTypes.string,

/**
* The height of the Segmented Control.
* The size of the Segmented Control.
*/
height: PropTypes.number,
size: PropTypes.oneOf(['small', 'medium', 'large']),

/**
* When true, the Segmented Control is disabled.
Expand Down
Loading