@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/components",
3
- "version": "33.0.0-rc.b495ba4a",
3
+ "version": "33.1.0-rc.6856bcf5",
4
4
  "description": "Primer react components",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib-esm/index.js",
@@ -65,7 +65,7 @@
65
65
  "@babel/cli": "7.14.5",
66
66
  "@babel/core": "7.14.8",
67
67
  "@babel/eslint-parser": "7.15.7",
68
- "@babel/plugin-proposal-nullish-coalescing-operator": "7.14.5",
68
+ "@babel/plugin-proposal-nullish-coalescing-operator": "7.16.0",
69
69
  "@babel/plugin-proposal-optional-chaining": "7.14.5",
70
70
  "@babel/plugin-transform-modules-commonjs": "7.14.5",
71
71
  "@babel/preset-react": "7.14.5",
@@ -75,7 +75,7 @@
75
75
  "@github/prettier-config": "0.0.4",
76
76
  "@rollup/plugin-commonjs": "19.0.2",
77
77
  "@rollup/plugin-node-resolve": "13.0.5",
78
- "@size-limit/preset-big-lib": "5.0.2",
78
+ "@size-limit/preset-big-lib": "7.0.3",
79
79
  "@storybook/addon-a11y": "6.3.12",
80
80
  "@storybook/addon-actions": "^6.3.12",
81
81
  "@storybook/addon-essentials": "6.3.12",
@@ -91,6 +91,7 @@
91
91
  "@types/jest-axe": "3.5.3",
92
92
  "@types/lodash.isempty": "4.4.6",
93
93
  "@types/lodash.isobject": "3.0.6",
94
+ "@types/node": "16.11.11",
94
95
  "@typescript-eslint/eslint-plugin": "4.31.2",
95
96
  "@typescript-eslint/parser": "4.26.1",
96
97
  "@wojtekmaj/enzyme-adapter-react-17": "0.6.3",
@@ -99,7 +100,7 @@
99
100
  "babel-plugin-add-react-displayname": "0.0.5",
100
101
  "babel-plugin-macros": "3.1.0",
101
102
  "babel-plugin-preval": "5.0.0",
102
- "babel-plugin-styled-components": "1.12.0",
103
+ "babel-plugin-styled-components": "2.0.2",
103
104
  "babel-plugin-transform-replace-expressions": "0.2.0",
104
105
  "babel-polyfill": "6.26.0",
105
106
  "chroma-js": "2.1.2",
@@ -116,6 +117,7 @@
116
117
  "eslint-plugin-primer-react": "0.7.0",
117
118
  "eslint-plugin-react": "7.24.0",
118
119
  "eslint-plugin-react-hooks": "4.2.0",
120
+ "front-matter": "4.0.2",
119
121
  "jest": "27.0.4",
120
122
  "jest-axe": "5.0.1",
121
123
  "jest-styled-components": "6.3.4",
@@ -128,16 +130,16 @@
128
130
  "react-dnd-html5-backend": "14.0.2",
129
131
  "react-dom": "17.0.2",
130
132
  "react-test-renderer": "17.0.2",
131
- "rollup": "2.56.3",
133
+ "rollup": "2.60.2",
132
134
  "rollup-plugin-babel": "4.4.0",
133
135
  "rollup-plugin-terser": "7.0.2",
134
136
  "rollup-plugin-visualizer": "5.5.0",
135
137
  "semver": "7.3.5",
136
- "size-limit": "5.0.2",
138
+ "size-limit": "7.0.3",
137
139
  "storybook-addon-performance": "0.16.1",
138
140
  "styled-components": "4.4.1",
139
141
  "ts-toolbelt": "9.6.0",
140
- "typescript": "4.2.2"
142
+ "typescript": "4.4.4"
141
143
  },
142
144
  "peerDependencies": {
143
145
  "react": "^17.0.0",
@@ -156,5 +158,9 @@
156
158
  "webpack": false,
157
159
  "running": false
158
160
  }
159
- ]
161
+ ],
162
+ "engines": {
163
+ "node": ">=16",
164
+ "npm": ">=8"
165
+ }
160
166
  }
@@ -0,0 +1,100 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
5
+ const fm = require('front-matter') // FIXME after this bugfix is merged https://github.com/jxson/front-matter/pull/77
6
+
7
+ const sourceDirectory = path.resolve(__dirname, '../../docs/content/')
8
+ const outputDir = path.resolve(__dirname, '../../dist/')
9
+
10
+ type ComponentStatus = {
11
+ [component: string]: string
12
+ }
13
+
14
+ /**
15
+ * Extracts the component status for each file in the given directory.
16
+ *
17
+ * @param filenames Array of filenames to read front-matter from
18
+ * @param dir Absolute path to directory containing files
19
+ * @returns A promise that resolves to an array containing outcome of file front-matter extraction
20
+ */
21
+ function getComponentStatuses(filenames: string[], dir: string) {
22
+ const promises: Promise<ComponentStatus | null>[] = []
23
+
24
+ const handleCallback = (
25
+ filename: string,
26
+ resolve: (value: ComponentStatus | null) => void,
27
+ reject: (value: unknown) => void
28
+ ) => {
29
+ fs.readFile(path.resolve(dir, filename), 'utf-8', (err, content) => {
30
+ if (err) return reject(err)
31
+
32
+ if (fm.test(content)) {
33
+ const {
34
+ attributes: {title, status}
35
+ } = fm(content)
36
+
37
+ if (status) {
38
+ return resolve({[title]: status})
39
+ }
40
+ }
41
+
42
+ resolve(null)
43
+ })
44
+ }
45
+
46
+ for (const filename of filenames) {
47
+ const promise: Promise<ComponentStatus | null> = new Promise((resolve, reject) => {
48
+ return handleCallback(filename, resolve, reject)
49
+ })
50
+ promises.push(promise)
51
+ }
52
+
53
+ return Promise.all(promises)
54
+ }
55
+
56
+ /**
57
+ * Orchestrates the process of reading component status for each file in the given directory.
58
+ *
59
+ * @param dir Directory to source files where status will be extracted from
60
+ * @returns A promise that resolves to an object containing component statuses
61
+ */
62
+ async function readFiles(dir: string) {
63
+ try {
64
+ const dirContents = fs.readdirSync(dir, {withFileTypes: true})
65
+ const filenames = dirContents.filter(dirent => dirent.isFile()).map(dirent => dirent.name)
66
+ const componentStatuses = await getComponentStatuses(filenames, dir)
67
+
68
+ return componentStatuses
69
+ .filter(Boolean)
70
+ .reverse()
71
+ .reduce(
72
+ (acc, file) => ({
73
+ ...acc,
74
+ ...file
75
+ }),
76
+ {}
77
+ )
78
+ } catch (err) {
79
+ throw new Error(`error reading files: ${err}`)
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Writes the component status to the given file.
85
+ */
86
+ async function build() {
87
+ try {
88
+ const componentStatuses = await readFiles(sourceDirectory)
89
+
90
+ if (!fs.existsSync(outputDir)) {
91
+ fs.mkdirSync(outputDir)
92
+ }
93
+
94
+ fs.writeFileSync(`${outputDir}/component-status.json`, JSON.stringify(componentStatuses))
95
+ } catch (error) {
96
+ throw new Error(`error building component status object: ${error}`)
97
+ }
98
+ }
99
+
100
+ build()
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env ruby
2
+ # Adapted from https://github.com/primer/view_components/blob/main/script/update-statuses-project.rb
3
+ # Usage: script/update-statuses-project
4
+ # frozen_string_literal: true
5
+
6
+ require "graphql/client"
7
+ require "graphql/client/http"
8
+
9
+ statuses = File.read(File.join(File.dirname(__FILE__), "../../dist/component-status.json"))
10
+ statuses_json = JSON.parse(statuses)
11
+
12
+ class QueryExecutionError < StandardError; end
13
+ NOTE_SEPARATOR = " --- "
14
+
15
+ module Github
16
+ GITHUB_ACCESS_TOKEN = ENV.fetch("GITHUB_TOKEN")
17
+ URL = "https://api.github.com/graphql"
18
+ HttpAdapter = GraphQL::Client::HTTP.new(URL) do
19
+ def headers(_)
20
+ {
21
+ "Authorization" => "Bearer #{GITHUB_ACCESS_TOKEN}",
22
+ "User-Agent" => "Ruby"
23
+ }
24
+ end
25
+ end
26
+ Schema = GraphQL::Client.load_schema(HttpAdapter)
27
+ Client = GraphQL::Client.new(schema: Schema, execute: HttpAdapter)
28
+ end
29
+
30
+ # Project is a GraphQL wrapper for interacting with GitHub projects
31
+ class Project
32
+ ProjectQuery = Github::Client.parse <<-'GRAPHQL'
33
+ query {
34
+ repository(owner: "primer", name: "react") {
35
+ project(number: 5) {
36
+ columns(first: 100) {
37
+ nodes {
38
+ name
39
+ id
40
+ databaseId
41
+ cards {
42
+ nodes {
43
+ id
44
+ databaseId
45
+ note
46
+ column {
47
+ name
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ GRAPHQL
57
+
58
+ CreateCard = Github::Client.parse <<-'GRAPHQL'
59
+ mutation($note: String!, $projectColumnId: ID!) {
60
+ addProjectCard(input:{note: $note, projectColumnId: $projectColumnId, clientMutationId: "prc-actions"}) {
61
+ __typename
62
+ }
63
+ }
64
+ GRAPHQL
65
+
66
+ MoveCard = Github::Client.parse <<-'GRAPHQL'
67
+ mutation($cardId: ID!, $columnId: ID!) {
68
+ moveProjectCard(input:{cardId: $cardId, columnId: $columnId, clientMutationId: "prc-actions"}) {
69
+ __typename
70
+ }
71
+ }
72
+ GRAPHQL
73
+
74
+ def self.create_card(note:, column_id:)
75
+ response = Github::Client.query(CreateCard, variables: { note: note, projectColumnId: column_id })
76
+ return unless response.errors.any?
77
+
78
+ raise QueryExecutionError, response.errors[:data].join(", ")
79
+ end
80
+
81
+ def self.move_card(card_id:, column_id:)
82
+ response = Github::Client.query(MoveCard, variables: { cardId: card_id, columnId: column_id })
83
+ return unless response.errors.any?
84
+
85
+ raise(QueryExecutionError, response.errors[:data].join(", "))
86
+ end
87
+
88
+ def self.fetch_columns
89
+ response = Github::Client.query(ProjectQuery)
90
+ return response.data.repository.project.columns unless response.errors.any?
91
+
92
+ raise(QueryExecutionError, response.errors[:data].join(", "))
93
+ end
94
+ end
95
+
96
+ columns = Project.fetch_columns.nodes
97
+
98
+ @column_mapping = {}
99
+ columns.each do |column|
100
+ @column_mapping[column.name.downcase] = column.id
101
+ end
102
+
103
+ @cards = columns.map(&:cards).map(&:nodes).flatten
104
+
105
+ def get_card(name_prefix:)
106
+ @cards.find { |card| card.note.start_with?(name_prefix) }
107
+ end
108
+
109
+ def on_correct_column?(card_id:, status:)
110
+ card = @cards.find { |c| c.id == card_id }
111
+ card.column.name.casecmp(status).zero?
112
+ end
113
+
114
+ def move_card(card_id:, status:)
115
+ column_id = @column_mapping[status.downcase]
116
+
117
+ puts "move card with #{card_id} to #{status} on column #{column_id}"
118
+
119
+ Project.move_card(card_id: card_id, column_id: column_id)
120
+ end
121
+
122
+ def create_card(component_name:, status:)
123
+ column_id = @column_mapping[status.downcase]
124
+
125
+ puts "create card with #{component_name} on #{status} on column #{column_id}"
126
+
127
+ Project.create_card(note: component_name, column_id: column_id)
128
+ end
129
+
130
+ statuses_json.each do |component_name, component_status|
131
+ card = get_card(name_prefix: component_name)
132
+
133
+ if card
134
+ if on_correct_column?(card_id: card.id, status: component_status)
135
+ puts "#{card.id} is on the right column. noop"
136
+ else
137
+ move_card(card_id: card.id, status: component_status)
138
+ end
139
+ else
140
+ create_card(component_name: component_name, status: component_status)
141
+ end
142
+ end
@@ -0,0 +1,14 @@
1
+ /** This context can be used by components that compose ActionList inside a Menu */
2
+
3
+ import React from 'react'
4
+
5
+ type ContextProps = {
6
+ container?: string
7
+ listRole?: string
8
+ itemRole?: string
9
+ // This can be any function, we don't know anything about the arguments
10
+ // to be more specific here, this is as good as (...args: any[]) => unknown
11
+ // eslint-disable-next-line @typescript-eslint/ban-types
12
+ afterSelect?: Function
13
+ }
14
+ export const ActionListContainerContext = React.createContext<ContextProps>({})
@@ -2,22 +2,27 @@ import React from 'react'
2
2
  import Box from '../Box'
3
3
  import {get} from '../constants'
4
4
  import {Theme} from '../ThemeProvider'
5
+ import {SxProp, merge} from '../sx'
5
6
 
6
7
  /**
7
8
  * Visually separates `Item`s or `Group`s in an `ActionList`.
8
9
  */
9
- export function Divider(): JSX.Element {
10
+
11
+ export const Divider: React.FC<SxProp> = ({sx = {}}) => {
10
12
  return (
11
13
  <Box
12
14
  as="li"
13
15
  role="separator"
14
- sx={{
15
- height: 1,
16
- backgroundColor: 'actionListItem.inlineDivider',
17
- marginTop: (theme: Theme) => `calc(${get('space.2')(theme)} - 1px)`,
18
- marginBottom: 2,
19
- listStyle: 'none' // hide the ::marker inserted by browser's stylesheet
20
- }}
16
+ sx={merge(
17
+ {
18
+ height: 1,
19
+ backgroundColor: 'actionListItem.inlineDivider',
20
+ marginTop: (theme: Theme) => `calc(${get('space.2')(theme)} - 1px)`,
21
+ marginBottom: 2,
22
+ listStyle: 'none' // hide the ::marker inserted by browser's stylesheet
23
+ },
24
+ sx as SxProp
25
+ )}
21
26
  data-component="ActionList.Divider"
22
27
  />
23
28
  )
@@ -8,6 +8,7 @@ import sx, {SxProp, merge} from '../sx'
8
8
  import createSlots from '../utils/create-slots'
9
9
  import {AriaRole} from '../utils/types'
10
10
  import {ListContext} from './List'
11
+ import {ActionListContainerContext} from './ActionListContainerContext'
11
12
  import {Selection} from './Selection'
12
13
 
13
14
  export const getVariantStyles = (variant: ItemProps['variant'], disabled: ItemProps['disabled']) => {
@@ -94,12 +95,14 @@ export const Item = React.forwardRef<HTMLLIElement, ItemProps>(
94
95
  onSelect,
95
96
  sx: sxProp = {},
96
97
  id,
98
+ role,
97
99
  _PrivateItemWrapper,
98
100
  ...props
99
101
  },
100
102
  forwardedRef
101
103
  ): JSX.Element => {
102
104
  const {variant: listVariant, showDividers} = React.useContext(ListContext)
105
+ const {itemRole, afterSelect} = React.useContext(ActionListContainerContext)
103
106
 
104
107
  const {theme} = useTheme()
105
108
 
@@ -168,22 +171,26 @@ export const Item = React.forwardRef<HTMLLIElement, ItemProps>(
168
171
 
169
172
  const clickHandler = React.useCallback(
170
173
  event => {
171
- if (typeof onSelect !== 'function') return
172
174
  if (disabled) return
173
- if (!event.defaultPrevented) onSelect(event)
175
+ if (!event.defaultPrevented) {
176
+ if (typeof onSelect === 'function') onSelect(event)
177
+ // if this Item is inside a Menu, close the Menu
178
+ if (typeof afterSelect === 'function') afterSelect()
179
+ }
174
180
  },
175
- [onSelect, disabled]
181
+ [onSelect, disabled, afterSelect]
176
182
  )
177
183
 
178
184
  const keyPressHandler = React.useCallback(
179
185
  event => {
180
- if (typeof onSelect !== 'function') return
181
186
  if (disabled) return
182
187
  if (!event.defaultPrevented && [' ', 'Enter'].includes(event.key)) {
183
- onSelect(event)
188
+ if (typeof onSelect === 'function') onSelect(event)
189
+ // if this Item is inside a Menu, close the Menu
190
+ if (typeof afterSelect === 'function') afterSelect()
184
191
  }
185
192
  },
186
- [onSelect, disabled]
193
+ [onSelect, disabled, afterSelect]
187
194
  )
188
195
 
189
196
  // use props.id if provided, otherwise generate one.
@@ -206,6 +213,7 @@ export const Item = React.forwardRef<HTMLLIElement, ItemProps>(
206
213
  tabIndex={disabled || _PrivateItemWrapper ? undefined : 0}
207
214
  aria-labelledby={`${labelId} ${slots.InlineDescription ? inlineDescriptionId : ''}`}
208
215
  aria-describedby={slots.BlockDescription ? blockDescriptionId : undefined}
216
+ role={role || itemRole}
209
217
  {...props}
210
218
  >
211
219
  <ItemWrapper>
@@ -3,6 +3,7 @@ import {ForwardRefComponent as PolymorphicForwardRefComponent} from '@radix-ui/r
3
3
  import styled from 'styled-components'
4
4
  import sx, {SxProp, merge} from '../sx'
5
5
  import {AriaRole} from '../utils/types'
6
+ import {ActionListContainerContext} from './ActionListContainerContext'
6
7
 
7
8
  export type ListProps = {
8
9
  /**
@@ -30,7 +31,7 @@ const ListBox = styled.ul<SxProp>(sx)
30
31
 
31
32
  export const List = React.forwardRef<HTMLUListElement, ListProps>(
32
33
  (
33
- {variant = 'inset', selectionVariant, showDividers = false, sx: sxProp = {}, ...props},
34
+ {variant = 'inset', selectionVariant, showDividers = false, role, sx: sxProp = {}, ...props},
34
35
  forwardedRef
35
36
  ): JSX.Element => {
36
37
  const styles = {
@@ -39,8 +40,11 @@ export const List = React.forwardRef<HTMLUListElement, ListProps>(
39
40
  paddingY: variant === 'inset' ? 2 : 0
40
41
  }
41
42
 
43
+ /** if list is inside a Menu, it will get a role from the Menu */
44
+ const {listRole} = React.useContext(ActionListContainerContext)
45
+
42
46
  return (
43
- <ListBox sx={merge(styles, sxProp as SxProp)} {...props} ref={forwardedRef}>
47
+ <ListBox sx={merge(styles, sxProp as SxProp)} role={role || listRole} {...props} ref={forwardedRef}>
44
48
  <ListContext.Provider value={{variant, selectionVariant, showDividers}}>{props.children}</ListContext.Provider>
45
49
  </ListBox>
46
50
  )
@@ -2,6 +2,7 @@ import React from 'react'
2
2
  import {CheckIcon} from '@primer/octicons-react'
3
3
  import {ListContext} from './List'
4
4
  import {GroupContext} from './Group'
5
+ import {ActionListContainerContext} from './ActionListContainerContext'
5
6
  import {ItemProps} from './Item'
6
7
  import {LeadingVisualContainer} from './Visuals'
7
8
 
@@ -9,6 +10,7 @@ type SelectionProps = Pick<ItemProps, 'selected'>
9
10
  export const Selection: React.FC<SelectionProps> = ({selected}) => {
10
11
  const {selectionVariant: listSelectionVariant} = React.useContext(ListContext)
11
12
  const {selectionVariant: groupSelectionVariant} = React.useContext(GroupContext)
13
+ const {container} = React.useContext(ActionListContainerContext)
12
14
 
13
15
  /** selectionVariant in Group can override the selectionVariant in List root */
14
16
  const selectionVariant = typeof groupSelectionVariant !== 'undefined' ? groupSelectionVariant : listSelectionVariant
@@ -23,6 +25,13 @@ export const Selection: React.FC<SelectionProps> = ({selected}) => {
23
25
  return null
24
26
  }
25
27
 
28
+ if (container === 'ActionMenu') {
29
+ throw new Error(
30
+ 'ActionList cannot have a selectionVariant inside ActionMenu, please use DropdownMenu or SelectPanel instead. More information: https://primer.style/design/components/action-list#application'
31
+ )
32
+ return null
33
+ }
34
+
26
35
  if (selectionVariant === 'single') {
27
36
  return <LeadingVisualContainer>{selected && <CheckIcon />}</LeadingVisualContainer>
28
37
  }
@@ -0,0 +1,116 @@
1
+ import Button, {ButtonProps} from './Button'
2
+ import React from 'react'
3
+ import {AnchoredOverlay, AnchoredOverlayProps} from './AnchoredOverlay'
4
+ import {OverlayProps} from './Overlay'
5
+ import {useProvidedRefOrCreate, useProvidedStateOrCreate} from './hooks'
6
+ import {Divider} from './ActionList2/Divider'
7
+ import {ActionListContainerContext} from './ActionList2/ActionListContainerContext'
8
+ import {MandateProps} from './utils/types'
9
+
10
+ type MenuContextProps = Pick<AnchoredOverlayProps, 'anchorRef' | 'renderAnchor' | 'open' | 'onOpen' | 'onClose'>
11
+ export const MenuContext = React.createContext<MenuContextProps>({renderAnchor: null, open: false})
12
+
13
+ export type ActionMenuProps = {
14
+ /**
15
+ * Recommended: `ActionMenu.Button` or `ActionMenu.Anchor` with `ActionMenu.Overlay`
16
+ */
17
+ children: React.ReactElement[] | React.ReactElement
18
+
19
+ /**
20
+ * If defined, will control the open/closed state of the overlay. Must be used in conjuction with `onOpenChange`.
21
+ */
22
+ open?: boolean
23
+
24
+ /**
25
+ * If defined, will control the open/closed state of the overlay. Must be used in conjuction with `open`.
26
+ */
27
+ onOpenChange?: (s: boolean) => void
28
+ } & Pick<AnchoredOverlayProps, 'anchorRef'>
29
+
30
+ const Menu: React.FC<ActionMenuProps> = ({
31
+ anchorRef: externalAnchorRef,
32
+ open,
33
+ onOpenChange,
34
+ children
35
+ }: ActionMenuProps) => {
36
+ const [combinedOpenState, setCombinedOpenState] = useProvidedStateOrCreate(open, onOpenChange, false)
37
+ const onOpen = React.useCallback(() => setCombinedOpenState(true), [setCombinedOpenState])
38
+ const onClose = React.useCallback(() => setCombinedOpenState(false), [setCombinedOpenState])
39
+
40
+ const anchorRef = useProvidedRefOrCreate(externalAnchorRef)
41
+ let renderAnchor: AnchoredOverlayProps['renderAnchor'] = null
42
+
43
+ // 🚨 Hack for good API!
44
+ // we strip out Anchor from children and pass it to AnchoredOverlay to render
45
+ // with additional props for accessibility
46
+ const contents = React.Children.map(children, child => {
47
+ if (child.type === MenuButton || child.type === Anchor) {
48
+ renderAnchor = anchorProps => React.cloneElement(child, anchorProps)
49
+ return null
50
+ }
51
+ return child
52
+ })
53
+
54
+ return (
55
+ <MenuContext.Provider value={{anchorRef, renderAnchor, open: combinedOpenState, onOpen, onClose}}>
56
+ {contents}
57
+ </MenuContext.Provider>
58
+ )
59
+ }
60
+
61
+ export type MenuAnchorProps = {children: React.ReactElement}
62
+ const Anchor = React.forwardRef<AnchoredOverlayProps['anchorRef'], MenuAnchorProps>(
63
+ ({children, ...anchorProps}, anchorRef) => {
64
+ return React.cloneElement(children, {...anchorProps, ref: anchorRef})
65
+ }
66
+ )
67
+
68
+ /** this component is syntactical sugar 🍭 */
69
+ export type MenuButtonProps = ButtonProps
70
+ const MenuButton = React.forwardRef<AnchoredOverlayProps['anchorRef'], ButtonProps>((props, anchorRef) => {
71
+ return (
72
+ <Anchor ref={anchorRef}>
73
+ <Button {...props} />
74
+ </Anchor>
75
+ )
76
+ })
77
+
78
+ type MenuOverlayProps = Partial<OverlayProps> & {
79
+ /**
80
+ * Recommended: `ActionList`
81
+ */
82
+ children: React.ReactElement[] | React.ReactElement
83
+ }
84
+ const Overlay: React.FC<MenuOverlayProps> = ({children, ...overlayProps}) => {
85
+ // we typecast anchorRef as required instead of optional
86
+ // because we know that we're setting it in context in Menu
87
+ const {anchorRef, renderAnchor, open, onOpen, onClose} = React.useContext(MenuContext) as MandateProps<
88
+ MenuContextProps,
89
+ 'anchorRef'
90
+ >
91
+
92
+ return (
93
+ <AnchoredOverlay
94
+ anchorRef={anchorRef}
95
+ renderAnchor={renderAnchor}
96
+ open={open}
97
+ onOpen={onOpen}
98
+ onClose={onClose}
99
+ overlayProps={overlayProps}
100
+ >
101
+ <ActionListContainerContext.Provider
102
+ value={{
103
+ container: 'ActionMenu',
104
+ listRole: 'menu',
105
+ itemRole: 'menuitem',
106
+ afterSelect: onClose
107
+ }}
108
+ >
109
+ {children}
110
+ </ActionListContainerContext.Provider>
111
+ </AnchoredOverlay>
112
+ )
113
+ }
114
+
115
+ Menu.displayName = 'ActionMenu'
116
+ export const ActionMenu = Object.assign(Menu, {Button: MenuButton, Anchor, Overlay, Divider})
@@ -8,9 +8,10 @@ const BranchName = styled.a<SxProp>`
8
8
  padding: 2px 6px;
9
9
  font-size: ${get('fontSizes.0')};
10
10
  font-family: ${get('fonts.mono')};
11
- color: ${get('colors.fg.muted')};
11
+ color: ${get('colors.accent.fg')};
12
12
  background-color: ${get('colors.accent.subtle')};
13
13
  border-radius: ${get('radii.2')};
14
+ text-decoration: none;
14
15
 
15
16
  ${sx};
16
17
  `