diff --git a/.changeset/actionlist2-actionmenu2-prefix-types.md b/.changeset/actionlist2-actionmenu2-prefix-types.md
new file mode 100644
index 00000000000..b7d4cd11224
--- /dev/null
+++ b/.changeset/actionlist2-actionmenu2-prefix-types.md
@@ -0,0 +1,21 @@
+---
+'@primer/react': major
+---
+
+`ActionList2` exported types are now prefixed with `ActionList`:
+
+```
+ListProps → ActionListProps
+GroupProps → ActionListGroupProps
+ItemProps → ActionListItemProps
+DescriptionProps → ActionListDescriptionProps
+LeadingVisualProps → ActionListLeadingVisualProps,
+TrailingVisualProps → ActionListTrailingVisualProps
+```
+
+`ActionMenu2` exported types are now prefixed with `ActionMenu`:
+
+```
+MenuButtonProps → ActionMenuButtonProps
+MenuAnchorProps → ActionMenuAnchorProps
+```
diff --git a/.changeset/deprecate-actionlistv1-promote-actionlistv2.md b/.changeset/deprecate-actionlistv1-promote-actionlistv2.md
new file mode 100644
index 00000000000..8aa0e6f82e0
--- /dev/null
+++ b/.changeset/deprecate-actionlistv1-promote-actionlistv2.md
@@ -0,0 +1,154 @@
+---
+'@primer/react': major
+---
+
+### ActionList
+
+ActionList has been rewritten with a composable API, design updates and accessibility fixes.
+
+See full list of props and examples: https://primer.style/react/ActionList
+
+
+
+ Before (v34) After (v35)
+
+
+
+
+```jsx
+
+```
+
+
+
+
+```jsx
+
+ New file
+ Copy link
+ Edit file
+
+ Delete file
+
+```
+
+
+
+
+
+
+```jsx
+ ,
+ text: 'mona',
+ description: 'Monalisa Octocat',
+ descriptionVariant: 'block'
+ },
+ {
+ key: '2',
+ leadingVisual: GearIcon,
+ text: 'View Settings',
+ trailingVisual: ArrowRightIcon
+ }
+ ]}
+/>
+```
+
+
+
+
+```jsx
+
+
+
+
+
+ github/primer
+
+
+
+
+
+ mona
+ Monalisa Octocat
+
+
+
+
+
+ View settings
+
+
+
+
+
+```
+
+
+
+
+
+
+```jsx
+
+```
+
+
+
+
+```jsx
+
+
+ repo:github/github
+
+
+
+
+ Table
+ Board Description>
+
+
+
+ View settings
+
+```
+
+
+
+
+
+To continue to use the deprecated API for now, change the import path to `@primer/react/deprecated`:
+
+```js
+import {ActionList} from '@primer/react/deprecated'
+```
+
+You can use the [one-time codemod](https://github.com/primer/react-migrate#readme) to change your import statements automatically.
diff --git a/.changeset/empty-pillows-hunt.md b/.changeset/empty-pillows-hunt.md
new file mode 100644
index 00000000000..922ed22e895
--- /dev/null
+++ b/.changeset/empty-pillows-hunt.md
@@ -0,0 +1,5 @@
+---
+'@primer/react': major
+---
+
+Prepare library for `v35`
diff --git a/.changeset/hip-buses-peel.md b/.changeset/hip-buses-peel.md
new file mode 100644
index 00000000000..8f801600bcd
--- /dev/null
+++ b/.changeset/hip-buses-peel.md
@@ -0,0 +1,5 @@
+---
+'@primer/react': minor
+---
+
+Adds CheckboxGroup and RadioGroup components to replace the ChoiceFieldset component
diff --git a/.changeset/nervous-pets-sleep.md b/.changeset/nervous-pets-sleep.md
new file mode 100644
index 00000000000..e29b04bf45b
--- /dev/null
+++ b/.changeset/nervous-pets-sleep.md
@@ -0,0 +1,116 @@
+---
+'@primer/react': major
+---
+
+
+
+The `CheckboxGroup` and `RadioGroup` components will be used to deprecate the `ChoiceFieldset` component. The deprecation of `ChoiceFieldset` also allows us to deprecate `ChoiceInputField`.
+
+`CheckboxGroup` and `RadioGroup` have the ability to render contextual content with your fieldset: labels, validation statuses, captions. It also handles the ARIA attributes that make the form controls accessible to assistive technology.
+
+
+
+ Before After
+
+
+
+
+```jsx
+import {ChoiceFieldset} from "@primer/react";
+
+// Multi-select
+
+ Prefered Primer component interface
+
+
+ Figma library
+ Primer CSS
+ Primer React components
+ Primer ViewComponents
+
+
+
+// Single select
+
+ Prefered Primer component interface
+
+
+ Figma library
+ Primer CSS
+ Primer React components
+ Primer ViewComponents
+
+
+
+```
+
+
+
+
+```jsx
+import {FormGroup, Checkbox} from "@primer/react";
+
+// Multi-select
+
+ Prefered Primer component interface
+
+
+ Figma
+
+
+
+ CSS
+
+
+
+ Primer React components
+
+
+
+ Primer ViewComponents
+
+
+
+// Single select
+
+ Prefered Primer component interface
+
+
+ Figma
+
+
+
+ CSS
+
+
+
+ Primer React components
+
+
+
+ Primer ViewComponents
+
+
+```
+
+
+
+
+
+Migration steps to CheckboxGroup and RadioGroup
+
+
+
+Upgrade to the new `CheckboxGroup` and `RadioGroup` components by referring to the examples in our documentation: [CheckboxGroup](https://primer.style/react/CheckboxGroup), [RadioGroup](https://primer.style/react/RadioGroup).
+
+or
+
+Continue using the deprecated `ChoiceFieldset` component :
+
+```js
+import {ChoiceFieldset} from '@primer/react/deprecated' // change your import statements
+```
+
+
+
+
diff --git a/.changeset/odd-apes-guess.md b/.changeset/odd-apes-guess.md
new file mode 100644
index 00000000000..e15f28b4809
--- /dev/null
+++ b/.changeset/odd-apes-guess.md
@@ -0,0 +1,12 @@
+---
+"@primer/react": major
+---
+
+`PageLayout` is being graduated from the `drafts` bundle to the `main` bundle.
+
+To upgrade, rewrite your imports accordingly:
+
+```diff
+- import {PageLayout} from '@primer/react/drafts'
++ import {PageLayout} from '@primer/react'
+```
diff --git a/.changeset/smooth-cameras-prove.md b/.changeset/smooth-cameras-prove.md
new file mode 100644
index 00000000000..9b6fbdbdc47
--- /dev/null
+++ b/.changeset/smooth-cameras-prove.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": major
+---
+
+Move deprecated components to deprecated folder
diff --git a/deprecated/package.json b/deprecated/package.json
new file mode 100644
index 00000000000..e7a211fd303
--- /dev/null
+++ b/deprecated/package.json
@@ -0,0 +1,9 @@
+{
+ "_comment1": "this is required only for typescript. once this is fixed https://github.com/microsoft/TypeScript/issues/33079 we can remove this hack",
+ "name": "@primer/react/deprecated",
+ "types": "../lib-esm/deprecated/index.d.ts",
+ "main": "../lib-esm/deprecated/index.js",
+ "type": "module",
+ "sideEffects": false
+ }
+
\ No newline at end of file
diff --git a/docs/content/ActionList.mdx b/docs/content/ActionList.mdx
index 7eb5066cca2..be09bd7efee 100644
--- a/docs/content/ActionList.mdx
+++ b/docs/content/ActionList.mdx
@@ -3,98 +3,447 @@ componentId: action_list
title: ActionList
status: Alpha
source: https://github.com/primer/react/tree/main/src/ActionList
+storybook: '/react/storybook?path=/story/composite-components-actionlist'
+description: An ActionList is a list of items that can be activated or selected. ActionList is the base component for many menu-type components, including DropdownMenu and ActionMenu.
---
-An `ActionList` is a list of items which can be activated or selected. `ActionList` is the base component for many of our menu-type components, including `DropdownMenu` and `ActionMenu`.
+import {Avatar} from '@primer/react'
+import {ActionList} from '@primer/react'
+import {LinkIcon, AlertIcon, ArrowRightIcon} from '@primer/octicons-react'
+import InlineCode from '@primer/gatsby-theme-doctocat/src/components/inline-code'
-## Minimal example
+
+
+
+
+
+
+ github.com/primer
+
+ A React implementation of GitHub's Primer Design System
+
+
+
+
+
+
+ mona
+ Monalisa Octocat
+
+
+
+
+
+ 4 vulnerabilities
+
+
+
+
+
+
+
+```js
+import {ActionList} from '@primer/react'
+```
+
+## Examples
+
+### Minimal example
```jsx live
-
+
+ New file
+ Copy link
+ Edit file
+
+ Delete file
+
```
-## Example with grouped items
+### With leading visual
+
+Leading visuals are optional and appear at the start of an item. They can be octicons, avatars, and other custom visuals that fit a small area.
+
```jsx live
-
+
+
+
+ github.com/primer
+
+
+
+ 4 vulnerabilities
+
+
+
+ mona
+
+
+
```
-## Example with custom item renderer
-
-```jsx
-
- },
- {
- text: 'React Router link',
- renderItem: props =>
- },
- {
- text: 'NextJS style',
- renderItem: props => (
-
-
-
- )
- }
- ]}
-/>
+### With trailing visual
+
+Trailing visual and trailing text can display auxiliary information. They're placed at the right of the item, and can denote status, keyboard shortcuts, or be used to set expectations about what the action does.
+
+```jsx live
+
+
+ New file
+ ⌘ + N
+
+
+ Copy link
+ ⌘ + C
+
+
+ Edit file
+ ⌘ + E
+
+
+ Delete file
+ ⌫
+
+
+```
+
+### With description and dividers
+
+Item dividers allow users to parse heavier amounts of information. They're placed between items and are useful in complex lists, particularly when descriptions or multi-line text is present.
+
+```jsx live
+
+
+
+
+
+ mona
+ Monalisa Octocat
+
+
+
+
+
+ hubot
+ Hubot
+
+
+
+
+
+ primer-css
+ GitHub Design Systems Bot
+
+
+```
+
+### With links
+
+When you want to add links to the List instead of actions, use `ActionList.LinkItem`
+
+
+```jsx live
+
+
+
+
+
+ github/primer
+
+
+
+
+
+ MIT License
+
+
+
+
+
+ 1.4k stars
+
+
+```
+
+### With groups
+
+```javascript live noinline
+const SelectFields = () => {
+ const [options, setOptions] = React.useState([
+ {text: 'Status', selected: true},
+ {text: 'Stage', selected: true},
+ {text: 'Assignee', selected: true},
+ {text: 'Team', selected: true},
+ {text: 'Estimate', selected: false},
+ {text: 'Due Date', selected: false}
+ ])
+
+ const visibleOptions = options.filter(option => option.selected)
+ const hiddenOptions = options.filter(option => !option.selected)
+
+ const toggle = text => {
+ setOptions(
+ options.map(option => {
+ if (option.text === text) option.selected = !option.selected
+ return option
+ })
+ )
+ }
+
+ return (
+
+
+ {visibleOptions.map(option => (
+ toggle(option.text)}>
+ {option.text}
+
+ ))}
+
+
+ {hiddenOptions.map((option, index) => (
+ toggle(option.text)}>
+ {option.text}
+
+ ))}
+ {hiddenOptions.length === 0 && No hidden fields }
+
+
+ )
+}
+
+render( )
```
## Props
-| Name | Type | Default | Description |
-| :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| items | `(ItemProps & Omit, keyof ItemProps>) \| ((Partial & {renderItem: RenderItemFn}) & {key?: Key})` | `undefined` | Required. A list of item objects conforming to the `ActionList.Item` props interface. |
-| renderItem | `(props: ItemProps) => JSX.Element` | `ActionList.Item` | Optional. If defined, each item in `items` will be passed to this function, allowing for `ActionList`-wide custom item rendering. |
-| groupMetadata | `GroupProps[]` | `undefined` | Optional. If defined, `ActionList` will group `items` into `ActionList.Group`s separated by `ActionList.Divider` according to their `groupId` property. |
-| showItemDividers | `boolean` | `false` | Optional. If `true` dividers will be displayed above each `ActionList.Item` which does not follow an `ActionList.Header` or `ActionList.Divider` |
+### ActionList
+
+
+
+
+ inset children are offset (vertically and horizontally) from list edges.{' '}
+ full children are flush (vertically and horizontally) with list edges
+ >
+ }
+ />
+
+
+ AriaRole
+ }
+ description={
+ <>
+ ARIA role describing the function of the list. listbox and{' '}
+ menu are a common values.
+ >
+ }
+ />
+
+
+
+### ActionList.Item
+
+
+
+
+ danger indicates that the item is destructive.
+ >
+ }
+ />
+
+
+
+ AriaRole
+ }
+ description={
+ <>
+ ARIA role describing the function of the item. option is a common value.
+ >
+ }
+ />
+
+
+
+### ActionList.LinkItem
+
+
+
+ MDN
+ }
+ />
+
+
+### ActionList.LeadingVisual
+
+
+
+
+
+
+### ActionList.TrailingVisual
+
+
+
+
+
+
+### ActionList.Description
+
+
+
+
+ inline descriptions are positioned beside primary text. block {' '}
+ descriptions are positioned below primary text.
+ >
+ }
+ />
+
+
+
+### ActionList.Group
+
+
+
+
+
+
+ inline descriptions are positioned beside primary text. block {' '}
+ descriptions are positioned below primary text.
+ >
+ }
+ />
+
+ Set selectionVariant at the group level.
+ >
+ }
+ />
+ AriaRole
+ }
+ description={
+ <>
+ ARIA role describing the function of the list inside the group. listbox and{' '}
+ menu are a common values.
+ >
+ }
+ />
+
+
+
+## Status
+
+
+
+## Further reading
+
+- [Interface guidelines: Action List](https://primer.style/design/components/action-list)
+
+## Related components
+
+- [ActionMenu](/drafts/ActionMenu2)
+- [DropdownMenu](/DropdownMenu)
+- [SelectPanel](/SelectPanel)
diff --git a/docs/content/CheckboxGroup.mdx b/docs/content/CheckboxGroup.mdx
new file mode 100644
index 00000000000..61198cd488c
--- /dev/null
+++ b/docs/content/CheckboxGroup.mdx
@@ -0,0 +1,317 @@
+---
+title: CheckboxGroup
+description: A `CheckboxGroup` is used to render a set of checkboxes to let users select one or more options
+status: Alpha
+source: https://github.com/primer/react/blob/main/src/CheckboxGroup/CheckboxGroup.tsx
+storybook: '/react/storybook/?path=/story/forms-checkboxgroup-examples--basic'
+---
+
+import {CheckboxGroup, Checkbox, Box} from '@primer/components'
+import {CheckIcon, XIcon, AlertIcon} from '@primer/octicons-react'
+import {ComponentChecklist} from '../src/component-checklist'
+
+## Examples
+
+### Basic
+
+```jsx live
+
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+
+```
+
+### Using onChange handlers
+
+```javascript live noinline
+const WithOnChangeHandlers = () => {
+ const [selectedCheckboxValues, setSelectedCheckboxValues] = React.useState(['one', 'two'])
+ const [lastSelectedCheckboxValue, setLastSelectedCheckboxValue] = React.useState()
+
+ const handleCheckboxGroupChange = (selectedValues, e) => {
+ setSelectedCheckboxValues(selectedValues)
+ setLastSelectedCheckboxValue(e.currentTarget.value)
+ }
+
+ const handleChoiceOneChange = e => {
+ alert('Choice one has its own handler')
+ }
+
+ return (
+
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+
+ {Boolean(selectedCheckboxValues.length) && (
+ The selected checkbox values are {selectedCheckboxValues.join(', ')}
+ )}
+ {Boolean(lastSelectedCheckboxValue) && The last affected checkbox value is {lastSelectedCheckboxValue}
}
+
+ )
+}
+
+render( )
+```
+
+### Disabled
+
+```jsx live
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+```
+
+### Required
+
+```jsx live
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+```
+
+### With validation
+
+```jsx live
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+ Your choices are wrong
+
+```
+
+### With caption
+
+```jsx live
+
+ Choices
+ You can pick any or all of these choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+```
+
+### A visually hidden label
+
+```jsx live
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+```
+
+### With an external label
+
+```jsx live
+<>
+
+ Choices
+
+
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+>
+```
+
+## Props
+
+### CheckboxGroup
+
+
+
+
+
+
+ The unique identifier for this input group. Used to associate the label, validation text, and caption text.{' '}
+ You may want a custom ID to make it easier to select elements in integration tests.
+
+ }
+ />
+
+
+
+
+
+### CheckboxGroup.Label
+
+A title for the set of choices. If a `CheckboxGroup.Label` is not passed as a child, you must pass the external title's ID to the `aria-describedby` prop on `CheckboxGroup`
+
+
+
+
+
+
+### CheckboxGroup.Description
+
+
+
+
+
+
+### CheckboxGroup.Validation
+
+If the user's selection has been flagged during validation, `CheckboxGroup.Validation` may be used to render contextual validation information to help the user complete their task
+
+
+
+
+
+
+
+## Status
+
+
diff --git a/docs/content/ChoiceFieldset.mdx b/docs/content/ChoiceFieldset.mdx
index fa2e9755183..b82b9ce7633 100644
--- a/docs/content/ChoiceFieldset.mdx
+++ b/docs/content/ChoiceFieldset.mdx
@@ -1,6 +1,6 @@
---
title: ChoiceFieldset
-status: Alpha
+status: Deprecated
source: https://github.com/primer/react/blob/main/src/ChoiceFieldset/ChoiceFieldset.tsx
storybook: '/react/storybook/?path=/story/forms-choicefieldset--radio-group'
---
@@ -11,11 +11,15 @@ import {ComponentChecklist} from '../src/component-checklist'
A `ChoiceFieldset` is a controlled component that is used to render a related set of checkbox or radio inputs.
+## Deprecation
+
+Use [CheckboxGroup](/CheckboxGroup) or [RadioGroup](/RadioGroup) instead.
+
## Examples
### Basic
-```jsx live
+```jsx live deprecated
Color mode
@@ -29,7 +33,7 @@ A `ChoiceFieldset` is a controlled component that is used to render a related se
### Using an onSelect handler
-```javascript live noinline
+```javascript live noinline deprecated
const WithOnSelectHandler = () => {
const [selectedChoices, setSelectedChoices] = React.useState([])
const choices = [
@@ -90,7 +94,7 @@ render( )
### Checkbox group
-```jsx live
+```jsx live deprecated
Prefered Primer component interface
@@ -105,7 +109,7 @@ render( )
### Disabled
-```jsx live
+```jsx live deprecated
Color mode
@@ -119,7 +123,7 @@ render( )
### Required
-```jsx live
+```jsx live deprecated
Color mode
@@ -133,7 +137,7 @@ render( )
### With pre-selected choices
-```jsx live
+```jsx live deprecated
Prefered Primer component interface
@@ -148,7 +152,7 @@ render( )
### With validation
-```javascript live noinline
+```javascript live noinline deprecated
const choices = [
{
value: 'figma',
@@ -218,7 +222,7 @@ render( )
### A visually hidden legend
-```jsx live
+```jsx live deprecated
Color mode
@@ -232,7 +236,7 @@ render( )
### With a ChoiceFieldset.Description
-```jsx live
+```jsx live deprecated
Notification preferences
@@ -267,7 +271,7 @@ render( )
### With one disabled item
-```jsx live
+```jsx live deprecated
Color mode
@@ -283,7 +287,7 @@ render( )
### Items with a caption and a leading visual
-```jsx live
+```jsx live deprecated
Notification preferences
diff --git a/docs/content/FormControl.mdx b/docs/content/FormControl.mdx
index 28660dde034..7be8334832a 100644
--- a/docs/content/FormControl.mdx
+++ b/docs/content/FormControl.mdx
@@ -7,7 +7,18 @@ source: https://github.com/primer/react/blob/main/src/FormControl/FormControl.ts
storybook: '/react/storybook?path=/story/forms-inputfield--text-input-field'
---
-import {FormControl, TextInputWithTokens, Autocomplete, Select, Textarea, Checkbox, Radio, Text} from '@primer/react'
+import {
+ FormControl,
+ TextInputWithTokens,
+ Autocomplete,
+ Select,
+ Textarea,
+ Checkbox,
+ CheckboxGroup,
+ Radio,
+ RadioGroup,
+ Text
+} from '@primer/react'
import {MarkGithubIcon} from '@primer/octicons-react'
## Examples
@@ -83,27 +94,38 @@ render(DifferentInputs)
### With checkbox and radio inputs
```jsx live
-
-
+
+
+ Checkboxes
+
+
+ Checkbox one
+
- Checkbox option one
-
+
+ Checkbox two
- Checkbox option two
-
+
+ Checkbox three
-
-
+
+
+
+ Radios
- Radio option one
-
+
+ Radio one
- Radio option two
-
+
+ Radio two
-
+
+
+ Radio three
+
+
```
@@ -280,8 +302,8 @@ A `FormControl.Label` must be passed for the field to be accessible to assistive
diff --git a/docs/content/drafts/PageLayout.mdx b/docs/content/PageLayout.mdx
similarity index 97%
rename from docs/content/drafts/PageLayout.mdx
rename to docs/content/PageLayout.mdx
index a04cfc87307..64d439c49d3 100644
--- a/docs/content/drafts/PageLayout.mdx
+++ b/docs/content/PageLayout.mdx
@@ -7,7 +7,7 @@ storybook: https://primer.style/react/storybook?path=/story/layout-pagelayout--p
---
```js
-import {PageLayout} from '@primer/react/drafts'
+import {PageLayout} from '@primer/react'
```
## Examples
@@ -20,7 +20,7 @@ See [storybook](https://primer.style/react/storybook?path=/story/layout-pagelayo
### Default
-```jsx live drafts
+```jsx live
@@ -39,7 +39,7 @@ See [storybook](https://primer.style/react/storybook?path=/story/layout-pagelayo
### With dividers
-```jsx live drafts
+```jsx live
@@ -58,7 +58,7 @@ See [storybook](https://primer.style/react/storybook?path=/story/layout-pagelayo
### With pane on left
-```jsx live drafts
+```jsx live
@@ -77,7 +77,7 @@ See [storybook](https://primer.style/react/storybook?path=/story/layout-pagelayo
### With condensed spacing
-```jsx live drafts
+```jsx live
@@ -96,7 +96,7 @@ See [storybook](https://primer.style/react/storybook?path=/story/layout-pagelayo
### Without header or footer
-```jsx live drafts
+```jsx live
diff --git a/docs/content/RadioGroup.mdx b/docs/content/RadioGroup.mdx
new file mode 100644
index 00000000000..e81af8f35df
--- /dev/null
+++ b/docs/content/RadioGroup.mdx
@@ -0,0 +1,316 @@
+---
+title: RadioGroup
+description: A `RadioGroup` is used to render a set of radio inputs to let users select a single option
+status: Alpha
+source: https://github.com/primer/react/blob/main/src/RadioGroup/RadioGroup.tsx
+storybook: '/react/storybook/?path=/story/forms-radiogroup-examples--basic'
+---
+
+import {RadioGroup, Radio, Box} from '@primer/components'
+import {CheckIcon, XIcon, AlertIcon} from '@primer/octicons-react'
+import {ComponentChecklist} from '../src/component-checklist'
+
+## Examples
+
+### Basic
+
+```jsx live
+
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+
+```
+
+### Using onChange handlers
+
+```javascript live noinline
+const WithOnChangeHandlers = () => {
+ const [selectedRadioValue, setSelectedCheckboxValues] = React.useState('two')
+ const [selectedRadioId, setSelectedRadioId] = React.useState()
+
+ const handleRadioGroupChange = (selectedValue, e) => {
+ setSelectedCheckboxValues(selectedValue)
+ setSelectedRadioId(e.currentTarget.id)
+ }
+
+ const handleChoiceOneChange = e => {
+ alert('Choice one has its own handler')
+ }
+
+ return (
+
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+
+ {selectedRadioValue && The selected radio value is {selectedRadioValue}
}
+ {selectedRadioId && The last selected radio ID is {selectedRadioId}
}
+
+ )
+}
+
+render( )
+```
+
+### Disabled
+
+```jsx live
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+```
+
+### Required
+
+```jsx live
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+```
+
+### With validation
+
+```jsx live
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+ Your choices are wrong
+
+```
+
+### With caption
+
+```jsx live
+
+ Choices
+ You can pick any of these choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+```
+
+### A visually hidden label
+
+```jsx live
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+```
+
+### With an external label
+
+```jsx live
+<>
+
+ Choices
+
+
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+>
+```
+
+## Props
+
+### RadioGroup
+
+
+
+
+
+
+ The unique identifier for this input group. Used to associate the label, validation text, and caption text.{' '}
+ You may want a custom ID to make it easier to select elements in integration tests.
+
+ }
+ />
+
+
+
+
+
+
+### RadioGroup.Label
+
+A title for the set of choices. If a `RadioGroup.Label` is not passed as a child, you must pass the external title's ID to the `aria-describedby` prop on `RadioGroup`
+
+
+
+
+
+
+### RadioGroup.Description
+
+
+
+
+
+
+### RadioGroup.Validation
+
+If the user's selection has been flagged during validation, `RadioGroup.Validation` may be used to render contextual validation information to help the user complete their task
+
+
+
+
+
+
+
+## Status
+
+
diff --git a/docs/content/deprecated/ActionList.mdx b/docs/content/deprecated/ActionList.mdx
new file mode 100644
index 00000000000..1bcf8880fc0
--- /dev/null
+++ b/docs/content/deprecated/ActionList.mdx
@@ -0,0 +1,136 @@
+---
+componentId: action_list
+title: ActionList
+status: Deprecated
+source: https://github.com/primer/react/tree/main/src/deprecated/ActionList
+---
+
+An `ActionList` is a list of items which can be activated or selected. `ActionList` is the base component for many of our menu-type components, including `DropdownMenu` and `ActionMenu`.
+
+## Deprecation
+
+Use [new version of ActionList](/ActionList) with composable API, design updates and accessibility fixes.
+
+**Before**
+
+```jsx
+
+```
+
+**After**
+
+```jsx
+
+ New file
+ Copy link
+ Edit file
+
+ Delete file
+
+```
+
+Or continue using deprecated API:
+
+```js
+import {ActionList} from '@primer/react/deprecated'
+```
+
+## Minimal example
+
+```jsx live deprecated
+
+```
+
+## Example with grouped items
+
+```jsx live deprecated
+
+```
+
+## Example with custom item renderer
+
+```jsx deprecated
+
+ },
+ {
+ text: 'React Router link',
+ renderItem: props =>
+ },
+ {
+ text: 'NextJS style',
+ renderItem: props => (
+
+
+
+ )
+ }
+ ]}
+/>
+```
+
+## Props
+
+| Name | Type | Default | Description |
+| :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| items | `(ItemProps & Omit, keyof ItemProps>) \| ((Partial & {renderItem: RenderItemFn}) & {key?: Key})` | `undefined` | Required. A list of item objects conforming to the `ActionList.Item` props interface. |
+| renderItem | `(props: ItemProps) => JSX.Element` | `ActionList.Item` | Optional. If defined, each item in `items` will be passed to this function, allowing for `ActionList`-wide custom item rendering. |
+| groupMetadata | `GroupProps[]` | `undefined` | Optional. If defined, `ActionList` will group `items` into `ActionList.Group`s separated by `ActionList.Divider` according to their `groupId` property. |
+| showItemDividers | `boolean` | `false` | Optional. If `true` dividers will be displayed above each `ActionList.Item` which does not follow an `ActionList.Header` or `ActionList.Divider` |
diff --git a/docs/content/deprecated/BorderBox.md b/docs/content/deprecated/BorderBox.md
index 45a206fd88e..2be97d6e22a 100644
--- a/docs/content/deprecated/BorderBox.md
+++ b/docs/content/deprecated/BorderBox.md
@@ -12,13 +12,13 @@ Use [Box](/Box) instead.
**Before**
-```jsx
+```jsx deprecated
Item 1
```
**After**
-```jsx
+```jsx deprecated
Item 1
@@ -26,7 +26,7 @@ Use [Box](/Box) instead.
## Default example
-```jsx live
+```jsx live deprecated
This is a BorderBox
```
diff --git a/docs/content/deprecated/Dialog.md b/docs/content/deprecated/Dialog.md
index aafa7065703..dcd79b30623 100644
--- a/docs/content/deprecated/Dialog.md
+++ b/docs/content/deprecated/Dialog.md
@@ -9,7 +9,7 @@ Use [Dialog2](/Dialog2) instead.
**Before**
-```jsx
+```jsx deprecated
setOpen(false)} aria-labelledby="header-id">
@@ -20,7 +20,7 @@ Use [Dialog2](/Dialog2) instead.
**After**
-```jsx
+```jsx deprecated
{
open && (
{([isOpen, setIsOpen]) => {
const returnFocusRef = React.useRef(null)
@@ -87,7 +87,7 @@ If you're running into z-index issues or are rendering the component inside of a
You can also pass any non-text content into the header:
-```jsx live
+```jsx deprecated live
{([isOpen, setIsOpen]) => {
const returnFocusRef = React.useRef(null)
diff --git a/docs/content/deprecated/Dropdown.md b/docs/content/deprecated/Dropdown.md
index b53bb11d42a..f08fd426d2f 100644
--- a/docs/content/deprecated/Dropdown.md
+++ b/docs/content/deprecated/Dropdown.md
@@ -5,7 +5,7 @@ status: Deprecated
## Deprecation
-Use [DropdownMenu](/DropdownMenu) instead.
+Use [ActionMenu](/ActionMenu) instead.
---
@@ -17,7 +17,7 @@ Dropdown.Menu wraps your menu content. Be sure to pass a `direction` prop to thi
## Default example
-```jsx live
+```jsx live deprecated
Dropdown
@@ -30,7 +30,7 @@ Dropdown.Menu wraps your menu content. Be sure to pass a `direction` prop to thi
## With custom button
-```jsx live
+```jsx live deprecated
Dropdown
diff --git a/docs/content/deprecated/Flex.md b/docs/content/deprecated/Flex.md
index 67e7fa385b7..5ad6838dfc8 100644
--- a/docs/content/deprecated/Flex.md
+++ b/docs/content/deprecated/Flex.md
@@ -12,7 +12,7 @@ Use [Box](/Box) instead.
**Before**
-```jsx
+```jsx deprecated
Item 1
@@ -22,7 +22,7 @@ Use [Box](/Box) instead.
**After**
-```jsx
+```jsx deprecated
Item 1
@@ -32,7 +32,7 @@ Use [Box](/Box) instead.
## Default example
-```jsx live
+```jsx deprecated live
diff --git a/docs/content/deprecated/Grid.md b/docs/content/deprecated/Grid.md
index 305bed992be..905630fe9e7 100644
--- a/docs/content/deprecated/Grid.md
+++ b/docs/content/deprecated/Grid.md
@@ -12,7 +12,7 @@ Use [Box](/Box) instead.
**Before**
-```jsx
+```jsx deprecated
1
@@ -25,7 +25,7 @@ Use [Box](/Box) instead.
**After**
-```jsx
+```jsx deprecated
1
@@ -38,7 +38,7 @@ Use [Box](/Box) instead.
## Default example
-```jsx live
+```jsx deprecated live
1
diff --git a/docs/content/deprecated/Position.md b/docs/content/deprecated/Position.md
index 74212be10fa..fb2006117ca 100644
--- a/docs/content/deprecated/Position.md
+++ b/docs/content/deprecated/Position.md
@@ -12,7 +12,7 @@ Use [Box](/Box) instead.
**Before**
-```jsx
+```jsx deprecated
<>
...
...
@@ -24,7 +24,7 @@ Use [Box](/Box) instead.
**After**
-```jsx
+```jsx deprecated
<>
...
...
@@ -36,7 +36,7 @@ Use [Box](/Box) instead.
## Default examples
-```jsx live
+```jsx deprecated live
Relative + Absolute
diff --git a/docs/content/deprecated/SelectMenu.md b/docs/content/deprecated/SelectMenu.md
index faea3452588..b6fbea07843 100644
--- a/docs/content/deprecated/SelectMenu.md
+++ b/docs/content/deprecated/SelectMenu.md
@@ -3,13 +3,19 @@ title: SelectMenu
status: Deprecated
---
+## Deprecation
+
+Use [ActionMenu](/ActionMenu) instead.
+
+---
+
The `SelectMenu` components are a suite of components which can be combined together to make several different variations of our GitHub select menu. At it's most basic form, a select menu is comprised of a `SelectMenu` wrapper, which contains a `summary` component of your choice and a `Select.Modal` which contains the select menu content. Use `SelectMenu.List` to wrap items in the select menu, and `SelectMenu.Item` to wrap each item.
Several additional components exist to provide even more functionality: `SelectMenu.Header`, `SelectMenu.Filter`, `SelectMenu.Tabs`, `SelectMenu.TabPanel` `SelectMenu.Footer` and `SelectMenu.Divider`.
## Basic Example
-```jsx live
+```jsx deprecated live
Projects
@@ -28,7 +34,7 @@ Several additional components exist to provide even more functionality: `SelectM
Main wrapper component for select menu.
-```jsx
+```jsx deprecated
{/* all other sub components are wrapped here*/}
```
@@ -56,7 +62,7 @@ SelectMenu.MenuContext is a [context object](https://reactjs.org/docs/context.ht
### Example Usage
-```jsx
+```jsx deprecated
import {SelectMenu, Button} from `@primer/react`
import React, {useContext} from 'react'
@@ -83,7 +89,7 @@ const MyButton = () => {
Used to wrap the content in a `SelectMenu`.
-```jsx
+```jsx deprecated
{/* all menu content is wrapped in the modal*/}
```
@@ -91,7 +97,7 @@ Used to wrap the content in a `SelectMenu`.
Use the `align='right'` prop to align the modal to the right. Note that this only modifies alignment for the modal, and not the SelectMenu itself. You will need to wrap the SelectMenu in a relatively positioned element for this to work properly.
-```jsx live
+```jsx deprecated live
Projects
@@ -120,7 +126,7 @@ Use the `align='right'` prop to align the modal to the right. Note that this onl
Used to wrap the select menu list content. All menu items **must** be wrapped in a SelectMenu.List in order for the accessbility keyboard handling to function properly. If you are using the `SelectMenu.TabPanel` you do not need to provide a `SelectMenu.List` as that component renders a `SelectMenu.List` as a wrapper.
-```jsx
+```jsx deprecated
{/* all menu list items are wrapped in the list*/}
```
@@ -136,7 +142,7 @@ Individual items in a select menu. SelectMenu.Item renders an anchor tag by defa
You can use a `button` tag instead by utilizing the [`as` prop](/core-concepts#the-as-prop). Be sure to consider [which HTML element is the right choice](https://marcysutton.com/links-vs-buttons-in-modern-web-applications) for your usage of the component.
-```jsx
+```jsx deprecated
{/* wraps an individual list item*/}
@@ -154,7 +160,7 @@ You can use a `button` tag instead by utilizing the [`as` prop](/core-concepts#t
Use a `SelectMenu.Filter` to add a filter UI to your select menu. Users are expected to implement their own filtering and manage the state of the `value` prop on the input. This gives users more flexibility over the type of filtering and type of content passed into each select menu item.
-```jsx live
+```jsx deprecated live
Projects
@@ -192,7 +198,7 @@ Each `Select.Menu` tab will need to have an `index` prop. The first tab should b
If you need access to the selected tab state, you can find it in the MenuContext object exported from `SelectMenu` as `MenuContext.selectedTab`.
-```jsx live
+```jsx deprecated live
Projects
@@ -227,7 +233,7 @@ Used for each individual tab inside of a `SelectMenu.Tabs`. Be sure to set the `
The `onClick` prop is optional and can be used for any events or data fetching you might need to trigger on tab clicks.
-```jsx
+```jsx deprecated
<>
@@ -249,7 +255,7 @@ Wraps the content for each tab. Make sure to use the `tabName` prop to identify
**Note**: SelectMenu.TabPanel wraps content in a SelectMenu.List, so adding a SelectMenu.List manually is not necessary.
-```jsx
+```jsx deprecated
{/* Wraps content for each tab */}
```
@@ -264,7 +270,7 @@ Wraps the content for each tab. Make sure to use the `tabName` prop to identify
Use a `SelectMenu.Divider` to add information between items in a `SelectMenu.List`.
-```jsx live
+```jsx deprecated live
Projects
@@ -290,7 +296,7 @@ Use a `SelectMenu.Divider` to add information between items in a `SelectMenu.Lis
Use a `SelectMenu.Footer` to add content to the bottom of the select menu.
-```jsx live
+```jsx deprecated live
Projects
@@ -316,7 +322,7 @@ Use a `SelectMenu.Footer` to add content to the bottom of the select menu.
Use a `SelectMenu.Header` to add a header to the top of the select menu content.
-```jsx live
+```jsx deprecated live
Projects
@@ -344,7 +350,7 @@ Use a `SelectMenu.LoadingAnimation` to add a loading animation inside of the Sel
**Note**: You will need to handle showing/hiding the appropriate modal content for your application during the loading state. We recommend always showing the `SelectMenu.Filter` and `SelectMenu.Header` (if used) and hiding the rest of the modal content during the loading state.
-```jsx live
+```jsx deprecated live
Projects
diff --git a/docs/content/drafts/ActionList2.mdx b/docs/content/drafts/ActionList2.mdx
deleted file mode 100644
index bf842be1fe6..00000000000
--- a/docs/content/drafts/ActionList2.mdx
+++ /dev/null
@@ -1,449 +0,0 @@
----
-componentId: action_list2
-title: ActionList v2
-status: Alpha
-source: https://github.com/primer/react/tree/main/src/ActionList2
-storybook: '/react/storybook?path=/story/composite-components-actionlist2'
-description: An ActionList is a list of items that can be activated or selected. ActionList is the base component for many menu-type components, including DropdownMenu and ActionMenu.
----
-
-import {Avatar} from '@primer/react'
-import {ActionList} from '@primer/react/drafts'
-import {LinkIcon, AlertIcon, ArrowRightIcon} from '@primer/octicons-react'
-import InlineCode from '@primer/gatsby-theme-doctocat/src/components/inline-code'
-
-
-
-
-
-
-
- github.com/primer
-
- A React implementation of GitHub's Primer Design System
-
-
-
-
-
-
- mona
- Monalisa Octocat
-
-
-
-
-
- 4 vulnerabilities
-
-
-
-
-
-
-
-```js
-import {ActionList} from '@primer/react/drafts'
-```
-
-## Examples
-
-### Minimal example
-
-```jsx live drafts
-
- New file
- Copy link
- Edit file
-
- Delete file
-
-```
-
-### With leading visual
-
-Leading visuals are optional and appear at the start of an item. They can be octicons, avatars, and other custom visuals that fit a small area.
-
-
-```jsx live drafts
-
-
-
- github.com/primer
-
-
-
- 4 vulnerabilities
-
-
-
- mona
-
-
-
-```
-
-### With trailing visual
-
-Trailing visual and trailing text can display auxiliary information. They're placed at the right of the item, and can denote status, keyboard shortcuts, or be used to set expectations about what the action does.
-
-```jsx live drafts
-
-
- New file
- ⌘ + N
-
-
- Copy link
- ⌘ + C
-
-
- Edit file
- ⌘ + E
-
-
- Delete file
- ⌫
-
-
-```
-
-### With description and dividers
-
-Item dividers allow users to parse heavier amounts of information. They're placed between items and are useful in complex lists, particularly when descriptions or multi-line text is present.
-
-```jsx live drafts
-
-
-
-
-
- mona
- Monalisa Octocat
-
-
-
-
-
- hubot
- Hubot
-
-
-
-
-
- primer-css
- GitHub Design Systems Bot
-
-
-```
-
-### With links
-
-When you want to add links to the List instead of actions, use `ActionList.LinkItem`
-
-
-```jsx live drafts
-
-
-
-
-
- github/primer
-
-
-
-
-
- MIT License
-
-
-
-
-
- 1.4k stars
-
-
-```
-
-### With groups
-
-```javascript live noinline drafts
-const SelectFields = () => {
- const [options, setOptions] = React.useState([
- {text: 'Status', selected: true},
- {text: 'Stage', selected: true},
- {text: 'Assignee', selected: true},
- {text: 'Team', selected: true},
- {text: 'Estimate', selected: false},
- {text: 'Due Date', selected: false}
- ])
-
- const visibleOptions = options.filter(option => option.selected)
- const hiddenOptions = options.filter(option => !option.selected)
-
- const toggle = text => {
- setOptions(
- options.map(option => {
- if (option.text === text) option.selected = !option.selected
- return option
- })
- )
- }
-
- return (
-
-
- {visibleOptions.map(option => (
- toggle(option.text)}>
- {option.text}
-
- ))}
-
-
- {hiddenOptions.map((option, index) => (
- toggle(option.text)}>
- {option.text}
-
- ))}
- {hiddenOptions.length === 0 && No hidden fields }
-
-
- )
-}
-
-render( )
-```
-
-## Props
-
-### ActionList
-
-
-
-
- inset children are offset (vertically and horizontally) from list edges.{' '}
- full children are flush (vertically and horizontally) with list edges
- >
- }
- />
-
-
- AriaRole
- }
- description={
- <>
- ARIA role describing the function of the list. listbox and{' '}
- menu are a common values.
- >
- }
- />
-
-
-
-### ActionList.Item
-
-
-
-
- danger indicates that the item is destructive.
- >
- }
- />
-
-
-
- AriaRole
- }
- description={
- <>
- ARIA role describing the function of the item. option is a common value.
- >
- }
- />
-
-
-
-### ActionList.LinkItem
-
-
-
- MDN
- }
- />
-
-
-### ActionList.LeadingVisual
-
-
-
-
-
-
-### ActionList.TrailingVisual
-
-
-
-
-
-
-### ActionList.Description
-
-
-
-
- inline descriptions are positioned beside primary text. block {' '}
- descriptions are positioned below primary text.
- >
- }
- />
-
-
-
-### ActionList.Group
-
-
-
-
-
-
- inline descriptions are positioned beside primary text. block {' '}
- descriptions are positioned below primary text.
- >
- }
- />
-
- Set selectionVariant at the group level.
- >
- }
- />
- AriaRole
- }
- description={
- <>
- ARIA role describing the function of the list inside the group. listbox and{' '}
- menu are a common values.
- >
- }
- />
-
-
-
-## Status
-
-
-
-## Further reading
-
-- [Interface guidelines: Action List](https://primer.style/design/components/action-list)
-
-## Related components
-
-- [ActionMenu](/drafts/ActionMenu2)
-- [DropdownMenu](/DropdownMenu)
-- [SelectPanel](/SelectPanel)
diff --git a/docs/content/drafts/ActionMenu2.mdx b/docs/content/drafts/ActionMenu2.mdx
index 4ccc9b3c5a3..5a1af58d8da 100644
--- a/docs/content/drafts/ActionMenu2.mdx
+++ b/docs/content/drafts/ActionMenu2.mdx
@@ -7,8 +7,8 @@ storybook: '/react/storybook?path=/story/composite-components-actionmenu2'
description: An ActionMenu is an ActionList-based component for creating a menu of actions that expands through a trigger button.
---
-import {Box, Avatar} from '@primer/react'
-import {ActionMenu, ActionList} from '@primer/react/drafts'
+import {Box, Avatar, ActionList} from '@primer/react'
+import {ActionMenu} from '@primer/react/drafts'
import {Props} from '../../src/props'
@@ -306,7 +306,7 @@ render(
| Name | Type | Default | Description |
| :--------------------------------------- | :-------------------- | :-----------------: | :-------------------------------------------------------------------------------------------- |
-| children\* | `React.ReactElement[] | React.ReactElement` | Required. Recommended: [`ActionList`](/drafts/ActionList2) |
+| children\* | `React.ReactElement[] | React.ReactElement` | Required. Recommended: [`ActionList`](/ActionList) |
| [OverlayProps](/Overlay#component-props) | - | - | Optional. Props to be spread on the internal [`AnchoredOverlay`](/AnchoredOverlay) component. |
## Status
@@ -336,6 +336,6 @@ render(
## Related components
-- [ActionList](/drafts/ActionList2)
+- [ActionList](/ActionList)
- [SelectPanel](/SelectPanel)
- [Button](/drafts/Button2)
diff --git a/docs/content/drafts/DropdownMenu2.mdx b/docs/content/drafts/DropdownMenu2.mdx
index 68cd522aa1e..8d517f5facb 100644
--- a/docs/content/drafts/DropdownMenu2.mdx
+++ b/docs/content/drafts/DropdownMenu2.mdx
@@ -7,8 +7,8 @@ storybook: '/react/storybook?path=/story/composite-components-dropdownmenu2'
description: Use DropdownMenu to select a single option from a list of menu options.
---
-import {Box, Avatar} from '@primer/react'
-import {DropdownMenu, ActionList} from '@primer/react/drafts'
+import {Box, Avatar, ActionList} from '@primer/react'
+import {DropdownMenu} from '@primer/react/drafts'
import {Props} from '../../src/props'
import State from '../../components/State'
import {CalendarIcon, IterationsIcon, NumberIcon, SingleSelectIcon, TypographyIcon} from '@primer/octicons-react'
@@ -312,7 +312,7 @@ Use `DropdownMenu` to select an option from a small list. If you’re looking fo
description={
<>
Recommended:{' '}
-
+
ActionList
>
@@ -359,6 +359,6 @@ Use `DropdownMenu` to select an option from a small list. If you’re looking fo
## Related components
-- [ActionList](/drafts/ActionList2)
+- [ActionList](/ActionList)
- [ActionMenu](/ActionMenu2)
- [SelectPanel](/SelectPanel)
diff --git a/docs/package-lock.json b/docs/package-lock.json
index 5aa240cad2d..bd8cfbb80b1 100644
--- a/docs/package-lock.json
+++ b/docs/package-lock.json
@@ -8,7 +8,7 @@
"name": "docs",
"version": "1.0.0",
"dependencies": {
- "@primer/gatsby-theme-doctocat": "^3.2.0",
+ "@primer/gatsby-theme-doctocat": "^3.2.1",
"@primer/octicons-react": "^16.1.0",
"@primer/primitives": "4.1.0",
"@styled-system/prop-types": "^5.1.0",
@@ -2719,69 +2719,28 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
+ "node_modules/@primer/behaviors": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.1.0.tgz",
+ "integrity": "sha512-Ej2OUc3ZIFaR7WwIUqESO1DTzmpb7wc8xbTVRT9s52jZQDjN7g5iljoK3ocYZm+BIAcKn3MvcwB42hEk4Ga4xQ=="
+ },
"node_modules/@primer/component-metadata": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@primer/component-metadata/-/component-metadata-0.4.1.tgz",
"integrity": "sha512-iy5ZEeIRN6pFFG7px2ruuA726yVB/n4lsgM3msfdg9qJzfS9qE2JCqq2OuvQ+yXUTxb3JKROaDSH403kdpFR4Q=="
},
- "node_modules/@primer/components": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@primer/components/-/components-30.3.0.tgz",
- "integrity": "sha512-5W2WQtTzBsGE12+SIcc49RlosgkoamFDMkwNh5kKuQq1Ni9fXjxfWQaykh8CaWydcywMfnZbPcESKnuu+KfLZQ==",
- "deprecated": "@primer/components has been renamed @primer/react. Use @primer/react instead.",
- "dependencies": {
- "@primer/octicons-react": "^13.0.0",
- "@primer/primitives": "4.8.1",
- "@radix-ui/react-polymorphic": "0.0.14",
- "@react-aria/ssr": "3.1.0",
- "@styled-system/css": "5.1.5",
- "@styled-system/props": "5.1.5",
- "@styled-system/theme-get": "5.1.2",
- "@types/history": "4.7.8",
- "@types/styled-components": "5.1.11",
- "@types/styled-system": "5.1.12",
- "@types/styled-system__css": "5.0.16",
- "@types/styled-system__theme-get": "5.0.1",
- "classnames": "2.3.1",
- "color2k": "1.2.4",
- "deepmerge": "4.2.2",
- "focus-visible": "5.2.0",
- "styled-system": "5.1.5"
- },
- "peerDependencies": {
- "react": "^17.0.0",
- "react-dom": "^17.0.0",
- "styled-components": "4.x || 5.x"
- }
- },
- "node_modules/@primer/components/node_modules/@primer/octicons-react": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-13.0.0.tgz",
- "integrity": "sha512-j5XppNRCvgaMZLPsVvvmp6GSh7P5pq6PUbsfLNBWi2Kz3KYDeoGDWbPr5MjoxFOGUn6Hjnt6qjHPRxahd11vLQ==",
- "engines": {
- "node": ">=8"
- },
- "peerDependencies": {
- "react": ">=15"
- }
- },
- "node_modules/@primer/components/node_modules/@primer/primitives": {
- "version": "4.8.1",
- "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-4.8.1.tgz",
- "integrity": "sha512-mgr6+EKpn4DixuhLt3drk7QmNQO8M7RYONWovg1nkV7p56jklhDLfZmp1luLUee37eQGAxx3ToStL6gqINFjnQ=="
- },
"node_modules/@primer/gatsby-theme-doctocat": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/@primer/gatsby-theme-doctocat/-/gatsby-theme-doctocat-3.2.0.tgz",
- "integrity": "sha512-C/8X4xHKWmVf+TKlzWzvj65+BRbcTv6rWr1VoiFQsf6hQacEvzxe6BiTvuvZTFQ+P7Ei1TWKPnTOGbqg0VUSnw==",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@primer/gatsby-theme-doctocat/-/gatsby-theme-doctocat-3.2.1.tgz",
+ "integrity": "sha512-fwt/gttBmy8cwH2MaSb+/CQayytiRzxvLc9L7QoqxBUsEHkqiqkzjNX46TK+tv+Ntzs/pKyacR5yqXVN5GtfOw==",
"dependencies": {
"@babel/preset-env": "^7.5.5",
"@babel/preset-react": "^7.0.0",
"@mdx-js/mdx": "^1.0.21",
"@mdx-js/react": "^1.0.21",
"@primer/component-metadata": "^0.4.0",
- "@primer/components": "^30.0.0",
"@primer/octicons-react": "^16.0.0",
+ "@primer/react": "^34.5.0",
"@styled-system/theme-get": "^5.0.12",
"@testing-library/jest-dom": "^4.1.0",
"@testing-library/react": "^9.1.3",
@@ -2849,6 +2808,56 @@
"resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-4.1.0.tgz",
"integrity": "sha512-bACUj3Xl2z5dnYKE/76BpNqN4/JXFngf26dbb7Goph2PU5wcf+Z2Kk3nXwtJPGG8JByO+FcU3gpZjLNoyuLMHQ=="
},
+ "node_modules/@primer/react": {
+ "version": "34.6.0",
+ "resolved": "https://registry.npmjs.org/@primer/react/-/react-34.6.0.tgz",
+ "integrity": "sha512-a0Mh6YmpEyQF6ad0mnfOJoC+y1heDM4uuvBcQQKJQ28DVeif5mn+slCD2C9ZQvnhkl4qnh3iqXOTxmKN5fCHNQ==",
+ "dependencies": {
+ "@primer/behaviors": "1.1.0",
+ "@primer/octicons-react": "16.1.1",
+ "@primer/primitives": "7.1.1",
+ "@radix-ui/react-polymorphic": "0.0.14",
+ "@react-aria/ssr": "3.1.0",
+ "@styled-system/css": "5.1.5",
+ "@styled-system/props": "5.1.5",
+ "@styled-system/theme-get": "5.1.2",
+ "@types/styled-components": "5.1.11",
+ "@types/styled-system": "5.1.12",
+ "@types/styled-system__css": "5.0.16",
+ "@types/styled-system__theme-get": "5.0.1",
+ "classnames": "2.3.1",
+ "color2k": "1.2.4",
+ "deepmerge": "4.2.2",
+ "focus-visible": "5.2.0",
+ "history": "5.0.0",
+ "styled-system": "5.1.5"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=7"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0",
+ "react-dom": "^17.0.0",
+ "styled-components": "4.x || 5.x"
+ }
+ },
+ "node_modules/@primer/react/node_modules/@primer/octicons-react": {
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-16.1.1.tgz",
+ "integrity": "sha512-xCxQ5z23ol7yDuJs85Lc4ARzyoay+b3zOhAKkEMU7chk0xi2hT2OnRP23QUudNNDPTGozX268RGYLexUa6P4xw==",
+ "engines": {
+ "node": ">=8"
+ },
+ "peerDependencies": {
+ "react": ">=15"
+ }
+ },
+ "node_modules/@primer/react/node_modules/@primer/primitives": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-7.1.1.tgz",
+ "integrity": "sha512-+Gwo89YK1OFi6oubTlah/zPxxzMNaMLy+inECAYI646KIFdzzhAsKWb3z5tSOu5Ff7no4isRV64rWfMSKLZclw=="
+ },
"node_modules/@radix-ui/react-polymorphic": {
"version": "0.0.14",
"resolved": "https://registry.npmjs.org/@radix-ui/react-polymorphic/-/react-polymorphic-0.0.14.tgz",
@@ -3310,11 +3319,6 @@
"@types/unist": "*"
}
},
- "node_modules/@types/history": {
- "version": "4.7.8",
- "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz",
- "integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA=="
- },
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
@@ -14726,6 +14730,14 @@
"node": "*"
}
},
+ "node_modules/history": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/history/-/history-5.0.0.tgz",
+ "integrity": "sha512-3NyRMKIiFSJmIPdq7FxkNMJkQ7ZEtVblOQ38VtKaA0zZMW1Eo6Q6W8oDKEflr1kNNTItSnk4JMCO1deeSgbLLg==",
+ "dependencies": {
+ "@babel/runtime": "^7.7.6"
+ }
+ },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -28408,8 +28420,7 @@
"ws": {
"version": "7.4.5",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
- "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
- "requires": {}
+ "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g=="
}
}
},
@@ -28767,8 +28778,7 @@
"@mdx-js/react": {
"version": "1.6.22",
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.22.tgz",
- "integrity": "sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==",
- "requires": {}
+ "integrity": "sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg=="
},
"@mdx-js/util": {
"version": "1.6.22",
@@ -28867,60 +28877,28 @@
}
}
},
+ "@primer/behaviors": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@primer/behaviors/-/behaviors-1.1.0.tgz",
+ "integrity": "sha512-Ej2OUc3ZIFaR7WwIUqESO1DTzmpb7wc8xbTVRT9s52jZQDjN7g5iljoK3ocYZm+BIAcKn3MvcwB42hEk4Ga4xQ=="
+ },
"@primer/component-metadata": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@primer/component-metadata/-/component-metadata-0.4.1.tgz",
"integrity": "sha512-iy5ZEeIRN6pFFG7px2ruuA726yVB/n4lsgM3msfdg9qJzfS9qE2JCqq2OuvQ+yXUTxb3JKROaDSH403kdpFR4Q=="
},
- "@primer/components": {
- "version": "30.3.0",
- "resolved": "https://registry.npmjs.org/@primer/components/-/components-30.3.0.tgz",
- "integrity": "sha512-5W2WQtTzBsGE12+SIcc49RlosgkoamFDMkwNh5kKuQq1Ni9fXjxfWQaykh8CaWydcywMfnZbPcESKnuu+KfLZQ==",
- "requires": {
- "@primer/octicons-react": "^13.0.0",
- "@primer/primitives": "4.8.1",
- "@radix-ui/react-polymorphic": "0.0.14",
- "@react-aria/ssr": "3.1.0",
- "@styled-system/css": "5.1.5",
- "@styled-system/props": "5.1.5",
- "@styled-system/theme-get": "5.1.2",
- "@types/history": "4.7.8",
- "@types/styled-components": "5.1.11",
- "@types/styled-system": "5.1.12",
- "@types/styled-system__css": "5.0.16",
- "@types/styled-system__theme-get": "5.0.1",
- "classnames": "2.3.1",
- "color2k": "1.2.4",
- "deepmerge": "4.2.2",
- "focus-visible": "5.2.0",
- "styled-system": "5.1.5"
- },
- "dependencies": {
- "@primer/octicons-react": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-13.0.0.tgz",
- "integrity": "sha512-j5XppNRCvgaMZLPsVvvmp6GSh7P5pq6PUbsfLNBWi2Kz3KYDeoGDWbPr5MjoxFOGUn6Hjnt6qjHPRxahd11vLQ==",
- "requires": {}
- },
- "@primer/primitives": {
- "version": "4.8.1",
- "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-4.8.1.tgz",
- "integrity": "sha512-mgr6+EKpn4DixuhLt3drk7QmNQO8M7RYONWovg1nkV7p56jklhDLfZmp1luLUee37eQGAxx3ToStL6gqINFjnQ=="
- }
- }
- },
"@primer/gatsby-theme-doctocat": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/@primer/gatsby-theme-doctocat/-/gatsby-theme-doctocat-3.2.0.tgz",
- "integrity": "sha512-C/8X4xHKWmVf+TKlzWzvj65+BRbcTv6rWr1VoiFQsf6hQacEvzxe6BiTvuvZTFQ+P7Ei1TWKPnTOGbqg0VUSnw==",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@primer/gatsby-theme-doctocat/-/gatsby-theme-doctocat-3.2.1.tgz",
+ "integrity": "sha512-fwt/gttBmy8cwH2MaSb+/CQayytiRzxvLc9L7QoqxBUsEHkqiqkzjNX46TK+tv+Ntzs/pKyacR5yqXVN5GtfOw==",
"requires": {
"@babel/preset-env": "^7.5.5",
"@babel/preset-react": "^7.0.0",
"@mdx-js/mdx": "^1.0.21",
"@mdx-js/react": "^1.0.21",
"@primer/component-metadata": "^0.4.0",
- "@primer/components": "^30.0.0",
"@primer/octicons-react": "^16.0.0",
+ "@primer/react": "^34.5.0",
"@styled-system/theme-get": "^5.0.12",
"@testing-library/jest-dom": "^4.1.0",
"@testing-library/react": "^9.1.3",
@@ -28970,19 +28948,54 @@
"@primer/octicons-react": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-16.2.0.tgz",
- "integrity": "sha512-943t5kkVRt+/hxVnKS27xweUp4lXFxwJaTrsit1C1xBB4kqdjzmYGpU84zwKsNyyV2Ch7may3umKjbI/mFBPPQ==",
- "requires": {}
+ "integrity": "sha512-943t5kkVRt+/hxVnKS27xweUp4lXFxwJaTrsit1C1xBB4kqdjzmYGpU84zwKsNyyV2Ch7may3umKjbI/mFBPPQ=="
},
"@primer/primitives": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-4.1.0.tgz",
"integrity": "sha512-bACUj3Xl2z5dnYKE/76BpNqN4/JXFngf26dbb7Goph2PU5wcf+Z2Kk3nXwtJPGG8JByO+FcU3gpZjLNoyuLMHQ=="
},
+ "@primer/react": {
+ "version": "34.6.0",
+ "resolved": "https://registry.npmjs.org/@primer/react/-/react-34.6.0.tgz",
+ "integrity": "sha512-a0Mh6YmpEyQF6ad0mnfOJoC+y1heDM4uuvBcQQKJQ28DVeif5mn+slCD2C9ZQvnhkl4qnh3iqXOTxmKN5fCHNQ==",
+ "requires": {
+ "@primer/behaviors": "1.1.0",
+ "@primer/octicons-react": "16.1.1",
+ "@primer/primitives": "7.1.1",
+ "@radix-ui/react-polymorphic": "0.0.14",
+ "@react-aria/ssr": "3.1.0",
+ "@styled-system/css": "5.1.5",
+ "@styled-system/props": "5.1.5",
+ "@styled-system/theme-get": "5.1.2",
+ "@types/styled-components": "5.1.11",
+ "@types/styled-system": "5.1.12",
+ "@types/styled-system__css": "5.0.16",
+ "@types/styled-system__theme-get": "5.0.1",
+ "classnames": "2.3.1",
+ "color2k": "1.2.4",
+ "deepmerge": "4.2.2",
+ "focus-visible": "5.2.0",
+ "history": "5.0.0",
+ "styled-system": "5.1.5"
+ },
+ "dependencies": {
+ "@primer/octicons-react": {
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-16.1.1.tgz",
+ "integrity": "sha512-xCxQ5z23ol7yDuJs85Lc4ARzyoay+b3zOhAKkEMU7chk0xi2hT2OnRP23QUudNNDPTGozX268RGYLexUa6P4xw=="
+ },
+ "@primer/primitives": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-7.1.1.tgz",
+ "integrity": "sha512-+Gwo89YK1OFi6oubTlah/zPxxzMNaMLy+inECAYI646KIFdzzhAsKWb3z5tSOu5Ff7no4isRV64rWfMSKLZclw=="
+ }
+ }
+ },
"@radix-ui/react-polymorphic": {
"version": "0.0.14",
"resolved": "https://registry.npmjs.org/@radix-ui/react-polymorphic/-/react-polymorphic-0.0.14.tgz",
- "integrity": "sha512-9nsMZEDU3LeIUeHJrpkkhZVxu/9Fc7P2g2I3WR+uA9mTbNC3hGaabi0dV6wg0CfHb+m4nSs1pejbE/5no3MJTA==",
- "requires": {}
+ "integrity": "sha512-9nsMZEDU3LeIUeHJrpkkhZVxu/9Fc7P2g2I3WR+uA9mTbNC3hGaabi0dV6wg0CfHb+m4nSs1pejbE/5no3MJTA=="
},
"@react-aria/ssr": {
"version": "3.1.0",
@@ -29387,11 +29400,6 @@
"@types/unist": "*"
}
},
- "@types/history": {
- "version": "4.7.8",
- "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz",
- "integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA=="
- },
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
@@ -29958,8 +29966,7 @@
"acorn-jsx": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
- "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
- "requires": {}
+ "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng=="
},
"acorn-walk": {
"version": "6.2.0",
@@ -29994,14 +30001,12 @@
"ajv-errors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
- "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==",
- "requires": {}
+ "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ=="
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
- "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
- "requires": {}
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="
},
"alphanum-sort": {
"version": "1.0.2",
@@ -30528,8 +30533,7 @@
"babel-plugin-remove-graphql-queries": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/babel-plugin-remove-graphql-queries/-/babel-plugin-remove-graphql-queries-2.16.1.tgz",
- "integrity": "sha512-PkHJuRodMp4p617a/ZVhV8elBhRoFpOTpdu2DaApXJFIsDJWhjZ8d4BGbbFCT/yKJrhRDTdqg1r5AhWEaEUKkw==",
- "requires": {}
+ "integrity": "sha512-PkHJuRodMp4p617a/ZVhV8elBhRoFpOTpdu2DaApXJFIsDJWhjZ8d4BGbbFCT/yKJrhRDTdqg1r5AhWEaEUKkw=="
},
"babel-plugin-styled-components": {
"version": "2.0.2",
@@ -32592,8 +32596,7 @@
"cssnano-utils": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-2.0.1.tgz",
- "integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==",
- "requires": {}
+ "integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ=="
},
"csso": {
"version": "4.2.0",
@@ -33224,8 +33227,7 @@
"ws": {
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
- "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
- "requires": {}
+ "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
}
}
},
@@ -33249,8 +33251,7 @@
"ws": {
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
- "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
- "requires": {}
+ "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
}
}
},
@@ -33972,8 +33973,7 @@
"eslint-plugin-react-hooks": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz",
- "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==",
- "requires": {}
+ "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ=="
},
"eslint-scope": {
"version": "5.1.1",
@@ -35754,8 +35754,7 @@
"babel-plugin-remove-graphql-queries": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/babel-plugin-remove-graphql-queries/-/babel-plugin-remove-graphql-queries-3.7.1.tgz",
- "integrity": "sha512-9fANNkzCZJ0i65FXGnoeg/knDPC3riazCDyRrcH/2DVovxChAMSN2mqh/7eohJ8IrB/0e6cwLO4VirqanSk1Hw==",
- "requires": {}
+ "integrity": "sha512-9fANNkzCZJ0i65FXGnoeg/knDPC3riazCDyRrcH/2DVovxChAMSN2mqh/7eohJ8IrB/0e6cwLO4VirqanSk1Hw=="
},
"braces": {
"version": "3.0.2",
@@ -37496,8 +37495,7 @@
"ws": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz",
- "integrity": "sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==",
- "requires": {}
+ "integrity": "sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw=="
}
}
},
@@ -37957,14 +37955,12 @@
"graphql-type-json": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz",
- "integrity": "sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==",
- "requires": {}
+ "integrity": "sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg=="
},
"graphql-ws": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.9.0.tgz",
- "integrity": "sha512-sHkK9+lUm20/BGawNEWNtVAeJzhZeBg21VmvmLoT5NdGVeZWv5PdIhkcayQIAgjSyyQ17WMKmbDijIPG2On+Ag==",
- "requires": {}
+ "integrity": "sha512-sHkK9+lUm20/BGawNEWNtVAeJzhZeBg21VmvmLoT5NdGVeZWv5PdIhkcayQIAgjSyyQ17WMKmbDijIPG2On+Ag=="
},
"gray-matter": {
"version": "4.0.3",
@@ -38224,6 +38220,14 @@
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
"integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="
},
+ "history": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/history/-/history-5.0.0.tgz",
+ "integrity": "sha512-3NyRMKIiFSJmIPdq7FxkNMJkQ7ZEtVblOQ38VtKaA0zZMW1Eo6Q6W8oDKEflr1kNNTItSnk4JMCO1deeSgbLLg==",
+ "requires": {
+ "@babel/runtime": "^7.7.6"
+ }
+ },
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -38480,8 +38484,7 @@
"icss-utils": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
- "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
- "requires": {}
+ "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA=="
},
"ieee754": {
"version": "1.2.1",
@@ -39227,8 +39230,7 @@
"isomorphic-ws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
- "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
- "requires": {}
+ "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w=="
},
"isstream": {
"version": "0.1.2",
@@ -39653,8 +39655,7 @@
"jest-pnp-resolver": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
- "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
- "requires": {}
+ "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w=="
},
"jest-regex-util": {
"version": "24.9.0",
@@ -40721,8 +40722,7 @@
"meros": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/meros/-/meros-1.1.4.tgz",
- "integrity": "sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==",
- "requires": {}
+ "integrity": "sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ=="
},
"methods": {
"version": "1.1.2",
@@ -42035,32 +42035,27 @@
"postcss-discard-comments": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz",
- "integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==",
- "requires": {}
+ "integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg=="
},
"postcss-discard-duplicates": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz",
- "integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==",
- "requires": {}
+ "integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA=="
},
"postcss-discard-empty": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz",
- "integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==",
- "requires": {}
+ "integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw=="
},
"postcss-discard-overridden": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz",
- "integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==",
- "requires": {}
+ "integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q=="
},
"postcss-flexbugs-fixes": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz",
- "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==",
- "requires": {}
+ "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ=="
},
"postcss-loader": {
"version": "5.3.0",
@@ -42186,8 +42181,7 @@
"postcss-modules-extract-imports": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
- "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
- "requires": {}
+ "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw=="
},
"postcss-modules-local-by-default": {
"version": "4.0.0",
@@ -42225,8 +42219,7 @@
"postcss-normalize-charset": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz",
- "integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==",
- "requires": {}
+ "integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg=="
},
"postcss-normalize-display-values": {
"version": "5.0.1",
@@ -42597,8 +42590,7 @@
"prism-react-renderer": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.2.1.tgz",
- "integrity": "sha512-w23ch4f75V1Tnz8DajsYKvY5lF7H1+WvzvLUcF0paFxkTHSp42RS0H5CttdN2Q8RR3DRGZ9v5xD/h3n8C8kGmg==",
- "requires": {}
+ "integrity": "sha512-w23ch4f75V1Tnz8DajsYKvY5lF7H1+WvzvLUcF0paFxkTHSp42RS0H5CttdN2Q8RR3DRGZ9v5xD/h3n8C8kGmg=="
},
"prismjs": {
"version": "1.26.0",
@@ -42983,8 +42975,7 @@
"react-docgen-typescript": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.0.0.tgz",
- "integrity": "sha512-lPf+KJKAo6a9klKyK4y8WwgaX+6t5/HkVjHOpJDMbmaXfXcV7zP0QgWtnEOc3ccEUXKvlHMGUMIS9f6Zgo1BSw==",
- "requires": {}
+ "integrity": "sha512-lPf+KJKAo6a9klKyK4y8WwgaX+6t5/HkVjHOpJDMbmaXfXcV7zP0QgWtnEOc3ccEUXKvlHMGUMIS9f6Zgo1BSw=="
},
"react-dom": {
"version": "17.0.1",
@@ -43065,8 +43056,7 @@
"react-frame-component": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/react-frame-component/-/react-frame-component-4.1.3.tgz",
- "integrity": "sha512-4PurhctiqnmC1F5prPZ+LdsalH7pZ3SFA5xoc0HBe8mSHctdLLt4Cr2WXfXOoajHBYq/yiipp9zOgx+vy8GiEA==",
- "requires": {}
+ "integrity": "sha512-4PurhctiqnmC1F5prPZ+LdsalH7pZ3SFA5xoc0HBe8mSHctdLLt4Cr2WXfXOoajHBYq/yiipp9zOgx+vy8GiEA=="
},
"react-helmet": {
"version": "5.2.1",
@@ -43171,8 +43161,7 @@
"react-simple-code-editor": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-simple-code-editor/-/react-simple-code-editor-0.11.0.tgz",
- "integrity": "sha512-xGfX7wAzspl113ocfKQAR8lWPhavGWHL3xSzNLeseDRHysT+jzRBi/ExdUqevSMos+7ZtdfeuBOXtgk9HTwsrw==",
- "requires": {}
+ "integrity": "sha512-xGfX7wAzspl113ocfKQAR8lWPhavGWHL3xSzNLeseDRHysT+jzRBi/ExdUqevSMos+7ZtdfeuBOXtgk9HTwsrw=="
},
"react-style-singleton": {
"version": "2.1.1",
@@ -45331,8 +45320,7 @@
"stylis-rule-sheet": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz",
- "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==",
- "requires": {}
+ "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw=="
},
"subscriptions-transport-ws": {
"version": "0.9.19",
@@ -46557,8 +46545,7 @@
"use-callback-ref": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz",
- "integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==",
- "requires": {}
+ "integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg=="
},
"use-sidecar": {
"version": "1.0.5",
diff --git a/docs/package.json b/docs/package.json
index 0346db00bfa..65bc766c8a2 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -13,7 +13,7 @@
"npm": ">=7"
},
"dependencies": {
- "@primer/gatsby-theme-doctocat": "^3.2.0",
+ "@primer/gatsby-theme-doctocat": "^3.2.1",
"@primer/octicons-react": "^16.1.0",
"@primer/primitives": "4.1.0",
"@styled-system/prop-types": "^5.1.0",
diff --git a/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js b/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js
index d8ee2ede36d..84a3d8c83fe 100644
--- a/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js
+++ b/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js
@@ -33,6 +33,7 @@ import {
} from '@primer/octicons-react'
import * as primerComponents from '@primer/react'
import * as drafts from '@primer/react/drafts'
+import * as deprecated from '@primer/react/deprecated'
import {Placeholder} from '@primer/react/Placeholder'
import React from 'react'
import {AnchoredOverlay} from '../../../../src/AnchoredOverlay'
@@ -51,6 +52,7 @@ export default function resolveScope(metastring) {
...doctocatComponents,
...primerComponents,
...(metastring.includes('drafts') ? drafts : {}),
+ ...deprecated,
ReactRouterLink,
State,
CheckIcon,
diff --git a/docs/src/@primer/gatsby-theme-doctocat/nav.yml b/docs/src/@primer/gatsby-theme-doctocat/nav.yml
index 9584bf38cf0..b19d2f81678 100644
--- a/docs/src/@primer/gatsby-theme-doctocat/nav.yml
+++ b/docs/src/@primer/gatsby-theme-doctocat/nav.yml
@@ -35,6 +35,8 @@
# url: /useOverlay
- title: Components
children:
+ - title: ActionList
+ url: /ActionList
- title: Autocomplete
url: /Autocomplete
- title: Avatar
@@ -53,10 +55,8 @@
url: /Buttons
- title: Checkbox
url: /Checkbox
- # Temporarily hiding ChoiceFieldset because the API
- # is being refactored with breaking changes
- # - title: ChoiceFieldset
- # url: /ChoiceFieldset
+ - title: CheckboxGroup
+ url: /CheckboxGroup
- title: CircleBadge
url: /CircleBadge
- title: CircleOcticon
@@ -87,6 +87,8 @@
url: /Overlay
- title: Pagehead
url: /Pagehead
+ - title: PageLayout
+ url: /PageLayout
- title: Pagination
url: /Pagination
- title: PointerBox
@@ -99,6 +101,8 @@
url: /ProgressBar
- title: Radio
url: /Radio
+ - title: RadioGroup
+ url: /RadioGroup
- title: Select
url: /Select
- title: SelectPanel
@@ -141,8 +145,6 @@
url: /drafts/LinkButton
- title: IconButton
url: /drafts/IconButton
- - title: ActionList v2
- url: /drafts/ActionList2
- title: ActionMenu v2
url: /drafts/ActionMenu2
- title: Deprecated
@@ -163,3 +165,5 @@
url: /FormGroup
- title: SelectMenu
url: /deprecated/SelectMenu
+ - title: ActionList
+ url: /deprecated/ActionList
diff --git a/package-lock.json b/package-lock.json
index 2a8634b87cf..43c3bdd8a8b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@primer/react",
- "version": "34.5.0",
+ "version": "34.6.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@primer/react",
- "version": "34.5.0",
+ "version": "34.6.0",
"license": "MIT",
"dependencies": {
"@primer/behaviors": "1.1.0",
diff --git a/package.json b/package.json
index 35cc8dcd0e4..986a6b2b706 100644
--- a/package.json
+++ b/package.json
@@ -7,12 +7,19 @@
"exports": {
".": {
"node": "./lib/index.js",
+ "require": "./lib/index.js",
"default": "./lib-esm/index.js"
},
"./drafts": {
"node": "./lib/drafts/index.js",
+ "require": "./lib/drafts/index.js",
"default": "./lib-esm/drafts/index.js"
},
+ "./deprecated": {
+ "node": "./lib/deprecated/index.js",
+ "require": "./lib/deprecated/index.js",
+ "default": "./lib-esm/deprecated/index.js"
+ },
"./lib-esm/*": {
"node": [
"./lib/*.js",
diff --git a/src/ActionList2/ActionListContainerContext.tsx b/src/ActionList/ActionListContainerContext.tsx
similarity index 100%
rename from src/ActionList2/ActionListContainerContext.tsx
rename to src/ActionList/ActionListContainerContext.tsx
diff --git a/src/ActionList2/Description.tsx b/src/ActionList/Description.tsx
similarity index 100%
rename from src/ActionList2/Description.tsx
rename to src/ActionList/Description.tsx
diff --git a/src/ActionList/Divider.tsx b/src/ActionList/Divider.tsx
index 0513b60f1ce..e286448eb42 100644
--- a/src/ActionList/Divider.tsx
+++ b/src/ActionList/Divider.tsx
@@ -1,25 +1,29 @@
import React from 'react'
-import styled from 'styled-components'
+import Box from '../Box'
import {get} from '../constants'
-
-export const StyledDivider = styled.div`
- height: 1px;
- background: ${get('colors.border.muted')};
- margin-top: calc(${get('space.2')} - 1px);
- margin-bottom: ${get('space.2')};
-`
+import {Theme} from '../ThemeProvider'
+import {SxProp, merge} from '../sx'
/**
* Visually separates `Item`s or `Group`s in an `ActionList`.
*/
-export function Divider(): JSX.Element {
- return
-}
-/**
- * `Divider` fulfills the `ItemPropsWithCustomRenderer` contract,
- * so it can be used inline in an `ActionList`’s `items` prop.
- * In other words, `items={[ActionList.Divider]}` is supported as a concise
- * alternative to `items={[{renderItem: () => }]}`.
- */
-Divider.renderItem = Divider
+export const Divider: React.FC = ({sx = {}}) => {
+ return (
+ `calc(${get('space.2')(theme)} - 1px)`,
+ marginBottom: 2,
+ listStyle: 'none' // hide the ::marker inserted by browser's stylesheet
+ },
+ sx as SxProp
+ )}
+ data-component="ActionList.Divider"
+ />
+ )
+}
diff --git a/src/ActionList/Group.tsx b/src/ActionList/Group.tsx
index 915b54e6643..d67be85f3ce 100644
--- a/src/ActionList/Group.tsx
+++ b/src/ActionList/Group.tsx
@@ -1,45 +1,110 @@
import React from 'react'
-import styled from 'styled-components'
-import sx, {SxProp} from '../sx'
-import {Header, HeaderProps} from './Header'
+import {useSSRSafeId} from '@react-aria/ssr'
+import Box from '../Box'
+import {SxProp} from '../sx'
+import {ListContext, ListProps} from './List'
+import {AriaRole} from '../utils/types'
-/**
- * Contract for props passed to the `Group` component.
- */
-export interface GroupProps extends React.ComponentPropsWithoutRef<'div'>, SxProp {
+export type GroupProps = {
/**
- * Props for a `Header` to render in the `Group`.
+ * Style variations. Usage is discretionary.
+ *
+ * - `"filled"` - Superimposed on a background, offset from nearby content
+ * - `"subtle"` - Relatively less offset from nearby content
*/
- header?: HeaderProps
-
+ variant?: 'subtle' | 'filled'
/**
- * The id of the group.
+ * Primary text which names a `Group`.
*/
- groupId?: string
-
+ title?: string
/**
- * `Items` to render in the `Group`.
+ * Secondary text which provides additional information about a `Group`.
*/
- items?: JSX.Element[]
-
+ auxiliaryText?: string
/**
- * Whether to display a divider above each `Item` in this `Group` when it does not follow a `Header` or `Divider`.
+ * The ARIA role describing the function of the list inside `Group` component. `listbox` or `menu` are a common values.
*/
- showItemDividers?: boolean
+ role?: AriaRole
+} & SxProp & {
+ /**
+ * Whether multiple Items or a single Item can be selected in the Group. Overrides value on ActionList root.
+ */
+ selectionVariant?: ListProps['selectionVariant'] | false
+ }
+
+type ContextProps = Pick
+export const GroupContext = React.createContext({})
+
+export const Group: React.FC = ({
+ title,
+ variant = 'subtle',
+ auxiliaryText,
+ selectionVariant,
+ role,
+ sx = {},
+ ...props
+}) => {
+ const labelId = useSSRSafeId()
+ const {role: listRole} = React.useContext(ListContext)
+
+ return (
+
+ {title && }
+
+
+ {props.children}
+
+
+
+ )
}
-const StyledGroup = styled.div`
- ${sx}
-`
+export type HeaderProps = Pick & {
+ labelId: string
+}
/**
- * Collects related `Items` in an `ActionList`.
+ * Displays the name and description of a `Group`.
+ *
+ * For visual presentation only. It's hidden from screen readers.
*/
-export function Group({header, items, ...props}: GroupProps): JSX.Element {
+const Header: React.FC = ({variant, title, auxiliaryText, labelId, ...props}) => {
+ const {variant: listVariant} = React.useContext(ListContext)
+
+ const styles = {
+ paddingY: '6px',
+ paddingX: listVariant === 'full' ? 2 : 3,
+ fontSize: 0,
+ fontWeight: 'bold',
+ color: 'fg.muted',
+ ...(variant === 'filled' && {
+ backgroundColor: 'canvas.subtle',
+ marginX: 0,
+ marginBottom: 2,
+ borderTop: '1px solid',
+ borderBottom: '1px solid',
+ borderColor: 'neutral.muted'
+ })
+ }
+
return (
-
- {header && }
- {items}
-
+
+ {title}
+ {auxiliaryText && {auxiliaryText} }
+
)
}
diff --git a/src/ActionList/Item.tsx b/src/ActionList/Item.tsx
index 0c37a1dc131..db1c4ed04db 100644
--- a/src/ActionList/Item.tsx
+++ b/src/ActionList/Item.tsx
@@ -1,66 +1,57 @@
-import {CheckIcon, IconProps} from '@primer/octicons-react'
-import React, {useCallback} from 'react'
-import {get} from '../constants'
-import sx, {SxProp} from '../sx'
-import Truncate from '../Truncate'
-import {ItemInput} from './List'
+import React from 'react'
+import {ForwardRefComponent as PolymorphicForwardRefComponent} from '@radix-ui/react-polymorphic'
+import {useSSRSafeId} from '@react-aria/ssr'
import styled from 'styled-components'
-import {StyledHeader} from './Header'
-import {StyledDivider} from './Divider'
import {useTheme} from '../ThemeProvider'
-import {
- activeDescendantActivatedDirectly,
- activeDescendantActivatedIndirectly,
- isActiveDescendantAttribute
-} from '@primer/behaviors'
-import {useSSRSafeId} from '@react-aria/ssr'
-import {ForwardRefComponent as PolymorphicForwardRefComponent} from '@radix-ui/react-polymorphic'
+import Box, {BoxProps} from '../Box'
+import sx, {SxProp, merge} from '../sx'
+import createSlots from '../utils/create-slots'
import {AriaRole} from '../utils/types'
+import {ListContext, ListProps} from './List'
+import {GroupContext, GroupProps} from './Group'
+import {ActionListContainerContext} from './ActionListContainerContext'
+import {Selection} from './Selection'
-/**
- * Contract for props passed to the `Item` component.
- */
-export interface ItemProps extends SxProp {
- /**
- * Primary text which names an `Item`.
- */
- text?: string
-
- /**
- * Secondary text which provides additional information about an `Item`.
- */
- description?: string
+export const getVariantStyles = (variant: ItemProps['variant'], disabled: ItemProps['disabled']) => {
+ if (disabled) {
+ return {
+ color: 'primer.fg.disabled',
+ iconColor: 'primer.fg.disabled',
+ annotationColor: 'primer.fg.disabled'
+ }
+ }
- /**
- * Secondary text style variations. Usage is discretionary.
- *
- * - `"inline"` - Secondary text is positioned beside primary text.
- * - `"block"` - Secondary text is positioned below primary text.
- */
- descriptionVariant?: 'inline' | 'block'
+ switch (variant) {
+ case 'danger':
+ return {
+ color: 'danger.fg',
+ iconColor: 'danger.fg',
+ annotationColor: 'fg.muted',
+ hoverColor: 'actionListItem.danger.hoverText'
+ }
+ default:
+ return {
+ color: 'fg.default',
+ iconColor: 'fg.muted',
+ annotationColor: 'fg.muted',
+ hoverColor: 'fg.default'
+ }
+ }
+}
+export type ItemProps = {
/**
- * Icon (or similar) positioned before `Item` text.
+ * Primary content for an Item
*/
- leadingVisual?: React.FunctionComponent
-
+ children?: React.ReactNode
/**
- * @deprecated Use `trailingVisual` instead
- * Icon (or similar) positioned after `Item` text.
+ * Callback that will trigger both on click selection and keyboard selection.
*/
- trailingIcon?: React.FunctionComponent
-
+ onSelect?: (event: React.MouseEvent | React.KeyboardEvent) => void
/**
- * @deprecated Use `trailingVisual` instead
- * Text positioned after `Item` text and optional trailing icon.
+ * Is the `Item` is currently selected?
*/
- trailingText?: string
-
- /**
- * Icon or text positioned after `Item` text.
- */
- trailingVisual?: React.ReactNode
-
+ selected?: boolean
/**
* Style variations associated with various `Item` types.
*
@@ -68,414 +59,211 @@ export interface ItemProps extends SxProp {
* - `"danger"` - A destructive action `Item`.
*/
variant?: 'default' | 'danger'
-
- /**
- * Whether to display a divider above the `Item` when it does not follow a `Header` or `Divider`.
- */
- showDivider?: boolean
-
- /**
- * For `Item`s which can be selected, whether the `Item` is currently selected.
- */
- selected?: boolean
-
- /**
- * For `Item`s which can be selected, whether `multiple` `Item`s or a `single` `Item` can be selected
- */
- selectionVariant?: 'single' | 'multiple'
-
- /**
- * Designates the group that an item belongs to.
- */
- groupId?: string
-
/**
* Items that are disabled can not be clicked, selected, or navigated through.
*/
disabled?: boolean
-
- /**
- * Callback that will trigger both on click selection and keyboard selection.
- */
- onAction?: (item: ItemProps, event: React.MouseEvent | React.KeyboardEvent) => void
-
/**
- * An id associated with this item. Should be unique between items
+ * The ARIA role describing the function of `Item` component. `option` is a common value.
*/
- id?: number | string
-
- /**
- * Node to be included inside the item before the text.
- */
- children?: React.ReactNode
-
+ role?: AriaRole
/**
- * The ARIA role describing the function of `List` component. `option` is a common value.
+ * id to attach to the root element of the Item
*/
- role?: AriaRole
-
+ id?: string
/**
- * An item to pass back in the `onAction` callback, meant as
+ * Private API for use internally only. Used by LinkItem to wrap contents in an anchor
*/
- item?: ItemInput
+ _PrivateItemWrapper?: React.FC
+} & SxProp
+
+const {Slots, Slot} = createSlots(['LeadingVisual', 'InlineDescription', 'BlockDescription', 'TrailingVisual'])
+export {Slot}
+export type ItemContext = Pick & {
+ inlineDescriptionId: string
+ blockDescriptionId: string
}
-const getItemVariant = (variant = 'default', disabled?: boolean) => {
- if (disabled) {
- return {
- color: get('colors.primer.fg.disabled'),
- iconColor: get('colors.primer.fg.disabled'),
- annotationColor: get('colors.primer.fg.disabled'),
- hoverCursor: 'default'
+const LiBox = styled.li(sx)
+export const TEXT_ROW_HEIGHT = '20px' // custom value off the scale
+
+export const Item = React.forwardRef(
+ (
+ {
+ variant = 'default',
+ disabled = false,
+ selected = undefined,
+ onSelect,
+ sx: sxProp = {},
+ id,
+ role,
+ _PrivateItemWrapper,
+ ...props
+ },
+ forwardedRef
+ ): JSX.Element => {
+ const {variant: listVariant, showDividers, selectionVariant: listSelectionVariant} = React.useContext(ListContext)
+ const {selectionVariant: groupSelectionVariant} = React.useContext(GroupContext)
+ const {container, afterSelect, selectionAttribute} = React.useContext(ActionListContainerContext)
+
+ let selectionVariant: ListProps['selectionVariant'] | GroupProps['selectionVariant']
+ if (typeof groupSelectionVariant !== 'undefined') selectionVariant = groupSelectionVariant
+ else selectionVariant = listSelectionVariant
+
+ /** Infer item role based on the container */
+ let itemRole: ItemProps['role']
+ if (container === 'ActionMenu' || container === 'DropdownMenu') {
+ if (selectionVariant === 'single') itemRole = 'menuitemradio'
+ else if (selectionVariant === 'multiple') itemRole = 'menuitemcheckbox'
+ else itemRole = 'menuitem'
}
- }
- switch (variant) {
- case 'danger':
- return {
- color: get('colors.danger.fg'),
- iconColor: get('colors.danger.fg'),
- annotationColor: get('colors.fg.muted'),
- hoverCursor: 'pointer',
- hoverBg: get('colors.actionListItem.danger.hoverBg'),
- focusBg: get('colors.actionListItem.danger.activeBg'),
- hoverText: get('colors.actionListItem.danger.hoverText')
- }
- default:
- return {
- color: get('colors.fg.default'),
- iconColor: get('colors.fg.muted'),
- annotationColor: get('colors.fg.muted'),
- hoverCursor: 'pointer',
- hoverBg: get('colors.actionListItem.default.hoverBg'),
- focusBg: get('colors.actionListItem.default.activeBg')
+ const {theme} = useTheme()
+
+ const styles = {
+ display: 'flex',
+ paddingX: 2,
+ fontSize: 1,
+ paddingY: '6px', // custom value off the scale
+ lineHeight: TEXT_ROW_HEIGHT,
+ minHeight: 5,
+ marginX: listVariant === 'inset' ? 2 : 0,
+ borderRadius: listVariant === 'inset' ? 2 : 0,
+ transition: 'background 33.333ms linear',
+ color: getVariantStyles(variant, disabled).color,
+ cursor: 'pointer',
+ '&[aria-disabled]': {cursor: 'not-allowed'},
+
+ '@media (hover: hover) and (pointer: fine)': {
+ ':hover:not([aria-disabled])': {
+ backgroundColor: `actionListItem.${variant}.hoverBg`,
+ color: getVariantStyles(variant, disabled).hoverColor
+ },
+ ':focus:not([data-focus-visible-added])': {
+ backgroundColor: `actionListItem.${variant}.selectedBg`,
+ color: getVariantStyles(variant, disabled).hoverColor,
+ outline: 'none'
+ },
+ '&[data-focus-visible-added]': {
+ // we don't use :focus-visible because not all browsers (safari) have it yet
+ outline: 'none',
+ border: `2 solid`,
+ boxShadow: `0 0 0 2px ${theme?.colors.accent.emphasis}`
+ },
+ ':active:not([aria-disabled])': {
+ backgroundColor: `actionListItem.${variant}.activeBg`,
+ color: getVariantStyles(variant, disabled).hoverColor
+ }
+ },
+
+ /** Divider styles */
+ '[data-component="ActionList.Item--DividerContainer"]': {
+ position: 'relative'
+ },
+ '[data-component="ActionList.Item--DividerContainer"]::before': {
+ content: '" "',
+ display: 'block',
+ position: 'absolute',
+ width: '100%',
+ top: '-7px',
+ border: '0 solid',
+ borderTopWidth: showDividers ? `1px` : '0',
+ borderColor: 'var(--divider-color, transparent)'
+ },
+ // show between 2 items
+ ':not(:first-of-type)': {'--divider-color': theme?.colors.actionListItem.inlineDivider},
+ // hide divider after dividers & group header, with higher importance!
+ '[data-component="ActionList.Divider"] + &': {'--divider-color': 'transparent !important'},
+ // hide border on current and previous item
+ '&:hover:not([aria-disabled]), &:focus:not([aria-disabled]), &[data-focus-visible-added]:not([aria-disabled])': {
+ '--divider-color': 'transparent'
+ },
+ '&:hover:not([aria-disabled]) + &, &:focus:not([aria-disabled]) + &, &[data-focus-visible-added] + li': {
+ '--divider-color': 'transparent'
}
- }
-}
-
-const DividedContent = styled.div`
- display: flex;
- min-width: 0;
-
- /* Required for dividers */
- position: relative;
- flex-grow: 1;
-`
-
-const MainContent = styled.div`
- align-items: baseline;
- display: flex;
- min-width: 0;
- flex-direction: var(--main-content-flex-direction);
- flex-grow: 1;
-`
-
-const StyledItem = styled.div<
- {
- variant: ItemProps['variant']
- showDivider: ItemProps['showDivider']
- item?: ItemInput
- } & SxProp
->`
- /* 6px vertical padding + 20px line height = 32px total height
- *
- * TODO: When rem-based spacing on a 4px scale lands, replace
- * hardcoded '6px' with 'calc((${get('space.s32')} - ${get('space.20')}) / 2)'.
- */
- padding: 6px ${get('space.2')};
- display: flex;
- border-radius: ${get('radii.2')};
- color: ${({variant, item}) => getItemVariant(variant, item?.disabled).color};
- // 2 frames on a 60hz monitor
- transition: background 33.333ms linear;
- text-decoration: none;
-
- @media (hover: hover) and (pointer: fine) {
- :hover {
- // allow override in case another item in the list is active/focused
- background: var(
- --item-hover-bg-override,
- ${({variant, item}) => getItemVariant(variant, item?.disabled).hoverBg}
- );
- color: ${({variant, item}) => getItemVariant(variant, item?.disabled).hoverText};
- cursor: ${({variant, item}) => getItemVariant(variant, item?.disabled).hoverCursor};
- }
- }
-
- // Item dividers
- :not(:first-of-type):not(${StyledDivider} + &):not(${StyledHeader} + &) {
- margin-top: ${({showDivider}) => (showDivider ? `1px` : '0')};
-
- ${DividedContent}::before {
- content: ' ';
- display: block;
- position: absolute;
- width: 100%;
- top: -7px;
- // NB: This 'get' won’t execute if it’s moved into the arrow function below.
- border: 0 solid ${get('colors.border.muted')};
- border-top-width: ${({showDivider}) => (showDivider ? `1px` : '0')};
}
- }
-
- // Item dividers should not be visible:
- // - above Hovered
- &:hover ${DividedContent}::before,
- // - below Hovered
- // '*' instead of '&' because '&' maps to separate class names depending on 'variant'
- :hover + * ${DividedContent}::before {
- // allow override in case another item in the list is active/focused
- border-color: var(--item-hover-divider-border-color-override, transparent) !important;
- }
-
- // - above Focused
- &:focus ${DividedContent}::before,
- // - below Focused
- // '*' instead of '&' because '&' maps to separate class names depending on 'variant'
- :focus + * ${DividedContent}::before,
- // - above Active Descendent
- &[${isActiveDescendantAttribute}] ${DividedContent}::before,
- // - below Active Descendent
- [${isActiveDescendantAttribute}] + & ${DividedContent}::before {
- // '!important' because all the ':not's above give higher specificity
- border-color: transparent !important;
- }
-
- // Active Descendant
- &[${isActiveDescendantAttribute}='${activeDescendantActivatedDirectly}'] {
- background: ${({variant, item}) => getItemVariant(variant, item?.disabled).focusBg};
- }
- &[${isActiveDescendantAttribute}='${activeDescendantActivatedIndirectly}'] {
- background: ${({variant, item}) => getItemVariant(variant, item?.disabled).hoverBg};
- }
- &:focus {
- background: ${({variant, item}) => getItemVariant(variant, item?.disabled).focusBg};
- outline: none;
- }
-
- &:active {
- background: ${({variant, item}) => getItemVariant(variant, item?.disabled).focusBg};
- }
-
- ${sx}
-`
-
-export const TextContainer = styled.span<{
- dangerouslySetInnerHtml?: React.DOMAttributes['dangerouslySetInnerHTML']
-}>``
-
-const BaseVisualContainer = styled.div<{variant?: ItemProps['variant']; disabled?: boolean}>`
- // Match visual height to adjacent text line height.
- // TODO: When rem-based spacing on a 4px scale lands, replace
- // hardcoded '20px' with '${get('space.s20')}'.
- height: 20px;
- width: ${get('space.3')};
- margin-right: ${get('space.2')};
- display: flex;
- justify-content: center;
- align-items: center;
- flex-shrink: 0;
-`
-
-const ColoredVisualContainer = styled(BaseVisualContainer)`
- svg {
- fill: ${({variant, disabled}) => getItemVariant(variant, disabled).iconColor};
- font-size: ${get('fontSizes.0')};
- }
-`
-
-const LeadingVisualContainer = styled(ColoredVisualContainer)`
- display: flex;
- flex-direction: column;
- justify-content: center;
-`
-
-const TrailingContent = styled(ColoredVisualContainer)`
- color: ${({variant, disabled}) => getItemVariant(variant, disabled).annotationColor}};
- margin-left: ${get('space.2')};
- margin-right: 0;
- width: auto;
- div:nth-child(2) {
- margin-left: ${get('space.2')};
- }
-`
-
-const DescriptionContainer = styled.span`
- color: ${get('colors.fg.muted')};
- font-size: ${get('fontSizes.0')};
- // TODO: When rem-based spacing on a 4px scale lands, replace
- // hardcoded '16px' with '${get('lh-12')}'.
- line-height: 16px;
- margin-left: var(--description-container-margin-left);
- min-width: 0;
- flex-grow: 1;
- flex-basis: var(--description-container-flex-basis);
-`
-
-const MultiSelectIcon = styled.svg<{selected?: boolean}>`
- rect {
- fill: ${({selected}) => (selected ? get('colors.accent.fg') : get('colors.canvas.default'))};
- stroke: ${({selected}) => (selected ? get('colors.accent.fg') : get('colors.border.default'))};
- shape-rendering: auto; // this is a workaround to override global style in github/github, see primer/react#1666
- }
- path {
- fill: ${get('colors.fg.onEmphasis')};
- boxshadow: ${get('shadow.small')};
- opacity: ${({selected}) => (selected ? 1 : 0)};
+ const clickHandler = React.useCallback(
+ event => {
+ if (disabled) return
+ if (!event.defaultPrevented) {
+ if (typeof onSelect === 'function') onSelect(event)
+ // if this Item is inside a Menu, close the Menu
+ if (typeof afterSelect === 'function') afterSelect()
+ }
+ },
+ [onSelect, disabled, afterSelect]
+ )
+
+ const keyPressHandler = React.useCallback(
+ event => {
+ if (disabled) return
+ if (!event.defaultPrevented && [' ', 'Enter'].includes(event.key)) {
+ if (typeof onSelect === 'function') onSelect(event)
+ // if this Item is inside a Menu, close the Menu
+ if (typeof afterSelect === 'function') afterSelect()
+ }
+ },
+ [onSelect, disabled, afterSelect]
+ )
+
+ // use props.id if provided, otherwise generate one.
+ const labelId = useSSRSafeId(id)
+ const inlineDescriptionId = useSSRSafeId(id && `${id}--inline-description`)
+ const blockDescriptionId = useSSRSafeId(id && `${id}--block-description`)
+
+ const ItemWrapper = _PrivateItemWrapper || React.Fragment
+
+ return (
+
+ {slots => (
+
+
+
+ {slots.LeadingVisual}
+
+
+
+
+ {props.children}
+
+ {slots.InlineDescription}
+
+ {slots.TrailingVisual}
+
+ {slots.BlockDescription}
+
+
+
+ )}
+
+ )
}
-`
+) as PolymorphicForwardRefComponent<'li', ItemProps>
-/**
- * An actionable or selectable `Item` with an optional icon and description.
- */
-export const Item = React.forwardRef((itemProps, ref) => {
- const {
- as: Component,
- text,
- description,
- descriptionVariant = 'inline',
- selected,
- selectionVariant,
- leadingVisual: LeadingVisual,
- trailingIcon: TrailingIcon,
- trailingVisual: TrailingVisual,
- trailingText,
- variant = 'default',
- showDivider,
- disabled,
- onAction,
- onKeyPress,
- children,
- onClick,
- id,
- ...props
- } = itemProps
-
- const labelId = useSSRSafeId()
- const descriptionId = useSSRSafeId()
-
- const keyPressHandler = useCallback(
- event => {
- if (disabled) {
- return
- }
- onKeyPress?.(event)
-
- if (!event.defaultPrevented && [' ', 'Enter'].includes(event.key)) {
- onAction?.(itemProps, event)
- }
- },
- [onAction, disabled, itemProps, onKeyPress]
- )
-
- const clickHandler = useCallback(
- event => {
- if (disabled) {
- return
- }
- onClick?.(event)
- if (!event.defaultPrevented) {
- onAction?.(itemProps, event)
- }
- },
- [onAction, disabled, itemProps, onClick]
- )
+Item.displayName = 'ActionList.Item'
- const {theme} = useTheme()
+const ConditionalBox: React.FC<{if: boolean} & BoxProps> = props => {
+ const {if: condition, ...rest} = props
- return (
-
- {!!selected === selected && (
-
- {selectionVariant === 'multiple' ? (
- <>
- {/**
- * we use a svg instead of an input because there should not
- * be an interactive element inside an option
- * svg copied from primer/css
- */}
-
-
-
-
- >
- ) : (
- selected &&
- )}
-
- )}
- {LeadingVisual && (
-
-
-
- )}
-
-
- {children}
- {text ? {text} : null}
- {description ? (
-
- {descriptionVariant === 'block' ? (
- description
- ) : (
-
- {description}
-
- )}
-
- ) : null}
-
- {/* backward compatibility: prefer TrailingVisual but fallback to TrailingIcon */}
- {TrailingVisual ? (
-
- {typeof TrailingVisual === 'function' ? : TrailingVisual}
-
- ) : TrailingIcon || trailingText ? (
-
- {trailingText}
- {TrailingIcon && }
-
- ) : null}
-
-
- )
-}) as PolymorphicForwardRefComponent<'div', ItemProps>
-
-Item.displayName = 'ActionList.Item'
+ if (condition) return {props.children}
+ else return <>{props.children}>
+}
diff --git a/src/ActionList2/LinkItem.tsx b/src/ActionList/LinkItem.tsx
similarity index 100%
rename from src/ActionList2/LinkItem.tsx
rename to src/ActionList/LinkItem.tsx
diff --git a/src/ActionList/List.tsx b/src/ActionList/List.tsx
index f0d4768396f..a3c060a532a 100644
--- a/src/ActionList/List.tsx
+++ b/src/ActionList/List.tsx
@@ -1,258 +1,73 @@
-import React, {Key} from 'react'
-import type {AriaRole} from '../utils/types'
-import {Group, GroupProps} from './Group'
-import {Item, ItemProps} from './Item'
-import {Divider} from './Divider'
+import React from 'react'
+import {ForwardRefComponent as PolymorphicForwardRefComponent} from '@radix-ui/react-polymorphic'
import styled from 'styled-components'
-import {get} from '../constants'
-import {SystemCssProperties} from '@styled-system/css'
-import {hasActiveDescendantAttribute} from '@primer/behaviors'
-import {Merge} from '../utils/types/Merge'
-
-type RenderItemFn = (props: ItemProps) => React.ReactElement
-
-export type ItemInput =
- | Merge, ItemProps>
- | ((Partial & {renderItem: RenderItemFn}) & {key?: Key})
-
-/**
- * Contract for props passed to the `List` component.
- */
-export interface ListPropsBase {
- /**
- * A collection of `Item` props and `Item`-level custom `Item` renderers.
- */
- items: ItemInput[]
-
- /**
- * The ARIA role describing the function of `List` component. `listbox` is a common value.
- */
- role?: AriaRole
-
- /**
- * id to attach to the base DOM node of the list
- */
- id?: string
-
- /**
- * A `List`-level custom `Item` renderer. Every `Item` within this `List`
- * without a `Group`-level or `Item`-level custom `Item` renderer will be
- * rendered using this function component.
- */
- renderItem?: RenderItemFn
+import sx, {SxProp, merge} from '../sx'
+import {AriaRole} from '../utils/types'
+import {ActionListContainerContext} from './ActionListContainerContext'
+export type ListProps = {
/**
- * A `List`-level custom `Group` renderer. Every `Group` within this `List`
- * without a `Group`-level custom `Item` renderer will be rendered using
- * this function component.
- */
- renderGroup?: typeof Group
-
- /**
- * Style variations. Usage is discretionary.
- *
- * - `"inset"` - `List` children are offset (vertically and horizontally) from `List`’s edges
- * - `"full"` - `List` children are flush (vertically and horizontally) with `List` edges
+ * `inset` children are offset (vertically and horizontally) from `List`’s edges, `full` children are flush (vertically and horizontally) with `List` edges
*/
variant?: 'inset' | 'full'
-
/**
- * For `Item`s which can be selected, whether `multiple` `Item`s or a `single` `Item` can be selected
+ * Whether multiple Items or a single Item can be selected.
*/
selectionVariant?: 'single' | 'multiple'
-
/**
- * Whether to display a divider above each `Item` in this `List` when it does not follow a `Header` or `Divider`.
+ * Display a divider above each `Item` in this `List` when it does not follow a `Header` or `Divider`.
*/
- showItemDividers?: boolean
-}
-
-/**
- * Contract for props passed to the `List` component, when its `Item`s are collected in `Group`s.
- */
-export interface GroupedListProps extends ListPropsBase {
- /**
- * A collection of `Group` props (except `items`), plus a unique group identifier
- * and `Group`-level custom `Item` or `Group` renderers.
- */
- groupMetadata: ((
- | Omit
- | Omit & {renderItem?: RenderItemFn; renderGroup?: typeof Group}, 'items'>
- ) & {groupId: string})[]
-
+ showDividers?: boolean
/**
- * A collection of `Item` props, plus associated group identifiers
- * and `Item`-level custom `Item` renderers.
- */
- items: ((ItemProps | (Partial & {renderItem: RenderItemFn})) & {groupId: string})[]
-}
-
-/**
- * Asserts that the given value fulfills the `GroupedListProps` contract.
- * @param props A value which fulfills either the `ListPropsBase` or the `GroupedListProps` contract.
- */
-function isGroupedListProps(props: ListProps): props is GroupedListProps {
- return 'groupMetadata' in props
-}
-
-/**
- * Contract for props passed to the `List` component.
- */
-export type ListProps = ListPropsBase | GroupedListProps
-
-const StyledList = styled.div`
- font-size: ${get('fontSizes.1')};
- /* 14px font-size * 1.428571429 = 20px line height
- *
- * TODO: When rem-based spacing on a 4px scale lands, replace
- * hardcoded '20px'
+ * The ARIA role describing the function of `List` component. `listbox` or `menu` are a common values.
*/
- line-height: 20px;
-
- &[${hasActiveDescendantAttribute}], &:focus-within {
- --item-hover-bg-override: none;
- --item-hover-divider-border-color-override: ${get('colors.border.muted')};
- }
-`
-
-/**
- * Returns `sx` prop values for `List` children matching the given `List` style variation.
- * @param variant `List` style variation.
- */
-function useListVariant(variant: ListProps['variant'] = 'inset'): {
- firstGroupStyle?: SystemCssProperties
- lastGroupStyle?: SystemCssProperties
- headerStyle?: SystemCssProperties
- itemStyle?: SystemCssProperties
-} {
- switch (variant) {
- case 'full':
- return {
- headerStyle: {paddingX: get('space.2')},
- itemStyle: {borderRadius: 0}
- }
- default:
- return {
- firstGroupStyle: {marginTop: get('space.2')},
- lastGroupStyle: {marginBottom: get('space.2')},
- itemStyle: {marginX: get('space.2')}
- }
- }
-}
-
-/**
- * Lists `Item`s, either grouped or ungrouped, with a `Divider` between each `Group`.
- */
-export const List = React.forwardRef((props, forwardedRef): JSX.Element => {
- // Get `sx` prop values for `List` children matching the given `List` style variation.
- const {firstGroupStyle, lastGroupStyle, headerStyle, itemStyle} = useListVariant(props.variant)
+ role?: AriaRole
+} & SxProp
+
+type ContextProps = Pick
+export const ListContext = React.createContext({})
+
+const ListBox = styled.ul(sx)
+
+export const List = React.forwardRef(
+ (
+ {variant = 'inset', selectionVariant, showDividers = false, role, sx: sxProp = {}, ...props},
+ forwardedRef
+ ): JSX.Element => {
+ const styles = {
+ margin: 0,
+ paddingInlineStart: 0, // reset ul styles
+ paddingY: variant === 'inset' ? 2 : 0
+ }
- /**
- * Render a `Group` using the first of the following renderers that is defined:
- * A `Group`-level or `List`-level custom `Group` renderer, or
- * the default `Group` renderer.
- */
- const renderGroup = (
- groupProps: GroupProps | (Partial & {renderItem?: typeof Item; renderGroup?: typeof Group})
- ) => {
- const GroupComponent = (('renderGroup' in groupProps && groupProps.renderGroup) ?? props.renderGroup) || Group
- return
- }
+ /** if list is inside a Menu, it will get a role from the Menu */
+ const {
+ listRole,
+ listLabelledBy,
+ selectionVariant: containerSelectionVariant // TODO: Remove after DropdownMenu2 deprecation
+ } = React.useContext(ActionListContainerContext)
- /**
- * Render an `Item` using the first of the following renderers that is defined:
- * An `Item`-level, `Group`-level, or `List`-level custom `Item` renderer,
- * or the default `Item` renderer.
- */
- const renderItem = (itemProps: ItemInput, item: ItemInput, itemIndex: number) => {
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
- const ItemComponent = ('renderItem' in itemProps && itemProps.renderItem) || props.renderItem || Item
- const key = ('key' in itemProps ? itemProps.key : undefined) ?? itemProps.id?.toString() ?? itemIndex.toString()
return (
-
+
+
+ {props.children}
+
+
)
}
-
- /**
- * An array of `Group`s, each with an associated `Header` and with an array of `Item`s belonging to that `Group`.
- */
- let groups: (GroupProps | (Partial & {renderItem?: typeof Item; renderGroup?: typeof Group}))[] = []
-
- // Collect rendered `Item`s into `Group`s, avoiding excess iteration over the lists of `items` and `groupMetadata`:
- if (!isGroupedListProps(props)) {
- // When no `groupMetadata`s is provided, collect rendered `Item`s into a single anonymous `Group`.
- groups = [{items: props.items.map((item, index) => renderItem(item, item, index)), groupId: '0'}]
- } else {
- // When `groupMetadata` is provided, collect rendered `Item`s into their associated `Group`s.
-
- /**
- * A map of group identifiers to `Group`s, each with an associated array of `Item`s belonging to that `Group`.
- */
- const groupMap = props.groupMetadata.reduce(
- (groupAccumulator, groupMetadata) => groupAccumulator.set(groupMetadata.groupId, groupMetadata),
- new Map & {renderItem?: typeof Item; renderGroup?: typeof Group})>()
- )
-
- for (const itemProps of props.items) {
- // Look up the group associated with the current item.
- const group = groupMap.get(itemProps.groupId)
- const itemIndex = group?.items?.length ?? 0
-
- // Upsert the group to include the current item (rendered).
- groupMap.set(itemProps.groupId, {
- ...group,
- items: [
- ...(group?.items ?? []),
- renderItem(
- {
- showDivider: group?.showItemDividers,
- ...(group && 'renderItem' in group && {renderItem: group.renderItem}),
- ...itemProps
- },
- itemProps,
- itemIndex
- )
- ]
- })
- }
-
- groups = [...groupMap.values()]
- }
-
- return (
-
- {groups.map(({header, ...groupProps}, index) => {
- const hasFilledHeader = header?.variant === 'filled'
- const shouldShowDivider = index > 0 && !hasFilledHeader
- return (
-
- {shouldShowDivider ? : null}
- {renderGroup({
- sx: {
- ...(index === 0 && firstGroupStyle),
- ...(index === groups.length - 1 && lastGroupStyle),
- ...(index > 0 && !shouldShowDivider && {mt: 2})
- },
- ...(header && {
- header: {
- ...header,
- sx: {...headerStyle, ...header.sx}
- }
- }),
- ...groupProps
- })}
-
- )
- })}
-
- )
-})
+) as PolymorphicForwardRefComponent<'ul', ListProps>
List.displayName = 'ActionList'
diff --git a/src/ActionList2/Selection.tsx b/src/ActionList/Selection.tsx
similarity index 100%
rename from src/ActionList2/Selection.tsx
rename to src/ActionList/Selection.tsx
diff --git a/src/ActionList2/Visuals.tsx b/src/ActionList/Visuals.tsx
similarity index 100%
rename from src/ActionList2/Visuals.tsx
rename to src/ActionList/Visuals.tsx
diff --git a/src/ActionList/index.ts b/src/ActionList/index.ts
index 71eb71708f8..f27a3b53d50 100644
--- a/src/ActionList/index.ts
+++ b/src/ActionList/index.ts
@@ -1,10 +1,19 @@
import {List} from './List'
import {Group} from './Group'
import {Item} from './Item'
+import {LinkItem} from './LinkItem'
import {Divider} from './Divider'
+import {Description} from './Description'
+import {LeadingVisual, TrailingVisual} from './Visuals'
+
export type {ListProps as ActionListProps} from './List'
-export type {GroupProps} from './Group'
-export type {ItemProps} from './Item'
+export type {GroupProps as ActionListGroupProps} from './Group'
+export type {ItemProps as ActionListItemProps} from './Item'
+export type {DescriptionProps as ActionListDescriptionProps} from './Description'
+export type {
+ LeadingVisualProps as ActionListLeadingVisualProps,
+ TrailingVisualProps as ActionListTrailingVisualProps
+} from './Visuals'
/**
* Collection of list-related components.
@@ -13,9 +22,21 @@ export const ActionList = Object.assign(List, {
/** Collects related `Items` in an `ActionList`. */
Group,
- /** An actionable or selectable `Item` with an optional icon and description. */
+ /** An actionable or selectable `Item` */
Item,
+ /** A `Item` that renders a full-size anchor inside ListItem */
+ LinkItem,
+
/** Visually separates `Item`s or `Group`s in an `ActionList`. */
- Divider
+ Divider,
+
+ /** Secondary text which provides additional information about an `Item`. */
+ Description,
+
+ /** Icon (or similar) positioned before `Item` text. */
+ LeadingVisual,
+
+ /** Icon (or similar) positioned after `Item` text. */
+ TrailingVisual
})
diff --git a/src/ActionList2/Divider.tsx b/src/ActionList2/Divider.tsx
deleted file mode 100644
index e286448eb42..00000000000
--- a/src/ActionList2/Divider.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react'
-import Box from '../Box'
-import {get} from '../constants'
-import {Theme} from '../ThemeProvider'
-import {SxProp, merge} from '../sx'
-
-/**
- * Visually separates `Item`s or `Group`s in an `ActionList`.
- */
-
-export const Divider: React.FC = ({sx = {}}) => {
- return (
- `calc(${get('space.2')(theme)} - 1px)`,
- marginBottom: 2,
- listStyle: 'none' // hide the ::marker inserted by browser's stylesheet
- },
- sx as SxProp
- )}
- data-component="ActionList.Divider"
- />
- )
-}
diff --git a/src/ActionList2/Group.tsx b/src/ActionList2/Group.tsx
deleted file mode 100644
index d67be85f3ce..00000000000
--- a/src/ActionList2/Group.tsx
+++ /dev/null
@@ -1,110 +0,0 @@
-import React from 'react'
-import {useSSRSafeId} from '@react-aria/ssr'
-import Box from '../Box'
-import {SxProp} from '../sx'
-import {ListContext, ListProps} from './List'
-import {AriaRole} from '../utils/types'
-
-export type GroupProps = {
- /**
- * Style variations. Usage is discretionary.
- *
- * - `"filled"` - Superimposed on a background, offset from nearby content
- * - `"subtle"` - Relatively less offset from nearby content
- */
- variant?: 'subtle' | 'filled'
- /**
- * Primary text which names a `Group`.
- */
- title?: string
- /**
- * Secondary text which provides additional information about a `Group`.
- */
- auxiliaryText?: string
- /**
- * The ARIA role describing the function of the list inside `Group` component. `listbox` or `menu` are a common values.
- */
- role?: AriaRole
-} & SxProp & {
- /**
- * Whether multiple Items or a single Item can be selected in the Group. Overrides value on ActionList root.
- */
- selectionVariant?: ListProps['selectionVariant'] | false
- }
-
-type ContextProps = Pick
-export const GroupContext = React.createContext({})
-
-export const Group: React.FC = ({
- title,
- variant = 'subtle',
- auxiliaryText,
- selectionVariant,
- role,
- sx = {},
- ...props
-}) => {
- const labelId = useSSRSafeId()
- const {role: listRole} = React.useContext(ListContext)
-
- return (
-
- {title && }
-
-
- {props.children}
-
-
-
- )
-}
-
-export type HeaderProps = Pick & {
- labelId: string
-}
-
-/**
- * Displays the name and description of a `Group`.
- *
- * For visual presentation only. It's hidden from screen readers.
- */
-const Header: React.FC = ({variant, title, auxiliaryText, labelId, ...props}) => {
- const {variant: listVariant} = React.useContext(ListContext)
-
- const styles = {
- paddingY: '6px',
- paddingX: listVariant === 'full' ? 2 : 3,
- fontSize: 0,
- fontWeight: 'bold',
- color: 'fg.muted',
- ...(variant === 'filled' && {
- backgroundColor: 'canvas.subtle',
- marginX: 0,
- marginBottom: 2,
- borderTop: '1px solid',
- borderBottom: '1px solid',
- borderColor: 'neutral.muted'
- })
- }
-
- return (
-
- {title}
- {auxiliaryText && {auxiliaryText} }
-
- )
-}
diff --git a/src/ActionList2/Item.tsx b/src/ActionList2/Item.tsx
deleted file mode 100644
index db1c4ed04db..00000000000
--- a/src/ActionList2/Item.tsx
+++ /dev/null
@@ -1,269 +0,0 @@
-import React from 'react'
-import {ForwardRefComponent as PolymorphicForwardRefComponent} from '@radix-ui/react-polymorphic'
-import {useSSRSafeId} from '@react-aria/ssr'
-import styled from 'styled-components'
-import {useTheme} from '../ThemeProvider'
-import Box, {BoxProps} from '../Box'
-import sx, {SxProp, merge} from '../sx'
-import createSlots from '../utils/create-slots'
-import {AriaRole} from '../utils/types'
-import {ListContext, ListProps} from './List'
-import {GroupContext, GroupProps} from './Group'
-import {ActionListContainerContext} from './ActionListContainerContext'
-import {Selection} from './Selection'
-
-export const getVariantStyles = (variant: ItemProps['variant'], disabled: ItemProps['disabled']) => {
- if (disabled) {
- return {
- color: 'primer.fg.disabled',
- iconColor: 'primer.fg.disabled',
- annotationColor: 'primer.fg.disabled'
- }
- }
-
- switch (variant) {
- case 'danger':
- return {
- color: 'danger.fg',
- iconColor: 'danger.fg',
- annotationColor: 'fg.muted',
- hoverColor: 'actionListItem.danger.hoverText'
- }
- default:
- return {
- color: 'fg.default',
- iconColor: 'fg.muted',
- annotationColor: 'fg.muted',
- hoverColor: 'fg.default'
- }
- }
-}
-
-export type ItemProps = {
- /**
- * Primary content for an Item
- */
- children?: React.ReactNode
- /**
- * Callback that will trigger both on click selection and keyboard selection.
- */
- onSelect?: (event: React.MouseEvent | React.KeyboardEvent) => void
- /**
- * Is the `Item` is currently selected?
- */
- selected?: boolean
- /**
- * Style variations associated with various `Item` types.
- *
- * - `"default"` - An action `Item`.
- * - `"danger"` - A destructive action `Item`.
- */
- variant?: 'default' | 'danger'
- /**
- * Items that are disabled can not be clicked, selected, or navigated through.
- */
- disabled?: boolean
- /**
- * The ARIA role describing the function of `Item` component. `option` is a common value.
- */
- role?: AriaRole
- /**
- * id to attach to the root element of the Item
- */
- id?: string
- /**
- * Private API for use internally only. Used by LinkItem to wrap contents in an anchor
- */
- _PrivateItemWrapper?: React.FC
-} & SxProp
-
-const {Slots, Slot} = createSlots(['LeadingVisual', 'InlineDescription', 'BlockDescription', 'TrailingVisual'])
-export {Slot}
-export type ItemContext = Pick & {
- inlineDescriptionId: string
- blockDescriptionId: string
-}
-
-const LiBox = styled.li(sx)
-export const TEXT_ROW_HEIGHT = '20px' // custom value off the scale
-
-export const Item = React.forwardRef(
- (
- {
- variant = 'default',
- disabled = false,
- selected = undefined,
- onSelect,
- sx: sxProp = {},
- id,
- role,
- _PrivateItemWrapper,
- ...props
- },
- forwardedRef
- ): JSX.Element => {
- const {variant: listVariant, showDividers, selectionVariant: listSelectionVariant} = React.useContext(ListContext)
- const {selectionVariant: groupSelectionVariant} = React.useContext(GroupContext)
- const {container, afterSelect, selectionAttribute} = React.useContext(ActionListContainerContext)
-
- let selectionVariant: ListProps['selectionVariant'] | GroupProps['selectionVariant']
- if (typeof groupSelectionVariant !== 'undefined') selectionVariant = groupSelectionVariant
- else selectionVariant = listSelectionVariant
-
- /** Infer item role based on the container */
- let itemRole: ItemProps['role']
- if (container === 'ActionMenu' || container === 'DropdownMenu') {
- if (selectionVariant === 'single') itemRole = 'menuitemradio'
- else if (selectionVariant === 'multiple') itemRole = 'menuitemcheckbox'
- else itemRole = 'menuitem'
- }
-
- const {theme} = useTheme()
-
- const styles = {
- display: 'flex',
- paddingX: 2,
- fontSize: 1,
- paddingY: '6px', // custom value off the scale
- lineHeight: TEXT_ROW_HEIGHT,
- minHeight: 5,
- marginX: listVariant === 'inset' ? 2 : 0,
- borderRadius: listVariant === 'inset' ? 2 : 0,
- transition: 'background 33.333ms linear',
- color: getVariantStyles(variant, disabled).color,
- cursor: 'pointer',
- '&[aria-disabled]': {cursor: 'not-allowed'},
-
- '@media (hover: hover) and (pointer: fine)': {
- ':hover:not([aria-disabled])': {
- backgroundColor: `actionListItem.${variant}.hoverBg`,
- color: getVariantStyles(variant, disabled).hoverColor
- },
- ':focus:not([data-focus-visible-added])': {
- backgroundColor: `actionListItem.${variant}.selectedBg`,
- color: getVariantStyles(variant, disabled).hoverColor,
- outline: 'none'
- },
- '&[data-focus-visible-added]': {
- // we don't use :focus-visible because not all browsers (safari) have it yet
- outline: 'none',
- border: `2 solid`,
- boxShadow: `0 0 0 2px ${theme?.colors.accent.emphasis}`
- },
- ':active:not([aria-disabled])': {
- backgroundColor: `actionListItem.${variant}.activeBg`,
- color: getVariantStyles(variant, disabled).hoverColor
- }
- },
-
- /** Divider styles */
- '[data-component="ActionList.Item--DividerContainer"]': {
- position: 'relative'
- },
- '[data-component="ActionList.Item--DividerContainer"]::before': {
- content: '" "',
- display: 'block',
- position: 'absolute',
- width: '100%',
- top: '-7px',
- border: '0 solid',
- borderTopWidth: showDividers ? `1px` : '0',
- borderColor: 'var(--divider-color, transparent)'
- },
- // show between 2 items
- ':not(:first-of-type)': {'--divider-color': theme?.colors.actionListItem.inlineDivider},
- // hide divider after dividers & group header, with higher importance!
- '[data-component="ActionList.Divider"] + &': {'--divider-color': 'transparent !important'},
- // hide border on current and previous item
- '&:hover:not([aria-disabled]), &:focus:not([aria-disabled]), &[data-focus-visible-added]:not([aria-disabled])': {
- '--divider-color': 'transparent'
- },
- '&:hover:not([aria-disabled]) + &, &:focus:not([aria-disabled]) + &, &[data-focus-visible-added] + li': {
- '--divider-color': 'transparent'
- }
- }
-
- const clickHandler = React.useCallback(
- event => {
- if (disabled) return
- if (!event.defaultPrevented) {
- if (typeof onSelect === 'function') onSelect(event)
- // if this Item is inside a Menu, close the Menu
- if (typeof afterSelect === 'function') afterSelect()
- }
- },
- [onSelect, disabled, afterSelect]
- )
-
- const keyPressHandler = React.useCallback(
- event => {
- if (disabled) return
- if (!event.defaultPrevented && [' ', 'Enter'].includes(event.key)) {
- if (typeof onSelect === 'function') onSelect(event)
- // if this Item is inside a Menu, close the Menu
- if (typeof afterSelect === 'function') afterSelect()
- }
- },
- [onSelect, disabled, afterSelect]
- )
-
- // use props.id if provided, otherwise generate one.
- const labelId = useSSRSafeId(id)
- const inlineDescriptionId = useSSRSafeId(id && `${id}--inline-description`)
- const blockDescriptionId = useSSRSafeId(id && `${id}--block-description`)
-
- const ItemWrapper = _PrivateItemWrapper || React.Fragment
-
- return (
-
- {slots => (
-
-
-
- {slots.LeadingVisual}
-
-
-
-
- {props.children}
-
- {slots.InlineDescription}
-
- {slots.TrailingVisual}
-
- {slots.BlockDescription}
-
-
-
- )}
-
- )
- }
-) as PolymorphicForwardRefComponent<'li', ItemProps>
-
-Item.displayName = 'ActionList.Item'
-
-const ConditionalBox: React.FC<{if: boolean} & BoxProps> = props => {
- const {if: condition, ...rest} = props
-
- if (condition) return {props.children}
- else return <>{props.children}>
-}
diff --git a/src/ActionList2/List.tsx b/src/ActionList2/List.tsx
deleted file mode 100644
index a3c060a532a..00000000000
--- a/src/ActionList2/List.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import React from 'react'
-import {ForwardRefComponent as PolymorphicForwardRefComponent} from '@radix-ui/react-polymorphic'
-import styled from 'styled-components'
-import sx, {SxProp, merge} from '../sx'
-import {AriaRole} from '../utils/types'
-import {ActionListContainerContext} from './ActionListContainerContext'
-
-export type ListProps = {
- /**
- * `inset` children are offset (vertically and horizontally) from `List`’s edges, `full` children are flush (vertically and horizontally) with `List` edges
- */
- variant?: 'inset' | 'full'
- /**
- * Whether multiple Items or a single Item can be selected.
- */
- selectionVariant?: 'single' | 'multiple'
- /**
- * Display a divider above each `Item` in this `List` when it does not follow a `Header` or `Divider`.
- */
- showDividers?: boolean
- /**
- * The ARIA role describing the function of `List` component. `listbox` or `menu` are a common values.
- */
- role?: AriaRole
-} & SxProp
-
-type ContextProps = Pick
-export const ListContext = React.createContext({})
-
-const ListBox = styled.ul(sx)
-
-export const List = React.forwardRef(
- (
- {variant = 'inset', selectionVariant, showDividers = false, role, sx: sxProp = {}, ...props},
- forwardedRef
- ): JSX.Element => {
- const styles = {
- margin: 0,
- paddingInlineStart: 0, // reset ul styles
- paddingY: variant === 'inset' ? 2 : 0
- }
-
- /** if list is inside a Menu, it will get a role from the Menu */
- const {
- listRole,
- listLabelledBy,
- selectionVariant: containerSelectionVariant // TODO: Remove after DropdownMenu2 deprecation
- } = React.useContext(ActionListContainerContext)
-
- return (
-
-
- {props.children}
-
-
- )
- }
-) as PolymorphicForwardRefComponent<'ul', ListProps>
-
-List.displayName = 'ActionList'
diff --git a/src/ActionList2/index.ts b/src/ActionList2/index.ts
deleted file mode 100644
index d05da47652f..00000000000
--- a/src/ActionList2/index.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import {List} from './List'
-import {Group} from './Group'
-import {Item} from './Item'
-import {LinkItem} from './LinkItem'
-import {Divider} from './Divider'
-import {Description} from './Description'
-import {LeadingVisual, TrailingVisual} from './Visuals'
-
-export type {ListProps as ActionListProps} from './List'
-export type {GroupProps} from './Group'
-export type {ItemProps} from './Item'
-export type {DescriptionProps} from './Description'
-export type {LeadingVisualProps, TrailingVisualProps} from './Visuals'
-
-/**
- * Collection of list-related components.
- */
-export const ActionList = Object.assign(List, {
- /** Collects related `Items` in an `ActionList`. */
- Group,
-
- /** An actionable or selectable `Item` */
- Item,
-
- /** A `Item` that renders a full-size anchor inside ListItem */
- LinkItem,
-
- /** Visually separates `Item`s or `Group`s in an `ActionList`. */
- Divider,
-
- /** Secondary text which provides additional information about an `Item`. */
- Description,
-
- /** Icon (or similar) positioned before `Item` text. */
- LeadingVisual,
-
- /** Icon (or similar) positioned after `Item` text. */
- TrailingVisual
-})
diff --git a/src/ActionMenu.tsx b/src/ActionMenu.tsx
index dc080001083..142f18c7e24 100644
--- a/src/ActionMenu.tsx
+++ b/src/ActionMenu.tsx
@@ -1,6 +1,6 @@
-import {GroupedListProps, List, ListPropsBase} from './ActionList/List'
-import {Item, ItemProps} from './ActionList/Item'
-import {Divider} from './ActionList/Divider'
+import {GroupedListProps, List, ListPropsBase} from './deprecated/ActionList/List'
+import {Item, ItemProps} from './deprecated/ActionList/Item'
+import {Divider} from './deprecated/ActionList/Divider'
import Button, {ButtonProps} from './Button'
import React, {useCallback, useMemo} from 'react'
import {AnchoredOverlay} from './AnchoredOverlay'
diff --git a/src/ActionMenu2.tsx b/src/ActionMenu2.tsx
index 1fab1223264..0107dd178ac 100644
--- a/src/ActionMenu2.tsx
+++ b/src/ActionMenu2.tsx
@@ -4,8 +4,8 @@ import {TriangleDownIcon} from '@primer/octicons-react'
import {AnchoredOverlay, AnchoredOverlayProps} from './AnchoredOverlay'
import {OverlayProps} from './Overlay'
import {useProvidedRefOrCreate, useProvidedStateOrCreate, useMenuInitialFocus, useTypeaheadFocus} from './hooks'
-import {Divider} from './ActionList2/Divider'
-import {ActionListContainerContext} from './ActionList2/ActionListContainerContext'
+import {Divider} from './ActionList/Divider'
+import {ActionListContainerContext} from './ActionList/ActionListContainerContext'
import {Button, ButtonProps} from './Button2'
import {MandateProps} from './utils/types'
@@ -64,15 +64,15 @@ const Menu: React.FC = ({
)
}
-export type MenuAnchorProps = {children: React.ReactElement}
-const Anchor = React.forwardRef(
+export type ActionMenuAnchorProps = {children: React.ReactElement}
+const Anchor = React.forwardRef(
({children, ...anchorProps}, anchorRef) => {
return React.cloneElement(children, {...anchorProps, ref: anchorRef})
}
)
/** this component is syntactical sugar 🍭 */
-export type MenuButtonProps = ButtonProps
+export type ActionMenuButtonProps = ButtonProps
const MenuButton = React.forwardRef((props, anchorRef) => {
return (
diff --git a/src/Autocomplete/AutocompleteMenu.tsx b/src/Autocomplete/AutocompleteMenu.tsx
index 4151132ef47..22e99c61652 100644
--- a/src/Autocomplete/AutocompleteMenu.tsx
+++ b/src/Autocomplete/AutocompleteMenu.tsx
@@ -1,7 +1,7 @@
import React, {useContext, useEffect, useMemo, useRef, useState} from 'react'
import {scrollIntoView} from '@primer/behaviors'
import type {ScrollIntoViewOptions} from '@primer/behaviors'
-import {ActionList, ItemProps} from '../ActionList'
+import {ActionList, ItemProps} from '../deprecated/ActionList'
import {useFocusZone} from '../hooks/useFocusZone'
import {ComponentProps, MandateProps} from '../utils/types'
import {Box, Spinner, useSSRSafeId} from '../'
diff --git a/src/Checkbox.tsx b/src/Checkbox.tsx
index 22b219b4fb7..d7aeb16090a 100644
--- a/src/Checkbox.tsx
+++ b/src/Checkbox.tsx
@@ -1,8 +1,9 @@
import styled from 'styled-components'
import {useProvidedRefOrCreate} from './hooks'
-import React, {InputHTMLAttributes, ReactElement, useLayoutEffect} from 'react'
+import React, {ChangeEventHandler, InputHTMLAttributes, ReactElement, useContext, useLayoutEffect} from 'react'
import sx, {SxProp} from './sx'
import {FormValidationStatus} from './utils/types/FormValidationStatus'
+import {CheckboxGroupContext} from './CheckboxGroup'
export type CheckboxProps = {
/**
@@ -21,12 +22,16 @@ export type CheckboxProps = {
* Indicates whether the checkbox must be checked
*/
required?: boolean
-
/**
* Indicates whether the checkbox validation state
*/
validationStatus?: FormValidationStatus
-} & InputHTMLAttributes &
+ /**
+ * A unique value that is never shown to the user.
+ * Used during form submission and to identify which checkbox inputs are selected
+ */
+ value: string
+} & Exclude, 'value'> &
SxProp
const StyledCheckbox = styled.input`
@@ -42,10 +47,15 @@ const StyledCheckbox = styled.input`
*/
const Checkbox = React.forwardRef(
(
- {checked, indeterminate, disabled, sx: sxProp, required, validationStatus, ...rest}: CheckboxProps,
+ {checked, indeterminate, disabled, onChange, sx: sxProp, required, validationStatus, value, ...rest}: CheckboxProps,
ref
): ReactElement => {
const checkboxRef = useProvidedRefOrCreate(ref as React.RefObject)
+ const checkboxGroupContext = useContext(CheckboxGroupContext)
+ const handleOnChange: ChangeEventHandler = e => {
+ checkboxGroupContext.onChange && checkboxGroupContext.onChange(e)
+ onChange && onChange(e)
+ }
useLayoutEffect(() => {
if (checkboxRef.current) {
@@ -65,6 +75,9 @@ const Checkbox = React.forwardRef(
required={required}
aria-required={required ? 'true' : 'false'}
aria-invalid={validationStatus === 'error' ? 'true' : 'false'}
+ onChange={handleOnChange}
+ value={value}
+ name={value}
{...rest}
/>
)
diff --git a/src/CheckboxGroup.tsx b/src/CheckboxGroup.tsx
new file mode 100644
index 00000000000..5e36ded7262
--- /dev/null
+++ b/src/CheckboxGroup.tsx
@@ -0,0 +1,76 @@
+import React, {ChangeEvent, ChangeEventHandler, createContext, FC} from 'react'
+import CheckboxOrRadioGroup, {CheckboxOrRadioGroupProps} from './_CheckboxOrRadioGroup'
+import CheckboxOrRadioGroupCaption from './_CheckboxOrRadioGroup/_CheckboxOrRadioGroupCaption'
+import CheckboxOrRadioGroupLabel from './_CheckboxOrRadioGroup/_CheckboxOrRadioGroupLabel'
+import CheckboxOrRadioGroupValidation from './_CheckboxOrRadioGroup/_CheckboxOrRadioGroupValidation'
+import {useRenderForcingRef} from './hooks'
+import {SxProp} from './sx'
+import {Checkbox, FormControl} from '.'
+
+type CheckboxGroupProps = {
+ /**
+ * An onChange handler that gets called when any of the checkboxes change
+ */
+ onChange?: (selected: string[], e?: ChangeEvent) => void
+} & CheckboxOrRadioGroupProps &
+ SxProp
+
+export const CheckboxGroupContext = createContext<{
+ disabled?: boolean
+ onChange?: ChangeEventHandler
+}>({})
+
+const CheckboxGroup: FC = ({children, disabled, onChange, ...rest}) => {
+ const formControlComponentChildren = React.Children.toArray(children)
+ .filter(child => React.isValidElement(child) && child.type === FormControl)
+ .map(formControlComponent =>
+ React.isValidElement(formControlComponent) ? formControlComponent.props.children : []
+ )
+ .flat()
+
+ const checkedCheckboxes = React.Children.toArray(formControlComponentChildren)
+ .filter(child => React.isValidElement(child) && child.type === Checkbox)
+ .map(
+ checkbox =>
+ React.isValidElement(checkbox) &&
+ (checkbox.props.checked || checkbox.props.defaultChecked) &&
+ checkbox.props.value
+ )
+ .filter(Boolean)
+ const [selectedCheckboxValues, setSelectedCheckboxValues] = useRenderForcingRef(checkedCheckboxes)
+
+ const updateSelectedCheckboxes: ChangeEventHandler = e => {
+ const {value, checked} = e.currentTarget
+
+ if (checked) {
+ setSelectedCheckboxValues([...(selectedCheckboxValues.current || []), value])
+ return
+ }
+
+ setSelectedCheckboxValues((selectedCheckboxValues.current || []).filter(selectedValue => selectedValue !== value))
+ }
+
+ return (
+ {
+ if (onChange) {
+ updateSelectedCheckboxes(e)
+ onChange(selectedCheckboxValues.current || [], e)
+ }
+ }
+ }}
+ >
+
+ {children}
+
+
+ )
+}
+
+export default Object.assign(CheckboxGroup, {
+ Caption: CheckboxOrRadioGroupCaption,
+ Label: CheckboxOrRadioGroupLabel,
+ Validation: CheckboxOrRadioGroupValidation
+})
diff --git a/src/ChoiceFieldset/ChoiceFieldset.tsx b/src/ChoiceFieldset/ChoiceFieldset.tsx
index 19597e9daca..35a4971aa11 100644
--- a/src/ChoiceFieldset/ChoiceFieldset.tsx
+++ b/src/ChoiceFieldset/ChoiceFieldset.tsx
@@ -1,4 +1,4 @@
-import React, {ComponentProps} from 'react'
+import React from 'react'
import {Box, useSSRSafeId} from '..'
import createSlots from '../utils/create-slots'
import {FormValidationStatus} from '../utils/types/FormValidationStatus'
@@ -56,6 +56,9 @@ export interface ChoiceFieldsetContext extends ChoiceFieldsetProps {
const {Slots, Slot} = createSlots(['Description', 'ChoiceList', 'Legend', 'Validation'])
export {Slot}
+/**
+ * @deprecated Use `CheckboxGroup` or `RadioGroup` instead. See https://primer.style/react/CheckboxGroup and https://primer.style/react/RadioGroup for more info
+ */
const ChoiceFieldset = >({
children,
disabled,
@@ -124,7 +127,6 @@ const ChoiceFieldset = >({
)
}
-export type InputFieldComponentProps = ComponentProps
export type {ChoiceFieldsetListProps} from './ChoiceFieldsetList'
export type {ChoiceFieldsetLegendProps} from './ChoiceFieldsetLegend'
export type {ChoiceFieldProps} from './ChoiceFieldsetListItem'
diff --git a/src/ChoiceInputField.tsx b/src/ChoiceInputField.tsx
index dc329766fba..054e40abae8 100644
--- a/src/ChoiceInputField.tsx
+++ b/src/ChoiceInputField.tsx
@@ -25,6 +25,9 @@ const getInputToRender = (inputType: 'radio' | 'checkbox', children?: React.Reac
)
}
+/**
+ * @deprecated Use `FormControl` instead. See https://primer.style/react/FormControl for more info
+ */
const ChoiceInputField: React.FC = ({children, disabled, id: idProp, validationStatus}) => {
const id = useSSRSafeId(idProp)
const captionChildren: React.ReactElement[] | undefined | null = React.Children.map(children, child =>
diff --git a/src/Dropdown.tsx b/src/Dropdown.tsx
index 70fea52a980..fba007a1c38 100644
--- a/src/Dropdown.tsx
+++ b/src/Dropdown.tsx
@@ -146,6 +146,9 @@ Dropdown.defaultProps = Details.defaultProps
export type DropdownCaretProps = ComponentProps
export type DropdownMenuProps = ComponentProps
export type DropdownItemProps = ComponentProps
+/**
+ * @deprecated Use ActionMenu instead. See https://primer.style/react/ActionMenu for more details.
+ */
export default Object.assign(Dropdown, {
Caret: DropdownCaret,
Menu: DropdownMenu,
diff --git a/src/DropdownMenu/DropdownMenu.tsx b/src/DropdownMenu/DropdownMenu.tsx
index 37b2687449e..3e084002318 100644
--- a/src/DropdownMenu/DropdownMenu.tsx
+++ b/src/DropdownMenu/DropdownMenu.tsx
@@ -1,7 +1,7 @@
import React, {useCallback, useMemo} from 'react'
-import {List, GroupedListProps, ListPropsBase, ItemInput} from '../ActionList/List'
+import {List, GroupedListProps, ListPropsBase, ItemInput} from '../deprecated/ActionList/List'
import {DropdownButton, DropdownButtonProps} from './DropdownButton'
-import {ItemProps} from '../ActionList/Item'
+import {ItemProps} from '../deprecated/ActionList/Item'
import {AnchoredOverlay} from '../AnchoredOverlay'
import {OverlayProps} from '../Overlay'
import {AnchoredOverlayWrapperAnchorProps} from '../AnchoredOverlay/AnchoredOverlay'
diff --git a/src/DropdownMenu2.tsx b/src/DropdownMenu2.tsx
index 1773eb8e85e..02697c66c66 100644
--- a/src/DropdownMenu2.tsx
+++ b/src/DropdownMenu2.tsx
@@ -5,8 +5,8 @@ import {Button, ButtonProps} from './Button2'
import {AnchoredOverlay, AnchoredOverlayProps} from './AnchoredOverlay'
import {OverlayProps} from './Overlay'
import {useProvidedRefOrCreate, useProvidedStateOrCreate, useMenuInitialFocus, useTypeaheadFocus} from './hooks'
-import {Divider} from './ActionList2/Divider'
-import {ActionListContainerContext} from './ActionList2/ActionListContainerContext'
+import {Divider} from './ActionList/Divider'
+import {ActionListContainerContext} from './ActionList/ActionListContainerContext'
import {MandateProps} from './utils/types'
type MenuContextProps = Pick<
diff --git a/src/FilteredActionList/FilteredActionList.tsx b/src/FilteredActionList/FilteredActionList.tsx
index ebbb16c59d3..665cf6a9c12 100644
--- a/src/FilteredActionList/FilteredActionList.tsx
+++ b/src/FilteredActionList/FilteredActionList.tsx
@@ -1,9 +1,9 @@
import React, {KeyboardEventHandler, useCallback, useEffect, useRef} from 'react'
import {useSSRSafeId} from '@react-aria/ssr'
-import {GroupedListProps, ListPropsBase} from '../ActionList/List'
+import {GroupedListProps, ListPropsBase} from '../deprecated/ActionList/List'
import TextInput, {TextInputProps} from '../TextInput'
import Box from '../Box'
-import {ActionList} from '../ActionList'
+import {ActionList} from '../deprecated/ActionList'
import Spinner from '../Spinner'
import {useFocusZone} from '../hooks/useFocusZone'
import {useProvidedStateOrCreate} from '../hooks/useProvidedStateOrCreate'
diff --git a/src/FormControl/FormControl.tsx b/src/FormControl/FormControl.tsx
index 0091fc65a16..c1ac5ea74f8 100644
--- a/src/FormControl/FormControl.tsx
+++ b/src/FormControl/FormControl.tsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, {useContext} from 'react'
import {Autocomplete, Box, Checkbox, Radio, Select, Textarea, TextInput, TextInputWithTokens, useSSRSafeId} from '..'
import FormControlCaption from './_FormControlCaption'
import FormControlLabel from './_FormControlLabel'
@@ -8,6 +8,7 @@ import ValidationAnimationContainer from '../_ValidationAnimationContainer'
import {get} from '../constants'
import FormControlLeadingVisual from './_FormControlLeadingVisual'
import {SxProp} from '../sx'
+import CheckboxOrRadioGroupContext from '../_CheckboxOrRadioGroup/_CheckboxOrRadioGroupContext'
export type FormControlProps = {
children?: React.ReactNode
@@ -30,25 +31,27 @@ export interface FormControlContext extends Pick {
+const FormControl = ({children, disabled: disabledProp, id: idProp, required, sx}: FormControlProps) => {
const expectedInputComponents = [Autocomplete, Checkbox, Radio, Select, TextInput, TextInputWithTokens, Textarea]
+ const choiceGroupContext = useContext(CheckboxOrRadioGroupContext)
+ const disabled = choiceGroupContext?.disabled || disabledProp
const id = useSSRSafeId(idProp)
const validationChild = React.Children.toArray(children).find(child =>
React.isValidElement(child) && child.type === FormControlValidation ? child : null
)
- const captionChildren: React.ReactElement[] | undefined | null = React.Children.map(children, child =>
+ const captionChild = React.Children.toArray(children).find(child =>
React.isValidElement(child) && child.type === FormControlCaption ? child : null
- )?.filter(Boolean)
- const labelChild: React.ReactNode | undefined | null = React.Children.toArray(children).find(
+ )
+ const labelChild = React.Children.toArray(children).find(
child => React.isValidElement(child) && child.type === FormControlLabel
)
- const validationMessageId = validationChild ? `${id}-validationMsg` : ''
- const validationStatus = React.isValidElement(validationChild) ? validationChild.props.variant : undefined
- const captionId = captionChildren?.length ? `${id}-caption` : undefined
+ const validationMessageId = validationChild && `${id}-validationMessage`
+ const captionId = captionChild && `${id}-caption`
+ const validationStatus = React.isValidElement(validationChild) && validationChild.props.variant
const InputComponent = React.Children.toArray(children).find(child =>
expectedInputComponents.some(inputComponent => React.isValidElement(child) && child.type === inputComponent)
)
- const inputProps = React.isValidElement(InputComponent) ? InputComponent.props : undefined
+ const inputProps = React.isValidElement(InputComponent) && InputComponent.props
const isChoiceInput =
React.isValidElement(InputComponent) && (InputComponent.type === Checkbox || InputComponent.type === Radio)
diff --git a/src/Radio.tsx b/src/Radio.tsx
index f9c36c42b91..64616927adc 100644
--- a/src/Radio.tsx
+++ b/src/Radio.tsx
@@ -1,7 +1,8 @@
import styled from 'styled-components'
-import React, {InputHTMLAttributes, ReactElement} from 'react'
+import React, {ChangeEventHandler, InputHTMLAttributes, ReactElement, useContext} from 'react'
import sx, {SxProp} from './sx'
import {FormValidationStatus} from './utils/types/FormValidationStatus'
+import {RadioGroupContext} from './RadioGroup'
export type RadioProps = {
/**
@@ -12,7 +13,7 @@ export type RadioProps = {
/**
* Name attribute of the input element. Required for grouping radio inputs
*/
- name: string
+ name?: string
/**
* Apply inactive visual appearance to the radio button
*/
@@ -49,9 +50,23 @@ const StyledRadio = styled.input`
*/
const Radio = React.forwardRef(
(
- {checked, disabled, sx: sxProp, required, validationStatus, value, name, ...rest}: RadioProps,
+ {checked, disabled, name: nameProp, onChange, sx: sxProp, required, validationStatus, value, ...rest}: RadioProps,
ref
): ReactElement => {
+ const radioGroupContext = useContext(RadioGroupContext)
+ const handleOnChange: ChangeEventHandler = e => {
+ radioGroupContext?.onChange && radioGroupContext.onChange(e)
+ onChange && onChange(e)
+ }
+ const name = nameProp || radioGroupContext?.name
+
+ if (!name) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ 'A radio input must have a `name` attribute. Pass `name` as a prop directly to each Radio, or nest them in a `RadioGroup` component with a `name` prop'
+ )
+ }
+
return (
(
aria-required={required ? 'true' : 'false'}
aria-invalid={validationStatus === 'error' ? 'true' : 'false'}
sx={sxProp}
+ onChange={handleOnChange}
{...rest}
/>
)
diff --git a/src/RadioGroup.tsx b/src/RadioGroup.tsx
new file mode 100644
index 00000000000..de86c0b1835
--- /dev/null
+++ b/src/RadioGroup.tsx
@@ -0,0 +1,63 @@
+import React, {ChangeEvent, ChangeEventHandler, createContext, FC} from 'react'
+import CheckboxOrRadioGroup, {CheckboxOrRadioGroupProps} from './_CheckboxOrRadioGroup'
+import CheckboxOrRadioGroupCaption from './_CheckboxOrRadioGroup/_CheckboxOrRadioGroupCaption'
+import CheckboxOrRadioGroupLabel from './_CheckboxOrRadioGroup/_CheckboxOrRadioGroupLabel'
+import CheckboxOrRadioGroupValidation from './_CheckboxOrRadioGroup/_CheckboxOrRadioGroupValidation'
+import {useRenderForcingRef} from './hooks'
+import {SxProp} from './sx'
+
+type RadioGroupProps = {
+ /**
+ * An onChange handler that gets called when the selection changes
+ */
+ onChange?: (selected: string | null, e?: ChangeEvent) => void
+ /**
+ * The name used to identify this group of radios
+ */
+ name: string
+} & CheckboxOrRadioGroupProps &
+ SxProp
+
+export const RadioGroupContext = createContext<{
+ disabled?: boolean
+ onChange?: ChangeEventHandler
+ name: string
+} | null>(null)
+
+const RadioGroup: FC = ({children, disabled, onChange, name, ...rest}) => {
+ const [selectedRadioValue, setSelectedRadioValue] = useRenderForcingRef(null)
+
+ const updateSelectedCheckboxes: ChangeEventHandler = e => {
+ const {value, checked} = e.currentTarget
+
+ if (checked) {
+ setSelectedRadioValue(value)
+ return
+ }
+ }
+
+ return (
+ {
+ if (onChange) {
+ updateSelectedCheckboxes(e)
+ onChange(selectedRadioValue.current, e)
+ }
+ }
+ }}
+ >
+
+ {children}
+
+
+ )
+}
+
+export default Object.assign(RadioGroup, {
+ Caption: CheckboxOrRadioGroupCaption,
+ Label: CheckboxOrRadioGroupLabel,
+ Validation: CheckboxOrRadioGroupValidation
+})
diff --git a/src/SelectMenu/SelectMenu.tsx b/src/SelectMenu/SelectMenu.tsx
index 5a6d3ff356c..5436a2215f1 100644
--- a/src/SelectMenu/SelectMenu.tsx
+++ b/src/SelectMenu/SelectMenu.tsx
@@ -107,6 +107,10 @@ export type {SelectMenuModalProps} from './SelectMenuModal'
export type {SelectMenuTabProps} from './SelectMenuTab'
export type {SelectMenuTabPanelProps} from './SelectMenuTabPanel'
export type {SelectMenuTabsProps} from './SelectMenuTabs'
+
+/**
+ * @deprecated Use ActionMenu instead. See https://primer.style/react/ActionMenu for more details.
+ */
export default Object.assign(SelectMenu, {
MenuContext,
List: SelectMenuList,
diff --git a/src/SelectPanel/SelectPanel.tsx b/src/SelectPanel/SelectPanel.tsx
index 5d688a55ae1..be8477f1786 100644
--- a/src/SelectPanel/SelectPanel.tsx
+++ b/src/SelectPanel/SelectPanel.tsx
@@ -1,10 +1,10 @@
import React, {useCallback, useMemo} from 'react'
import {FilteredActionList, FilteredActionListProps} from '../FilteredActionList'
import {OverlayProps} from '../Overlay'
-import {ItemInput} from '../ActionList/List'
+import {ItemInput} from '../deprecated/ActionList/List'
import {FocusZoneHookSettings} from '../hooks/useFocusZone'
import {DropdownButton} from '../DropdownMenu'
-import {ItemProps} from '../ActionList'
+import {ItemProps} from '../deprecated/ActionList'
import {AnchoredOverlay, AnchoredOverlayProps} from '../AnchoredOverlay'
import {TextInputProps} from '../TextInput'
import {useProvidedStateOrCreate} from '../hooks/useProvidedStateOrCreate'
diff --git a/src/_CheckboxOrRadioGroup/CheckboxOrRadioGroup.tsx b/src/_CheckboxOrRadioGroup/CheckboxOrRadioGroup.tsx
new file mode 100644
index 00000000000..fb1c963c236
--- /dev/null
+++ b/src/_CheckboxOrRadioGroup/CheckboxOrRadioGroup.tsx
@@ -0,0 +1,185 @@
+import React from 'react'
+import {Box, Checkbox, FormControl, Radio, useSSRSafeId} from '..'
+import ValidationAnimationContainer from '../_ValidationAnimationContainer'
+import CheckboxOrRadioGroupCaption from './_CheckboxOrRadioGroupCaption'
+import CheckboxOrRadioGroupLabel from './_CheckboxOrRadioGroupLabel'
+import CheckboxOrRadioGroupValidation from './_CheckboxOrRadioGroupValidation'
+import {Slots} from './slots'
+import styled from 'styled-components'
+import {get} from '../constants'
+import CheckboxOrRadioGroupContext from './_CheckboxOrRadioGroupContext'
+import VisuallyHidden from '../_VisuallyHidden'
+import {SxProp} from '../sx'
+
+export type CheckboxOrRadioGroupProps = {
+ /**
+ * Used when associating the input group with a label other than `CheckboxOrRadioGroup.Label`
+ */
+ ['aria-labelledby']?: string
+ /**
+ * Whether the input group allows user input
+ */
+ disabled?: boolean
+ /**
+ * The unique identifier for this input group. Used to associate the label, validation text, and caption text.
+ * You may want a custom ID to make it easier to select elements in integration tests.
+ */
+ id?: string
+ /**
+ * If true, the user must make a selection before the owning form can be submitted
+ */
+ required?: boolean
+} & SxProp
+
+export type CheckboxOrRadioGroupContext = {
+ validationMessageId?: string
+ captionId?: string
+} & CheckboxOrRadioGroupProps
+
+const Body = styled.div`
+ display: flex;
+ flex-direction: column;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+
+ > * + * {
+ margin-top: ${get('space.2')};
+ }
+`
+
+const CheckboxOrRadioGroup: React.FC = ({
+ 'aria-labelledby': ariaLabelledby,
+ children,
+ disabled,
+ id: idProp,
+ required,
+ sx
+}) => {
+ const expectedInputComponents = [Checkbox, Radio]
+ const labelChild = React.Children.toArray(children).find(
+ child => React.isValidElement(child) && child.type === CheckboxOrRadioGroupLabel
+ )
+ const validationChild = React.Children.toArray(children).find(child =>
+ React.isValidElement(child) && child.type === CheckboxOrRadioGroupValidation ? child : null
+ )
+ const captionChild = React.Children.toArray(children).find(child =>
+ React.isValidElement(child) && child.type === CheckboxOrRadioGroupCaption ? child : null
+ )
+ const id = useSSRSafeId(idProp)
+ const validationMessageId = validationChild && `${id}-validationMessage`
+ const captionId = captionChild && `${id}-caption`
+ const checkIfOnlyContainsChoiceInputs = () => {
+ const formControlComponentChildren = React.Children.toArray(children)
+ .filter(child => React.isValidElement(child) && child.type === FormControl)
+ .map(formControlComponent =>
+ React.isValidElement(formControlComponent) ? formControlComponent.props.children : []
+ )
+ .flat()
+
+ return Boolean(
+ React.Children.toArray(formControlComponentChildren).find(child =>
+ expectedInputComponents.some(inputComponent => React.isValidElement(child) && child.type === inputComponent)
+ )
+ )
+ }
+
+ if (!checkIfOnlyContainsChoiceInputs()) {
+ // eslint-disable-next-line no-console
+ console.warn('Only `Checkbox` and `Radio` form controls should be used in a `CheckboxOrRadioGroup`.')
+ }
+
+ if (!labelChild && !ariaLabelledby) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ 'A choice group must be labelled using a `CheckboxOrRadioGroup.Label` child, or by passing `aria-labelledby` to the CheckboxOrRadioGroup component.'
+ )
+ }
+
+ return (
+
+ {slots => {
+ const isLegendVisible = React.isValidElement(labelChild) && !labelChild.props.visuallyHidden
+
+ return (
+
+
+
+ {labelChild ? (
+ /*
+ Placing the caption text and validation text in the provides a better user
+ experience for more screenreaders.
+
+ Reference: https://blog.tenon.io/accessible-validation-of-checkbox-and-radiobutton-groups/
+ */
+
+ {slots.Label}
+ {slots.Caption}
+ {React.isValidElement(slots.Validation) && slots.Validation.props.children && (
+ {slots.Validation.props.children}
+ )}
+
+ ) : (
+ /*
+ If CheckboxOrRadioGroup.Label wasn't passed as a child, we don't render a
+ but we still want to render a caption
+ */
+ slots.Caption
+ )}
+
+
+ {React.Children.toArray(children).filter(child => React.isValidElement(child))}
+
+
+ {validationChild && (
+
+ aria-hidden={Boolean(labelChild)}
+ show
+ >
+ {slots.Validation}
+
+ )}
+
+
+ )
+ }}
+
+ )
+}
+
+CheckboxOrRadioGroup.defaultProps = {
+ disabled: false,
+ required: false
+}
+
+export type {CheckboxOrRadioGroupLabelProps} from './_CheckboxOrRadioGroupLabel'
+export default Object.assign(CheckboxOrRadioGroup, {
+ Caption: CheckboxOrRadioGroupCaption,
+ Label: CheckboxOrRadioGroupLabel,
+ Validation: CheckboxOrRadioGroupValidation
+})
diff --git a/src/_CheckboxOrRadioGroup/_CheckboxOrRadioGroupCaption.tsx b/src/_CheckboxOrRadioGroup/_CheckboxOrRadioGroupCaption.tsx
new file mode 100644
index 00000000000..3a569d0ccce
--- /dev/null
+++ b/src/_CheckboxOrRadioGroup/_CheckboxOrRadioGroupCaption.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import {Text} from '..'
+import {SxProp} from '../sx'
+import {CheckboxOrRadioGroupContext} from './CheckboxOrRadioGroup'
+import {Slot} from './slots'
+
+const CheckboxOrRadioGroupCaption: React.FC = ({children, sx}) => (
+
+ {({disabled, captionId}: CheckboxOrRadioGroupContext) => (
+
+ {children}
+
+ )}
+
+)
+
+export default CheckboxOrRadioGroupCaption
diff --git a/src/_CheckboxOrRadioGroup/_CheckboxOrRadioGroupContext.tsx b/src/_CheckboxOrRadioGroup/_CheckboxOrRadioGroupContext.tsx
new file mode 100644
index 00000000000..415efeb281e
--- /dev/null
+++ b/src/_CheckboxOrRadioGroup/_CheckboxOrRadioGroupContext.tsx
@@ -0,0 +1,7 @@
+import {createContext} from 'react'
+
+const CheckboxOrRadioGroupContext = createContext<{
+ disabled?: boolean
+} | null>(null)
+
+export default CheckboxOrRadioGroupContext
diff --git a/src/_CheckboxOrRadioGroup/_CheckboxOrRadioGroupLabel.tsx b/src/_CheckboxOrRadioGroup/_CheckboxOrRadioGroupLabel.tsx
new file mode 100644
index 00000000000..7fbc34eca25
--- /dev/null
+++ b/src/_CheckboxOrRadioGroup/_CheckboxOrRadioGroupLabel.tsx
@@ -0,0 +1,45 @@
+import React from 'react'
+import {Box} from '..'
+import {SxProp} from '../sx'
+import VisuallyHidden from '../_VisuallyHidden'
+import {CheckboxOrRadioGroupContext} from './CheckboxOrRadioGroup'
+import {Slot} from './slots'
+
+export type CheckboxOrRadioGroupLabelProps = {
+ /**
+ * Whether to visually hide the fieldset legend
+ */
+ visuallyHidden?: boolean
+} & SxProp
+
+const CheckboxOrRadioGroupLabel: React.FC = ({children, visuallyHidden, sx}) => (
+
+ {({required, disabled}: CheckboxOrRadioGroupContext) => (
+
+ {required ? (
+
+ {children}
+ *
+
+ ) : (
+ children
+ )}
+
+ )}
+
+)
+
+CheckboxOrRadioGroupLabel.defaultProps = {
+ visuallyHidden: false
+}
+
+export default CheckboxOrRadioGroupLabel
diff --git a/src/_CheckboxOrRadioGroup/_CheckboxOrRadioGroupValidation.tsx b/src/_CheckboxOrRadioGroup/_CheckboxOrRadioGroupValidation.tsx
new file mode 100644
index 00000000000..67538b52627
--- /dev/null
+++ b/src/_CheckboxOrRadioGroup/_CheckboxOrRadioGroupValidation.tsx
@@ -0,0 +1,23 @@
+import React from 'react'
+import InputValidation from '../_InputValidation'
+import {SxProp} from '../sx'
+import {FormValidationStatus} from '../utils/types/FormValidationStatus'
+import {CheckboxOrRadioGroupContext} from './CheckboxOrRadioGroup'
+import {Slot} from './slots'
+
+export type CheckboxOrRadioGroupValidationProps = {
+ /** Changes the visual style to match the validation status */
+ variant: FormValidationStatus
+} & SxProp
+
+const CheckboxOrRadioGroupValidation: React.FC = ({children, variant, sx}) => (
+
+ {({validationMessageId = ''}: CheckboxOrRadioGroupContext) => (
+
+ {children}
+
+ )}
+
+)
+
+export default CheckboxOrRadioGroupValidation
diff --git a/src/_CheckboxOrRadioGroup/index.ts b/src/_CheckboxOrRadioGroup/index.ts
new file mode 100644
index 00000000000..ceebd3ba524
--- /dev/null
+++ b/src/_CheckboxOrRadioGroup/index.ts
@@ -0,0 +1,2 @@
+export {default} from './CheckboxOrRadioGroup'
+export type {CheckboxOrRadioGroupProps} from './CheckboxOrRadioGroup'
diff --git a/src/_CheckboxOrRadioGroup/slots.ts b/src/_CheckboxOrRadioGroup/slots.ts
new file mode 100644
index 00000000000..75ba8a8171b
--- /dev/null
+++ b/src/_CheckboxOrRadioGroup/slots.ts
@@ -0,0 +1,3 @@
+import createSlots from '../utils/create-slots'
+
+export const {Slots, Slot} = createSlots(['Caption', 'Label', 'Validation'])
diff --git a/src/_InputLabel.tsx b/src/_InputLabel.tsx
index 0d664464429..6118c7c9633 100644
--- a/src/_InputLabel.tsx
+++ b/src/_InputLabel.tsx
@@ -20,7 +20,7 @@ const InputLabel: React.FC = ({children, disabled, required, vis
fontSize: 1,
display: 'block',
color: disabled ? 'fg.muted' : 'fg.default',
- cursor: 'pointer',
+ cursor: disabled ? 'default' : 'pointer',
...sx
}}
>
diff --git a/src/_InputValidation.tsx b/src/_InputValidation.tsx
index 20aa0fd30f6..9b29e04798f 100644
--- a/src/_InputValidation.tsx
+++ b/src/_InputValidation.tsx
@@ -40,7 +40,7 @@ const InputValidation: React.FC = ({children, id, validationStatus, sx})
}}
>
{IconComponent && (
-
+
)}
diff --git a/src/_ValidationAnimationContainer.tsx b/src/_ValidationAnimationContainer.tsx
index c4206d3658a..591a8c9c745 100644
--- a/src/_ValidationAnimationContainer.tsx
+++ b/src/_ValidationAnimationContainer.tsx
@@ -1,8 +1,8 @@
-import React, {useEffect, useState} from 'react'
+import React, {HTMLProps, useEffect, useState} from 'react'
import styled, {keyframes, css} from 'styled-components'
import {Box} from '.'
-interface Props {
+interface Props extends HTMLProps {
show?: boolean
}
diff --git a/src/_VisuallyHidden.tsx b/src/_VisuallyHidden.tsx
index 9237f91a086..abb75c85abf 100644
--- a/src/_VisuallyHidden.tsx
+++ b/src/_VisuallyHidden.tsx
@@ -26,7 +26,7 @@ const VisuallyHidden = styled.span`
`
VisuallyHidden.defaultProps = {
- isVisible: true
+ isVisible: false
}
export default VisuallyHidden
diff --git a/src/__tests__/ActionList.test.tsx b/src/__tests__/ActionList.test.tsx
index 0003873d445..8325fa7d17c 100644
--- a/src/__tests__/ActionList.test.tsx
+++ b/src/__tests__/ActionList.test.tsx
@@ -1,36 +1,63 @@
-import {cleanup, render as HTMLRender} from '@testing-library/react'
+import {cleanup, render as HTMLRender, waitFor, fireEvent} from '@testing-library/react'
import 'babel-polyfill'
import {axe, toHaveNoViolations} from 'jest-axe'
import React from 'react'
import theme from '../theme'
import {ActionList} from '../ActionList'
-import {behavesAsComponent, checkExports} from '../utils/testing'
-import {BaseStyles, ThemeProvider} from '..'
+import {behavesAsComponent, checkExports, checkStoriesForAxeViolations} from '../utils/testing'
+import {BaseStyles, ThemeProvider, SSRProvider} from '..'
+import '@testing-library/jest-dom'
expect.extend(toHaveNoViolations)
function SimpleActionList(): JSX.Element {
return (
-
-
-
+
+
+
+ New file
+
+ Copy link
+ Edit file
+ Delete file
+
+
+
)
}
+const projects = [
+ {name: 'Primer Backlog', scope: 'GitHub'},
+ {name: 'Primer React', scope: 'github/primer'},
+ {name: 'Disabled Project', scope: 'github/primer', disabled: true}
+]
+function SingleSelectListStory(): JSX.Element {
+ const [selectedIndex, setSelectedIndex] = React.useState(0)
+
+ return (
+
+ {projects.map((project, index) => (
+ setSelectedIndex(index)}
+ disabled={project.disabled}
+ >
+ {project.name}
+
+ ))}
+
+ )
+}
+
describe('ActionList', () => {
behavesAsComponent({
Component: ActionList,
options: {skipAs: true, skipSx: true},
- toRender: () =>
+ toRender: () =>
})
checkExports('ActionList', {
@@ -44,10 +71,88 @@ describe('ActionList', () => {
expect(results).toHaveNoViolations()
cleanup()
})
-})
-describe('ActionList.Item', () => {
- behavesAsComponent({
- Component: ActionList.Item
+ it('should fire onSelect on click and keypress', async () => {
+ const component = HTMLRender( )
+ const options = await waitFor(() => component.getAllByRole('option'))
+
+ expect(options[0]).toHaveAttribute('aria-selected', 'true')
+ expect(options[1]).toHaveAttribute('aria-selected', 'false')
+
+ fireEvent.click(options[1])
+
+ expect(options[0]).toHaveAttribute('aria-selected', 'false')
+ expect(options[1]).toHaveAttribute('aria-selected', 'true')
+
+ // We pass keycode here to navigate a implementation detail in react-testing-library
+ // https://github.com/testing-library/react-testing-library/issues/269#issuecomment-455854112
+ fireEvent.keyPress(options[0], {key: 'Enter', charCode: 13})
+
+ expect(options[0]).toHaveAttribute('aria-selected', 'true')
+ expect(options[1]).toHaveAttribute('aria-selected', 'false')
+
+ fireEvent.keyPress(options[1], {key: ' ', charCode: 32})
+
+ expect(options[0]).toHaveAttribute('aria-selected', 'false')
+ expect(options[1]).toHaveAttribute('aria-selected', 'true')
+
+ cleanup()
+ })
+
+ it('should skip onSelect on disabled items', async () => {
+ const component = HTMLRender( )
+ const options = await waitFor(() => component.getAllByRole('option'))
+
+ expect(options[0]).toHaveAttribute('aria-selected', 'true')
+ expect(options[2]).toHaveAttribute('aria-selected', 'false')
+
+ fireEvent.click(options[2])
+
+ expect(options[0]).toHaveAttribute('aria-selected', 'true')
+ expect(options[2]).toHaveAttribute('aria-selected', 'false')
+
+ fireEvent.keyPress(options[2], {key: 'Enter', charCode: 13})
+
+ expect(options[0]).toHaveAttribute('aria-selected', 'true')
+ expect(options[2]).toHaveAttribute('aria-selected', 'false')
+
+ cleanup()
+ })
+
+ it('should throw when selected is provided without a selectionVariant on parent', async () => {
+ // we expect console.error to be called, so we suppress that in the test
+ const mockError = jest.spyOn(console, 'error').mockImplementation(() => jest.fn())
+
+ expect(() => {
+ HTMLRender(
+
+
+ Primer React
+
+
+ )
+ }).toThrow('For Item to be selected, ActionList or ActionList.Group needs to have a selectionVariant defined')
+
+ cleanup()
+ mockError.mockRestore()
})
+
+ it('should not crash when clicking an item without an onSelect', async () => {
+ const component = HTMLRender(
+
+ Primer React
+
+ )
+ const option = await waitFor(() => component.getByRole('option'))
+ expect(option).toBeInTheDocument()
+
+ fireEvent.click(option)
+ fireEvent.keyPress(option, {key: 'Enter', charCode: 13})
+ expect(option).toBeInTheDocument()
+
+ cleanup()
+ })
+
+ checkStoriesForAxeViolations('ActionList/fixtures')
+ checkStoriesForAxeViolations('ActionList/examples')
})
diff --git a/src/__tests__/ActionList2.test.tsx b/src/__tests__/ActionList2.test.tsx
deleted file mode 100644
index d266852fb20..00000000000
--- a/src/__tests__/ActionList2.test.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import {cleanup, render as HTMLRender, waitFor, fireEvent} from '@testing-library/react'
-import 'babel-polyfill'
-import {axe, toHaveNoViolations} from 'jest-axe'
-import React from 'react'
-import theme from '../theme'
-import {ActionList} from '../ActionList2'
-import {behavesAsComponent, checkExports, checkStoriesForAxeViolations} from '../utils/testing'
-import {BaseStyles, ThemeProvider, SSRProvider} from '..'
-import '@testing-library/jest-dom'
-expect.extend(toHaveNoViolations)
-
-function SimpleActionList(): JSX.Element {
- return (
-
-
-
-
- New file
-
- Copy link
- Edit file
- Delete file
-
-
-
-
- )
-}
-
-const projects = [
- {name: 'Primer Backlog', scope: 'GitHub'},
- {name: 'Primer React', scope: 'github/primer'},
- {name: 'Disabled Project', scope: 'github/primer', disabled: true}
-]
-function SingleSelectListStory(): JSX.Element {
- const [selectedIndex, setSelectedIndex] = React.useState(0)
-
- return (
-
- {projects.map((project, index) => (
- setSelectedIndex(index)}
- disabled={project.disabled}
- >
- {project.name}
-
- ))}
-
- )
-}
-
-describe('ActionList', () => {
- behavesAsComponent({
- Component: ActionList,
- options: {skipAs: true, skipSx: true},
- toRender: () =>
- })
-
- checkExports('ActionList2', {
- default: undefined,
- ActionList
- })
-
- it('should have no axe violations', async () => {
- const {container} = HTMLRender( )
- const results = await axe(container)
- expect(results).toHaveNoViolations()
- cleanup()
- })
-
- it('should fire onSelect on click and keypress', async () => {
- const component = HTMLRender( )
- const options = await waitFor(() => component.getAllByRole('option'))
-
- expect(options[0]).toHaveAttribute('aria-selected', 'true')
- expect(options[1]).toHaveAttribute('aria-selected', 'false')
-
- fireEvent.click(options[1])
-
- expect(options[0]).toHaveAttribute('aria-selected', 'false')
- expect(options[1]).toHaveAttribute('aria-selected', 'true')
-
- // We pass keycode here to navigate a implementation detail in react-testing-library
- // https://github.com/testing-library/react-testing-library/issues/269#issuecomment-455854112
- fireEvent.keyPress(options[0], {key: 'Enter', charCode: 13})
-
- expect(options[0]).toHaveAttribute('aria-selected', 'true')
- expect(options[1]).toHaveAttribute('aria-selected', 'false')
-
- fireEvent.keyPress(options[1], {key: ' ', charCode: 32})
-
- expect(options[0]).toHaveAttribute('aria-selected', 'false')
- expect(options[1]).toHaveAttribute('aria-selected', 'true')
-
- cleanup()
- })
-
- it('should skip onSelect on disabled items', async () => {
- const component = HTMLRender( )
- const options = await waitFor(() => component.getAllByRole('option'))
-
- expect(options[0]).toHaveAttribute('aria-selected', 'true')
- expect(options[2]).toHaveAttribute('aria-selected', 'false')
-
- fireEvent.click(options[2])
-
- expect(options[0]).toHaveAttribute('aria-selected', 'true')
- expect(options[2]).toHaveAttribute('aria-selected', 'false')
-
- fireEvent.keyPress(options[2], {key: 'Enter', charCode: 13})
-
- expect(options[0]).toHaveAttribute('aria-selected', 'true')
- expect(options[2]).toHaveAttribute('aria-selected', 'false')
-
- cleanup()
- })
-
- it('should throw when selected is provided without a selectionVariant on parent', async () => {
- // we expect console.error to be called, so we suppress that in the test
- const mockError = jest.spyOn(console, 'error').mockImplementation(() => jest.fn())
-
- expect(() => {
- HTMLRender(
-
-
- Primer React
-
-
- )
- }).toThrow('For Item to be selected, ActionList or ActionList.Group needs to have a selectionVariant defined')
-
- cleanup()
- mockError.mockRestore()
- })
-
- it('should not crash when clicking an item without an onSelect', async () => {
- const component = HTMLRender(
-
- Primer React
-
- )
- const option = await waitFor(() => component.getByRole('option'))
- expect(option).toBeInTheDocument()
-
- fireEvent.click(option)
- fireEvent.keyPress(option, {key: 'Enter', charCode: 13})
- expect(option).toBeInTheDocument()
-
- cleanup()
- })
-
- checkStoriesForAxeViolations('ActionList2/fixtures')
- checkStoriesForAxeViolations('ActionList2/examples')
-})
diff --git a/src/__tests__/ActionMenu.test.tsx b/src/__tests__/ActionMenu.test.tsx
index 504693992eb..80a5423d3ad 100644
--- a/src/__tests__/ActionMenu.test.tsx
+++ b/src/__tests__/ActionMenu.test.tsx
@@ -6,7 +6,7 @@ import theme from '../theme'
import {ActionMenu} from '../ActionMenu'
import {behavesAsComponent, checkExports} from '../utils/testing'
import {BaseStyles, SSRProvider, ThemeProvider} from '..'
-import {ItemProps} from '../ActionList/Item'
+import {ItemProps} from '../deprecated/ActionList/Item'
expect.extend(toHaveNoViolations)
const items = [
diff --git a/src/__tests__/ActionMenu2.test.tsx b/src/__tests__/ActionMenu2.test.tsx
index 20cfb9fcda2..2f465c277cf 100644
--- a/src/__tests__/ActionMenu2.test.tsx
+++ b/src/__tests__/ActionMenu2.test.tsx
@@ -4,7 +4,7 @@ import {axe, toHaveNoViolations} from 'jest-axe'
import React from 'react'
import theme from '../theme'
import {ActionMenu} from '../ActionMenu2'
-import {ActionList} from '../ActionList2'
+import {ActionList} from '../ActionList'
import {behavesAsComponent, checkExports, checkStoriesForAxeViolations} from '../utils/testing'
import {BaseStyles, ThemeProvider, SSRProvider} from '..'
import {SingleSelection, MixedSelection} from '../../src/stories/ActionMenu2/examples.stories'
diff --git a/src/__tests__/Autocomplete.test.tsx b/src/__tests__/Autocomplete.test.tsx
index 8c2b89d046c..3e17f37dcb0 100644
--- a/src/__tests__/Autocomplete.test.tsx
+++ b/src/__tests__/Autocomplete.test.tsx
@@ -10,7 +10,7 @@ import BaseStyles from '../BaseStyles'
import {ThemeProvider} from '../ThemeProvider'
import userEvent from '@testing-library/user-event'
import {AutocompleteMenuInternalProps} from '../Autocomplete/AutocompleteMenu'
-import {ItemProps} from '../ActionList'
+import {ItemProps} from '../deprecated/ActionList'
import {MandateProps} from '../utils/types'
expect.extend(toHaveNoViolations)
diff --git a/src/__tests__/BorderBox.test.tsx b/src/__tests__/BorderBox.test.tsx
index 0fb7c93d0c3..428ff7f4f82 100644
--- a/src/__tests__/BorderBox.test.tsx
+++ b/src/__tests__/BorderBox.test.tsx
@@ -1,6 +1,6 @@
import React from 'react'
import theme from '../theme'
-import {BorderBox} from '..'
+import {BorderBox} from '../deprecated'
import {render, behavesAsComponent, checkExports} from '../utils/testing'
import {render as HTMLRender, cleanup} from '@testing-library/react'
import {axe, toHaveNoViolations} from 'jest-axe'
diff --git a/src/__tests__/CheckboxGroup.test.tsx b/src/__tests__/CheckboxGroup.test.tsx
new file mode 100644
index 00000000000..a7c99427bef
--- /dev/null
+++ b/src/__tests__/CheckboxGroup.test.tsx
@@ -0,0 +1,167 @@
+import React from 'react'
+import '@testing-library/jest-dom/extend-expect'
+import {render} from '@testing-library/react'
+import {Checkbox, CheckboxGroup, FormControl, SSRProvider} from '..'
+import {behavesAsComponent, checkExports, checkStoriesForAxeViolations} from '../utils/testing'
+import userEvent from '@testing-library/user-event'
+import {CheckboxGroupContext} from '../CheckboxGroup'
+
+describe('CheckboxGroup', () => {
+ const mockWarningFn = jest.fn()
+
+ beforeAll(() => {
+ jest.spyOn(global.console, 'warn').mockImplementation(mockWarningFn)
+ })
+ afterAll(() => {
+ jest.clearAllMocks()
+ })
+ behavesAsComponent({
+ Component: CheckboxGroup,
+ options: {skipAs: true, skipSx: true}, // skipping sx check because we have to render this in a to keep snapshots consistent
+ toRender: () => (
+
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+
+ )
+ })
+ checkExports('CheckboxGroup', {
+ default: CheckboxGroup,
+ CheckboxGroupContext
+ })
+ it('renders a disabled group of inputs', () => {
+ const {getAllByRole, getByRole} = render(
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ )
+ const checkboxInputs = getAllByRole('checkbox') as HTMLInputElement[]
+ const fieldset = getByRole('group') as HTMLFieldSetElement
+
+ for (const checkboxInput of checkboxInputs) {
+ expect(checkboxInput.disabled).toBe(true)
+ }
+
+ expect(fieldset.disabled).toBe(true)
+ })
+ it('renders a required group of inputs', () => {
+ const {getByTitle} = render(
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ )
+ const requiredIndicator = getByTitle('required field')
+
+ expect(requiredIndicator).toBeInTheDocument()
+ })
+ it('calls onChange handlers passed to CheckboxGroup and Checkbox', () => {
+ const handleParentChange = jest.fn()
+ const handleCheckboxChange = jest.fn()
+ const {getByLabelText} = render(
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ )
+ const checkbox = getByLabelText('Choice one') as HTMLInputElement
+
+ expect(handleParentChange).not.toHaveBeenCalled()
+ expect(handleCheckboxChange).not.toHaveBeenCalled()
+ userEvent.click(checkbox)
+ expect(handleParentChange).toHaveBeenCalled()
+ expect(handleCheckboxChange).toHaveBeenCalled()
+ })
+ it('calls onChange handler on CheckboxGroup with selected values', () => {
+ const handleParentChange = jest.fn()
+ const {getByLabelText} = render(
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ )
+
+ const checkbox = getByLabelText('Choice one') as HTMLInputElement
+
+ expect(handleParentChange).not.toHaveBeenCalled()
+ userEvent.click(checkbox)
+ expect(handleParentChange).toHaveBeenCalledWith(
+ ['two', 'one'],
+ expect.objectContaining({
+ target: expect.objectContaining({
+ value: 'one'
+ })
+ })
+ )
+ userEvent.click(checkbox)
+ expect(handleParentChange).toHaveBeenCalledWith(
+ ['two'],
+ expect.objectContaining({
+ target: expect.objectContaining({
+ value: 'one'
+ })
+ })
+ )
+ })
+})
+
+checkStoriesForAxeViolations('CheckboxGroup/fixtures')
+checkStoriesForAxeViolations('CheckboxGroup/examples')
diff --git a/src/__tests__/CheckboxOrRadioGroup.test.tsx b/src/__tests__/CheckboxOrRadioGroup.test.tsx
new file mode 100644
index 00000000000..59e91a901dd
--- /dev/null
+++ b/src/__tests__/CheckboxOrRadioGroup.test.tsx
@@ -0,0 +1,207 @@
+import React from 'react'
+import '@testing-library/jest-dom/extend-expect'
+import {render, within} from '@testing-library/react'
+import {Checkbox, FormControl, Radio, SSRProvider, TextInput} from '..'
+import {behavesAsComponent, checkExports} from '../utils/testing'
+import CheckboxOrRadioGroup from '../_CheckboxOrRadioGroup'
+
+const INPUT_GROUP_LABEL = 'Choices'
+
+describe('CheckboxOrRadioGroup', () => {
+ const mockWarningFn = jest.fn()
+
+ beforeAll(() => {
+ jest.spyOn(global.console, 'warn').mockImplementation(mockWarningFn)
+ })
+ afterAll(() => {
+ jest.clearAllMocks()
+ })
+ behavesAsComponent({
+ Component: CheckboxOrRadioGroup,
+ options: {skipAs: true, skipSx: true}, // skipping sx check because we have to render this in a to keep snapshots consistent
+ toRender: () => (
+
+
+ {INPUT_GROUP_LABEL}
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+
+ )
+ })
+ checkExports('_CheckboxOrRadioGroup', {
+ default: CheckboxOrRadioGroup
+ })
+ it('renders a group of inputs with a caption in the ', () => {
+ render(
+
+ {INPUT_GROUP_LABEL}
+ Caption text
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ )
+ const legend = document.getElementsByTagName('legend')[0]
+ const caption = within(legend).getByText('Caption text')
+
+ expect(caption).toBeInTheDocument()
+ })
+ it('renders a group of inputs with a validation message in the ', () => {
+ render(
+
+ {INPUT_GROUP_LABEL}
+ Caption text
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+ Validation text
+
+ )
+ const legend = document.getElementsByTagName('legend')[0]
+ const validationMsg = within(legend).getByText('Validation text')
+
+ expect(validationMsg).toBeInTheDocument()
+ })
+ it('renders with a hidden label', () => {
+ const {getByText} = render(
+
+ {INPUT_GROUP_LABEL}
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ )
+ const legend = getByText(INPUT_GROUP_LABEL)
+
+ expect(legend).toBeInTheDocument()
+ })
+ it('uses a legend to label the input group', () => {
+ const {getByRole} = render(
+
+ {INPUT_GROUP_LABEL}
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ )
+
+ expect(getByRole('group', {name: INPUT_GROUP_LABEL})).toBeTruthy()
+ })
+ it('associates a label with the input group when the label is not a child of CheckboxOrRadioGroup', () => {
+ const INPUT_GROUP_LABEL_ID = 'the-label'
+ const {getByLabelText} = render(
+ <>
+
+
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ >
+ )
+ const fieldset = getByLabelText(INPUT_GROUP_LABEL)
+
+ expect(fieldset).toBeInTheDocument()
+ })
+ it('logs a warning when trying to render a group without a label', () => {
+ const consoleSpy = jest.spyOn(global.console, 'warn')
+
+ render(
+
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ )
+
+ expect(consoleSpy).toHaveBeenCalled()
+ })
+ it('logs a warning when trying to render an input component other than Radio or Checkbox', () => {
+ const consoleSpy = jest.spyOn(global.console, 'warn')
+
+ render(
+
+ {INPUT_GROUP_LABEL}
+
+ Choice one
+
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ )
+
+ expect(consoleSpy).toHaveBeenCalled()
+ })
+})
diff --git a/src/__tests__/ChoiceFieldset.test.tsx b/src/__tests__/ChoiceFieldset.test.tsx
index 87c46ee84f0..235bb346569 100644
--- a/src/__tests__/ChoiceFieldset.test.tsx
+++ b/src/__tests__/ChoiceFieldset.test.tsx
@@ -1,10 +1,10 @@
import React from 'react'
import {render} from '../utils/testing'
import {render as HTMLRender} from '@testing-library/react'
-import {ChoiceFieldset, Item, SSRProvider} from '..'
+import {SSRProvider} from '..'
import {MarkGithubIcon} from '@primer/octicons-react'
import userEvent from '@testing-library/user-event'
-import {ChoiceFieldsetProps} from '../ChoiceFieldset'
+import ChoiceFieldset, {Item, ChoiceFieldsetProps} from '../ChoiceFieldset'
import {ChoiceFieldsetListProps} from '../ChoiceFieldset/ChoiceFieldsetList'
const SelectableChoicelistFieldset: React.FC = ({
diff --git a/src/__tests__/ChoiceInputField.test.tsx b/src/__tests__/ChoiceInputField.test.tsx
index c6d1393e8da..032f48db43b 100644
--- a/src/__tests__/ChoiceInputField.test.tsx
+++ b/src/__tests__/ChoiceInputField.test.tsx
@@ -2,7 +2,8 @@ import React from 'react'
import {render, cleanup} from '@testing-library/react'
import {axe, toHaveNoViolations} from 'jest-axe'
import 'babel-polyfill'
-import {Checkbox, ChoiceInputField, Radio, SSRProvider} from '..'
+import {Checkbox, Radio, SSRProvider} from '..'
+import ChoiceInputField from '../ChoiceInputField'
import {MarkGithubIcon} from '@primer/octicons-react'
expect.extend(toHaveNoViolations)
diff --git a/src/__tests__/Dropdown.test.tsx b/src/__tests__/Dropdown.test.tsx
index 5d91c01800b..a327c620e51 100644
--- a/src/__tests__/Dropdown.test.tsx
+++ b/src/__tests__/Dropdown.test.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import {Dropdown} from '..'
+import {Dropdown} from '../deprecated'
import {behavesAsComponent, checkExports} from '../utils/testing'
import {render as HTMLRender, cleanup} from '@testing-library/react'
import {axe, toHaveNoViolations} from 'jest-axe'
diff --git a/src/__tests__/DropdownMenu.test.tsx b/src/__tests__/DropdownMenu.test.tsx
index aa93d7f5fb7..3a8665ccc55 100644
--- a/src/__tests__/DropdownMenu.test.tsx
+++ b/src/__tests__/DropdownMenu.test.tsx
@@ -6,7 +6,7 @@ import theme from '../theme'
import {DropdownMenu, DropdownButton} from '../DropdownMenu'
import {behavesAsComponent, checkExports} from '../utils/testing'
import {BaseStyles, ThemeProvider, SSRProvider} from '..'
-import {ItemInput} from '../ActionList/List'
+import {ItemInput} from '../deprecated/ActionList/List'
expect.extend(toHaveNoViolations)
diff --git a/src/__tests__/DropdownMenu2.test.tsx b/src/__tests__/DropdownMenu2.test.tsx
index 04917c6e3a4..2e91a21b0c0 100644
--- a/src/__tests__/DropdownMenu2.test.tsx
+++ b/src/__tests__/DropdownMenu2.test.tsx
@@ -4,7 +4,7 @@ import {toHaveNoViolations} from 'jest-axe'
import React from 'react'
import theme from '../theme'
import {DropdownMenu} from '../DropdownMenu2'
-import {ActionList} from '../ActionList2'
+import {ActionList} from '../ActionList'
import {behavesAsComponent, checkExports, checkStoriesForAxeViolations} from '../utils/testing'
import {BaseStyles, ThemeProvider, SSRProvider} from '..'
import '@testing-library/jest-dom'
diff --git a/src/__tests__/Flex.test.tsx b/src/__tests__/Flex.test.tsx
index 802bef9b689..7f7cd93828f 100644
--- a/src/__tests__/Flex.test.tsx
+++ b/src/__tests__/Flex.test.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import {Flex} from '..'
+import {Flex} from '../deprecated'
import {render, behavesAsComponent, checkExports} from '../utils/testing'
import {render as HTMLRender, cleanup} from '@testing-library/react'
import {axe, toHaveNoViolations} from 'jest-axe'
diff --git a/src/__tests__/FormControl.test.tsx b/src/__tests__/FormControl.test.tsx
index 34786c56fa0..34724e7c730 100644
--- a/src/__tests__/FormControl.test.tsx
+++ b/src/__tests__/FormControl.test.tsx
@@ -251,8 +251,8 @@ describe('FormControl', () => {
const inputNode = getByLabelText(LABEL_TEXT)
const validationNode = getByText(ERROR_TEXT)
- expect(validationNode.getAttribute('id')).toBe(`${fieldId}-validationMsg`)
- expect(inputNode.getAttribute('aria-describedby')).toBe(`${fieldId}-validationMsg`)
+ expect(validationNode.getAttribute('id')).toBe(`${fieldId}-validationMessage`)
+ expect(inputNode.getAttribute('aria-describedby')).toBe(`${fieldId}-validationMessage`)
})
})
diff --git a/src/__tests__/Grid.test.tsx b/src/__tests__/Grid.test.tsx
index 721fa4b7e57..dc05ddf0b66 100644
--- a/src/__tests__/Grid.test.tsx
+++ b/src/__tests__/Grid.test.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import {Grid} from '..'
+import {Grid} from '../deprecated'
import {render, behavesAsComponent, checkExports} from '../utils/testing'
import {render as HTMLRender, cleanup} from '@testing-library/react'
import {axe, toHaveNoViolations} from 'jest-axe'
diff --git a/src/__tests__/Position.test.tsx b/src/__tests__/Position.test.tsx
index e145285e917..629bd09b478 100644
--- a/src/__tests__/Position.test.tsx
+++ b/src/__tests__/Position.test.tsx
@@ -1,5 +1,6 @@
import React from 'react'
-import {Box, Position, Absolute, Fixed, Relative, Sticky} from '..'
+import {Box} from '..'
+import {Position, Absolute, Fixed, Relative, Sticky} from '../deprecated'
import {render, behavesAsComponent, checkExports} from '../utils/testing'
import {render as HTMLRender, cleanup} from '@testing-library/react'
import {axe, toHaveNoViolations} from 'jest-axe'
diff --git a/src/__tests__/RadioGroup.test.tsx b/src/__tests__/RadioGroup.test.tsx
new file mode 100644
index 00000000000..037a0d0369e
--- /dev/null
+++ b/src/__tests__/RadioGroup.test.tsx
@@ -0,0 +1,158 @@
+import React from 'react'
+import '@testing-library/jest-dom/extend-expect'
+import {render} from '@testing-library/react'
+import {RadioGroup, FormControl, Radio, SSRProvider} from '..'
+import {behavesAsComponent, checkExports, checkStoriesForAxeViolations} from '../utils/testing'
+import userEvent from '@testing-library/user-event'
+import {RadioGroupContext} from '../RadioGroup'
+
+describe('RadioGroup', () => {
+ const mockWarningFn = jest.fn()
+
+ beforeAll(() => {
+ jest.spyOn(global.console, 'warn').mockImplementation(mockWarningFn)
+ })
+ afterAll(() => {
+ jest.clearAllMocks()
+ })
+ behavesAsComponent({
+ Component: RadioGroup,
+ options: {skipAs: true, skipSx: true}, // skipping sx check because we have to render this in a to keep snapshots consistent
+ toRender: () => (
+
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+
+ )
+ })
+ checkExports('RadioGroup', {
+ default: RadioGroup,
+ RadioGroupContext
+ })
+ it('renders a disabled group of inputs', () => {
+ const {getAllByRole, getByRole} = render(
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ )
+ const radioInputs = getAllByRole('radio') as HTMLInputElement[]
+ const fieldset = getByRole('group') as HTMLFieldSetElement
+
+ for (const radioInput of radioInputs) {
+ expect(radioInput.disabled).toBe(true)
+ }
+
+ expect(fieldset.disabled).toBe(true)
+ })
+ it('renders a required group of inputs', () => {
+ const {getByTitle} = render(
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ )
+ const requiredIndicator = getByTitle('required field')
+
+ expect(requiredIndicator).toBeInTheDocument()
+ })
+ it('calls onChange handlers passed to RadioGroup and Radio', () => {
+ const handleParentChange = jest.fn()
+ const handleRadioChange = jest.fn()
+ const {getByLabelText} = render(
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ )
+ const checkbox = getByLabelText('Choice one') as HTMLInputElement
+
+ expect(handleParentChange).not.toHaveBeenCalled()
+ expect(handleRadioChange).not.toHaveBeenCalled()
+ userEvent.click(checkbox)
+ expect(handleParentChange).toHaveBeenCalled()
+ expect(handleRadioChange).toHaveBeenCalled()
+ })
+ it('calls onChange handler on RadioGroup with selected value', () => {
+ const handleParentChange = jest.fn()
+ const {getByLabelText} = render(
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ )
+
+ const checkbox = getByLabelText('Choice one') as HTMLInputElement
+
+ expect(handleParentChange).not.toHaveBeenCalled()
+ userEvent.click(checkbox)
+ expect(handleParentChange).toHaveBeenCalledWith(
+ 'one',
+ expect.objectContaining({
+ target: expect.objectContaining({
+ value: 'one'
+ })
+ })
+ )
+ })
+})
+
+checkStoriesForAxeViolations('RadioGroup/fixtures')
+checkStoriesForAxeViolations('RadioGroup/examples')
diff --git a/src/__tests__/SelectMenu.test.tsx b/src/__tests__/SelectMenu.test.tsx
index 24abc9ba01b..95e31f88a68 100644
--- a/src/__tests__/SelectMenu.test.tsx
+++ b/src/__tests__/SelectMenu.test.tsx
@@ -1,5 +1,6 @@
import React from 'react'
-import {SelectMenu, Button} from '..'
+import {SelectMenu} from '../deprecated'
+import {Button} from '..'
import {mount, render, renderRoot, COMPONENT_DISPLAY_NAME_REGEX, checkExports} from '../utils/testing'
import {render as HTMLRender, cleanup} from '@testing-library/react'
import {axe, toHaveNoViolations} from 'jest-axe'
diff --git a/src/__tests__/SelectPanel.test.tsx b/src/__tests__/SelectPanel.test.tsx
index aefd92e25b8..b88cb8f3149 100644
--- a/src/__tests__/SelectPanel.test.tsx
+++ b/src/__tests__/SelectPanel.test.tsx
@@ -6,7 +6,7 @@ import theme from '../theme'
import {SelectPanel} from '../SelectPanel'
import {behavesAsComponent, checkExports} from '../utils/testing'
import {BaseStyles, SSRProvider, ThemeProvider} from '..'
-import {ItemInput} from '../ActionList/List'
+import {ItemInput} from '../deprecated/ActionList/List'
expect.extend(toHaveNoViolations)
diff --git a/src/__tests__/__snapshots__/ActionList.test.tsx.snap b/src/__tests__/__snapshots__/ActionList.test.tsx.snap
index 852201546b0..5bbbad35875 100644
--- a/src/__tests__/__snapshots__/ActionList.test.tsx.snap
+++ b/src/__tests__/__snapshots__/ActionList.test.tsx.snap
@@ -1,223 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ActionList renders consistently 1`] = `
-.c1 {
- margin-top: 8px;
- margin-bottom: 8px;
-}
-
.c0 {
- font-size: 14px;
- line-height: 20px;
-}
-
-.c0[data-has-active-descendant],
-.c0:focus-within {
- --item-hover-bg-override: none;
- --item-hover-divider-border-color-override: hsla(210,18%,87%,1);
+ margin: 0;
+ padding-inline-start: 0;
+ padding-top: 8px;
+ padding-bottom: 8px;
}
-
-`;
-
-exports[`ActionList.Item renders consistently 1`] = `
-.c3 {
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- min-width: 0;
- position: relative;
- -webkit-box-flex: 1;
- -webkit-flex-grow: 1;
- -ms-flex-positive: 1;
- flex-grow: 1;
-}
-
-.c4 {
- -webkit-align-items: baseline;
- -webkit-box-align: baseline;
- -ms-flex-align: baseline;
- align-items: baseline;
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- min-width: 0;
- -webkit-flex-direction: var(--main-content-flex-direction);
- -ms-flex-direction: var(--main-content-flex-direction);
- flex-direction: var(--main-content-flex-direction);
- -webkit-box-flex: 1;
- -webkit-flex-grow: 1;
- -ms-flex-positive: 1;
- flex-grow: 1;
-}
-
-.c5:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) {
- margin-top: 0;
-}
-
-.c5:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) .c2::before {
- content: ' ';
- display: block;
- position: absolute;
- width: 100%;
- top: -7px;
- border: 0 solid hsla(210,18%,87%,1);
- border-top-width: 0;
-}
-
-.c5:hover .c2::before,
-.c5:hover + * .c2::before {
- border-color: var(--item-hover-divider-border-color-override,transparent) !important;
-}
-
-.c5:focus .c2::before,
-.c5:focus + * .c2::before,
-.c5[data-is-active-descendant] .c2::before,
-[data-is-active-descendant] + .c5 .c2::before {
- border-color: transparent !important;
-}
-
-.c6:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) {
- margin-top: 0;
-}
-
-.c6:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) .c2::before {
- content: ' ';
- display: block;
- position: absolute;
- width: 100%;
- top: -7px;
- border: 0 solid hsla(210,18%,87%,1);
- border-top-width: 0;
-}
-
-.c6:hover .c2::before,
-.c6:hover + * .c2::before {
- border-color: var(--item-hover-divider-border-color-override,transparent) !important;
-}
-
-.c6:focus .c2::before,
-.c6:focus + * .c2::before,
-.c6[data-is-active-descendant] .c2::before,
-[data-is-active-descendant] + .c6 .c2::before {
- border-color: transparent !important;
-}
-
-.c7:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) {
- margin-top: 0;
-}
-
-.c7:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) .c2::before {
- content: ' ';
- display: block;
- position: absolute;
- width: 100%;
- top: -7px;
- border: 0 solid hsla(210,18%,87%,1);
- border-top-width: 0;
-}
-
-.c7:hover .c2::before,
-.c7:hover + * .c2::before {
- border-color: var(--item-hover-divider-border-color-override,transparent) !important;
-}
-
-.c7:focus .c2::before,
-.c7:focus + * .c2::before,
-.c7[data-is-active-descendant] .c2::before,
-[data-is-active-descendant] + .c7 .c2::before {
- border-color: transparent !important;
-}
-
-.c1 {
- padding: 6px 8px;
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- border-radius: 6px;
- color: #24292f;
- -webkit-transition: background 33.333ms linear;
- transition: background 33.333ms linear;
- -webkit-text-decoration: none;
- text-decoration: none;
-}
-
-.c1:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) {
- margin-top: 0;
-}
-
-.c1:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) .c2::before {
- content: ' ';
- display: block;
- position: absolute;
- width: 100%;
- top: -7px;
- border: 0 solid hsla(210,18%,87%,1);
- border-top-width: 0;
-}
-
-.c1:hover .c2::before,
-.c1:hover + * .c2::before {
- border-color: var(--item-hover-divider-border-color-override,transparent) !important;
-}
-
-.c1:focus .c2::before,
-.c1:focus + * .c2::before,
-.c1[data-is-active-descendant] .c2::before,
-[data-is-active-descendant] + .c1 .c2::before {
- border-color: transparent !important;
-}
-
-.c1[data-is-active-descendant='activated-directly'] {
- background: rgba(208,215,222,0.48);
-}
-
-.c1[data-is-active-descendant='activated-indirectly'] {
- background: rgba(208,215,222,0.32);
-}
-
-.c1:focus {
- background: rgba(208,215,222,0.48);
- outline: none;
-}
-
-.c1:active {
- background: rgba(208,215,222,0.48);
-}
-
-@media (hover:hover) and (pointer:fine) {
- .c1:hover {
- background: var( --item-hover-bg-override,rgba(208,215,222,0.32) );
- cursor: pointer;
- }
-}
-
-
+/>
`;
diff --git a/src/__tests__/__snapshots__/ActionList2.test.tsx.snap b/src/__tests__/__snapshots__/ActionList2.test.tsx.snap
deleted file mode 100644
index 5bbbad35875..00000000000
--- a/src/__tests__/__snapshots__/ActionList2.test.tsx.snap
+++ /dev/null
@@ -1,14 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ActionList renders consistently 1`] = `
-.c0 {
- margin: 0;
- padding-inline-start: 0;
- padding-top: 8px;
- padding-bottom: 8px;
-}
-
-
-`;
diff --git a/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap b/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap
index af9bb835cd8..ed74f706514 100644
--- a/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap
+++ b/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap
@@ -442,7 +442,20 @@ Array [
type="text"
/>
,
- .c2 {
+ .c0 {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ -webkit-clip: rect(0,0,0,0);
+ clip: rect(0,0,0,0);
+ white-space: nowrap;
+ border-width: 0;
+}
+
+.c2 {
margin-top: 8px;
margin-bottom: 8px;
}
@@ -618,19 +631,6 @@ Array [
--item-hover-divider-border-color-override: hsla(210,18%,87%,1);
}
-.c0 {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- -webkit-clip: rect(0,0,0,0);
- clip: rect(0,0,0,0);
- white-space: nowrap;
- border-width: 0;
-}
-
@media (hover:hover) and (pointer:fine) {
.c4:hover {
background: var( --item-hover-bg-override,rgba(208,215,222,0.32) );
@@ -1386,7 +1386,20 @@ Array [
type="text"
/>
,
- .c2 {
+ .c0 {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ -webkit-clip: rect(0,0,0,0);
+ clip: rect(0,0,0,0);
+ white-space: nowrap;
+ border-width: 0;
+}
+
+.c2 {
margin-top: 8px;
margin-bottom: 8px;
}
@@ -1525,19 +1538,6 @@ Array [
--item-hover-divider-border-color-override: hsla(210,18%,87%,1);
}
-.c0 {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- -webkit-clip: rect(0,0,0,0);
- clip: rect(0,0,0,0);
- white-space: nowrap;
- border-width: 0;
-}
-
@media (hover:hover) and (pointer:fine) {
.c4:hover {
background: var( --item-hover-bg-override,rgba(208,215,222,0.32) );
@@ -2240,7 +2240,20 @@ Array [
type="text"
/>
,
- .c2 {
+ .c0 {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ -webkit-clip: rect(0,0,0,0);
+ clip: rect(0,0,0,0);
+ white-space: nowrap;
+ border-width: 0;
+}
+
+.c2 {
margin-top: 8px;
margin-bottom: 8px;
}
@@ -2390,19 +2403,6 @@ Array [
--item-hover-divider-border-color-override: hsla(210,18%,87%,1);
}
-.c0 {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- -webkit-clip: rect(0,0,0,0);
- clip: rect(0,0,0,0);
- white-space: nowrap;
- border-width: 0;
-}
-
@media (hover:hover) and (pointer:fine) {
.c4:hover {
background: var( --item-hover-bg-override,rgba(208,215,222,0.32) );
@@ -3105,7 +3105,20 @@ Array [
type="text"
/>
,
- .c2 {
+ .c0 {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ -webkit-clip: rect(0,0,0,0);
+ clip: rect(0,0,0,0);
+ white-space: nowrap;
+ border-width: 0;
+}
+
+.c2 {
margin-top: 8px;
margin-bottom: 8px;
}
@@ -3212,19 +3225,6 @@ Array [
--item-hover-divider-border-color-override: hsla(210,18%,87%,1);
}
-.c0 {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- -webkit-clip: rect(0,0,0,0);
- clip: rect(0,0,0,0);
- white-space: nowrap;
- border-width: 0;
-}
-
@media (hover:hover) and (pointer:fine) {
.c4:hover {
background: var( --item-hover-bg-override,rgba(208,215,222,0.32) );
@@ -3548,7 +3548,20 @@ Array [
id="customInput"
type="text"
/>,
- .c2 {
+ .c0 {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ -webkit-clip: rect(0,0,0,0);
+ clip: rect(0,0,0,0);
+ white-space: nowrap;
+ border-width: 0;
+}
+
+.c2 {
margin-top: 8px;
margin-bottom: 8px;
}
@@ -3655,19 +3668,6 @@ Array [
--item-hover-divider-border-color-override: hsla(210,18%,87%,1);
}
-.c0 {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- -webkit-clip: rect(0,0,0,0);
- clip: rect(0,0,0,0);
- white-space: nowrap;
- border-width: 0;
-}
-
@media (hover:hover) and (pointer:fine) {
.c4:hover {
background: var( --item-hover-bg-override,rgba(208,215,222,0.32) );
diff --git a/src/__tests__/__snapshots__/Checkbox.test.tsx.snap b/src/__tests__/__snapshots__/Checkbox.test.tsx.snap
index 91b973a8e8e..1f1f73e7285 100644
--- a/src/__tests__/__snapshots__/Checkbox.test.tsx.snap
+++ b/src/__tests__/__snapshots__/Checkbox.test.tsx.snap
@@ -11,6 +11,7 @@ exports[`Checkbox renders consistently 1`] = `
aria-invalid="false"
aria-required="false"
className="c0"
+ onChange={[Function]}
type="checkbox"
/>
`;
diff --git a/src/__tests__/__snapshots__/CheckboxGroup.test.tsx.snap b/src/__tests__/__snapshots__/CheckboxGroup.test.tsx.snap
new file mode 100644
index 00000000000..b75d144d4f4
--- /dev/null
+++ b/src/__tests__/__snapshots__/CheckboxGroup.test.tsx.snap
@@ -0,0 +1,125 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CheckboxGroup renders consistently 1`] = `
+.c0 {
+ margin: 0;
+ padding: 0;
+ border: none;
+}
+
+.c1 {
+ margin-bottom: 8px;
+ padding: 0;
+}
+
+.c3 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+}
+
+.c4 > input {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+.c2 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.c2 > * + * {
+ margin-top: 8px;
+}
+
+.c5 {
+ cursor: pointer;
+}
+
+
+`;
diff --git a/src/__tests__/__snapshots__/CheckboxOrRadioGroup.test.tsx.snap b/src/__tests__/__snapshots__/CheckboxOrRadioGroup.test.tsx.snap
new file mode 100644
index 00000000000..9d3641efc54
--- /dev/null
+++ b/src/__tests__/__snapshots__/CheckboxOrRadioGroup.test.tsx.snap
@@ -0,0 +1,125 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CheckboxOrRadioGroup renders consistently 1`] = `
+.c0 {
+ margin: 0;
+ padding: 0;
+ border: none;
+}
+
+.c1 {
+ margin-bottom: 8px;
+ padding: 0;
+}
+
+.c3 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+}
+
+.c4 > input {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+.c2 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.c2 > * + * {
+ margin-top: 8px;
+}
+
+.c5 {
+ cursor: pointer;
+}
+
+
+`;
diff --git a/src/__tests__/__snapshots__/ChoiceFieldset.test.tsx.snap b/src/__tests__/__snapshots__/ChoiceFieldset.test.tsx.snap
index 73ef8296197..293456083ea 100644
--- a/src/__tests__/__snapshots__/ChoiceFieldset.test.tsx.snap
+++ b/src/__tests__/__snapshots__/ChoiceFieldset.test.tsx.snap
@@ -34,11 +34,6 @@ exports[`ChoiceFieldset renders a disabled list 1`] = `
flex-direction: column;
}
-.c6 {
- cursor: pointer;
- cursor: not-allowed;
-}
-
.c2 {
color: #57606a;
font-size: 16px;
@@ -50,7 +45,12 @@ exports[`ChoiceFieldset renders a disabled list 1`] = `
font-size: 14px;
display: block;
color: #57606a;
+ cursor: default;
+}
+
+.c6 {
cursor: pointer;
+ cursor: not-allowed;
}
.c3 {
@@ -198,10 +198,6 @@ exports[`ChoiceFieldset renders a fieldset with a description 1`] = `
flex-direction: column;
}
-.c7 {
- cursor: pointer;
-}
-
.c2 {
font-size: 16px;
padding: 0;
@@ -215,6 +211,10 @@ exports[`ChoiceFieldset renders a fieldset with a description 1`] = `
cursor: pointer;
}
+.c7 {
+ cursor: pointer;
+}
+
.c4 {
display: -webkit-box;
display: -webkit-flex;
@@ -385,10 +385,6 @@ exports[`ChoiceFieldset renders a list of items with leading visuals and caption
fill: currentColor;
}
-.c6 {
- cursor: pointer;
-}
-
.c2 {
font-size: 16px;
padding: 0;
@@ -402,6 +398,10 @@ exports[`ChoiceFieldset renders a list of items with leading visuals and caption
cursor: pointer;
}
+.c6 {
+ cursor: pointer;
+}
+
.c3 {
display: -webkit-box;
display: -webkit-flex;
@@ -693,10 +693,6 @@ exports[`ChoiceFieldset renders with a hidden legend 1`] = `
flex-direction: column;
}
-.c5 {
- cursor: pointer;
-}
-
.c7 {
font-weight: 600;
font-size: 14px;
@@ -718,6 +714,10 @@ exports[`ChoiceFieldset renders with a hidden legend 1`] = `
border-width: 0;
}
+.c5 {
+ cursor: pointer;
+}
+
.c2 {
display: -webkit-box;
display: -webkit-flex;
@@ -878,8 +878,9 @@ exports[`ChoiceFieldset renders with a success validation message 1`] = `
display: flex;
}
-.c6 {
- cursor: pointer;
+.c11 {
+ -webkit-animation: 170ms eGcHP cubic-bezier(0.44,0.74,0.36,1);
+ animation: 170ms eGcHP cubic-bezier(0.44,0.74,0.36,1);
}
.c2 {
@@ -895,9 +896,8 @@ exports[`ChoiceFieldset renders with a success validation message 1`] = `
cursor: pointer;
}
-.c11 {
- -webkit-animation: 170ms eGcHP cubic-bezier(0.44,0.74,0.36,1);
- animation: 170ms eGcHP cubic-bezier(0.44,0.74,0.36,1);
+.c6 {
+ cursor: pointer;
}
.c3 {
@@ -1038,6 +1038,7 @@ exports[`ChoiceFieldset renders with a success validation message 1`] = `
font-size="0"
>
`;
diff --git a/src/__tests__/__snapshots__/RadioGroup.test.tsx.snap b/src/__tests__/__snapshots__/RadioGroup.test.tsx.snap
new file mode 100644
index 00000000000..24b76c16ca0
--- /dev/null
+++ b/src/__tests__/__snapshots__/RadioGroup.test.tsx.snap
@@ -0,0 +1,125 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`RadioGroup renders consistently 1`] = `
+.c0 {
+ margin: 0;
+ padding: 0;
+ border: none;
+}
+
+.c1 {
+ margin-bottom: 8px;
+ padding: 0;
+}
+
+.c3 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+}
+
+.c4 > input {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+.c2 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.c2 > * + * {
+ margin-top: 8px;
+}
+
+.c5 {
+ cursor: pointer;
+}
+
+
+`;
diff --git a/src/__tests__/deprecated/ActionList.test.tsx b/src/__tests__/deprecated/ActionList.test.tsx
new file mode 100644
index 00000000000..7432588b70a
--- /dev/null
+++ b/src/__tests__/deprecated/ActionList.test.tsx
@@ -0,0 +1,53 @@
+import {cleanup, render as HTMLRender} from '@testing-library/react'
+import 'babel-polyfill'
+import {axe, toHaveNoViolations} from 'jest-axe'
+import React from 'react'
+import theme from '../../theme'
+import {ActionList} from '../../deprecated/ActionList'
+import {behavesAsComponent, checkExports} from '../../utils/testing'
+import {BaseStyles, ThemeProvider} from '../..'
+expect.extend(toHaveNoViolations)
+
+function SimpleActionList(): JSX.Element {
+ return (
+
+
+
+
+
+ )
+}
+
+describe('ActionList', () => {
+ behavesAsComponent({
+ Component: ActionList,
+ options: {skipAs: true, skipSx: true},
+ toRender: () =>
+ })
+
+ checkExports('deprecated/ActionList', {
+ default: undefined,
+ ActionList
+ })
+
+ it('should have no axe violations', async () => {
+ const {container} = HTMLRender( )
+ const results = await axe(container)
+ expect(results).toHaveNoViolations()
+ cleanup()
+ })
+})
+
+describe('ActionList.Item', () => {
+ behavesAsComponent({
+ Component: ActionList.Item
+ })
+})
diff --git a/src/__tests__/ActionList.types.test.tsx b/src/__tests__/deprecated/ActionList.types.test.tsx
similarity index 93%
rename from src/__tests__/ActionList.types.test.tsx
rename to src/__tests__/deprecated/ActionList.types.test.tsx
index 8ff53f3d295..bcb1f2e3cb7 100644
--- a/src/__tests__/ActionList.types.test.tsx
+++ b/src/__tests__/deprecated/ActionList.types.test.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import {ActionList} from '..'
+import {ActionList} from '../../deprecated/ActionList'
export function emptyList() {
return
diff --git a/src/__tests__/deprecated/__snapshots__/ActionList.test.tsx.snap b/src/__tests__/deprecated/__snapshots__/ActionList.test.tsx.snap
new file mode 100644
index 00000000000..852201546b0
--- /dev/null
+++ b/src/__tests__/deprecated/__snapshots__/ActionList.test.tsx.snap
@@ -0,0 +1,223 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ActionList renders consistently 1`] = `
+.c1 {
+ margin-top: 8px;
+ margin-bottom: 8px;
+}
+
+.c0 {
+ font-size: 14px;
+ line-height: 20px;
+}
+
+.c0[data-has-active-descendant],
+.c0:focus-within {
+ --item-hover-bg-override: none;
+ --item-hover-divider-border-color-override: hsla(210,18%,87%,1);
+}
+
+
+`;
+
+exports[`ActionList.Item renders consistently 1`] = `
+.c3 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ min-width: 0;
+ position: relative;
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+}
+
+.c4 {
+ -webkit-align-items: baseline;
+ -webkit-box-align: baseline;
+ -ms-flex-align: baseline;
+ align-items: baseline;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ min-width: 0;
+ -webkit-flex-direction: var(--main-content-flex-direction);
+ -ms-flex-direction: var(--main-content-flex-direction);
+ flex-direction: var(--main-content-flex-direction);
+ -webkit-box-flex: 1;
+ -webkit-flex-grow: 1;
+ -ms-flex-positive: 1;
+ flex-grow: 1;
+}
+
+.c5:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) {
+ margin-top: 0;
+}
+
+.c5:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) .c2::before {
+ content: ' ';
+ display: block;
+ position: absolute;
+ width: 100%;
+ top: -7px;
+ border: 0 solid hsla(210,18%,87%,1);
+ border-top-width: 0;
+}
+
+.c5:hover .c2::before,
+.c5:hover + * .c2::before {
+ border-color: var(--item-hover-divider-border-color-override,transparent) !important;
+}
+
+.c5:focus .c2::before,
+.c5:focus + * .c2::before,
+.c5[data-is-active-descendant] .c2::before,
+[data-is-active-descendant] + .c5 .c2::before {
+ border-color: transparent !important;
+}
+
+.c6:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) {
+ margin-top: 0;
+}
+
+.c6:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) .c2::before {
+ content: ' ';
+ display: block;
+ position: absolute;
+ width: 100%;
+ top: -7px;
+ border: 0 solid hsla(210,18%,87%,1);
+ border-top-width: 0;
+}
+
+.c6:hover .c2::before,
+.c6:hover + * .c2::before {
+ border-color: var(--item-hover-divider-border-color-override,transparent) !important;
+}
+
+.c6:focus .c2::before,
+.c6:focus + * .c2::before,
+.c6[data-is-active-descendant] .c2::before,
+[data-is-active-descendant] + .c6 .c2::before {
+ border-color: transparent !important;
+}
+
+.c7:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) {
+ margin-top: 0;
+}
+
+.c7:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) .c2::before {
+ content: ' ';
+ display: block;
+ position: absolute;
+ width: 100%;
+ top: -7px;
+ border: 0 solid hsla(210,18%,87%,1);
+ border-top-width: 0;
+}
+
+.c7:hover .c2::before,
+.c7:hover + * .c2::before {
+ border-color: var(--item-hover-divider-border-color-override,transparent) !important;
+}
+
+.c7:focus .c2::before,
+.c7:focus + * .c2::before,
+.c7[data-is-active-descendant] .c2::before,
+[data-is-active-descendant] + .c7 .c2::before {
+ border-color: transparent !important;
+}
+
+.c1 {
+ padding: 6px 8px;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ border-radius: 6px;
+ color: #24292f;
+ -webkit-transition: background 33.333ms linear;
+ transition: background 33.333ms linear;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+}
+
+.c1:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) {
+ margin-top: 0;
+}
+
+.c1:not(:first-of-type):not(.c9 + .c0):not(.c8 + .c0) .c2::before {
+ content: ' ';
+ display: block;
+ position: absolute;
+ width: 100%;
+ top: -7px;
+ border: 0 solid hsla(210,18%,87%,1);
+ border-top-width: 0;
+}
+
+.c1:hover .c2::before,
+.c1:hover + * .c2::before {
+ border-color: var(--item-hover-divider-border-color-override,transparent) !important;
+}
+
+.c1:focus .c2::before,
+.c1:focus + * .c2::before,
+.c1[data-is-active-descendant] .c2::before,
+[data-is-active-descendant] + .c1 .c2::before {
+ border-color: transparent !important;
+}
+
+.c1[data-is-active-descendant='activated-directly'] {
+ background: rgba(208,215,222,0.48);
+}
+
+.c1[data-is-active-descendant='activated-indirectly'] {
+ background: rgba(208,215,222,0.32);
+}
+
+.c1:focus {
+ background: rgba(208,215,222,0.48);
+ outline: none;
+}
+
+.c1:active {
+ background: rgba(208,215,222,0.48);
+}
+
+@media (hover:hover) and (pointer:fine) {
+ .c1:hover {
+ background: var( --item-hover-bg-override,rgba(208,215,222,0.32) );
+ cursor: pointer;
+ }
+}
+
+
+`;
diff --git a/src/deprecated/ActionList/Divider.tsx b/src/deprecated/ActionList/Divider.tsx
new file mode 100644
index 00000000000..48102214ba8
--- /dev/null
+++ b/src/deprecated/ActionList/Divider.tsx
@@ -0,0 +1,25 @@
+import React from 'react'
+import styled from 'styled-components'
+import {get} from '../../constants'
+
+export const StyledDivider = styled.div`
+ height: 1px;
+ background: ${get('colors.border.muted')};
+ margin-top: calc(${get('space.2')} - 1px);
+ margin-bottom: ${get('space.2')};
+`
+
+/**
+ * Visually separates `Item`s or `Group`s in an `ActionList`.
+ */
+export function Divider(): JSX.Element {
+ return
+}
+
+/**
+ * `Divider` fulfills the `ItemPropsWithCustomRenderer` contract,
+ * so it can be used inline in an `ActionList`’s `items` prop.
+ * In other words, `items={[ActionList.Divider]}` is supported as a concise
+ * alternative to `items={[{renderItem: () => }]}`.
+ */
+Divider.renderItem = Divider
diff --git a/src/deprecated/ActionList/Group.tsx b/src/deprecated/ActionList/Group.tsx
new file mode 100644
index 00000000000..638ea4abf64
--- /dev/null
+++ b/src/deprecated/ActionList/Group.tsx
@@ -0,0 +1,45 @@
+import React from 'react'
+import styled from 'styled-components'
+import sx, {SxProp} from '../../sx'
+import {Header, HeaderProps} from './Header'
+
+/**
+ * Contract for props passed to the `Group` component.
+ */
+export interface GroupProps extends React.ComponentPropsWithoutRef<'div'>, SxProp {
+ /**
+ * Props for a `Header` to render in the `Group`.
+ */
+ header?: HeaderProps
+
+ /**
+ * The id of the group.
+ */
+ groupId?: string
+
+ /**
+ * `Items` to render in the `Group`.
+ */
+ items?: JSX.Element[]
+
+ /**
+ * Whether to display a divider above each `Item` in this `Group` when it does not follow a `Header` or `Divider`.
+ */
+ showItemDividers?: boolean
+}
+
+const StyledGroup = styled.div`
+ ${sx}
+`
+
+/**
+ * Collects related `Items` in an `ActionList`.
+ */
+export function Group({header, items, ...props}: GroupProps): JSX.Element {
+ return (
+
+ {header && }
+ {items}
+
+ )
+}
diff --git a/src/ActionList/Header.tsx b/src/deprecated/ActionList/Header.tsx
similarity index 96%
rename from src/ActionList/Header.tsx
rename to src/deprecated/ActionList/Header.tsx
index 9ac7cbd318d..c165e324efb 100644
--- a/src/ActionList/Header.tsx
+++ b/src/deprecated/ActionList/Header.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import styled, {css} from 'styled-components'
-import {get} from '../constants'
-import sx, {SxProp} from '../sx'
+import {get} from '../../constants'
+import sx, {SxProp} from '../../sx'
/**
* Contract for props passed to the `Header` component.
diff --git a/src/deprecated/ActionList/Item.tsx b/src/deprecated/ActionList/Item.tsx
new file mode 100644
index 00000000000..70f4a3db399
--- /dev/null
+++ b/src/deprecated/ActionList/Item.tsx
@@ -0,0 +1,481 @@
+import {CheckIcon, IconProps} from '@primer/octicons-react'
+import React, {useCallback} from 'react'
+import {get} from '../../constants'
+import sx, {SxProp} from '../../sx'
+import Truncate from '../../Truncate'
+import {ItemInput} from './List'
+import styled from 'styled-components'
+import {StyledHeader} from './Header'
+import {StyledDivider} from './Divider'
+import {useTheme} from '../../ThemeProvider'
+import {
+ activeDescendantActivatedDirectly,
+ activeDescendantActivatedIndirectly,
+ isActiveDescendantAttribute
+} from '@primer/behaviors'
+import {useSSRSafeId} from '@react-aria/ssr'
+import {ForwardRefComponent as PolymorphicForwardRefComponent} from '@radix-ui/react-polymorphic'
+import {AriaRole} from '../../utils/types'
+
+/**
+ * Contract for props passed to the `Item` component.
+ */
+export interface ItemProps extends SxProp {
+ /**
+ * Primary text which names an `Item`.
+ */
+ text?: string
+
+ /**
+ * Secondary text which provides additional information about an `Item`.
+ */
+ description?: string
+
+ /**
+ * Secondary text style variations. Usage is discretionary.
+ *
+ * - `"inline"` - Secondary text is positioned beside primary text.
+ * - `"block"` - Secondary text is positioned below primary text.
+ */
+ descriptionVariant?: 'inline' | 'block'
+
+ /**
+ * Icon (or similar) positioned before `Item` text.
+ */
+ leadingVisual?: React.FunctionComponent
+
+ /**
+ * @deprecated Use `trailingVisual` instead
+ * Icon (or similar) positioned after `Item` text.
+ */
+ trailingIcon?: React.FunctionComponent
+
+ /**
+ * @deprecated Use `trailingVisual` instead
+ * Text positioned after `Item` text and optional trailing icon.
+ */
+ trailingText?: string
+
+ /**
+ * Icon or text positioned after `Item` text.
+ */
+ trailingVisual?: React.ReactNode
+
+ /**
+ * Style variations associated with various `Item` types.
+ *
+ * - `"default"` - An action `Item`.
+ * - `"danger"` - A destructive action `Item`.
+ */
+ variant?: 'default' | 'danger'
+
+ /**
+ * Whether to display a divider above the `Item` when it does not follow a `Header` or `Divider`.
+ */
+ showDivider?: boolean
+
+ /**
+ * For `Item`s which can be selected, whether the `Item` is currently selected.
+ */
+ selected?: boolean
+
+ /**
+ * For `Item`s which can be selected, whether `multiple` `Item`s or a `single` `Item` can be selected
+ */
+ selectionVariant?: 'single' | 'multiple'
+
+ /**
+ * Designates the group that an item belongs to.
+ */
+ groupId?: string
+
+ /**
+ * Items that are disabled can not be clicked, selected, or navigated through.
+ */
+ disabled?: boolean
+
+ /**
+ * Callback that will trigger both on click selection and keyboard selection.
+ */
+ onAction?: (item: ItemProps, event: React.MouseEvent | React.KeyboardEvent) => void
+
+ /**
+ * An id associated with this item. Should be unique between items
+ */
+ id?: number | string
+
+ /**
+ * Node to be included inside the item before the text.
+ */
+ children?: React.ReactNode
+
+ /**
+ * The ARIA role describing the function of `List` component. `option` is a common value.
+ */
+ role?: AriaRole
+
+ /**
+ * An item to pass back in the `onAction` callback, meant as
+ */
+ item?: ItemInput
+}
+
+const getItemVariant = (variant = 'default', disabled?: boolean) => {
+ if (disabled) {
+ return {
+ color: get('colors.primer.fg.disabled'),
+ iconColor: get('colors.primer.fg.disabled'),
+ annotationColor: get('colors.primer.fg.disabled'),
+ hoverCursor: 'default'
+ }
+ }
+
+ switch (variant) {
+ case 'danger':
+ return {
+ color: get('colors.danger.fg'),
+ iconColor: get('colors.danger.fg'),
+ annotationColor: get('colors.fg.muted'),
+ hoverCursor: 'pointer',
+ hoverBg: get('colors.actionListItem.danger.hoverBg'),
+ focusBg: get('colors.actionListItem.danger.activeBg'),
+ hoverText: get('colors.actionListItem.danger.hoverText')
+ }
+ default:
+ return {
+ color: get('colors.fg.default'),
+ iconColor: get('colors.fg.muted'),
+ annotationColor: get('colors.fg.muted'),
+ hoverCursor: 'pointer',
+ hoverBg: get('colors.actionListItem.default.hoverBg'),
+ focusBg: get('colors.actionListItem.default.activeBg')
+ }
+ }
+}
+
+const DividedContent = styled.div`
+ display: flex;
+ min-width: 0;
+
+ /* Required for dividers */
+ position: relative;
+ flex-grow: 1;
+`
+
+const MainContent = styled.div`
+ align-items: baseline;
+ display: flex;
+ min-width: 0;
+ flex-direction: var(--main-content-flex-direction);
+ flex-grow: 1;
+`
+
+const StyledItem = styled.div<
+ {
+ variant: ItemProps['variant']
+ showDivider: ItemProps['showDivider']
+ item?: ItemInput
+ } & SxProp
+>`
+ /* 6px vertical padding + 20px line height = 32px total height
+ *
+ * TODO: When rem-based spacing on a 4px scale lands, replace
+ * hardcoded '6px' with 'calc((${get('space.s32')} - ${get('space.20')}) / 2)'.
+ */
+ padding: 6px ${get('space.2')};
+ display: flex;
+ border-radius: ${get('radii.2')};
+ color: ${({variant, item}) => getItemVariant(variant, item?.disabled).color};
+ // 2 frames on a 60hz monitor
+ transition: background 33.333ms linear;
+ text-decoration: none;
+
+ @media (hover: hover) and (pointer: fine) {
+ :hover {
+ // allow override in case another item in the list is active/focused
+ background: var(
+ --item-hover-bg-override,
+ ${({variant, item}) => getItemVariant(variant, item?.disabled).hoverBg}
+ );
+ color: ${({variant, item}) => getItemVariant(variant, item?.disabled).hoverText};
+ cursor: ${({variant, item}) => getItemVariant(variant, item?.disabled).hoverCursor};
+ }
+ }
+
+ // Item dividers
+ :not(:first-of-type):not(${StyledDivider} + &):not(${StyledHeader} + &) {
+ margin-top: ${({showDivider}) => (showDivider ? `1px` : '0')};
+
+ ${DividedContent}::before {
+ content: ' ';
+ display: block;
+ position: absolute;
+ width: 100%;
+ top: -7px;
+ // NB: This 'get' won’t execute if it’s moved into the arrow function below.
+ border: 0 solid ${get('colors.border.muted')};
+ border-top-width: ${({showDivider}) => (showDivider ? `1px` : '0')};
+ }
+ }
+
+ // Item dividers should not be visible:
+ // - above Hovered
+ &:hover ${DividedContent}::before,
+ // - below Hovered
+ // '*' instead of '&' because '&' maps to separate class names depending on 'variant'
+ :hover + * ${DividedContent}::before {
+ // allow override in case another item in the list is active/focused
+ border-color: var(--item-hover-divider-border-color-override, transparent) !important;
+ }
+
+ // - above Focused
+ &:focus ${DividedContent}::before,
+ // - below Focused
+ // '*' instead of '&' because '&' maps to separate class names depending on 'variant'
+ :focus + * ${DividedContent}::before,
+ // - above Active Descendent
+ &[${isActiveDescendantAttribute}] ${DividedContent}::before,
+ // - below Active Descendent
+ [${isActiveDescendantAttribute}] + & ${DividedContent}::before {
+ // '!important' because all the ':not's above give higher specificity
+ border-color: transparent !important;
+ }
+
+ // Active Descendant
+ &[${isActiveDescendantAttribute}='${activeDescendantActivatedDirectly}'] {
+ background: ${({variant, item}) => getItemVariant(variant, item?.disabled).focusBg};
+ }
+ &[${isActiveDescendantAttribute}='${activeDescendantActivatedIndirectly}'] {
+ background: ${({variant, item}) => getItemVariant(variant, item?.disabled).hoverBg};
+ }
+
+ &:focus {
+ background: ${({variant, item}) => getItemVariant(variant, item?.disabled).focusBg};
+ outline: none;
+ }
+
+ &:active {
+ background: ${({variant, item}) => getItemVariant(variant, item?.disabled).focusBg};
+ }
+
+ ${sx}
+`
+
+export const TextContainer = styled.span<{
+ dangerouslySetInnerHtml?: React.DOMAttributes['dangerouslySetInnerHTML']
+}>``
+
+const BaseVisualContainer = styled.div<{variant?: ItemProps['variant']; disabled?: boolean}>`
+ // Match visual height to adjacent text line height.
+ // TODO: When rem-based spacing on a 4px scale lands, replace
+ // hardcoded '20px' with '${get('space.s20')}'.
+ height: 20px;
+ width: ${get('space.3')};
+ margin-right: ${get('space.2')};
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-shrink: 0;
+`
+
+const ColoredVisualContainer = styled(BaseVisualContainer)`
+ svg {
+ fill: ${({variant, disabled}) => getItemVariant(variant, disabled).iconColor};
+ font-size: ${get('fontSizes.0')};
+ }
+`
+
+const LeadingVisualContainer = styled(ColoredVisualContainer)`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+`
+
+const TrailingContent = styled(ColoredVisualContainer)`
+ color: ${({variant, disabled}) => getItemVariant(variant, disabled).annotationColor}};
+ margin-left: ${get('space.2')};
+ margin-right: 0;
+ width: auto;
+ div:nth-child(2) {
+ margin-left: ${get('space.2')};
+ }
+`
+
+const DescriptionContainer = styled.span`
+ color: ${get('colors.fg.muted')};
+ font-size: ${get('fontSizes.0')};
+ // TODO: When rem-based spacing on a 4px scale lands, replace
+ // hardcoded '16px' with '${get('lh-12')}'.
+ line-height: 16px;
+ margin-left: var(--description-container-margin-left);
+ min-width: 0;
+ flex-grow: 1;
+ flex-basis: var(--description-container-flex-basis);
+`
+
+const MultiSelectIcon = styled.svg<{selected?: boolean}>`
+ rect {
+ fill: ${({selected}) => (selected ? get('colors.accent.fg') : get('colors.canvas.default'))};
+ stroke: ${({selected}) => (selected ? get('colors.accent.fg') : get('colors.border.default'))};
+ shape-rendering: auto; // this is a workaround to override global style in github/github, see primer/react#1666
+ }
+ path {
+ fill: ${get('colors.fg.onEmphasis')};
+ boxshadow: ${get('shadow.small')};
+ opacity: ${({selected}) => (selected ? 1 : 0)};
+ }
+`
+
+/**
+ * An actionable or selectable `Item` with an optional icon and description.
+ */
+export const Item = React.forwardRef((itemProps, ref) => {
+ const {
+ as: Component,
+ text,
+ description,
+ descriptionVariant = 'inline',
+ selected,
+ selectionVariant,
+ leadingVisual: LeadingVisual,
+ trailingIcon: TrailingIcon,
+ trailingVisual: TrailingVisual,
+ trailingText,
+ variant = 'default',
+ showDivider,
+ disabled,
+ onAction,
+ onKeyPress,
+ children,
+ onClick,
+ id,
+ ...props
+ } = itemProps
+
+ const labelId = useSSRSafeId()
+ const descriptionId = useSSRSafeId()
+
+ const keyPressHandler = useCallback(
+ event => {
+ if (disabled) {
+ return
+ }
+ onKeyPress?.(event)
+
+ if (!event.defaultPrevented && [' ', 'Enter'].includes(event.key)) {
+ onAction?.(itemProps, event)
+ }
+ },
+ [onAction, disabled, itemProps, onKeyPress]
+ )
+
+ const clickHandler = useCallback(
+ event => {
+ if (disabled) {
+ return
+ }
+ onClick?.(event)
+ if (!event.defaultPrevented) {
+ onAction?.(itemProps, event)
+ }
+ },
+ [onAction, disabled, itemProps, onClick]
+ )
+
+ const {theme} = useTheme()
+
+ return (
+
+ {!!selected === selected && (
+
+ {selectionVariant === 'multiple' ? (
+ <>
+ {/**
+ * we use a svg instead of an input because there should not
+ * be an interactive element inside an option
+ * svg copied from primer/css
+ */}
+
+
+
+
+ >
+ ) : (
+ selected &&
+ )}
+
+ )}
+ {LeadingVisual && (
+
+
+
+ )}
+
+
+ {children}
+ {text ? {text} : null}
+ {description ? (
+
+ {descriptionVariant === 'block' ? (
+ description
+ ) : (
+
+ {description}
+
+ )}
+
+ ) : null}
+
+ {/* backward compatibility: prefer TrailingVisual but fallback to TrailingIcon */}
+ {TrailingVisual ? (
+
+ {typeof TrailingVisual === 'function' ? : TrailingVisual}
+
+ ) : TrailingIcon || trailingText ? (
+
+ {trailingText}
+ {TrailingIcon && }
+
+ ) : null}
+
+
+ )
+}) as PolymorphicForwardRefComponent<'div', ItemProps>
+
+Item.displayName = 'ActionList.Item'
diff --git a/src/deprecated/ActionList/List.tsx b/src/deprecated/ActionList/List.tsx
new file mode 100644
index 00000000000..0af5190d7c1
--- /dev/null
+++ b/src/deprecated/ActionList/List.tsx
@@ -0,0 +1,258 @@
+import React, {Key} from 'react'
+import type {AriaRole} from '../../utils/types'
+import {Group, GroupProps} from './Group'
+import {Item, ItemProps} from './Item'
+import {Divider} from './Divider'
+import styled from 'styled-components'
+import {get} from '../../constants'
+import {SystemCssProperties} from '@styled-system/css'
+import {hasActiveDescendantAttribute} from '@primer/behaviors'
+import {Merge} from '../../utils/types/Merge'
+
+type RenderItemFn = (props: ItemProps) => React.ReactElement
+
+export type ItemInput =
+ | Merge, ItemProps>
+ | ((Partial & {renderItem: RenderItemFn}) & {key?: Key})
+
+/**
+ * Contract for props passed to the `List` component.
+ */
+export interface ListPropsBase {
+ /**
+ * A collection of `Item` props and `Item`-level custom `Item` renderers.
+ */
+ items: ItemInput[]
+
+ /**
+ * The ARIA role describing the function of `List` component. `listbox` is a common value.
+ */
+ role?: AriaRole
+
+ /**
+ * id to attach to the base DOM node of the list
+ */
+ id?: string
+
+ /**
+ * A `List`-level custom `Item` renderer. Every `Item` within this `List`
+ * without a `Group`-level or `Item`-level custom `Item` renderer will be
+ * rendered using this function component.
+ */
+ renderItem?: RenderItemFn
+
+ /**
+ * A `List`-level custom `Group` renderer. Every `Group` within this `List`
+ * without a `Group`-level custom `Item` renderer will be rendered using
+ * this function component.
+ */
+ renderGroup?: typeof Group
+
+ /**
+ * Style variations. Usage is discretionary.
+ *
+ * - `"inset"` - `List` children are offset (vertically and horizontally) from `List`’s edges
+ * - `"full"` - `List` children are flush (vertically and horizontally) with `List` edges
+ */
+ variant?: 'inset' | 'full'
+
+ /**
+ * For `Item`s which can be selected, whether `multiple` `Item`s or a `single` `Item` can be selected
+ */
+ selectionVariant?: 'single' | 'multiple'
+
+ /**
+ * Whether to display a divider above each `Item` in this `List` when it does not follow a `Header` or `Divider`.
+ */
+ showItemDividers?: boolean
+}
+
+/**
+ * Contract for props passed to the `List` component, when its `Item`s are collected in `Group`s.
+ */
+export interface GroupedListProps extends ListPropsBase {
+ /**
+ * A collection of `Group` props (except `items`), plus a unique group identifier
+ * and `Group`-level custom `Item` or `Group` renderers.
+ */
+ groupMetadata: ((
+ | Omit
+ | Omit & {renderItem?: RenderItemFn; renderGroup?: typeof Group}, 'items'>
+ ) & {groupId: string})[]
+
+ /**
+ * A collection of `Item` props, plus associated group identifiers
+ * and `Item`-level custom `Item` renderers.
+ */
+ items: ((ItemProps | (Partial & {renderItem: RenderItemFn})) & {groupId: string})[]
+}
+
+/**
+ * Asserts that the given value fulfills the `GroupedListProps` contract.
+ * @param props A value which fulfills either the `ListPropsBase` or the `GroupedListProps` contract.
+ */
+function isGroupedListProps(props: ListProps): props is GroupedListProps {
+ return 'groupMetadata' in props
+}
+
+/**
+ * Contract for props passed to the `List` component.
+ */
+export type ListProps = ListPropsBase | GroupedListProps
+
+const StyledList = styled.div`
+ font-size: ${get('fontSizes.1')};
+ /* 14px font-size * 1.428571429 = 20px line height
+ *
+ * TODO: When rem-based spacing on a 4px scale lands, replace
+ * hardcoded '20px'
+ */
+ line-height: 20px;
+
+ &[${hasActiveDescendantAttribute}], &:focus-within {
+ --item-hover-bg-override: none;
+ --item-hover-divider-border-color-override: ${get('colors.border.muted')};
+ }
+`
+
+/**
+ * Returns `sx` prop values for `List` children matching the given `List` style variation.
+ * @param variant `List` style variation.
+ */
+function useListVariant(variant: ListProps['variant'] = 'inset'): {
+ firstGroupStyle?: SystemCssProperties
+ lastGroupStyle?: SystemCssProperties
+ headerStyle?: SystemCssProperties
+ itemStyle?: SystemCssProperties
+} {
+ switch (variant) {
+ case 'full':
+ return {
+ headerStyle: {paddingX: get('space.2')},
+ itemStyle: {borderRadius: 0}
+ }
+ default:
+ return {
+ firstGroupStyle: {marginTop: get('space.2')},
+ lastGroupStyle: {marginBottom: get('space.2')},
+ itemStyle: {marginX: get('space.2')}
+ }
+ }
+}
+
+/**
+ * Lists `Item`s, either grouped or ungrouped, with a `Divider` between each `Group`.
+ */
+export const List = React.forwardRef((props, forwardedRef): JSX.Element => {
+ // Get `sx` prop values for `List` children matching the given `List` style variation.
+ const {firstGroupStyle, lastGroupStyle, headerStyle, itemStyle} = useListVariant(props.variant)
+
+ /**
+ * Render a `Group` using the first of the following renderers that is defined:
+ * A `Group`-level or `List`-level custom `Group` renderer, or
+ * the default `Group` renderer.
+ */
+ const renderGroup = (
+ groupProps: GroupProps | (Partial & {renderItem?: typeof Item; renderGroup?: typeof Group})
+ ) => {
+ const GroupComponent = (('renderGroup' in groupProps && groupProps.renderGroup) ?? props.renderGroup) || Group
+ return
+ }
+
+ /**
+ * Render an `Item` using the first of the following renderers that is defined:
+ * An `Item`-level, `Group`-level, or `List`-level custom `Item` renderer,
+ * or the default `Item` renderer.
+ */
+ const renderItem = (itemProps: ItemInput, item: ItemInput, itemIndex: number) => {
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ const ItemComponent = ('renderItem' in itemProps && itemProps.renderItem) || props.renderItem || Item
+ const key = ('key' in itemProps ? itemProps.key : undefined) ?? itemProps.id?.toString() ?? itemIndex.toString()
+ return (
+
+ )
+ }
+
+ /**
+ * An array of `Group`s, each with an associated `Header` and with an array of `Item`s belonging to that `Group`.
+ */
+ let groups: (GroupProps | (Partial & {renderItem?: typeof Item; renderGroup?: typeof Group}))[] = []
+
+ // Collect rendered `Item`s into `Group`s, avoiding excess iteration over the lists of `items` and `groupMetadata`:
+ if (!isGroupedListProps(props)) {
+ // When no `groupMetadata`s is provided, collect rendered `Item`s into a single anonymous `Group`.
+ groups = [{items: props.items.map((item, index) => renderItem(item, item, index)), groupId: '0'}]
+ } else {
+ // When `groupMetadata` is provided, collect rendered `Item`s into their associated `Group`s.
+
+ /**
+ * A map of group identifiers to `Group`s, each with an associated array of `Item`s belonging to that `Group`.
+ */
+ const groupMap = props.groupMetadata.reduce(
+ (groupAccumulator, groupMetadata) => groupAccumulator.set(groupMetadata.groupId, groupMetadata),
+ new Map & {renderItem?: typeof Item; renderGroup?: typeof Group})>()
+ )
+
+ for (const itemProps of props.items) {
+ // Look up the group associated with the current item.
+ const group = groupMap.get(itemProps.groupId)
+ const itemIndex = group?.items?.length ?? 0
+
+ // Upsert the group to include the current item (rendered).
+ groupMap.set(itemProps.groupId, {
+ ...group,
+ items: [
+ ...(group?.items ?? []),
+ renderItem(
+ {
+ showDivider: group?.showItemDividers,
+ ...(group && 'renderItem' in group && {renderItem: group.renderItem}),
+ ...itemProps
+ },
+ itemProps,
+ itemIndex
+ )
+ ]
+ })
+ }
+
+ groups = [...groupMap.values()]
+ }
+
+ return (
+
+ {groups.map(({header, ...groupProps}, index) => {
+ const hasFilledHeader = header?.variant === 'filled'
+ const shouldShowDivider = index > 0 && !hasFilledHeader
+ return (
+
+ {shouldShowDivider ? : null}
+ {renderGroup({
+ sx: {
+ ...(index === 0 && firstGroupStyle),
+ ...(index === groups.length - 1 && lastGroupStyle),
+ ...(index > 0 && !shouldShowDivider && {mt: 2})
+ },
+ ...(header && {
+ header: {
+ ...header,
+ sx: {...headerStyle, ...header.sx}
+ }
+ }),
+ ...groupProps
+ })}
+
+ )
+ })}
+
+ )
+})
+
+List.displayName = 'ActionList'
diff --git a/src/deprecated/ActionList/index.ts b/src/deprecated/ActionList/index.ts
new file mode 100644
index 00000000000..ab78d4249fc
--- /dev/null
+++ b/src/deprecated/ActionList/index.ts
@@ -0,0 +1,21 @@
+import {List} from './List'
+import {Group} from './Group'
+import {Item} from './Item'
+import {Divider} from './Divider'
+export type {ListProps as ActionListProps} from './List'
+export type {GroupProps} from './Group'
+export type {ItemProps} from './Item'
+
+/**
+ * @deprecated Use ActionList with composable API instead. See https://primer.style/react/ActionList for more details.
+ */
+export const ActionList = Object.assign(List, {
+ /** Collects related `Items` in an `ActionList`. */
+ Group,
+
+ /** An actionable or selectable `Item` with an optional icon and description. */
+ Item,
+
+ /** Visually separates `Item`s or `Group`s in an `ActionList`. */
+ Divider
+})
diff --git a/src/deprecated/index.ts b/src/deprecated/index.ts
new file mode 100644
index 00000000000..91859cd15d7
--- /dev/null
+++ b/src/deprecated/index.ts
@@ -0,0 +1,41 @@
+/** This is the place where we keep components that are deprecated.
+ * We don't recommend using it in production.
+ * If you already use them, please move to the suggested alternative components
+ *
+ * But, they are published on npm and you can import them.
+ * example: import {FormGroup} from '@primer/react/deprecated
+ */
+
+export {default as BorderBox} from '../BorderBox'
+export type {BorderBoxProps} from '../BorderBox'
+export {default as ChoiceFieldset, Item} from '../ChoiceFieldset'
+export {default as ChoiceInputField} from '../ChoiceInputField'
+export {default as Flex} from '../Flex'
+export type {FlexProps} from '../Flex'
+export {default as Grid} from '../Grid'
+export type {GridProps} from '../Grid'
+export {default as Position, Absolute, Fixed, Relative, Sticky} from '../Position'
+export type {PositionProps, AbsoluteProps, FixedProps, RelativeProps, StickyProps} from '../Position'
+export {default as Dropdown} from '../Dropdown'
+export type {
+ DropdownProps,
+ DropdownCaretProps,
+ DropdownButtonProps,
+ DropdownItemProps,
+ DropdownMenuProps
+} from '../Dropdown'
+export {default as SelectMenu} from '../SelectMenu'
+export type {
+ SelectMenuProps,
+ SelectMenuDividerProps,
+ SelectMenuFilterProps,
+ SelectMenuFooterProps,
+ SelectMenuItemProps,
+ SelectMenuListProps,
+ SelectMenuModalProps,
+ SelectMenuTabsProps,
+ SelectMenuHeaderProps,
+ SelectMenuTabProps,
+ SelectMenuTabPanelProps,
+ SelectMenuLoadingAnimationProps
+} from '../SelectMenu'
diff --git a/src/drafts/index.ts b/src/drafts/index.ts
index 291e56dafbb..1c27dea498a 100644
--- a/src/drafts/index.ts
+++ b/src/drafts/index.ts
@@ -6,9 +6,7 @@
*/
// Components
-export * from '../ActionList2'
export * from '../Button2'
export * from '../ActionMenu2'
export * from '../DropdownMenu2'
export * from '../Label2'
-export * from '../PageLayout'
diff --git a/src/hooks/useRenderForcingRef.ts b/src/hooks/useRenderForcingRef.ts
index 60ddeb56fd1..3d095934a97 100644
--- a/src/hooks/useRenderForcingRef.ts
+++ b/src/hooks/useRenderForcingRef.ts
@@ -6,8 +6,8 @@ import {MutableRefObject, RefObject, useCallback, useRef, useState} from 'react'
* In these situations, we need to force a re-render, which is done here by the useState hook.
* @type TRef The type of the RefObject which should be created.
*/
-export function useRenderForcingRef() {
- const [refCurrent, setRefCurrent] = useState(null)
+export function useRenderForcingRef(value?: TRef) {
+ const [refCurrent, setRefCurrent] = useState(value || null)
const ref = useRef(null) as MutableRefObject
ref.current = refCurrent
diff --git a/src/index.ts b/src/index.ts
index 6a5c26f0c47..252cbd8f1e4 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -6,16 +6,8 @@ export {default as ThemeProvider, useTheme, useColorSchemeVar} from './ThemeProv
export type {ThemeProviderProps} from './ThemeProvider'
// Layout
-export {default as BorderBox} from './BorderBox'
-export type {BorderBoxProps} from './BorderBox'
export {default as Box} from './Box'
export type {BoxProps} from './Box'
-export {default as Flex} from './Flex'
-export type {FlexProps} from './Flex'
-export {default as Grid} from './Grid'
-export type {GridProps} from './Grid'
-export {default as Position, Absolute, Fixed, Relative, Sticky} from './Position'
-export type {PositionProps, AbsoluteProps, FixedProps, RelativeProps, StickyProps} from './Position'
// Hooks
export {default as useDetails} from './hooks/useDetails'
@@ -30,6 +22,14 @@ export {useConfirm} from './Dialog/ConfirmationDialog'
export {default as Radio} from './Radio'
export type {RadioProps} from './Radio'
export {ActionList} from './ActionList'
+export type {
+ ActionListProps,
+ ActionListGroupProps,
+ ActionListItemProps,
+ ActionListDescriptionProps,
+ ActionListLeadingVisualProps,
+ ActionListTrailingVisualProps
+} from './ActionList'
export {ActionMenu} from './ActionMenu'
export type {ActionMenuProps} from './ActionMenu'
export {default as Autocomplete} from './Autocomplete'
@@ -66,12 +66,11 @@ export type {
} from './Button'
export {default as Caret} from './Caret'
export type {CaretProps} from './Caret'
-export {default as ChoiceInputField} from './ChoiceInputField'
export {default as CircleBadge} from './CircleBadge'
export type {CircleBadgeProps, CircleBadgeIconProps} from './CircleBadge'
export {default as CircleOcticon} from './CircleOcticon'
export type {CircleOcticonProps} from './CircleOcticon'
-export {default as ChoiceFieldset, Item} from './ChoiceFieldset'
+export {default as CheckboxGroup} from './CheckboxGroup'
export {default as CounterLabel} from './CounterLabel'
export type {CounterLabelProps} from './CounterLabel'
export {default as Details} from './Details'
@@ -79,14 +78,7 @@ export type {DetailsProps} from './Details'
export {default as Dialog} from './Dialog'
export type {DialogProps, DialogHeaderProps} from './Dialog'
export {ConfirmationDialog} from './Dialog/ConfirmationDialog'
-export {default as Dropdown} from './Dropdown'
-export type {
- DropdownProps,
- DropdownCaretProps,
- DropdownButtonProps,
- DropdownItemProps,
- DropdownMenuProps
-} from './Dropdown'
+
export {DropdownButton, DropdownMenu} from './DropdownMenu'
// not exporting new DropdownMenu types yet due to conflict with Dropdown types above
// export type {DropdownButtonProps, DropdownMenuProps} from './DropdownMenu'
@@ -124,22 +116,8 @@ export type {PopoverProps, PopoverContentProps} from './Popover'
// export type {PortalProps} from './Portal'
export {default as ProgressBar} from './ProgressBar'
export type {ProgressBarProps} from './ProgressBar'
-export {default as SelectMenu} from './SelectMenu'
+export {default as RadioGroup} from './RadioGroup'
export {default as Select} from './Select'
-export type {
- SelectMenuProps,
- SelectMenuDividerProps,
- SelectMenuFilterProps,
- SelectMenuFooterProps,
- SelectMenuItemProps,
- SelectMenuListProps,
- SelectMenuModalProps,
- SelectMenuTabsProps,
- SelectMenuHeaderProps,
- SelectMenuTabProps,
- SelectMenuTabPanelProps,
- SelectMenuLoadingAnimationProps
-} from './SelectMenu'
export {SelectPanel} from './SelectPanel'
export type {SelectPanelProps} from './SelectPanel'
export {default as SideNav} from './SideNav'
diff --git a/src/stories/ActionList2/examples.stories.tsx b/src/stories/ActionList/examples.stories.tsx
similarity index 98%
rename from src/stories/ActionList2/examples.stories.tsx
rename to src/stories/ActionList/examples.stories.tsx
index bb923be9c4f..fdf306934bd 100644
--- a/src/stories/ActionList2/examples.stories.tsx
+++ b/src/stories/ActionList/examples.stories.tsx
@@ -16,8 +16,7 @@ import {
} from '@primer/octicons-react'
import {ThemeProvider} from '../..'
-import {ActionList as _ActionList} from '../../ActionList2'
-import {Header} from '../../ActionList/Header'
+import {ActionList} from '../../ActionList'
import BaseStyles from '../../BaseStyles'
import Avatar from '../../Avatar'
import TextInput from '../../TextInput'
@@ -25,12 +24,8 @@ import Spinner from '../../Spinner'
import Box from '../../Box'
import Text from '../../Text'
-const ActionList = Object.assign(_ActionList, {
- Header
-})
-
const meta: Meta = {
- title: 'Composite components/ActionList2/examples',
+ title: 'Composite components/ActionList/examples',
component: ActionList,
decorators: [
(Story: React.ComponentType): JSX.Element => (
diff --git a/src/stories/ActionList2/fixtures.stories.tsx b/src/stories/ActionList/fixtures.stories.tsx
similarity index 98%
rename from src/stories/ActionList2/fixtures.stories.tsx
rename to src/stories/ActionList/fixtures.stories.tsx
index a7fbcddd3c9..301b58684b7 100644
--- a/src/stories/ActionList2/fixtures.stories.tsx
+++ b/src/stories/ActionList/fixtures.stories.tsx
@@ -28,20 +28,15 @@ import styled from 'styled-components'
import {DndProvider, useDrag, useDrop} from 'react-dnd'
import {HTML5Backend} from 'react-dnd-html5-backend'
import {Label, ThemeProvider} from '../..'
-import {ActionList as _ActionList, ItemProps} from '../../ActionList2'
-import {Header} from '../../ActionList/Header'
+import {ActionList, ActionListItemProps} from '../../ActionList'
import BaseStyles from '../../BaseStyles'
import Avatar from '../../Avatar'
import {ButtonInvisible} from '../../Button'
import Box from '../../Box'
import {AnchoredOverlay} from '../../AnchoredOverlay'
-const ActionList = Object.assign(_ActionList, {
- Header
-})
-
const meta: Meta = {
- title: 'Composite components/ActionList2/fixtures',
+ title: 'Composite components/ActionList/fixtures',
component: ActionList,
decorators: [
(Story: React.ComponentType): JSX.Element => (
@@ -476,14 +471,14 @@ export function LinkItemStory(): JSX.Element {
as ReactRouterLink
-
+
@@ -491,7 +486,7 @@ export function LinkItemStory(): JSX.Element {
NextJS style Link
-
+
@@ -995,8 +990,8 @@ MemexSortable.storyName = 'Memex Sortable List'
type SortableItemProps = {
option: Option
- role: ItemProps['role']
- onSelect: ItemProps['onSelect']
+ role: ActionListItemProps['role']
+ onSelect: ActionListItemProps['onSelect']
reorder: ({optionToMove, moveAfterOption}: {optionToMove: Option; moveAfterOption: Option}) => void
}
const SortableItem: React.FC = ({option, role, onSelect, reorder}) => {
diff --git a/src/stories/ActionMenu.stories.tsx b/src/stories/ActionMenu.stories.tsx
index 5d9165b5969..7f421ce2a46 100644
--- a/src/stories/ActionMenu.stories.tsx
+++ b/src/stories/ActionMenu.stories.tsx
@@ -17,7 +17,7 @@ import {ThemeProvider} from '..'
import {ActionMenu, ActionMenuProps} from '../ActionMenu'
import Link, {LinkProps} from '../Link'
import Button from '../Button'
-import {ActionList, ItemProps} from '../ActionList'
+import {ActionList, ItemProps} from '../deprecated/ActionList'
import BaseStyles from '../BaseStyles'
import {DropdownButton} from '../DropdownMenu'
diff --git a/src/stories/ActionMenu2/examples.stories.tsx b/src/stories/ActionMenu2/examples.stories.tsx
index 8f3592d316b..3f363b4a2bc 100644
--- a/src/stories/ActionMenu2/examples.stories.tsx
+++ b/src/stories/ActionMenu2/examples.stories.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import {Meta} from '@storybook/react'
-import {ThemeProvider, BaseStyles, Box, Text, Avatar} from '../..'
-import {ActionMenu, ActionList} from '../../drafts'
+import {ThemeProvider, BaseStyles, Box, Text, Avatar, ActionList} from '../..'
+import {ActionMenu} from '../../drafts'
import {
GearIcon,
MilestoneIcon,
diff --git a/src/stories/ActionMenu2/fixtures.stories.tsx b/src/stories/ActionMenu2/fixtures.stories.tsx
index bc867f300f4..d59b9efdd3f 100644
--- a/src/stories/ActionMenu2/fixtures.stories.tsx
+++ b/src/stories/ActionMenu2/fixtures.stories.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import {Meta} from '@storybook/react'
-import {ThemeProvider, BaseStyles, Box, Text, TextInput, StyledOcticon, FormGroup} from '../..'
-import {ActionMenu, ActionList, Button, IconButton} from '../../drafts'
+import {ThemeProvider, BaseStyles, Box, Text, TextInput, StyledOcticon, FormGroup, ActionList} from '../..'
+import {ActionMenu, Button, IconButton} from '../../drafts'
import {
ServerIcon,
PlusCircleIcon,
diff --git a/src/stories/Checkbox.stories.tsx b/src/stories/Checkbox.stories.tsx
index 924bc188067..4f9b911ba4a 100644
--- a/src/stories/Checkbox.stories.tsx
+++ b/src/stories/Checkbox.stories.tsx
@@ -49,7 +49,7 @@ const StyledSubLabel = styled(Text)`
font-size: 13px;
`
-export const Default = (args: CheckboxProps) => {
+export const Default = ({value: _value, ...args}: CheckboxProps) => {
const [isChecked, setChecked] = useState(false)
const handleChange = (event: React.ChangeEvent) => {
@@ -60,28 +60,28 @@ export const Default = (args: CheckboxProps) => {
return (
<>
-
+
Default checkbox
controlled
-
+
Always checked
checked="true"
-
+
Always unchecked
checked="false"
-
+
Inactive
disabled="true"
@@ -91,7 +91,7 @@ export const Default = (args: CheckboxProps) => {
)
}
-export const Uncontrolled = (args: CheckboxProps) => {
+export const Uncontrolled = ({value: _value, ...args}: CheckboxProps) => {
const checkboxRef = useRef(null)
useLayoutEffect(() => {
@@ -102,7 +102,7 @@ export const Uncontrolled = (args: CheckboxProps) => {
return (
-
+
Uncontrolled checkbox
Checked by default
@@ -111,7 +111,7 @@ export const Uncontrolled = (args: CheckboxProps) => {
)
}
-export const Indeterminate = (args: CheckboxProps) => {
+export const Indeterminate = ({value: _value, ...args}: CheckboxProps) => {
const [checkboxes, setCheckboxes] = useState([false, false, false, false])
const handleChange = (_: React.ChangeEvent, index: number) => {
@@ -133,6 +133,7 @@ export const Indeterminate = (args: CheckboxProps) => {
<>
{
{checkboxes.map((field, index) => (
handleChange(event, index)}
diff --git a/src/stories/CheckboxGroup/examples.stories.tsx b/src/stories/CheckboxGroup/examples.stories.tsx
new file mode 100644
index 00000000000..97d41cecbfa
--- /dev/null
+++ b/src/stories/CheckboxGroup/examples.stories.tsx
@@ -0,0 +1,69 @@
+import React from 'react'
+import {Meta} from '@storybook/react'
+import {BaseStyles, Checkbox, CheckboxGroup, FormControl, ThemeProvider} from '../../'
+import {ComponentProps} from '../../utils/types'
+
+type Args = ComponentProps
+
+export default {
+ title: 'Forms/CheckboxGroup/examples',
+ component: CheckboxGroup,
+ argTypes: {
+ disabled: {
+ defaultValue: false
+ },
+ required: {
+ defaultValue: false
+ }
+ },
+ parameters: {controls: {exclude: ['aria-labelledby', 'id', 'onChange', 'sx']}},
+ decorators: [
+ Story => {
+ return (
+
+
+
+
+
+ )
+ }
+ ]
+} as Meta
+
+export const Basic = (args: Args) => (
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+)
+
+export const WithCaptionAndValidation = (args: Args) => (
+
+ Choices
+ You can pick any or all of these choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+ Your choices are wrong
+
+)
diff --git a/src/stories/CheckboxGroup/fixtures.stories.tsx b/src/stories/CheckboxGroup/fixtures.stories.tsx
new file mode 100644
index 00000000000..e80080b10e3
--- /dev/null
+++ b/src/stories/CheckboxGroup/fixtures.stories.tsx
@@ -0,0 +1,79 @@
+import React from 'react'
+import {Meta} from '@storybook/react'
+import {BaseStyles, Box, Checkbox, CheckboxGroup, FormControl, ThemeProvider} from '../../'
+import {ComponentProps} from '../../utils/types'
+
+type Args = ComponentProps
+
+export default {
+ title: 'Forms/CheckboxGroup/fixtures',
+ component: CheckboxGroup,
+ argTypes: {
+ disabled: {
+ defaultValue: false
+ },
+ required: {
+ defaultValue: false
+ }
+ },
+ parameters: {controls: {exclude: ['aria-labelledby', 'id', 'onChange', 'sx']}},
+ decorators: [
+ Story => {
+ return (
+
+
+
+
+
+ )
+ }
+ ]
+} as Meta
+
+export const WithExternalLabel = (args: Args) => (
+ <>
+
+ Choices
+
+
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ >
+)
+
+export const WithHiddenLabel = (args: Args) => (
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+)
diff --git a/src/stories/ChoiceFieldset.stories.tsx b/src/stories/ChoiceFieldset.stories.tsx
deleted file mode 100644
index 747726cf188..00000000000
--- a/src/stories/ChoiceFieldset.stories.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import React from 'react'
-import {Meta} from '@storybook/react'
-import {MarkGithubIcon} from '@primer/octicons-react'
-import {BaseStyles, Box, ThemeProvider} from '..'
-import ChoiceFieldset from '../ChoiceFieldset'
-import Item from '../ChoiceFieldset/ChoiceFieldsetListItem'
-import {ComponentProps} from '../utils/types'
-import {ChoiceFieldsetListProps} from '../ChoiceFieldset/ChoiceFieldsetList'
-
-type Args = ComponentProps & ChoiceFieldsetListProps
-
-export default {
- title: 'Forms/ChoiceFieldset',
- component: ChoiceFieldset,
- argTypes: {
- disabled: {
- defaultValue: false
- },
- required: {
- defaultValue: false
- },
- selectionVariant: {
- options: ['single', 'multiple'],
- control: {type: 'radio'},
- defaultValue: 'single'
- },
- validationResult: {
- options: ['invalidChoice', 'validChoice'],
- control: {type: 'radio'},
- defaultValue: 'validChoice'
- },
- validationMap: {
- defaultValue: {
- invalidChoice: 'error',
- validChoice: 'success'
- }
- }
- },
- parameters: {controls: {exclude: ['id', 'validationMap', 'validationResult', 'name', 'onSelect']}},
- decorators: [
- Story => {
- return (
-
-
-
-
-
- )
- }
- ]
-} as Meta
-
-export const RadioGroup = ({selectionVariant, ...restArgs}: Args) => (
-
- Legend
-
- Label one
- Label two
-
-
-)
-RadioGroup.storyName = 'Radio group (default)'
-
-export const CheckboxGroup = ({selectionVariant: _selectionVariant, ...restArgs}: Args) => (
-
- Legend
-
- Label one
- Label two
-
-
-)
-CheckboxGroup.parameters = {controls: {exclude: ['id', 'selectionVariant']}}
-CheckboxGroup.storyName = 'Checkbox group'
-
-export const WithOnSelectHandler = ({selectionVariant: _selectionVariant, ...restArgs}: Args) => {
- const [selectedChoices, setSelectedChoices] = React.useState(['one'])
-
- return (
-
-
- {
- setSelectedChoices(selectedValues)
- }}
- selected={selectedChoices}
- {...restArgs}
- >
- Legend
-
- Label one
- Label two
-
-
-
-
- The values of your selected choices:
-
- {selectedChoices.map(choice => (
- {choice}
- ))}
-
-
-
- )
-}
-
-WithOnSelectHandler.storyName = 'With onSelect handler'
-
-export const Required = ({selectionVariant, ...restArgs}: Args) => (
-
- Legend
-
- Label one
- Label two
-
-
-)
-Required.parameters = {controls: {exclude: ['required']}}
-
-export const Disabled = ({selectionVariant, ...restArgs}: Args) => (
-
- Legend
-
- Label one
- Label two
-
-
-)
-Disabled.parameters = {controls: {exclude: ['disabled']}}
-
-export const WithVisuallyHiddenLegend = ({selectionVariant, ...restArgs}: Args) => (
-
- Legend
-
- Label one
- Label two
-
-
-)
-
-export const WithDescription = ({selectionVariant, ...restArgs}: Args) => (
-
- Legend
- Hint: any selection is valid
-
- Label one
- Label two
-
-
-)
-
-export const WithValidation = ({selectionVariant, ...restArgs}: Args) => (
-
- Legend
-
- Label one
- Label two
-
- You made the right selection
- You made the wrong selection
-
-)
-WithValidation.parameters = {controls: {exclude: ['id']}}
-
-export const WithLeadingVisual = ({selectionVariant, ...restArgs}: Args) => (
-
- Legend
-
-
-
-
-
- Label one
-
-
-
-
-
- Label two
-
-
-
-)
-WithLeadingVisual.storyName = 'Choices with a LeadingVisual'
-
-export const WithChoiceCaptions = ({selectionVariant, ...restArgs}: Args) => (
-
- Legend
-
-
- Label one
- Caption
-
-
- Label two
- Caption
-
-
-
-)
-WithChoiceCaptions.storyName = 'Choices with a Caption'
diff --git a/src/stories/DropdownMenu.stories.tsx b/src/stories/DropdownMenu.stories.tsx
index 1f524c0de8d..13812de3b11 100644
--- a/src/stories/DropdownMenu.stories.tsx
+++ b/src/stories/DropdownMenu.stories.tsx
@@ -1,7 +1,7 @@
import {Meta} from '@storybook/react'
import React from 'react'
import {theme, ThemeProvider} from '..'
-import {ItemInput} from '../ActionList/List'
+import {ItemInput} from '../deprecated/ActionList/List'
import BaseStyles from '../BaseStyles'
import Box from '../Box'
import {DropdownMenu, DropdownButton} from '../DropdownMenu'
diff --git a/src/stories/DropdownMenu2/examples.stories.tsx b/src/stories/DropdownMenu2/examples.stories.tsx
index a461dbbf47f..c72878f65e1 100644
--- a/src/stories/DropdownMenu2/examples.stories.tsx
+++ b/src/stories/DropdownMenu2/examples.stories.tsx
@@ -3,7 +3,7 @@ import {Meta} from '@storybook/react'
import {ThemeProvider} from '../..'
import BaseStyles from '../../BaseStyles'
import {DropdownMenu} from '../../DropdownMenu2'
-import {ActionList} from '../../ActionList2'
+import {ActionList} from '../../ActionList'
import Box from '../../Box'
import Text from '../../Text'
import {
diff --git a/src/stories/DropdownMenu2/fixtures.stories.tsx b/src/stories/DropdownMenu2/fixtures.stories.tsx
index d39782e46c1..b0c462b9b17 100644
--- a/src/stories/DropdownMenu2/fixtures.stories.tsx
+++ b/src/stories/DropdownMenu2/fixtures.stories.tsx
@@ -3,7 +3,7 @@ import {Meta} from '@storybook/react'
import {ThemeProvider} from '../..'
import BaseStyles from '../../BaseStyles'
import {DropdownMenu} from '../../DropdownMenu2'
-import {ActionList} from '../../ActionList2'
+import {ActionList} from '../../ActionList'
import {Button} from '../../Button2'
import Box from '../../Box'
import Text from '../../Text'
diff --git a/src/stories/Overlay.stories.tsx b/src/stories/Overlay.stories.tsx
index dd635c6338a..5b4d69802fe 100644
--- a/src/stories/Overlay.stories.tsx
+++ b/src/stories/Overlay.stories.tsx
@@ -17,14 +17,14 @@ import {
Checkbox,
ChoiceInputField,
TextInput,
- ActionList,
Link,
- Label
+ Label,
+ ActionList
} from '..'
import type {AnchorSide} from '@primer/behaviors'
import {DropdownMenu, DropdownButton} from '../DropdownMenu'
-import {ActionMenu, ActionList as ActionList2} from '../drafts'
-import {ItemInput} from '../ActionList/List'
+import {ActionMenu} from '../drafts'
+import {ItemInput} from '../deprecated/ActionList/List'
export default {
title: 'Internal components/Overlay',
@@ -367,13 +367,13 @@ export const MemexNestedOverlays = () => {
{duration}
-
+
{durations.map(item => (
- setDuration(item)}>
+ setDuration(item)}>
{item}
-
+
))}
-
+
diff --git a/src/stories/RadioGroup/examples.stories.tsx b/src/stories/RadioGroup/examples.stories.tsx
new file mode 100644
index 00000000000..2554583163a
--- /dev/null
+++ b/src/stories/RadioGroup/examples.stories.tsx
@@ -0,0 +1,69 @@
+import React from 'react'
+import {Meta} from '@storybook/react'
+import {BaseStyles, Radio, RadioGroup, FormControl, ThemeProvider} from '../../'
+import {ComponentProps} from '../../utils/types'
+
+type Args = ComponentProps
+
+export default {
+ title: 'Forms/RadioGroup/examples',
+ component: RadioGroup,
+ argTypes: {
+ disabled: {
+ defaultValue: false
+ },
+ required: {
+ defaultValue: false
+ }
+ },
+ parameters: {controls: {exclude: ['aria-labelledby', 'id', 'name', 'onChange', 'sx']}},
+ decorators: [
+ Story => {
+ return (
+
+
+
+
+
+ )
+ }
+ ]
+} as Meta
+
+export const Basic = ({name: _name, ...args}: Args) => (
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+)
+
+export const WithCaptionAndValidation = ({name: _name, ...args}: Args) => (
+
+ Choices
+ You can pick any or all of these choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+ Your choices are wrong
+
+)
diff --git a/src/stories/RadioGroup/fixtures.stories.tsx b/src/stories/RadioGroup/fixtures.stories.tsx
new file mode 100644
index 00000000000..cb6b2b62d36
--- /dev/null
+++ b/src/stories/RadioGroup/fixtures.stories.tsx
@@ -0,0 +1,79 @@
+import React from 'react'
+import {Meta} from '@storybook/react'
+import {BaseStyles, Box, Radio, RadioGroup, FormControl, ThemeProvider} from '../../'
+import {ComponentProps} from '../../utils/types'
+
+type Args = ComponentProps
+
+export default {
+ title: 'Forms/RadioGroup/fixtures',
+ component: RadioGroup,
+ argTypes: {
+ disabled: {
+ defaultValue: false
+ },
+ required: {
+ defaultValue: false
+ }
+ },
+ parameters: {controls: {exclude: ['aria-labelledby', 'id', 'name', 'onChange', 'sx']}},
+ decorators: [
+ Story => {
+ return (
+
+
+
+
+
+ )
+ }
+ ]
+} as Meta
+
+export const WithExternalLabel = ({name: _name, ...args}: Args) => (
+ <>
+
+ Choices
+
+
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+ >
+)
+
+export const WithHiddenLabel = ({name: _name, ...args}: Args) => (
+
+ Choices
+
+
+ Choice one
+
+
+
+ Choice two
+
+
+
+ Choice three
+
+
+)
diff --git a/src/stories/SelectPanel.stories.tsx b/src/stories/SelectPanel.stories.tsx
index 31bd00556fd..6978e4ac174 100644
--- a/src/stories/SelectPanel.stories.tsx
+++ b/src/stories/SelectPanel.stories.tsx
@@ -2,7 +2,7 @@ import type {OverlayProps} from '../Overlay'
import {Meta} from '@storybook/react'
import React, {useRef, useState} from 'react'
import {theme, ThemeProvider} from '..'
-import {ItemInput} from '../ActionList/List'
+import {ItemInput} from '../deprecated/ActionList/List'
import BaseStyles from '../BaseStyles'
import {DropdownButton} from '../DropdownMenu'
import {SelectPanel} from '../SelectPanel'
diff --git a/src/stories/ActionList.stories.tsx b/src/stories/deprecated/ActionList.stories.tsx
similarity index 97%
rename from src/stories/ActionList.stories.tsx
rename to src/stories/deprecated/ActionList.stories.tsx
index 46e21e2c660..615511ac43d 100644
--- a/src/stories/ActionList.stories.tsx
+++ b/src/stories/deprecated/ActionList.stories.tsx
@@ -14,18 +14,18 @@ import {
import {Meta} from '@storybook/react'
import React, {forwardRef} from 'react'
import styled from 'styled-components'
-import {Label, ThemeProvider} from '..'
-import {ActionList as _ActionList} from '../ActionList'
-import {Header} from '../ActionList/Header'
-import BaseStyles from '../BaseStyles'
-import sx from '../sx'
+import {Label, ThemeProvider} from '../..'
+import {ActionList as _ActionList} from '../../deprecated/ActionList'
+import {Header} from '../../deprecated/ActionList/Header'
+import BaseStyles from '../../BaseStyles'
+import sx from '../../sx'
const ActionList = Object.assign(_ActionList, {
Header
})
const meta: Meta = {
- title: 'Composite components/ActionList',
+ title: 'Deprecated components/ActionList',
component: ActionList,
decorators: [
(Story: React.ComponentType): JSX.Element => (