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

Sign up to get free protection for your applications and to get access to all the features.
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