@primer/components 33.0.0-rc.b495ba4a → 33.1.0-rc.6856bcf5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (234) hide show
  1. package/.devcontainer/devcontainer.json +1 -1
  2. package/.github/workflows/ci.yml +1 -1
  3. package/.github/workflows/release.yml +6 -27
  4. package/.github/workflows/release_canary.yml +4 -60
  5. package/.github/workflows/release_candidate.yml +5 -51
  6. package/.github/workflows/statuses.yml +32 -0
  7. package/.gitignore +1 -0
  8. package/.nvmrc +1 -1
  9. package/CHANGELOG.md +20 -0
  10. package/contributor-docs/CONTRIBUTING.md +14 -61
  11. package/dist/browser.esm.js +2 -2209
  12. package/dist/browser.esm.js.map +1 -1
  13. package/dist/browser.umd.js +2 -2209
  14. package/dist/browser.umd.js.map +1 -1
  15. package/docs/content/AnchoredOverlay.mdx +121 -1
  16. package/docs/content/Avatar.mdx +29 -14
  17. package/docs/content/AvatarPair.mdx +47 -0
  18. package/docs/content/AvatarStack.mdx +14 -6
  19. package/docs/content/Box.mdx +13 -11
  20. package/docs/content/BranchName.mdx +52 -0
  21. package/docs/content/{Breadcrumbs.md → Breadcrumbs.mdx} +21 -13
  22. package/docs/content/Link.mdx +75 -0
  23. package/docs/content/Radio.md +90 -0
  24. package/docs/content/TextInput.mdx +125 -0
  25. package/docs/content/drafts/ActionList2.mdx +484 -0
  26. package/docs/content/drafts/ActionMenu2.mdx +302 -0
  27. package/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js +9 -1
  28. package/docs/src/@primer/gatsby-theme-doctocat/mdx-components.js +15 -2
  29. package/docs/src/@primer/gatsby-theme-doctocat/nav.yml +4 -0
  30. package/docs/src/component-checklist.js +10 -2
  31. package/docs/src/props-table.js +165 -0
  32. package/docs/src/props.js +14 -28
  33. package/lib/ActionList/Header.js +1 -1
  34. package/lib/ActionList/Item.js +10 -10
  35. package/lib/ActionList/List.js +1 -1
  36. package/lib/ActionList2/ActionListContainerContext.d.ts +10 -0
  37. package/lib/ActionList2/ActionListContainerContext.js +15 -0
  38. package/lib/ActionList2/Divider.d.ts +3 -2
  39. package/lib/ActionList2/Divider.js +10 -5
  40. package/lib/ActionList2/Item.js +22 -8
  41. package/lib/ActionList2/List.js +12 -2
  42. package/lib/ActionList2/Selection.js +11 -0
  43. package/lib/ActionList2/index.d.ts +1 -2
  44. package/lib/ActionMenu2.d.ts +317 -0
  45. package/lib/ActionMenu2.js +125 -0
  46. package/lib/Autocomplete/Autocomplete.d.ts +2 -1
  47. package/lib/Autocomplete/AutocompleteInput.d.ts +2 -1
  48. package/lib/BaseStyles.js +2 -20
  49. package/lib/BorderBox.js +1 -1
  50. package/lib/Box.js +1 -1
  51. package/lib/BranchName.js +1 -1
  52. package/lib/Breadcrumbs.js +3 -3
  53. package/lib/Button/Button.d.ts +2 -2
  54. package/lib/Button/Button.js +1 -1
  55. package/lib/Button/ButtonClose.d.ts +2 -2
  56. package/lib/Button/ButtonDanger.d.ts +2 -2
  57. package/lib/Button/ButtonGroup.js +1 -1
  58. package/lib/Button/ButtonInvisible.d.ts +2 -2
  59. package/lib/Button/ButtonOutline.d.ts +2 -2
  60. package/lib/Button/ButtonPrimary.d.ts +2 -2
  61. package/lib/Checkbox.d.ts +1 -1
  62. package/lib/Checkbox.js +1 -1
  63. package/lib/CircleOcticon.d.ts +35 -35
  64. package/lib/Details.js +1 -1
  65. package/lib/Dialog.d.ts +37 -37
  66. package/lib/Dropdown.d.ts +6 -6
  67. package/lib/DropdownMenu/DropdownButton.d.ts +6 -3
  68. package/lib/FilterList.d.ts +1 -1
  69. package/lib/FilteredActionList/FilteredActionList.js +1 -1
  70. package/lib/Flex.js +1 -1
  71. package/lib/LabelGroup.js +1 -1
  72. package/lib/Overlay.js +1 -1
  73. package/lib/Pagination/Pagination.js +2 -2
  74. package/lib/Position.d.ts +4 -4
  75. package/lib/Position.js +1 -1
  76. package/lib/Radio.d.ts +38 -0
  77. package/lib/Radio.js +55 -0
  78. package/lib/SelectMenu/SelectMenu.d.ts +11 -10
  79. package/lib/SelectMenu/SelectMenu.js +1 -1
  80. package/lib/SelectMenu/SelectMenuFilter.js +1 -1
  81. package/lib/SelectMenu/SelectMenuFooter.js +1 -1
  82. package/lib/SelectMenu/SelectMenuItem.d.ts +1 -1
  83. package/lib/SelectMenu/SelectMenuItem.js +1 -1
  84. package/lib/SelectMenu/SelectMenuModal.d.ts +1 -1
  85. package/lib/SelectMenu/SelectMenuTab.js +1 -1
  86. package/lib/SelectMenu/SelectMenuTabPanel.js +1 -1
  87. package/lib/SelectMenu/SelectMenuTabs.js +1 -1
  88. package/lib/StateLabel.js +1 -1
  89. package/lib/StyledOcticon.js +1 -1
  90. package/lib/SubNav.js +3 -3
  91. package/lib/TextInputWithTokens.d.ts +2 -1
  92. package/lib/ThemeProvider.d.ts +1 -0
  93. package/lib/ThemeProvider.js +17 -4
  94. package/lib/Timeline.js +4 -4
  95. package/lib/Token/AvatarToken.d.ts +1 -1
  96. package/lib/Token/AvatarToken.js +1 -1
  97. package/lib/Token/IssueLabelToken.d.ts +1 -1
  98. package/lib/Token/Token.d.ts +1 -1
  99. package/lib/Token/TokenBase.js +1 -1
  100. package/lib/Tooltip.js +1 -1
  101. package/lib/UnderlineNav.js +2 -2
  102. package/lib/__tests__/Radio.test.d.ts +2 -0
  103. package/lib/__tests__/Radio.test.js +202 -0
  104. package/lib/__tests__/ThemeProvider.test.js +114 -0
  105. package/lib/drafts.d.ts +1 -0
  106. package/lib/drafts.js +13 -0
  107. package/lib/hooks/index.d.ts +1 -0
  108. package/lib/hooks/index.js +9 -1
  109. package/lib/index.d.ts +2 -0
  110. package/lib/index.js +8 -0
  111. package/lib/stories/ActionList.stories.js +3 -3
  112. package/lib/stories/ActionList2.stories.js +1 -1
  113. package/lib/stories/ActionMenu2.stories.js +455 -0
  114. package/lib/stories/Checkbox.stories.js +4 -4
  115. package/lib/stories/Radio.stories.js +146 -0
  116. package/lib/stories/ThemeProvider.stories.js +1 -5
  117. package/lib/stories/useFocusTrap.stories.js +1 -11
  118. package/lib/stories/useFocusZone.stories.js +2 -6
  119. package/lib-esm/ActionList/Header.js +1 -1
  120. package/lib-esm/ActionList/Item.js +10 -10
  121. package/lib-esm/ActionList/List.js +1 -1
  122. package/lib-esm/ActionList2/ActionListContainerContext.d.ts +10 -0
  123. package/lib-esm/ActionList2/ActionListContainerContext.js +3 -0
  124. package/lib-esm/ActionList2/Divider.d.ts +3 -2
  125. package/lib-esm/ActionList2/Divider.js +8 -5
  126. package/lib-esm/ActionList2/Item.js +20 -8
  127. package/lib-esm/ActionList2/List.js +10 -2
  128. package/lib-esm/ActionList2/Selection.js +9 -0
  129. package/lib-esm/ActionList2/index.d.ts +1 -2
  130. package/lib-esm/ActionMenu2.d.ts +317 -0
  131. package/lib-esm/ActionMenu2.js +100 -0
  132. package/lib-esm/Autocomplete/Autocomplete.d.ts +2 -1
  133. package/lib-esm/Autocomplete/AutocompleteInput.d.ts +2 -1
  134. package/lib-esm/BaseStyles.js +2 -20
  135. package/lib-esm/BorderBox.js +1 -1
  136. package/lib-esm/Box.js +1 -1
  137. package/lib-esm/BranchName.js +1 -1
  138. package/lib-esm/Breadcrumbs.js +3 -3
  139. package/lib-esm/Button/Button.d.ts +2 -2
  140. package/lib-esm/Button/Button.js +1 -1
  141. package/lib-esm/Button/ButtonClose.d.ts +2 -2
  142. package/lib-esm/Button/ButtonDanger.d.ts +2 -2
  143. package/lib-esm/Button/ButtonGroup.js +1 -1
  144. package/lib-esm/Button/ButtonInvisible.d.ts +2 -2
  145. package/lib-esm/Button/ButtonOutline.d.ts +2 -2
  146. package/lib-esm/Button/ButtonPrimary.d.ts +2 -2
  147. package/lib-esm/Checkbox.d.ts +1 -1
  148. package/lib-esm/Checkbox.js +1 -1
  149. package/lib-esm/CircleOcticon.d.ts +35 -35
  150. package/lib-esm/Details.js +1 -1
  151. package/lib-esm/Dialog.d.ts +37 -37
  152. package/lib-esm/Dropdown.d.ts +6 -6
  153. package/lib-esm/DropdownMenu/DropdownButton.d.ts +6 -3
  154. package/lib-esm/FilterList.d.ts +1 -1
  155. package/lib-esm/FilteredActionList/FilteredActionList.js +1 -1
  156. package/lib-esm/Flex.js +1 -1
  157. package/lib-esm/LabelGroup.js +1 -1
  158. package/lib-esm/Overlay.js +1 -1
  159. package/lib-esm/Pagination/Pagination.js +2 -2
  160. package/lib-esm/Position.d.ts +4 -4
  161. package/lib-esm/Position.js +1 -1
  162. package/lib-esm/Radio.d.ts +38 -0
  163. package/lib-esm/Radio.js +40 -0
  164. package/lib-esm/SelectMenu/SelectMenu.d.ts +11 -10
  165. package/lib-esm/SelectMenu/SelectMenu.js +1 -1
  166. package/lib-esm/SelectMenu/SelectMenuFilter.js +1 -1
  167. package/lib-esm/SelectMenu/SelectMenuFooter.js +1 -1
  168. package/lib-esm/SelectMenu/SelectMenuItem.d.ts +1 -1
  169. package/lib-esm/SelectMenu/SelectMenuItem.js +1 -1
  170. package/lib-esm/SelectMenu/SelectMenuModal.d.ts +1 -1
  171. package/lib-esm/SelectMenu/SelectMenuTab.js +1 -1
  172. package/lib-esm/SelectMenu/SelectMenuTabPanel.js +1 -1
  173. package/lib-esm/SelectMenu/SelectMenuTabs.js +1 -1
  174. package/lib-esm/StateLabel.js +1 -1
  175. package/lib-esm/StyledOcticon.js +1 -1
  176. package/lib-esm/SubNav.js +3 -3
  177. package/lib-esm/TextInputWithTokens.d.ts +2 -1
  178. package/lib-esm/ThemeProvider.d.ts +1 -0
  179. package/lib-esm/ThemeProvider.js +17 -4
  180. package/lib-esm/Timeline.js +4 -4
  181. package/lib-esm/Token/AvatarToken.d.ts +1 -1
  182. package/lib-esm/Token/AvatarToken.js +1 -1
  183. package/lib-esm/Token/IssueLabelToken.d.ts +1 -1
  184. package/lib-esm/Token/Token.d.ts +1 -1
  185. package/lib-esm/Token/TokenBase.js +1 -1
  186. package/lib-esm/Tooltip.js +1 -1
  187. package/lib-esm/UnderlineNav.js +2 -2
  188. package/lib-esm/__tests__/Radio.test.d.ts +2 -0
  189. package/lib-esm/__tests__/Radio.test.js +183 -0
  190. package/lib-esm/__tests__/ThemeProvider.test.js +114 -0
  191. package/lib-esm/drafts.d.ts +1 -0
  192. package/lib-esm/drafts.js +2 -1
  193. package/lib-esm/hooks/index.d.ts +1 -0
  194. package/lib-esm/hooks/index.js +2 -1
  195. package/lib-esm/index.d.ts +2 -0
  196. package/lib-esm/index.js +1 -0
  197. package/lib-esm/stories/ActionList.stories.js +3 -3
  198. package/lib-esm/stories/ActionList2.stories.js +1 -1
  199. package/lib-esm/stories/ActionMenu2.stories.js +393 -0
  200. package/lib-esm/stories/Checkbox.stories.js +5 -5
  201. package/lib-esm/stories/Radio.stories.js +121 -0
  202. package/lib-esm/stories/ThemeProvider.stories.js +1 -5
  203. package/lib-esm/stories/useFocusTrap.stories.js +1 -11
  204. package/lib-esm/stories/useFocusZone.stories.js +2 -6
  205. package/package-lock.json +1366 -3544
  206. package/package.json +14 -8
  207. package/script/component-status-project/build.ts +100 -0
  208. package/script/component-status-project/deploy.rb +142 -0
  209. package/src/ActionList2/ActionListContainerContext.tsx +14 -0
  210. package/src/ActionList2/Divider.tsx +13 -8
  211. package/src/ActionList2/Item.tsx +14 -6
  212. package/src/ActionList2/List.tsx +6 -2
  213. package/src/ActionList2/Selection.tsx +9 -0
  214. package/src/ActionMenu2.tsx +116 -0
  215. package/src/BranchName.tsx +2 -1
  216. package/src/Radio.tsx +76 -0
  217. package/src/ThemeProvider.tsx +22 -5
  218. package/src/__tests__/Radio.test.tsx +174 -0
  219. package/src/__tests__/ThemeProvider.test.tsx +116 -0
  220. package/src/__tests__/__snapshots__/BranchName.test.tsx.snap +3 -1
  221. package/src/__tests__/__snapshots__/Radio.test.tsx.snap +16 -0
  222. package/src/drafts.ts +1 -0
  223. package/src/hooks/index.ts +1 -0
  224. package/src/index.ts +2 -0
  225. package/src/stories/ActionMenu2.stories.tsx +605 -0
  226. package/src/stories/Checkbox.stories.tsx +1 -3
  227. package/src/stories/Radio.stories.tsx +126 -0
  228. package/stats.html +1 -1
  229. package/tsconfig.build.json +1 -1
  230. package/tsconfig.json +1 -1
  231. package/docs/content/ActionList2.mdx +0 -379
  232. package/docs/content/BranchName.md +0 -39
  233. package/docs/content/Link.md +0 -29
  234. package/docs/content/TextInput.md +0 -42
package/src/Radio.tsx ADDED
@@ -0,0 +1,76 @@
1
+ import styled from 'styled-components'
2
+ import React, {InputHTMLAttributes, ReactElement} from 'react'
3
+ import sx, {SxProp} from './sx'
4
+
5
+ export type RadioProps = {
6
+ /**
7
+ * A unique value that is never shown to the user.
8
+ * Used during form submission and to identify which radio button in a group is selected
9
+ */
10
+ value: string
11
+ /**
12
+ * Name attribute of the input element. Required for grouping radio inputs
13
+ */
14
+ name: string
15
+ /**
16
+ * Apply inactive visual appearance to the radio button
17
+ */
18
+ disabled?: boolean
19
+ /**
20
+ * Indicates whether the radio button is selected
21
+ */
22
+ checked?: boolean
23
+ /**
24
+ * Forward a ref to the underlying input element
25
+ */
26
+ ref?: React.RefObject<HTMLInputElement>
27
+ /**
28
+ * Indicates whether the radio button must be checked before the form can be submitted
29
+ */
30
+ required?: boolean
31
+ /**
32
+ * Indicates whether the radio button validation state is non-standard
33
+ */
34
+ validationStatus?: 'error' | 'success' // TODO: hoist to Validation typings
35
+ } & InputHTMLAttributes<HTMLInputElement> &
36
+ SxProp
37
+
38
+ const StyledRadio = styled.input`
39
+ cursor: pointer;
40
+
41
+ ${props => props.disabled && `cursor: not-allowed;`}
42
+
43
+ ${sx}
44
+ `
45
+
46
+ /**
47
+ * An accessible, native radio component for selecting one option from a list.
48
+ */
49
+ const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
50
+ (
51
+ {checked, disabled, sx: sxProp, required, validationStatus, value, name, ...rest}: RadioProps,
52
+ ref
53
+ ): ReactElement => {
54
+ return (
55
+ <StyledRadio
56
+ type="radio"
57
+ value={value}
58
+ name={name}
59
+ ref={ref}
60
+ disabled={disabled}
61
+ aria-disabled={disabled ? 'true' : 'false'}
62
+ checked={checked}
63
+ aria-checked={checked ? 'true' : 'false'}
64
+ required={required}
65
+ aria-required={required ? 'true' : 'false'}
66
+ aria-invalid={validationStatus === 'error' ? 'true' : 'false'}
67
+ sx={sxProp}
68
+ {...rest}
69
+ />
70
+ )
71
+ }
72
+ )
73
+
74
+ Radio.displayName = 'Radio'
75
+
76
+ export default Radio
@@ -24,6 +24,7 @@ const ThemeContext = React.createContext<{
24
24
  colorScheme?: string
25
25
  colorMode?: ColorModeWithAuto
26
26
  resolvedColorMode?: ColorMode
27
+ resolvedColorScheme?: string
27
28
  dayScheme?: string
28
29
  nightScheme?: string
29
30
  setColorMode: React.Dispatch<React.SetStateAction<ColorModeWithAuto>>
@@ -52,7 +53,10 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({children, ...props}
52
53
  const systemColorMode = useSystemColorMode()
53
54
  const resolvedColorMode = resolveColorMode(colorMode, systemColorMode)
54
55
  const colorScheme = chooseColorScheme(resolvedColorMode, dayScheme, nightScheme)
55
- const resolvedTheme = React.useMemo(() => applyColorScheme(theme, colorScheme), [theme, colorScheme])
56
+ const {resolvedTheme, resolvedColorScheme} = React.useMemo(
57
+ () => applyColorScheme(theme, colorScheme),
58
+ [theme, colorScheme]
59
+ )
56
60
 
57
61
  // Update state if props change
58
62
  React.useEffect(() => {
@@ -74,6 +78,7 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({children, ...props}
74
78
  colorScheme,
75
79
  colorMode,
76
80
  resolvedColorMode,
81
+ resolvedColorScheme,
77
82
  dayScheme,
78
83
  nightScheme,
79
84
  setColorMode,
@@ -156,9 +161,15 @@ function chooseColorScheme(colorMode: ColorMode, dayScheme: string, nightScheme:
156
161
  }
157
162
  }
158
163
 
159
- function applyColorScheme(theme: Theme, colorScheme: string) {
164
+ function applyColorScheme(
165
+ theme: Theme,
166
+ colorScheme: string
167
+ ): {resolvedTheme: Theme; resolvedColorScheme: string | undefined} {
160
168
  if (!theme.colorSchemes) {
161
- return theme
169
+ return {
170
+ resolvedTheme: theme,
171
+ resolvedColorScheme: undefined
172
+ }
162
173
  }
163
174
 
164
175
  if (!theme.colorSchemes[colorScheme]) {
@@ -167,10 +178,16 @@ function applyColorScheme(theme: Theme, colorScheme: string) {
167
178
 
168
179
  // Apply the first defined color scheme
169
180
  const defaultColorScheme = Object.keys(theme.colorSchemes)[0]
170
- return deepmerge(theme, theme.colorSchemes[defaultColorScheme])
181
+ return {
182
+ resolvedTheme: deepmerge(theme, theme.colorSchemes[defaultColorScheme]),
183
+ resolvedColorScheme: defaultColorScheme
184
+ }
171
185
  }
172
186
 
173
- return deepmerge(theme, theme.colorSchemes[colorScheme])
187
+ return {
188
+ resolvedTheme: deepmerge(theme, theme.colorSchemes[colorScheme]),
189
+ resolvedColorScheme: colorScheme
190
+ }
174
191
  }
175
192
 
176
193
  export default ThemeProvider
@@ -0,0 +1,174 @@
1
+ import React from 'react'
2
+ import {Radio} from '..'
3
+ import {behavesAsComponent, checkExports} from '../utils/testing'
4
+ import {render, cleanup, fireEvent} from '@testing-library/react'
5
+ import {toHaveNoViolations} from 'jest-axe'
6
+ import 'babel-polyfill'
7
+ import '@testing-library/jest-dom'
8
+
9
+ expect.extend(toHaveNoViolations)
10
+
11
+ describe('Radio', () => {
12
+ const defaultProps = {
13
+ name: 'mock',
14
+ value: 'mock value'
15
+ }
16
+
17
+ beforeEach(() => {
18
+ jest.resetAllMocks()
19
+ cleanup()
20
+ })
21
+
22
+ behavesAsComponent({Component: Radio})
23
+
24
+ checkExports('Radio', {
25
+ default: Radio
26
+ })
27
+
28
+ it('renders a valid radio input', () => {
29
+ const {getByRole} = render(<Radio {...defaultProps} />)
30
+
31
+ const radio = getByRole('radio')
32
+
33
+ expect(radio).toBeDefined()
34
+ })
35
+
36
+ it('renders an unchecked radio by default', () => {
37
+ const {getByRole} = render(<Radio {...defaultProps} />)
38
+
39
+ const radio = getByRole('radio') as HTMLInputElement
40
+
41
+ expect(radio.checked).toEqual(false)
42
+ })
43
+
44
+ it('accepts and applies value and name attributes', () => {
45
+ const {getByRole} = render(<Radio {...defaultProps} />)
46
+
47
+ const radio = getByRole('radio') as HTMLInputElement
48
+
49
+ expect(radio).toHaveAttribute('name', defaultProps.name)
50
+ expect(radio).toHaveAttribute('value', defaultProps.value)
51
+ })
52
+
53
+ it('renders an active radio when checked attribute is passed', () => {
54
+ const handleChange = jest.fn()
55
+ const {getByRole} = render(<Radio {...defaultProps} checked onChange={handleChange} />)
56
+
57
+ const radio = getByRole('radio') as HTMLInputElement
58
+
59
+ expect(radio.checked).toEqual(true)
60
+ })
61
+
62
+ it('accepts a change handler that can alter a single radio state', () => {
63
+ const handleChange = jest.fn()
64
+ const {getByRole} = render(<Radio {...defaultProps} onChange={handleChange} />)
65
+
66
+ const radio = getByRole('radio') as HTMLInputElement
67
+
68
+ expect(radio.checked).toEqual(false)
69
+
70
+ fireEvent.click(radio)
71
+ expect(handleChange).toHaveBeenCalled()
72
+ expect(radio.checked).toEqual(true)
73
+ })
74
+
75
+ it('renders correct behavior for multiple radio buttons in a group', () => {
76
+ const handleChange = jest.fn()
77
+ const RadioGroup = () => (
78
+ <form>
79
+ <Radio {...defaultProps} value="radio-one" onChange={handleChange} />
80
+ <Radio {...defaultProps} value="radio-two" onChange={handleChange} />
81
+ </form>
82
+ )
83
+ const {getByDisplayValue} = render(<RadioGroup />)
84
+
85
+ const radioOne = getByDisplayValue('radio-one') as HTMLInputElement
86
+ const radioTwo = getByDisplayValue('radio-two') as HTMLInputElement
87
+
88
+ expect(radioOne).not.toBeChecked()
89
+ expect(radioTwo).not.toBeChecked()
90
+
91
+ fireEvent.click(radioOne)
92
+
93
+ expect(radioOne).toBeChecked()
94
+ expect(radioTwo).not.toBeChecked()
95
+
96
+ fireEvent.click(radioTwo)
97
+
98
+ expect(radioOne).not.toBeChecked()
99
+ expect(radioTwo).toBeChecked()
100
+ })
101
+
102
+ it('renders an inactive radio state correctly', () => {
103
+ const handleChange = jest.fn()
104
+ const {getByRole, rerender} = render(<Radio {...defaultProps} disabled onChange={handleChange} />)
105
+
106
+ const radio = getByRole('radio') as HTMLInputElement
107
+
108
+ expect(radio.disabled).toEqual(true)
109
+ expect(radio).not.toBeChecked()
110
+ expect(radio).toHaveAttribute('aria-disabled', 'true')
111
+
112
+ fireEvent.change(radio)
113
+
114
+ expect(radio.disabled).toEqual(true)
115
+ expect(radio).not.toBeChecked()
116
+ expect(radio).toHaveAttribute('aria-disabled', 'true')
117
+
118
+ // remove disabled attribute and retest
119
+ rerender(<Radio {...defaultProps} onChange={handleChange} />)
120
+
121
+ expect(radio).toHaveAttribute('aria-disabled', 'false')
122
+ })
123
+
124
+ it('renders an uncontrolled component correctly', () => {
125
+ const {getByRole} = render(<Radio {...defaultProps} defaultChecked />)
126
+
127
+ const radio = getByRole('radio') as HTMLInputElement
128
+
129
+ expect(radio.checked).toEqual(true)
130
+ })
131
+
132
+ it('renders an aria-checked attribute correctly', () => {
133
+ const handleChange = jest.fn()
134
+ const {getByRole, rerender} = render(<Radio {...defaultProps} checked={false} onChange={handleChange} />)
135
+
136
+ const radio = getByRole('radio') as HTMLInputElement
137
+
138
+ expect(radio).toHaveAttribute('aria-checked', 'false')
139
+
140
+ rerender(<Radio {...defaultProps} checked={true} onChange={handleChange} />)
141
+
142
+ expect(radio).toHaveAttribute('aria-checked', 'true')
143
+ })
144
+
145
+ it('renders an invalid aria state when validation prop indicates an error', () => {
146
+ const handleChange = jest.fn()
147
+ const {getByRole, rerender} = render(<Radio {...defaultProps} onChange={handleChange} />)
148
+
149
+ const radio = getByRole('radio') as HTMLInputElement
150
+
151
+ expect(radio).toHaveAttribute('aria-invalid', 'false')
152
+
153
+ rerender(<Radio {...defaultProps} onChange={handleChange} validationStatus="success" />)
154
+
155
+ expect(radio).toHaveAttribute('aria-invalid', 'false')
156
+
157
+ rerender(<Radio {...defaultProps} onChange={handleChange} validationStatus="error" />)
158
+
159
+ expect(radio).toHaveAttribute('aria-invalid', 'true')
160
+ })
161
+
162
+ it('renders an aria state indicating the field is required', () => {
163
+ const handleChange = jest.fn()
164
+ const {getByRole, rerender} = render(<Radio {...defaultProps} onChange={handleChange} />)
165
+
166
+ const radio = getByRole('radio') as HTMLInputElement
167
+
168
+ expect(radio).toHaveAttribute('aria-required', 'false')
169
+
170
+ rerender(<Radio {...defaultProps} onChange={handleChange} required />)
171
+
172
+ expect(radio).toHaveAttribute('aria-required', 'true')
173
+ })
174
+ })
@@ -439,3 +439,119 @@ describe('useColorSchemeVar', () => {
439
439
  expect(screen.getByText('Hello')).toHaveStyleRule('background-color', 'blue')
440
440
  })
441
441
  })
442
+
443
+ describe('useTheme().resolvedColorScheme', () => {
444
+ it('is undefined when not in a theme', () => {
445
+ const Component = () => {
446
+ const {resolvedColorScheme} = useTheme()
447
+
448
+ return <Text data-testid="text">{resolvedColorScheme}</Text>
449
+ }
450
+
451
+ render(<Component />)
452
+
453
+ expect(screen.getByTestId('text').textContent).toEqual('')
454
+ })
455
+
456
+ it('is undefined when the theme has no colorScheme object', () => {
457
+ const Component = () => {
458
+ const {resolvedColorScheme} = useTheme()
459
+
460
+ return <Text data-testid="text">{resolvedColorScheme}</Text>
461
+ }
462
+
463
+ render(
464
+ <ThemeProvider theme={{color: 'red'}}>
465
+ <Component />
466
+ </ThemeProvider>
467
+ )
468
+
469
+ expect(screen.getByTestId('text').textContent).toEqual('')
470
+ })
471
+
472
+ it('is the same as the applied colorScheme, when that colorScheme is in the theme', () => {
473
+ const Component = () => {
474
+ const {resolvedColorScheme} = useTheme()
475
+
476
+ return <Text data-testid="text">{resolvedColorScheme}</Text>
477
+ }
478
+
479
+ const schemeToApply = 'dark'
480
+
481
+ render(
482
+ <ThemeProvider theme={exampleTheme} colorMode="day" dayScheme={schemeToApply}>
483
+ <Component />
484
+ </ThemeProvider>
485
+ )
486
+
487
+ expect(exampleTheme.colorSchemes).toHaveProperty(schemeToApply)
488
+ expect(screen.getByTestId('text').textContent).toEqual(schemeToApply)
489
+ })
490
+
491
+ it('is the value of the fallback colorScheme applied when attempting to apply an invalid colorScheme', () => {
492
+ const Component = () => {
493
+ const {resolvedColorScheme} = useTheme()
494
+
495
+ return <Text data-testid="text">{resolvedColorScheme}</Text>
496
+ }
497
+
498
+ const schemeToApply = 'totally-invalid-colorscheme'
499
+ render(
500
+ <ThemeProvider theme={exampleTheme} colorMode="day" dayScheme={schemeToApply}>
501
+ <Component />
502
+ </ThemeProvider>
503
+ )
504
+
505
+ const defaultThemeColorScheme = Object.keys(exampleTheme.colorSchemes)[0]
506
+
507
+ expect(defaultThemeColorScheme).not.toEqual(schemeToApply)
508
+ expect(exampleTheme.colorSchemes).not.toHaveProperty(schemeToApply)
509
+ expect(screen.getByTestId('text').textContent).toEqual('light')
510
+ })
511
+
512
+ describe('nested theme', () => {
513
+ it('is the same as the applied colorScheme, when that colorScheme is in the theme', () => {
514
+ const Component = () => {
515
+ const {resolvedColorScheme} = useTheme()
516
+
517
+ return <Text data-testid="text">{resolvedColorScheme}</Text>
518
+ }
519
+
520
+ const schemeToApply = 'dark'
521
+
522
+ render(
523
+ <ThemeProvider theme={exampleTheme} colorMode="day" dayScheme={schemeToApply}>
524
+ <ThemeProvider>
525
+ <Component />
526
+ </ThemeProvider>
527
+ </ThemeProvider>
528
+ )
529
+
530
+ expect(exampleTheme.colorSchemes).toHaveProperty(schemeToApply)
531
+ expect(screen.getByTestId('text').textContent).toEqual(schemeToApply)
532
+ })
533
+
534
+ it('is the value of the fallback colorScheme applied when attempting to apply an invalid colorScheme', () => {
535
+ const Component = () => {
536
+ const {resolvedColorScheme} = useTheme()
537
+
538
+ return <Text data-testid="text">{resolvedColorScheme}</Text>
539
+ }
540
+
541
+ const schemeToApply = 'totally-invalid-colorscheme'
542
+ render(
543
+ <ThemeProvider theme={exampleTheme} colorMode="day" dayScheme={schemeToApply}>
544
+ <ThemeProvider>
545
+ <Component />
546
+ </ThemeProvider>
547
+ </ThemeProvider>
548
+ )
549
+
550
+ const defaultThemeColorScheme = Object.keys(exampleTheme.colorSchemes)[0]
551
+
552
+ expect(defaultThemeColorScheme).not.toEqual(schemeToApply)
553
+ expect(exampleTheme.colorSchemes).not.toHaveProperty(schemeToApply)
554
+ expect(screen.getByTestId('text').textContent).toEqual('light')
555
+ })
556
+ })
557
+ })
@@ -6,9 +6,11 @@ exports[`BranchName renders consistently 1`] = `
6
6
  padding: 2px 6px;
7
7
  font-size: 12px;
8
8
  font-family: SFMono-Regular,Consolas,"Liberation Mono",Menlo,Courier,monospace;
9
- color: #57606a;
9
+ color: #0969da;
10
10
  background-color: #ddf4ff;
11
11
  border-radius: 6px;
12
+ -webkit-text-decoration: none;
13
+ text-decoration: none;
12
14
  }
13
15
 
14
16
  <a
@@ -0,0 +1,16 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`Radio renders consistently 1`] = `
4
+ .c0 {
5
+ cursor: pointer;
6
+ }
7
+
8
+ <input
9
+ aria-checked="false"
10
+ aria-disabled="false"
11
+ aria-invalid="false"
12
+ aria-required="false"
13
+ className="c0"
14
+ type="radio"
15
+ />
16
+ `;
package/src/drafts.ts CHANGED
@@ -8,3 +8,4 @@
8
8
  // Components
9
9
  export * from './ActionList2'
10
10
  export * from './NewButton'
11
+ export * from './ActionMenu2'
@@ -9,3 +9,4 @@ export {useAnchoredPosition} from './useAnchoredPosition'
9
9
  export {useOverlay} from './useOverlay'
10
10
  export type {UseOverlaySettings} from './useOverlay'
11
11
  export {useRenderForcingRef} from './useRenderForcingRef'
12
+ export {useProvidedStateOrCreate} from './useProvidedStateOrCreate'
package/src/index.ts CHANGED
@@ -27,6 +27,8 @@ export {useOverlay} from './hooks/useOverlay'
27
27
  export {useConfirm} from './Dialog/ConfirmationDialog'
28
28
 
29
29
  // Components
30
+ export {default as Radio} from './Radio'
31
+ export type {RadioProps} from './Radio'
30
32
  export {ActionList} from './ActionList'
31
33
  export {ActionMenu} from './ActionMenu'
32
34
  export type {ActionMenuProps} from './ActionMenu'