@sproutsocial/seeds-react-menu 1.12.3 → 1.15.10

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 (35) hide show
  1. package/.turbo/turbo-build.log +12 -12
  2. package/CHANGELOG.md +193 -0
  3. package/dist/esm/v3/index.js +726 -186
  4. package/dist/esm/v3/index.js.map +1 -1
  5. package/dist/index.d.mts +22 -22
  6. package/dist/index.d.ts +22 -22
  7. package/dist/v3/index.d.mts +199 -50
  8. package/dist/v3/index.d.ts +199 -50
  9. package/dist/v3/index.js +732 -185
  10. package/dist/v3/index.js.map +1 -1
  11. package/package.json +14 -14
  12. package/src/v3/ActionMenu.stories.tsx +260 -0
  13. package/src/v3/ActionMenu.tsx +345 -0
  14. package/src/v3/ActionMenuStyles.tsx +154 -0
  15. package/src/v3/Common/ComboboxStyles.tsx +41 -7
  16. package/src/v3/Common/SelectStyles.tsx +20 -6
  17. package/src/v3/Common/useSuppressEscapeClear.ts +20 -0
  18. package/src/v3/MenuButton.stories.tsx +118 -0
  19. package/src/v3/MenuButton.tsx +68 -0
  20. package/src/v3/MenuButtonStyles.tsx +38 -0
  21. package/src/v3/ModalStories.stories.tsx +159 -0
  22. package/src/v3/MultiCombobox.stories.tsx +157 -0
  23. package/src/v3/MultiCombobox.tsx +28 -8
  24. package/src/v3/MultiSearchableSelect.stories.tsx +42 -0
  25. package/src/v3/MultiSearchableSelect.tsx +11 -6
  26. package/src/v3/MultiSelect.stories.tsx +40 -0
  27. package/src/v3/MultiSelect.tsx +12 -3
  28. package/src/v3/SingleCombobox.stories.tsx +36 -0
  29. package/src/v3/SingleCombobox.tsx +17 -7
  30. package/src/v3/SingleSearchableSelect.stories.tsx +38 -0
  31. package/src/v3/SingleSearchableSelect.tsx +15 -7
  32. package/src/v3/SingleSelect.stories.tsx +36 -0
  33. package/src/v3/SingleSelect.tsx +12 -3
  34. package/src/v3/__tests__/MenuButton.test.tsx +80 -0
  35. package/src/v3/index.ts +2 -0
@@ -0,0 +1,154 @@
1
+ import { Menu } from "@base-ui/react/menu";
2
+ import styled, { css } from "styled-components";
3
+ import { focusRing } from "@sproutsocial/seeds-react-mixins";
4
+
5
+ export const ActionMenuTrigger = styled(Menu.Trigger)``;
6
+
7
+ export const ActionMenuPositioner = styled(Menu.Positioner)`
8
+ min-width: var(--anchor-width);
9
+ z-index: 7;
10
+ `;
11
+
12
+ // Opt out of Base UI Drawer's swipe-to-dismiss pointer capture when the menu
13
+ // is rendered inside a Drawer. Without this, the Drawer's viewport captures
14
+ // pointermove/up on portaled popups and MenuItem clicks never fire.
15
+ export const ActionMenuPopup = styled(Menu.Popup).attrs({
16
+ "data-base-ui-swipe-ignore": "",
17
+ })`
18
+ font-family: ${({ theme }) => theme.fontFamily};
19
+ background: ${({ theme }) => theme.colors.container.background.base};
20
+ border: 1px solid ${({ theme }) => theme.colors.container.border.base};
21
+ border-radius: ${({ theme }) => theme.radii[500]};
22
+ box-shadow: ${({ theme }) => theme.shadows.low};
23
+ outline: none;
24
+ max-height: min(var(--available-height, 400px), 400px);
25
+ overflow-y: auto;
26
+ padding: ${({ theme }) => theme.space[300]};
27
+
28
+ &[data-open] {
29
+ animation: action-menu-popup-in 120ms ease-out;
30
+ }
31
+
32
+ &[data-closed] {
33
+ animation: action-menu-popup-out 100ms ease-in;
34
+ }
35
+
36
+ @keyframes action-menu-popup-in {
37
+ from {
38
+ opacity: 0;
39
+ transform: scale(0.95);
40
+ }
41
+ to {
42
+ opacity: 1;
43
+ transform: scale(1);
44
+ }
45
+ }
46
+
47
+ @keyframes action-menu-popup-out {
48
+ from {
49
+ opacity: 1;
50
+ transform: scale(1);
51
+ }
52
+ to {
53
+ opacity: 0;
54
+ transform: scale(0.95);
55
+ }
56
+ }
57
+ `;
58
+
59
+ export const ActionMenuGroup = styled(Menu.Group)`
60
+ display: block;
61
+ `;
62
+
63
+ export const ActionMenuGroupLabel = styled(Menu.GroupLabel)`
64
+ padding: ${({ theme }) => `${theme.space[300]}`};
65
+ font-size: ${({ theme }) => theme.typography[100].fontSize};
66
+ color: ${({ theme }) => theme.colors.text.body};
67
+ `;
68
+
69
+ export const ActionMenuSeparator = styled(Menu.Separator)`
70
+ height: 1px;
71
+ margin: ${({ theme }) => `${theme.space[300]} -${theme.space[300]}`};
72
+ background: ${({ theme }) => theme.colors.container.border.base};
73
+ `;
74
+
75
+ const menuItemStyles = css`
76
+ box-sizing: border-box;
77
+ display: block;
78
+ width: 100%;
79
+ border-radius: ${({ theme }) => theme.radii[500]};
80
+ background-color: ${({ theme }) => theme.colors.listItem.background.base};
81
+ border: 1px solid ${({ theme }) => theme.colors.listItem.background.base};
82
+ color: ${({ theme }) => theme.colors.text.body};
83
+ font-family: ${({ theme }) => theme.fontFamily};
84
+ font-weight: ${({ theme }) => theme.fontWeights.normal};
85
+ text-align: left;
86
+ text-decoration: none;
87
+ padding: ${({ theme }) => theme.space[300]};
88
+ outline: 0;
89
+ ${({ theme }) => theme.typography[200]};
90
+ transition: all ${({ theme }) => theme.duration.fast};
91
+ cursor: pointer;
92
+ user-select: none;
93
+
94
+ &[data-highlighted] {
95
+ border-color: ${({ theme }) => theme.colors.listItem.border.base};
96
+ background-color: ${({ theme }) => theme.colors.listItem.background.hover};
97
+ color: ${({ theme }) => theme.colors.text.body};
98
+ text-decoration: none;
99
+ }
100
+
101
+ &[data-disabled] {
102
+ cursor: not-allowed;
103
+ opacity: 0.4;
104
+ }
105
+ `;
106
+
107
+ export const StyledMenuItem = styled(Menu.Item)`
108
+ ${menuItemStyles}
109
+ `;
110
+
111
+ export const StyledCheckboxItem = styled(Menu.CheckboxItem)`
112
+ ${menuItemStyles}
113
+ display: flex;
114
+ align-items: center;
115
+ gap: ${({ theme }) => theme.space[300]};
116
+ `;
117
+
118
+ export const StyledRadioItem = styled(Menu.RadioItem)`
119
+ ${menuItemStyles}
120
+ display: flex;
121
+ align-items: center;
122
+ gap: ${({ theme }) => theme.space[300]};
123
+ `;
124
+
125
+ export const StyledCheckboxItemIndicator = styled(Menu.CheckboxItemIndicator)`
126
+ display: flex;
127
+ align-items: center;
128
+ width: 16px;
129
+ color: ${({ theme }) => theme.colors.text.body};
130
+ `;
131
+
132
+ export const StyledRadioItemIndicator = styled(Menu.RadioItemIndicator)`
133
+ display: flex;
134
+ align-items: center;
135
+ width: 16px;
136
+ color: ${({ theme }) => theme.colors.text.body};
137
+ `;
138
+
139
+ export const StyledSubmenuTrigger = styled(Menu.SubmenuTrigger)`
140
+ ${menuItemStyles}
141
+ display: flex;
142
+ align-items: center;
143
+ gap: ${({ theme }) => theme.space[300]};
144
+ justify-content: space-between;
145
+
146
+ &[data-popup-open] {
147
+ border-color: ${({ theme }) => theme.colors.listItem.border.base};
148
+ background-color: ${({ theme }) => theme.colors.listItem.background.hover};
149
+ }
150
+ `;
151
+
152
+ export const StyledLinkItem = styled(Menu.LinkItem)`
153
+ ${menuItemStyles}
154
+ `;
@@ -1,7 +1,16 @@
1
1
  import { Combobox } from "@base-ui/react/combobox";
2
- import styled from "styled-components";
2
+ import styled, { css } from "styled-components";
3
3
  import { focusRing } from "@sproutsocial/seeds-react-mixins";
4
4
  import Checkbox from "@sproutsocial/seeds-react-checkbox";
5
+ import { StyledChevron } from "./SelectIcons";
6
+
7
+ const invalidStyles = css`
8
+ border-color: ${({ theme }) => theme.colors.form.border.error};
9
+
10
+ ${StyledChevron} {
11
+ color: ${({ theme }) => theme.colors.icon.error};
12
+ }
13
+ `;
5
14
 
6
15
  export const ComboboxLabel = styled.label`
7
16
  font-size: ${({ theme }) => theme.typography[200].fontSize};
@@ -9,7 +18,9 @@ export const ComboboxLabel = styled.label`
9
18
  color: ${({ theme }) => theme.colors.text.body};
10
19
  `;
11
20
 
12
- export const ComboboxInputGroup = styled(Combobox.InputGroup)`
21
+ export const ComboboxInputGroup = styled(Combobox.InputGroup)<{
22
+ $isInvalid?: boolean;
23
+ }>`
13
24
  display: flex;
14
25
  align-items: center;
15
26
  min-width: 160px;
@@ -30,6 +41,8 @@ export const ComboboxInputGroup = styled(Combobox.InputGroup)`
30
41
  opacity: 0.4;
31
42
  cursor: not-allowed;
32
43
  }
44
+
45
+ ${({ $isInvalid }) => $isInvalid && invalidStyles}
33
46
  `;
34
47
 
35
48
  export const ComboboxInput = styled(Combobox.Input)`
@@ -81,7 +94,7 @@ export const ComboboxActionButtons = styled.div`
81
94
  gap: ${({ theme }) => theme.space[100]};
82
95
  `;
83
96
 
84
- export const ComboboxClear = styled(Combobox.Clear)`
97
+ export const ComboboxClear = styled(Combobox.Clear).attrs({ tabIndex: 0 })`
85
98
  display: flex;
86
99
  align-items: center;
87
100
  justify-content: center;
@@ -98,6 +111,10 @@ export const ComboboxClear = styled(Combobox.Clear)`
98
111
  color: ${({ theme }) => theme.colors.text.body};
99
112
  }
100
113
 
114
+ &:focus-visible {
115
+ ${focusRing}
116
+ }
117
+
101
118
  &[data-empty] {
102
119
  display: none;
103
120
  }
@@ -133,7 +150,7 @@ export const ComboboxPopup = styled(Combobox.Popup)`
133
150
  box-shadow: ${({ theme }) => theme.shadows.medium};
134
151
  outline: none;
135
152
  width: 100%;
136
- max-height: min(var(--base-ui-available-height, 400px), 400px);
153
+ max-height: min(var(--available-height, 400px), 400px);
137
154
  overflow-y: auto;
138
155
  padding: 0;
139
156
 
@@ -315,9 +332,12 @@ export const ComboboxToken = styled(Combobox.Chips)`
315
332
  display: flex;
316
333
  width: 100%;
317
334
  flex-wrap: wrap;
335
+ gap: ${({ theme }) => theme.space[200]};
318
336
  `;
319
337
 
320
- export const ComboboxTokenInputGroup = styled(Combobox.InputGroup)`
338
+ export const ComboboxTokenInputGroup = styled(Combobox.InputGroup)<{
339
+ $isInvalid?: boolean;
340
+ }>`
321
341
  display: flex;
322
342
  align-items: center;
323
343
  flex-wrap: wrap;
@@ -341,6 +361,8 @@ export const ComboboxTokenInputGroup = styled(Combobox.InputGroup)`
341
361
  opacity: 0.4;
342
362
  cursor: not-allowed;
343
363
  }
364
+
365
+ ${({ $isInvalid }) => $isInvalid && invalidStyles}
344
366
  `;
345
367
 
346
368
  export const Token = styled.span`
@@ -375,11 +397,17 @@ export const TokenRemove = styled.button`
375
397
  cursor: pointer;
376
398
  padding: 0;
377
399
  line-height: 1;
400
+
401
+ &:focus-visible {
402
+ ${focusRing}
403
+ }
378
404
  `;
379
405
 
380
406
  // Searchable select components
381
407
 
382
- export const SearchableSelectTrigger = styled(Combobox.Trigger)`
408
+ export const SearchableSelectTrigger = styled(Combobox.Trigger)<{
409
+ $isInvalid?: boolean;
410
+ }>`
383
411
  display: flex;
384
412
  align-items: center;
385
413
  justify-content: space-between;
@@ -411,6 +439,8 @@ export const SearchableSelectTrigger = styled(Combobox.Trigger)`
411
439
  opacity: 0.4;
412
440
  cursor: not-allowed;
413
441
  }
442
+
443
+ ${({ $isInvalid }) => $isInvalid && invalidStyles}
414
444
  `;
415
445
 
416
446
  export const SearchableSelectTriggerIcon = styled(Combobox.Icon)`
@@ -464,7 +494,9 @@ export const SearchableSelectList = styled(Combobox.List)`
464
494
  }
465
495
  `;
466
496
 
467
- export const SearchableSelectTokenTrigger = styled(Combobox.Trigger)`
497
+ export const SearchableSelectTokenTrigger = styled(Combobox.Trigger)<{
498
+ $isInvalid?: boolean;
499
+ }>`
468
500
  display: flex;
469
501
  align-items: center;
470
502
  flex-wrap: wrap;
@@ -500,4 +532,6 @@ export const SearchableSelectTokenTrigger = styled(Combobox.Trigger)`
500
532
  opacity: 0.4;
501
533
  cursor: not-allowed;
502
534
  }
535
+
536
+ ${({ $isInvalid }) => $isInvalid && invalidStyles}
503
537
  `;
@@ -1,12 +1,22 @@
1
1
  import { Select } from "@base-ui/react/select";
2
- import styled from "styled-components";
2
+ import styled, { css } from "styled-components";
3
3
  import { focusRing } from "@sproutsocial/seeds-react-mixins";
4
+ import { StyledChevron } from "./SelectIcons";
4
5
 
5
- export const Field = styled.div`
6
- display: flex;
6
+ const invalidStyles = css`
7
+ border-color: ${({ theme }) => theme.colors.form.border.error};
8
+
9
+ ${StyledChevron} {
10
+ color: ${({ theme }) => theme.colors.icon.error};
11
+ }
12
+ `;
13
+
14
+ export const Field = styled.div<{ $fullWidth?: boolean }>`
15
+ display: ${({ $fullWidth }) =>
16
+ $fullWidth === false ? "inline-flex" : "flex"};
7
17
  flex-direction: column;
8
18
  gap: ${({ theme }) => theme.space[200]};
9
- width: 100%;
19
+ ${({ $fullWidth }) => $fullWidth !== false && "width: 100%;"}
10
20
  `;
11
21
 
12
22
  export const SelectLabel = styled(Select.Label)`
@@ -15,7 +25,9 @@ export const SelectLabel = styled(Select.Label)`
15
25
  color: ${({ theme }) => theme.colors.text.body};
16
26
  `;
17
27
 
18
- export const SelectTrigger = styled(Select.Trigger)`
28
+ export const SelectTrigger = styled(Select.Trigger)<{
29
+ $isInvalid?: boolean;
30
+ }>`
19
31
  display: flex;
20
32
  align-items: center;
21
33
  justify-content: space-between;
@@ -50,6 +62,8 @@ export const SelectTrigger = styled(Select.Trigger)`
50
62
  opacity: 0.4;
51
63
  cursor: not-allowed;
52
64
  }
65
+
66
+ ${({ $isInvalid }) => $isInvalid && invalidStyles}
53
67
  `;
54
68
 
55
69
  export const SelectValue = styled(Select.Value)`
@@ -96,7 +110,7 @@ export const SelectPopup = styled(Select.Popup)`
96
110
  border-radius: ${({ theme }) => theme.radii[500]};
97
111
  box-shadow: ${({ theme }) => theme.shadows.low};
98
112
  outline: none;
99
- max-height: min(var(--base-ui-available-height, 400px), 400px);
113
+ max-height: min(var(--available-height, 400px), 400px);
100
114
  overflow-y: auto;
101
115
  padding: ${({ theme }) => theme.space[200]};
102
116
 
@@ -0,0 +1,20 @@
1
+ import * as React from "react";
2
+
3
+ // When the popup is closed, Base UI's ComboboxInput onKeyDown handler clears
4
+ // both the typed input value and the committed selection on Escape (an
5
+ // ARIA-permitted but destructive interpretation of the combobox pattern). We
6
+ // suppress that path by stopping the synthetic event in capture phase before
7
+ // it reaches the input's React bubble-phase handler. The popup-open Escape
8
+ // (handled by Base UI's document listener in useDismiss) and the parent
9
+ // Modal/Dialog Escape (handled by Radix's document-capture listener, which
10
+ // runs before any React synthetic dispatch) are both unaffected.
11
+ export function useSuppressEscapeClear(open: boolean) {
12
+ return React.useCallback(
13
+ (event: React.KeyboardEvent) => {
14
+ if (event.key === "Escape" && !open) {
15
+ event.stopPropagation();
16
+ }
17
+ },
18
+ [open]
19
+ );
20
+ }
@@ -0,0 +1,118 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { action } from "@storybook/addon-actions";
4
+ import { Box } from "@sproutsocial/seeds-react-box";
5
+ import { MenuButton } from "./MenuButton";
6
+ import { MenuItem } from "./ActionMenu";
7
+
8
+ const meta: Meta<typeof MenuButton> = {
9
+ title: "Really Under Development/V3/MenuButton",
10
+ component: MenuButton,
11
+ decorators: [
12
+ (Story) => (
13
+ <Box>
14
+ <Story />
15
+ </Box>
16
+ ),
17
+ ],
18
+ };
19
+
20
+ export default meta;
21
+ type Story = StoryObj<typeof MenuButton>;
22
+
23
+ // ─── Basic ────────────────────────────────────────────────────────────────────
24
+
25
+ export const Basic: Story = {
26
+ name: "Basic",
27
+ render: () => (
28
+ <MenuButton
29
+ menuButtonAriaLabel="More compose options"
30
+ actionMenu={
31
+ <>
32
+ <MenuItem onClick={action("Compose")}>Compose</MenuItem>
33
+ <MenuItem onClick={action("View Messages")}>View Messages</MenuItem>
34
+ <MenuItem onClick={action("Analyze post")}>Analyze post</MenuItem>
35
+ </>
36
+ }
37
+ onClick={action("Open Menu")}
38
+ >
39
+ Open Menu
40
+ </MenuButton>
41
+ ),
42
+ };
43
+
44
+ // ─── Primary ──────────────────────────────────────────────────────────────────
45
+
46
+ export const Primary: Story = {
47
+ name: "Primary",
48
+ render: () => {
49
+ // The dropdown selects the action; the main button reflects and performs it.
50
+ const actions = [
51
+ { label: "Save as Draft", run: action("Save as Draft") },
52
+ { label: "Save and Activate", run: action("Save and Activate") },
53
+ ];
54
+ const [selected, setSelected] = React.useState(actions[0]);
55
+
56
+ return (
57
+ <MenuButton
58
+ appearance="primary"
59
+ menuButtonAriaLabel="More save options"
60
+ actionMenu={
61
+ <>
62
+ {actions.map((a) => (
63
+ <MenuItem key={a.label} onClick={() => setSelected(a)}>
64
+ {a.label}
65
+ </MenuItem>
66
+ ))}
67
+ </>
68
+ }
69
+ onClick={(e) => selected.run(e)}
70
+ >
71
+ {selected.label}
72
+ </MenuButton>
73
+ );
74
+ },
75
+ };
76
+
77
+ // ─── Secondary ────────────────────────────────────────────────────────────────
78
+
79
+ export const Secondary: Story = {
80
+ name: "Secondary",
81
+ render: () => (
82
+ <MenuButton
83
+ appearance="secondary"
84
+ menuButtonAriaLabel="More actions"
85
+ actionMenu={
86
+ <>
87
+ <MenuItem onClick={action("Compose")}>Compose</MenuItem>
88
+ <MenuItem onClick={action("View Messages")}>View Messages</MenuItem>
89
+ </>
90
+ }
91
+ onClick={action("Open Menu")}
92
+ >
93
+ Open Menu
94
+ </MenuButton>
95
+ ),
96
+ };
97
+
98
+ // ─── Disabled ─────────────────────────────────────────────────────────────────
99
+
100
+ export const Disabled: Story = {
101
+ name: "Disabled",
102
+ render: () => (
103
+ <MenuButton
104
+ appearance="primary"
105
+ disabled
106
+ menuButtonAriaLabel="More actions"
107
+ actionMenu={
108
+ <>
109
+ <MenuItem onClick={action("Compose")}>Compose</MenuItem>
110
+ <MenuItem onClick={action("View Messages")}>View Messages</MenuItem>
111
+ </>
112
+ }
113
+ onClick={action("Open Menu")}
114
+ >
115
+ Open Menu
116
+ </MenuButton>
117
+ ),
118
+ };
@@ -0,0 +1,68 @@
1
+ import * as React from "react";
2
+ import { type TypeButtonProps } from "@sproutsocial/seeds-react-button";
3
+ import { Icon } from "@sproutsocial/seeds-react-icon";
4
+ import { ActionMenu } from "./ActionMenu";
5
+ import {
6
+ MenuButtonContainer,
7
+ MainButton,
8
+ TriggerButton,
9
+ } from "./MenuButtonStyles";
10
+
11
+ // ─── MenuButton ─────────────────────────────────────────────────────────────
12
+
13
+ export interface MenuButtonProps extends TypeButtonProps {
14
+ /**
15
+ * Menu item content rendered inside the dropdown half — the children of an
16
+ * ActionMenu (MenuItems / MenuGroups).
17
+ */
18
+ actionMenu: React.ReactNode;
19
+ /**
20
+ * Accessible label for the chevron-only trigger button. Required so consumers
21
+ * supply a localizable label rather than relying on a hardcoded fallback.
22
+ */
23
+ menuButtonAriaLabel: string;
24
+ }
25
+
26
+ /**
27
+ * A "split button": a primary Button rendered immediately next to an ActionMenu
28
+ * whose trigger is an identical Button showing only a chevron-down icon. The
29
+ * two read as one control — the main button's right corners and the trigger's
30
+ * left corners are squared off with a 1px gap between them.
31
+ */
32
+ export function MenuButton({
33
+ actionMenu,
34
+ appearance = "primary",
35
+ size,
36
+ disabled,
37
+ menuButtonAriaLabel,
38
+ children,
39
+ ...rest
40
+ }: MenuButtonProps) {
41
+ return (
42
+ <MenuButtonContainer>
43
+ <MainButton
44
+ appearance={appearance}
45
+ size={size}
46
+ disabled={disabled}
47
+ {...rest}
48
+ >
49
+ {children}
50
+ </MainButton>
51
+ <ActionMenu
52
+ disabled={disabled}
53
+ trigger={
54
+ <TriggerButton
55
+ appearance={appearance}
56
+ size={size}
57
+ disabled={disabled}
58
+ ariaLabel={menuButtonAriaLabel}
59
+ >
60
+ <Icon name="chevron-down-outline" fixedWidth aria-hidden />
61
+ </TriggerButton>
62
+ }
63
+ >
64
+ {actionMenu}
65
+ </ActionMenu>
66
+ </MenuButtonContainer>
67
+ );
68
+ }
@@ -0,0 +1,38 @@
1
+ import styled from "styled-components";
2
+ import { Button } from "@sproutsocial/seeds-react-button";
3
+
4
+ // A 1px gap separates the two halves of the split button (fixed pixel value
5
+ // per the design, not a theme token).
6
+ export const MenuButtonContainer = styled.div`
7
+ display: inline-flex;
8
+ gap: 1px;
9
+ `;
10
+
11
+ // Square off the inner (right) corners of the main button so it reads as one
12
+ // control with the trigger. The `&&` specificity bump beats the base Button
13
+ // Container's own `border-radius` (equal-specificity, source-order dependent).
14
+ export const MainButton = styled(Button)`
15
+ && {
16
+ border-top-right-radius: 0;
17
+ border-bottom-right-radius: 0;
18
+ }
19
+ `;
20
+
21
+ // Square off the inner (left) corners of the chevron trigger button. The
22
+ // chevron rotates 180° while the menu is open: base-ui's Menu.Trigger sets
23
+ // `data-popup-open` on this element (the base Button spreads it onto its root
24
+ // DOM node), mirroring the open-state selector convention in ActionMenuStyles.
25
+ export const TriggerButton = styled(Button)`
26
+ && {
27
+ border-top-left-radius: 0;
28
+ border-bottom-left-radius: 0;
29
+ }
30
+
31
+ .Icon-svg {
32
+ transition: transform ${({ theme }) => theme.duration.fast} ease;
33
+ }
34
+
35
+ &[data-popup-open] .Icon-svg {
36
+ transform: rotate(180deg);
37
+ }
38
+ `;