@primer/components 32.1.1-rc.b4502a34 → 33.0.0-rc.9f3670b7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/contributor-docs/CONTRIBUTING.md +14 -58
  3. package/dist/browser.esm.js +104 -108
  4. package/dist/browser.esm.js.map +1 -1
  5. package/dist/browser.umd.js +104 -108
  6. package/dist/browser.umd.js.map +1 -1
  7. package/docs/content/BranchName.md +6 -5
  8. package/docs/content/Details.md +4 -8
  9. package/docs/content/Heading.md +5 -10
  10. package/docs/content/Label.md +6 -7
  11. package/docs/content/ProgressBar.mdx +7 -6
  12. package/docs/content/Text.md +0 -6
  13. package/docs/content/{ActionList2.mdx → drafts/ActionList2.mdx} +5 -9
  14. package/docs/content/drafts/ActionMenu2.mdx +251 -0
  15. package/docs/content/status.mdx +1 -1
  16. package/docs/content/system-props.mdx +1 -1
  17. package/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js +9 -1
  18. package/docs/src/@primer/gatsby-theme-doctocat/nav.yml +1 -1
  19. package/lib/ActionList2/Divider.d.ts +3 -2
  20. package/lib/ActionList2/Divider.js +10 -5
  21. package/lib/ActionList2/Item.js +21 -5
  22. package/lib/ActionList2/List.js +11 -1
  23. package/lib/ActionList2/MenuContext.d.ts +10 -0
  24. package/lib/ActionList2/MenuContext.js +15 -0
  25. package/lib/ActionList2/Selection.js +11 -0
  26. package/lib/ActionList2/index.d.ts +1 -2
  27. package/lib/ActionMenu2.d.ts +310 -0
  28. package/lib/ActionMenu2.js +91 -0
  29. package/lib/Avatar.d.ts +1 -2
  30. package/lib/Avatar.js +1 -1
  31. package/lib/BranchName.d.ts +1 -2
  32. package/lib/BranchName.js +1 -1
  33. package/lib/Details.d.ts +1 -2
  34. package/lib/Details.js +1 -3
  35. package/lib/Dropdown.d.ts +2 -66
  36. package/lib/Heading.d.ts +1 -2
  37. package/lib/Heading.js +1 -6
  38. package/lib/ProgressBar.d.ts +16 -11
  39. package/lib/ProgressBar.js +6 -10
  40. package/lib/Spinner.d.ts +1 -2
  41. package/lib/Spinner.js +1 -3
  42. package/lib/__tests__/Avatar.test.js +4 -2
  43. package/lib/__tests__/Avatar.types.test.d.ts +3 -0
  44. package/lib/__tests__/Avatar.types.test.js +31 -0
  45. package/lib/__tests__/BranchName.types.test.d.ts +3 -0
  46. package/lib/__tests__/BranchName.types.test.js +28 -0
  47. package/lib/__tests__/Details.types.test.d.ts +3 -0
  48. package/lib/__tests__/Details.types.test.js +28 -0
  49. package/lib/__tests__/Heading.test.js +63 -30
  50. package/lib/__tests__/Heading.types.test.d.ts +3 -0
  51. package/lib/__tests__/Heading.types.test.js +28 -0
  52. package/lib/drafts.d.ts +1 -0
  53. package/lib/drafts.js +13 -0
  54. package/lib/stories/ActionMenu2.stories.js +433 -0
  55. package/lib-esm/ActionList2/Divider.d.ts +3 -2
  56. package/lib-esm/ActionList2/Divider.js +8 -5
  57. package/lib-esm/ActionList2/Item.js +19 -5
  58. package/lib-esm/ActionList2/List.js +9 -1
  59. package/lib-esm/ActionList2/MenuContext.d.ts +10 -0
  60. package/lib-esm/ActionList2/MenuContext.js +3 -0
  61. package/lib-esm/ActionList2/Selection.js +9 -0
  62. package/lib-esm/ActionList2/index.d.ts +1 -2
  63. package/lib-esm/ActionMenu2.d.ts +310 -0
  64. package/lib-esm/ActionMenu2.js +67 -0
  65. package/lib-esm/Avatar.d.ts +1 -2
  66. package/lib-esm/Avatar.js +2 -2
  67. package/lib-esm/BranchName.d.ts +1 -2
  68. package/lib-esm/BranchName.js +2 -2
  69. package/lib-esm/Details.d.ts +1 -2
  70. package/lib-esm/Details.js +1 -2
  71. package/lib-esm/Dropdown.d.ts +2 -66
  72. package/lib-esm/Heading.d.ts +1 -2
  73. package/lib-esm/Heading.js +2 -6
  74. package/lib-esm/ProgressBar.d.ts +16 -11
  75. package/lib-esm/ProgressBar.js +7 -11
  76. package/lib-esm/Spinner.d.ts +1 -2
  77. package/lib-esm/Spinner.js +1 -2
  78. package/lib-esm/__tests__/Avatar.test.js +4 -2
  79. package/lib-esm/__tests__/Avatar.types.test.d.ts +3 -0
  80. package/lib-esm/__tests__/Avatar.types.test.js +16 -0
  81. package/lib-esm/__tests__/BranchName.types.test.d.ts +3 -0
  82. package/lib-esm/__tests__/BranchName.types.test.js +13 -0
  83. package/lib-esm/__tests__/Details.types.test.d.ts +3 -0
  84. package/lib-esm/__tests__/Details.types.test.js +13 -0
  85. package/lib-esm/__tests__/Heading.test.js +62 -30
  86. package/lib-esm/__tests__/Heading.types.test.d.ts +3 -0
  87. package/lib-esm/__tests__/Heading.types.test.js +13 -0
  88. package/lib-esm/drafts.d.ts +1 -0
  89. package/lib-esm/drafts.js +2 -1
  90. package/lib-esm/stories/ActionMenu2.stories.js +376 -0
  91. package/package.json +1 -1
  92. package/src/ActionList2/Divider.tsx +13 -8
  93. package/src/ActionList2/Item.tsx +13 -3
  94. package/src/ActionList2/List.tsx +6 -2
  95. package/src/ActionList2/MenuContext.tsx +6 -0
  96. package/src/ActionList2/Selection.tsx +9 -0
  97. package/src/ActionMenu2.tsx +94 -0
  98. package/src/Avatar.tsx +2 -4
  99. package/src/BranchName.tsx +3 -3
  100. package/src/Details.tsx +1 -5
  101. package/src/Heading.tsx +2 -9
  102. package/src/ProgressBar.tsx +11 -10
  103. package/src/Spinner.tsx +1 -3
  104. package/src/__tests__/Avatar.test.tsx +1 -1
  105. package/src/__tests__/Avatar.types.test.tsx +11 -0
  106. package/src/__tests__/BranchName.types.test.tsx +11 -0
  107. package/src/__tests__/Details.types.test.tsx +11 -0
  108. package/src/__tests__/Heading.test.tsx +71 -25
  109. package/src/__tests__/Heading.types.test.tsx +11 -0
  110. package/src/drafts.ts +1 -0
  111. package/src/stories/ActionMenu2.stories.tsx +551 -0
  112. package/stats.html +1 -1
@@ -0,0 +1,376 @@
1
+ function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
+
3
+ import React from 'react';
4
+ import { ThemeProvider } from '..';
5
+ import BaseStyles from '../BaseStyles';
6
+ import { ActionMenu } from '../ActionMenu2';
7
+ import { ActionList } from '../ActionList2';
8
+ import Button, { ButtonInvisible } from '../Button';
9
+ import Box from '../Box';
10
+ import Text from '../Text';
11
+ import TextInput from '../TextInput';
12
+ import StyledOcticon from '../StyledOcticon';
13
+ import FormGroup from '../FormGroup';
14
+ import { ServerIcon, PlusCircleIcon, TriangleDownIcon, KebabHorizontalIcon, PencilIcon, ArchiveIcon, TrashIcon, ProjectIcon, ListUnorderedIcon, ArrowDownIcon, SearchIcon, VersionsIcon, TableIcon } from '@primer/octicons-react';
15
+ const meta = {
16
+ title: 'Composite components/ActionMenu2',
17
+ component: ActionMenu,
18
+ decorators: [Story => /*#__PURE__*/React.createElement(ThemeProvider, null, /*#__PURE__*/React.createElement(BaseStyles, null, /*#__PURE__*/React.createElement(Story, null)))],
19
+ parameters: {
20
+ controls: {
21
+ disabled: true
22
+ }
23
+ }
24
+ };
25
+ export default meta;
26
+ export function SimpleListStory() {
27
+ const [actionFired, fireAction] = React.useState('');
28
+
29
+ const onSelect = name => fireAction(name);
30
+
31
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "Simple Menu"), /*#__PURE__*/React.createElement("h2", null, "Last option activated: ", actionFired), /*#__PURE__*/React.createElement(ActionMenu, null, /*#__PURE__*/React.createElement(ActionMenu.Button, null, "Menu"), /*#__PURE__*/React.createElement(ActionList, null, /*#__PURE__*/React.createElement(ActionList.Item, {
32
+ onSelect: () => onSelect('Copy link')
33
+ }, "Copy link", /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318C")), /*#__PURE__*/React.createElement(ActionList.Item, {
34
+ onSelect: () => onSelect('Quote reply')
35
+ }, "Quote reply", /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318Q")), /*#__PURE__*/React.createElement(ActionList.Item, {
36
+ onSelect: () => onSelect('Edit comment')
37
+ }, "Edit comment", /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318E")), /*#__PURE__*/React.createElement(ActionList.Divider, null), /*#__PURE__*/React.createElement(ActionList.Item, {
38
+ variant: "danger",
39
+ onSelect: () => onSelect('Delete file')
40
+ }, "Delete file", /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318D")))));
41
+ }
42
+ SimpleListStory.storyName = 'Simple Menu';
43
+ export function ActionsStory() {
44
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "Actions"), /*#__PURE__*/React.createElement(ActionMenu, {
45
+ overlayProps: {
46
+ width: 'medium'
47
+ }
48
+ }, /*#__PURE__*/React.createElement(ActionMenu.Button, {
49
+ "aria-label": "Open Actions Menu"
50
+ }, /*#__PURE__*/React.createElement(ServerIcon, null)), /*#__PURE__*/React.createElement(ActionList, null, /*#__PURE__*/React.createElement(ActionList.Item, null, /*#__PURE__*/React.createElement(ActionList.LeadingVisual, null, /*#__PURE__*/React.createElement(ServerIcon, null)), "Open current Codespace", /*#__PURE__*/React.createElement(ActionList.Description, {
51
+ variant: "block"
52
+ }, "Your existing Codespace will be opened to its previous state, and you'll be asked to manually switch to new-branch."), /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318O")), /*#__PURE__*/React.createElement(ActionList.Item, null, /*#__PURE__*/React.createElement(ActionList.LeadingVisual, null, /*#__PURE__*/React.createElement(PlusCircleIcon, null)), "Create new Codespace", /*#__PURE__*/React.createElement(ActionList.Description, {
53
+ variant: "block"
54
+ }, "Create a brand new Codespace with a fresh image and checkout this branch."), /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318C")))));
55
+ }
56
+ ActionsStory.storyName = 'Actions';
57
+ export function ExternalAnchor() {
58
+ const [actionFired, fireAction] = React.useState('');
59
+
60
+ const onSelect = name => fireAction(name);
61
+
62
+ const [open, setOpen] = React.useState(false);
63
+ const triggerRef = /*#__PURE__*/React.createRef();
64
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "External Anchor"), /*#__PURE__*/React.createElement("h2", null, "External Open State: ", open ? 'Open' : 'Closed'), /*#__PURE__*/React.createElement("h2", null, "Last option activated: ", actionFired), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Button, {
65
+ ref: triggerRef,
66
+ onClick: () => setOpen(!open)
67
+ }, open ? 'Close Menu' : 'Open Menu')), /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement(ActionMenu, {
68
+ open: open,
69
+ onOpenChange: setOpen,
70
+ anchorRef: triggerRef
71
+ }, /*#__PURE__*/React.createElement(ActionList, null, /*#__PURE__*/React.createElement(ActionList.Item, {
72
+ onSelect: () => onSelect('Copy link')
73
+ }, "Copy link", /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318C")), /*#__PURE__*/React.createElement(ActionList.Item, {
74
+ onSelect: () => onSelect('Quote reply')
75
+ }, "Quote reply", /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318Q")), /*#__PURE__*/React.createElement(ActionList.Item, {
76
+ onSelect: () => onSelect('Edit comment')
77
+ }, "Edit comment", /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318E")), /*#__PURE__*/React.createElement(ActionList.Divider, null), /*#__PURE__*/React.createElement(ActionList.Item, {
78
+ variant: "danger",
79
+ onSelect: () => onSelect('Delete file')
80
+ }, "Delete file", /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318D")))));
81
+ }
82
+ ExternalAnchor.storyName = 'External Anchor';
83
+ export function ControlledMenu() {
84
+ const [actionFired, fireAction] = React.useState('');
85
+
86
+ const onSelect = name => fireAction(name);
87
+
88
+ const [open, setOpen] = React.useState(false);
89
+ const triggerRef = /*#__PURE__*/React.createRef();
90
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "Controlled Menu"), /*#__PURE__*/React.createElement("h2", null, "External Open State: ", open ? 'Open' : 'Closed'), /*#__PURE__*/React.createElement("h2", null, "Last option activated: ", actionFired), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Button, {
91
+ ref: triggerRef,
92
+ onClick: () => setOpen(!open)
93
+ }, open ? 'Close Menu' : 'Open Menu')), /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement(ActionMenu, {
94
+ open: open,
95
+ onOpenChange: setOpen,
96
+ overlayProps: {
97
+ // Because the component is controlled from outside, but the anchor is still internal,
98
+ // clicking the external button should not be counted as "clicking outside"
99
+ ignoreClickRefs: [triggerRef]
100
+ }
101
+ }, /*#__PURE__*/React.createElement(ActionMenu.Button, null, "Anchor"), /*#__PURE__*/React.createElement(ActionList, null, /*#__PURE__*/React.createElement(ActionList.Item, {
102
+ onSelect: () => onSelect('Copy link')
103
+ }, "Copy link", /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318C")), /*#__PURE__*/React.createElement(ActionList.Item, {
104
+ onSelect: () => onSelect('Quote reply')
105
+ }, "Quote reply", /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318Q")), /*#__PURE__*/React.createElement(ActionList.Item, {
106
+ onSelect: () => onSelect('Edit comment')
107
+ }, "Edit comment", /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318E")), /*#__PURE__*/React.createElement(ActionList.Divider, null), /*#__PURE__*/React.createElement(ActionList.Item, {
108
+ variant: "danger",
109
+ onSelect: () => onSelect('Delete file')
110
+ }, "Delete file", /*#__PURE__*/React.createElement(ActionList.TrailingVisual, null, "\u2318D")))));
111
+ }
112
+ ControlledMenu.storyName = 'Controlled Menu';
113
+ export function CustomAnchor() {
114
+ const [actionFired, fireAction] = React.useState('');
115
+
116
+ const onSelect = name => fireAction(name);
117
+
118
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "Custom Anchor"), /*#__PURE__*/React.createElement("h2", null, "Last option activated: ", actionFired), /*#__PURE__*/React.createElement(ActionMenu, null, /*#__PURE__*/React.createElement(ActionMenu.Anchor, null, /*#__PURE__*/React.createElement("summary", {
119
+ style: {
120
+ cursor: 'pointer'
121
+ },
122
+ "aria-label": "Open column options"
123
+ }, /*#__PURE__*/React.createElement(KebabHorizontalIcon, null))), /*#__PURE__*/React.createElement(ActionList, null, /*#__PURE__*/React.createElement(ActionList.Item, {
124
+ onSelect: () => onSelect('Rename')
125
+ }, /*#__PURE__*/React.createElement(ActionList.LeadingVisual, null, /*#__PURE__*/React.createElement(PencilIcon, null)), "Rename"), /*#__PURE__*/React.createElement(ActionList.Item, {
126
+ onSelect: () => onSelect('Archive')
127
+ }, /*#__PURE__*/React.createElement(ActionList.LeadingVisual, null, /*#__PURE__*/React.createElement(ArchiveIcon, null)), "Archive all cards"), /*#__PURE__*/React.createElement(ActionList.Item, {
128
+ variant: "danger",
129
+ onSelect: () => onSelect('Delete file')
130
+ }, /*#__PURE__*/React.createElement(ActionList.LeadingVisual, null, /*#__PURE__*/React.createElement(TrashIcon, null)), "Delete"))));
131
+ }
132
+ CustomAnchor.storyName = 'Custom Anchor';
133
+ export function MemexTableMenu() {
134
+ const [name, setName] = React.useState('Estimate');
135
+ const inputRef = /*#__PURE__*/React.createRef();
136
+ /** To add custom components to the Menu,
137
+ * you need to switch to a controlled menu
138
+ */
139
+
140
+ const [open, setOpen] = React.useState(false);
141
+
142
+ const handleKeyPress = event => {
143
+ if (event.key === 'Enter') {
144
+ setName(event.currentTarget.value);
145
+ setOpen(false);
146
+ }
147
+ };
148
+ /** This requires inside knowledge. If you to do this with onBlur
149
+ * on the input, it doesn't work :(
150
+ */
151
+
152
+
153
+ const handleClickOutside = () => {
154
+ if (inputRef.current) setName(inputRef.current.value);
155
+ setOpen(false);
156
+ };
157
+
158
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "Memex Table Menu"), /*#__PURE__*/React.createElement(Box, {
159
+ sx: {
160
+ width: 200,
161
+ display: 'flex',
162
+ justifyContent: 'space-between',
163
+ p: 2,
164
+ border: '1px solid',
165
+ borderColor: 'border.default'
166
+ }
167
+ }, /*#__PURE__*/React.createElement(Text, {
168
+ sx: {
169
+ fontSize: 0,
170
+ fontWeight: 'bold'
171
+ }
172
+ }, name), /*#__PURE__*/React.createElement(ActionMenu, {
173
+ open: open,
174
+ onOpenChange: setOpen,
175
+ overlayProps: {
176
+ onClickOutside: handleClickOutside
177
+ }
178
+ }, /*#__PURE__*/React.createElement(ActionMenu.Button, {
179
+ "aria-label": "Open Estimate column options menu",
180
+ sx: {
181
+ p: 0,
182
+ display: 'flex',
183
+ alignItems: 'center',
184
+ justifyContent: 'center'
185
+ }
186
+ }, /*#__PURE__*/React.createElement(TriangleDownIcon, null)), /*#__PURE__*/React.createElement(TextInput, {
187
+ ref: inputRef,
188
+ sx: {
189
+ m: 2
190
+ },
191
+ defaultValue: name,
192
+ onKeyPress: handleKeyPress
193
+ }), /*#__PURE__*/React.createElement(ActionMenu.Divider, {
194
+ sx: {
195
+ m: 0
196
+ }
197
+ }), /*#__PURE__*/React.createElement(ActionList, null, /*#__PURE__*/React.createElement(ActionList.Item, null, "Sort ascending (123...)"), /*#__PURE__*/React.createElement(ActionList.Item, null, "Sort descending (123...)"), /*#__PURE__*/React.createElement(ActionList.Divider, null), /*#__PURE__*/React.createElement(ActionList.Item, null, "Filter by values"), /*#__PURE__*/React.createElement(ActionList.Item, null, "Group by values"), /*#__PURE__*/React.createElement(ActionList.Divider, null), /*#__PURE__*/React.createElement(ActionList.Item, {
198
+ disabled: true
199
+ }, "Hide field"), /*#__PURE__*/React.createElement(ActionList.Item, {
200
+ variant: "danger"
201
+ }, "Delete file")))));
202
+ }
203
+ MemexTableMenu.storyName = 'Memex Table Menu';
204
+ /* copied from github/memex */
205
+
206
+ const LayoutToggleItem = ({
207
+ selected,
208
+ children,
209
+ Icon,
210
+ ...props
211
+ }) => {
212
+ return /*#__PURE__*/React.createElement(FormGroup, {
213
+ sx: {
214
+ flex: 'auto',
215
+ borderRadius: 2,
216
+ border: '1px solid',
217
+ borderColor: selected ? 'accent.emphasis' : 'border.default',
218
+ textAlign: 'center',
219
+ cursor: 'pointer',
220
+ backgroundColor: selected ? 'accent.subtle' : '',
221
+ boxShadow: selected ? theme => `inset 0 0 0 1px ${theme.colors.accent.emphasis}` : '',
222
+ mb: 2,
223
+ mt: 1,
224
+ '&:hover': {
225
+ backgroundColor: !selected ? 'canvas.subtle' : ''
226
+ },
227
+ '&:first-of-type': {
228
+ borderTopRightRadius: '0px',
229
+ borderBottomRightRadius: '0px',
230
+ borderRight: selected ? undefined : '0'
231
+ },
232
+ '&:last-of-type': {
233
+ borderTopLeftRadius: '0px',
234
+ borderBottomLeftRadius: '0px',
235
+ borderLeft: selected ? undefined : '0'
236
+ }
237
+ }
238
+ }, /*#__PURE__*/React.createElement(FormGroup.Label, _extends({
239
+ htmlFor: "layout-selector",
240
+ sx: {
241
+ fontWeight: 'normal',
242
+ cursor: 'pointer',
243
+ px: 3,
244
+ py: 2,
245
+ mb: 0
246
+ }
247
+ }, props), /*#__PURE__*/React.createElement(Box, {
248
+ sx: {
249
+ textAlign: 'center',
250
+ flexDirection: 'column',
251
+ m: 'auto',
252
+ alignItems: 'center',
253
+ display: 'flex'
254
+ }
255
+ }, /*#__PURE__*/React.createElement(Icon, {
256
+ size: "medium"
257
+ }), /*#__PURE__*/React.createElement(Text, {
258
+ sx: {
259
+ color: selected ? 'fg.default' : 'fg.muted',
260
+ fontSize: 0
261
+ }
262
+ }, children))));
263
+ };
264
+
265
+ LayoutToggleItem.displayName = "LayoutToggleItem";
266
+
267
+ /* copied from github/memex */
268
+ const ViewChangeButtons = ({
269
+ setOpen
270
+ }) => /*#__PURE__*/React.createElement(Box, {
271
+ sx: {
272
+ display: 'flex'
273
+ }
274
+ }, /*#__PURE__*/React.createElement(ButtonInvisible, {
275
+ onClick: () => setOpen(false),
276
+ sx: {
277
+ flex: 'auto',
278
+ minWidth: '50%',
279
+ borderRight: '1px solid',
280
+ borderColor: 'border.default',
281
+ borderRadius: 0,
282
+ mt: -2,
283
+ mb: -2,
284
+ py: 3,
285
+ '&:hover': {
286
+ bg: 'canvas.inset'
287
+ }
288
+ }
289
+ }, "Save changes"), /*#__PURE__*/React.createElement(ButtonInvisible, {
290
+ onClick: () => setOpen(false),
291
+ sx: {
292
+ flex: 'auto',
293
+ color: 'fg.muted',
294
+ borderRadius: 0,
295
+ mt: -2,
296
+ mb: -2,
297
+ py: 3,
298
+ fontWeight: 'normal',
299
+ '&:hover': {
300
+ bg: 'canvas.inset'
301
+ }
302
+ }
303
+ }, "Discard changes"));
304
+
305
+ ViewChangeButtons.displayName = "ViewChangeButtons";
306
+ export function MemexViewOptionsMenu() {
307
+ const [open, setOpen] = React.useState(false);
308
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "Memex View Options Menu"), /*#__PURE__*/React.createElement(Box, {
309
+ sx: {
310
+ display: 'flex',
311
+ alignItems: 'center'
312
+ }
313
+ }, /*#__PURE__*/React.createElement(Text, {
314
+ sx: {
315
+ fontSize: 1,
316
+ mr: 3
317
+ }
318
+ }, /*#__PURE__*/React.createElement(StyledOcticon, {
319
+ icon: ProjectIcon,
320
+ sx: {
321
+ mr: 2
322
+ }
323
+ }), "React"), /*#__PURE__*/React.createElement(ActionMenu, {
324
+ open: open,
325
+ onOpenChange: setOpen,
326
+ overlayProps: {
327
+ width: 'medium'
328
+ }
329
+ }, /*#__PURE__*/React.createElement(ActionMenu.Button, {
330
+ "aria-label": "Open View options menu",
331
+ sx: {
332
+ p: 0,
333
+ width: 18,
334
+ height: 18,
335
+ display: 'flex',
336
+ alignItems: 'center',
337
+ justifyContent: 'center'
338
+ }
339
+ }, /*#__PURE__*/React.createElement(TriangleDownIcon, null)), /*#__PURE__*/React.createElement(ActionList, null, /*#__PURE__*/React.createElement(ActionList.Group, {
340
+ title: "Layout"
341
+ }, /*#__PURE__*/React.createElement("li", {
342
+ style: {
343
+ listStyle: 'none'
344
+ }
345
+ }, /*#__PURE__*/React.createElement(Box, {
346
+ sx: {
347
+ mx: 3,
348
+ display: 'flex'
349
+ }
350
+ }, /*#__PURE__*/React.createElement(LayoutToggleItem, {
351
+ selected: true,
352
+ Icon: TableIcon
353
+ }, "Table"), /*#__PURE__*/React.createElement(LayoutToggleItem, {
354
+ selected: false,
355
+ Icon: ProjectIcon
356
+ }, "Board")))), /*#__PURE__*/React.createElement(ActionList.Divider, null), /*#__PURE__*/React.createElement(ActionList.Group, {
357
+ title: "Configuration"
358
+ }, /*#__PURE__*/React.createElement(ActionList.Item, null, /*#__PURE__*/React.createElement(ActionList.LeadingVisual, null, /*#__PURE__*/React.createElement(ListUnorderedIcon, null)), "Title, Assignees, Status, Labels, Repositories"), /*#__PURE__*/React.createElement(ActionList.Item, null, /*#__PURE__*/React.createElement(ActionList.LeadingVisual, null, /*#__PURE__*/React.createElement(ListUnorderedIcon, null)), "group: none"), /*#__PURE__*/React.createElement(ActionList.Item, null, /*#__PURE__*/React.createElement(ActionList.LeadingVisual, null, /*#__PURE__*/React.createElement(ArrowDownIcon, null)), "sort: manual"), /*#__PURE__*/React.createElement(ActionList.Item, null, /*#__PURE__*/React.createElement(ActionList.LeadingVisual, null, /*#__PURE__*/React.createElement(SearchIcon, null)), "Search or filter this view")), /*#__PURE__*/React.createElement(ActionList.Divider, null), /*#__PURE__*/React.createElement(ActionList.Item, null, /*#__PURE__*/React.createElement(ActionList.LeadingVisual, null, /*#__PURE__*/React.createElement(PencilIcon, null)), "Rename view"), /*#__PURE__*/React.createElement(ActionList.Item, null, /*#__PURE__*/React.createElement(ActionList.LeadingVisual, null, /*#__PURE__*/React.createElement(VersionsIcon, null)), "Save changes to new view"), /*#__PURE__*/React.createElement(ActionList.Item, {
359
+ disabled: true
360
+ }, /*#__PURE__*/React.createElement(ActionList.LeadingVisual, null, /*#__PURE__*/React.createElement(TrashIcon, null)), "Delete view"), /*#__PURE__*/React.createElement(ActionList.Divider, null), /*#__PURE__*/React.createElement("li", {
361
+ style: {
362
+ listStyle: 'none'
363
+ }
364
+ }, /*#__PURE__*/React.createElement(ViewChangeButtons, {
365
+ setOpen: setOpen
366
+ }))))));
367
+ }
368
+ MemexViewOptionsMenu.storyName = 'Memex View Options Menu';
369
+ export function UnexpectedSelectionVariant() {
370
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", null, "Expect error if selectionVariant is passed"), /*#__PURE__*/React.createElement(ActionMenu, null, /*#__PURE__*/React.createElement(ActionMenu.Button, null, "Menu"), /*#__PURE__*/React.createElement(ActionList, {
371
+ selectionVariant: "multiple"
372
+ }, /*#__PURE__*/React.createElement(ActionList.Item, null, "Copy link"), /*#__PURE__*/React.createElement(ActionList.Item, null, "Quote reply"), /*#__PURE__*/React.createElement(ActionList.Item, null, "Edit comment"), /*#__PURE__*/React.createElement(ActionList.Divider, null), /*#__PURE__*/React.createElement(ActionList.Item, {
373
+ variant: "danger"
374
+ }, "Delete file"))));
375
+ }
376
+ UnexpectedSelectionVariant.storyName = 'Unexpected selectionVariant';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/components",
3
- "version": "32.1.1-rc.b4502a34",
3
+ "version": "33.0.0-rc.9f3670b7",
4
4
  "description": "Primer react components",
5
5
  "main": "lib/index.js",
6
6
  "module": "lib-esm/index.js",
@@ -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 {MenuContext} from './MenuContext'
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(MenuContext)
103
106
 
104
107
  const {theme} = useTheme()
105
108
 
@@ -170,9 +173,13 @@ export const Item = React.forwardRef<HTMLLIElement, ItemProps>(
170
173
  event => {
171
174
  if (typeof onSelect !== 'function') return
172
175
  if (disabled) return
173
- if (!event.defaultPrevented) onSelect(event)
176
+ if (!event.defaultPrevented) {
177
+ onSelect(event)
178
+ // if this Item is inside a Menu, close the Menu
179
+ if (typeof afterSelect === 'function') afterSelect()
180
+ }
174
181
  },
175
- [onSelect, disabled]
182
+ [onSelect, disabled, afterSelect]
176
183
  )
177
184
 
178
185
  const keyPressHandler = React.useCallback(
@@ -181,9 +188,11 @@ export const Item = React.forwardRef<HTMLLIElement, ItemProps>(
181
188
  if (disabled) return
182
189
  if (!event.defaultPrevented && [' ', 'Enter'].includes(event.key)) {
183
190
  onSelect(event)
191
+ // if this Item is inside a Menu, close the Menu
192
+ if (typeof afterSelect === 'function') afterSelect()
184
193
  }
185
194
  },
186
- [onSelect, disabled]
195
+ [onSelect, disabled, afterSelect]
187
196
  )
188
197
 
189
198
  // use props.id if provided, otherwise generate one.
@@ -206,6 +215,7 @@ export const Item = React.forwardRef<HTMLLIElement, ItemProps>(
206
215
  tabIndex={disabled || _PrivateItemWrapper ? undefined : 0}
207
216
  aria-labelledby={`${labelId} ${slots.InlineDescription ? inlineDescriptionId : ''}`}
208
217
  aria-describedby={slots.BlockDescription ? blockDescriptionId : undefined}
218
+ role={role || itemRole}
209
219
  {...props}
210
220
  >
211
221
  <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 {MenuContext} from './MenuContext'
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(MenuContext)
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
  )
@@ -0,0 +1,6 @@
1
+ /** This context can be used by components that compose ActionList inside a Menu */
2
+
3
+ import React from 'react'
4
+
5
+ type ContextProps = {parent?: string; listRole?: string; itemRole?: string; afterSelect?: () => void}
6
+ export const MenuContext = React.createContext<ContextProps>({})
@@ -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 {MenuContext} from './MenuContext'
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 {parent} = React.useContext(MenuContext)
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 (parent === '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,94 @@
1
+ import Button, {ButtonProps} from './Button'
2
+ import React from 'react'
3
+ import {AnchoredOverlay} from './AnchoredOverlay'
4
+ import {useProvidedStateOrCreate} from './hooks/useProvidedStateOrCreate'
5
+ import {OverlayProps} from './Overlay'
6
+ import {useProvidedRefOrCreate} from './hooks'
7
+ import {AnchoredOverlayWrapperAnchorProps} from './AnchoredOverlay/AnchoredOverlay'
8
+ import {Divider} from './ActionList2/Divider'
9
+ import {MenuContext as ActionListMenuContext} from './ActionList2/MenuContext'
10
+
11
+ type ActionMenuBaseProps = {
12
+ /**
13
+ * Recommended: `ActionMenu.Button` or `ActionMenu.Anchor` with ActionList`
14
+ */
15
+ children: React.ReactElement[] | React.ReactElement
16
+
17
+ /**
18
+ * If defined, will control the open/closed state of the overlay. Must be used in conjuction with `onOpenChange`.
19
+ */
20
+ open?: boolean
21
+
22
+ /**
23
+ * If defined, will control the open/closed state of the overlay. Must be used in conjuction with `open`.
24
+ */
25
+ onOpenChange?: (s: boolean) => void
26
+
27
+ /**
28
+ * Props to be spread on the internal `Overlay` component.
29
+ */
30
+ overlayProps?: Partial<OverlayProps>
31
+ }
32
+
33
+ export type ActionMenuProps = ActionMenuBaseProps & AnchoredOverlayWrapperAnchorProps
34
+
35
+ const ActionMenuBase: React.FC<ActionMenuProps> = ({
36
+ anchorRef: externalAnchorRef,
37
+ open,
38
+ onOpenChange,
39
+ overlayProps,
40
+ children
41
+ }: ActionMenuProps) => {
42
+ const [combinedOpenState, setCombinedOpenState] = useProvidedStateOrCreate(open, onOpenChange, false)
43
+ const anchorRef = useProvidedRefOrCreate(externalAnchorRef)
44
+ const onOpen = React.useCallback(() => setCombinedOpenState(true), [setCombinedOpenState])
45
+ const onClose = React.useCallback(() => setCombinedOpenState(false), [setCombinedOpenState])
46
+
47
+ let renderAnchor: AnchoredOverlayWrapperAnchorProps['renderAnchor'] = null
48
+ const contents: React.ReactElement[] = []
49
+
50
+ React.Children.map(children, child => {
51
+ if (child.type === MenuButton || child.type === Anchor) {
52
+ renderAnchor = anchorProps => React.cloneElement(child, anchorProps)
53
+ } else {
54
+ contents.push(child)
55
+ }
56
+ })
57
+
58
+ return (
59
+ <AnchoredOverlay
60
+ renderAnchor={renderAnchor}
61
+ anchorRef={anchorRef}
62
+ open={combinedOpenState}
63
+ onOpen={onOpen}
64
+ onClose={onClose}
65
+ overlayProps={overlayProps}
66
+ >
67
+ <ActionListMenuContext.Provider
68
+ value={{parent: 'ActionMenu', listRole: 'menu', itemRole: 'menuitem', afterSelect: onClose}}
69
+ >
70
+ {contents}
71
+ </ActionListMenuContext.Provider>
72
+ </AnchoredOverlay>
73
+ )
74
+ }
75
+
76
+ type AnchorRef = AnchoredOverlayWrapperAnchorProps['anchorRef']
77
+
78
+ export type MenuAnchorProps = {children: React.ReactElement}
79
+ const Anchor = React.forwardRef<AnchorRef, MenuAnchorProps>(({children, ...anchorProps}, anchorRef) => {
80
+ return React.cloneElement(children, {...anchorProps, ref: anchorRef})
81
+ })
82
+
83
+ /** this component is syntactical sugar 🍭 */
84
+ export type MenuButtonProps = ButtonProps
85
+ const MenuButton = React.forwardRef<AnchorRef, ButtonProps>((props, anchorRef) => {
86
+ return (
87
+ <Anchor ref={anchorRef}>
88
+ <Button {...props} />
89
+ </Anchor>
90
+ )
91
+ })
92
+
93
+ ActionMenuBase.displayName = 'ActionMenu'
94
+ export const ActionMenu = Object.assign(ActionMenuBase, {Button: MenuButton, Anchor, Divider})
package/src/Avatar.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import styled from 'styled-components'
2
- import {COMMON, get, SystemCommonProps} from './constants'
2
+ import {get} from './constants'
3
3
  import sx, {SxProp} from './sx'
4
4
  import {ComponentProps} from './utils/types'
5
5
 
@@ -12,8 +12,7 @@ type StyledAvatarProps = {
12
12
  src: string
13
13
  /** Provide alt text when the Avatar is used without the user's name next to it. */
14
14
  alt?: string
15
- } & SystemCommonProps &
16
- SxProp
15
+ } & SxProp
17
16
 
18
17
  function getBorderRadius({size, square}: StyledAvatarProps) {
19
18
  if (square) {
@@ -32,7 +31,6 @@ const Avatar = styled.img.attrs<StyledAvatarProps>(props => ({
32
31
  line-height: ${get('lineHeights.condensedUltra')};
33
32
  vertical-align: middle;
34
33
  border-radius: ${props => getBorderRadius(props)};
35
- ${COMMON};
36
34
  ${sx}
37
35
  `
38
36