@sproutsocial/seeds-react-menu 1.16.16 → 1.18.0

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 (84) hide show
  1. package/.turbo/turbo-build.log +14 -22
  2. package/CHANGELOG.md +51 -0
  3. package/dist/esm/index.js +542 -229
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/esm/v3/index.js +870 -1081
  6. package/dist/esm/v3/index.js.map +1 -1
  7. package/dist/v3/index.d.mts +82 -38
  8. package/dist/v3/index.d.ts +82 -38
  9. package/dist/v3/index.js +887 -1098
  10. package/dist/v3/index.js.map +1 -1
  11. package/dist/v3/menu.css +947 -0
  12. package/package.json +20 -21
  13. package/src/ActionMenu/ActionMenu.stories.tsx +1 -1
  14. package/src/Autocomplete/Autocomplete.stories.tsx +1 -1
  15. package/src/MenuContent/MenuContent.stories.tsx +1 -1
  16. package/src/MenuFooter/MenuFooter.stories.tsx +1 -1
  17. package/src/MenuGroup/MenuGroup.stories.tsx +1 -1
  18. package/src/MenuHeader/MenuHeader.stories.tsx +1 -1
  19. package/src/MenuItem/MenuItem.stories.tsx +1 -1
  20. package/src/MenuToggleButton/MenuToggleButton.stories.tsx +1 -1
  21. package/src/MultiSelectMenu/MultiSelectMenu.stories.tsx +1 -1
  22. package/src/NestedMenu/NestedMenu.stories.tsx +1 -1
  23. package/src/SingleSelectMenu/SingleSelectMenu.stories.tsx +1 -1
  24. package/src/SubmenuItem/SubmenuItem.stories.tsx +1 -1
  25. package/src/v3/ActionMenu.stories.tsx +1 -1
  26. package/src/v3/ActionMenuStyles.tsx +169 -149
  27. package/src/v3/Common/ComboboxStyles.tsx +396 -511
  28. package/src/v3/Common/SelectIcons.tsx +17 -8
  29. package/src/v3/Common/SelectItem.tsx +25 -38
  30. package/src/v3/Common/SelectStyles.tsx +146 -144
  31. package/src/v3/Common/cn.ts +38 -0
  32. package/src/v3/MenuButton.stories.tsx +1 -1
  33. package/src/v3/MenuButtonStyles.tsx +55 -34
  34. package/src/v3/ModalStories.stories.tsx +1 -1
  35. package/src/v3/MultiCombobox.stories.tsx +1 -1
  36. package/src/v3/MultiCombobox.tsx +7 -0
  37. package/src/v3/MultiSearchableSelect.stories.tsx +1 -1
  38. package/src/v3/MultiSearchableSelect.tsx +15 -8
  39. package/src/v3/MultiSelect.stories.tsx +1 -1
  40. package/src/v3/MultiSelect.tsx +20 -17
  41. package/src/v3/SingleCombobox.stories.tsx +1 -1
  42. package/src/v3/SingleCombobox.tsx +7 -0
  43. package/src/v3/SingleSearchableSelect.stories.tsx +1 -1
  44. package/src/v3/SingleSearchableSelect.tsx +7 -0
  45. package/src/v3/SingleSelect.stories.tsx +1 -1
  46. package/src/v3/SingleSelect.tsx +22 -14
  47. package/src/v3/__tests__/ariaLabels.test.tsx +133 -0
  48. package/src/v3/menu.css +947 -0
  49. package/tsup.config.ts +0 -1
  50. package/dist/esm/chunk-ROQATIB7.js +0 -357
  51. package/dist/esm/chunk-ROQATIB7.js.map +0 -1
  52. package/dist/esm/v2/index.js +0 -2089
  53. package/dist/esm/v2/index.js.map +0 -1
  54. package/dist/v2/index.d.mts +0 -108
  55. package/dist/v2/index.d.ts +0 -108
  56. package/dist/v2/index.js +0 -2280
  57. package/dist/v2/index.js.map +0 -1
  58. package/src/v2/Autocomplete/Autocomplete.stories.tsx +0 -645
  59. package/src/v2/Autocomplete/MultiAutocomplete.tsx +0 -487
  60. package/src/v2/Autocomplete/SingleAutocomplete.tsx +0 -366
  61. package/src/v2/Autocomplete/index.ts +0 -2
  62. package/src/v2/Common-V2/ComboboxContent.tsx +0 -457
  63. package/src/v2/Common-V2/MenuGroup.tsx +0 -60
  64. package/src/v2/Common-V2/MenuGroupTypes.ts +0 -30
  65. package/src/v2/Common-V2/MenuHeading.tsx +0 -83
  66. package/src/v2/Common-V2/MenuItem.tsx +0 -138
  67. package/src/v2/Common-V2/Popout.tsx +0 -124
  68. package/src/v2/Common-V2/PopoutTypes.tsx +0 -109
  69. package/src/v2/Common-V2/SelectToggleButton.tsx +0 -99
  70. package/src/v2/Common-V2/index.ts +0 -11
  71. package/src/v2/Common-V2/props.ts +0 -84
  72. package/src/v2/Common-V2/styles.tsx +0 -41
  73. package/src/v2/Common-V2/types.ts +0 -16
  74. package/src/v2/Common-V2/useHighlightedIndex.ts +0 -62
  75. package/src/v2/Common-V2/utils.ts +0 -238
  76. package/src/v2/SearchableSelect/AutocompleteContent.tsx +0 -325
  77. package/src/v2/SearchableSelect/SearchableMultiSelect.tsx +0 -527
  78. package/src/v2/SearchableSelect/SearchableSelect.tsx +0 -395
  79. package/src/v2/SearchableSelect/index.ts +0 -12
  80. package/src/v2/Select/MultiSelect.tsx +0 -417
  81. package/src/v2/Select/Select.stories.tsx +0 -593
  82. package/src/v2/Select/SingleSelect.tsx +0 -285
  83. package/src/v2/Select/index.ts +0 -2
  84. package/src/v2/index.ts +0 -2
@@ -1,17 +1,26 @@
1
1
  import * as React from "react";
2
- import styled from "styled-components";
3
2
  import { Icon } from "@sproutsocial/seeds-react-icon";
3
+ import { cn } from "./cn";
4
4
 
5
- export interface StyledChevronProps {
5
+ export interface StyledChevronProps extends React.ComponentProps<"div"> {
6
6
  $isOpen?: boolean;
7
7
  }
8
8
 
9
- export const StyledChevron = styled.div<StyledChevronProps>`
10
- display: flex;
11
- align-items: center;
12
- transition: transform 0.15s;
13
- ${({ $isOpen }) => $isOpen && "transform: rotate(-180deg);"}
14
- `;
9
+ export const StyledChevron = React.forwardRef<
10
+ HTMLDivElement,
11
+ StyledChevronProps
12
+ >(({ $isOpen, className, ...rest }, ref) => (
13
+ <div
14
+ ref={ref}
15
+ className={cn(
16
+ "seeds-menu-chevron",
17
+ { "seeds-menu-chevron--open": !!$isOpen },
18
+ className
19
+ )}
20
+ {...rest}
21
+ />
22
+ ));
23
+ StyledChevron.displayName = "StyledChevron";
15
24
 
16
25
  export function ChevronIcon() {
17
26
  return <Icon name="chevron-down-outline" fixedWidth aria-hidden />;
@@ -1,50 +1,37 @@
1
1
  import * as React from "react";
2
2
  import { Select } from "@base-ui/react/select";
3
- import styled from "styled-components";
4
3
  import Radio from "@sproutsocial/seeds-react-radio";
5
4
  import Checkbox from "@sproutsocial/seeds-react-checkbox";
6
5
  import { Icon } from "@sproutsocial/seeds-react-icon";
6
+ import { cn, type WithStringClassName } from "./cn";
7
7
 
8
- // ─── Styled components ────────────────────────────────────────────────────────
8
+ // ─── CSS-class components (see menu.css) ────────────────────────────────────
9
9
 
10
- export const StyledItem = styled(Select.Item)`
11
- display: flex;
12
- align-items: center;
13
- gap: 8px;
14
- padding: 6px 8px;
15
- border-radius: ${({ theme }) => theme.radii[500]};
16
- font-size: 14px;
17
- color: ${({ theme }) => theme.colors.text.body};
18
- cursor: default;
19
- outline: none;
10
+ export const StyledItem = React.forwardRef<
11
+ React.ElementRef<typeof Select.Item>,
12
+ WithStringClassName<React.ComponentProps<typeof Select.Item>>
13
+ >(({ className, ...rest }, ref) => (
14
+ <Select.Item
15
+ ref={ref}
16
+ className={cn("seeds-select-item", className)}
17
+ {...rest}
18
+ />
19
+ ));
20
+ StyledItem.displayName = "StyledItem";
20
21
 
21
- &[data-highlighted] {
22
- background: ${({ theme }) => theme.colors.listItem.background.hover};
23
- }
22
+ export const StyledItemIndicator = React.forwardRef<
23
+ React.ElementRef<typeof Select.ItemIndicator>,
24
+ WithStringClassName<React.ComponentProps<typeof Select.ItemIndicator>>
25
+ >(({ className, ...rest }, ref) => (
26
+ <Select.ItemIndicator
27
+ ref={ref}
28
+ className={cn("seeds-select-item-indicator", className)}
29
+ {...rest}
30
+ />
31
+ ));
32
+ StyledItemIndicator.displayName = "StyledItemIndicator";
24
33
 
25
- &[data-selected] {
26
- font-weight: ${({ theme }) => theme.fontWeights.semibold};
27
- }
28
-
29
- &[data-disabled] {
30
- opacity: 0.5;
31
- cursor: not-allowed;
32
- }
33
- `;
34
-
35
- export const StyledItemIndicator = styled(Select.ItemIndicator)`
36
- display: flex;
37
- align-items: center;
38
- width: 16px;
39
- color: ${({ theme }) => theme.colors.text.body};
40
- visibility: hidden;
41
-
42
- &[data-selected] {
43
- visibility: visible;
44
- }
45
- `;
46
-
47
- export const StyledItemText = styled(Select.ItemText)``;
34
+ export const StyledItemText = Select.ItemText;
48
35
 
49
36
  // ─── Icons ────────────────────────────────────────────────────────────────────
50
37
 
@@ -1,146 +1,148 @@
1
+ import * as React from "react";
1
2
  import { Select } from "@base-ui/react/select";
2
- import styled, { css } from "styled-components";
3
- import { focusRing } from "@sproutsocial/seeds-react-mixins";
4
- import { StyledChevron } from "./SelectIcons";
5
-
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"};
17
- flex-direction: column;
18
- gap: ${({ theme }) => theme.space[200]};
19
- ${({ $fullWidth }) => $fullWidth !== false && "width: 100%;"}
20
- `;
21
-
22
- export const SelectLabel = styled(Select.Label)`
23
- font-size: ${({ theme }) => theme.typography[200].fontSize};
24
- font-weight: ${({ theme }) => theme.fontWeights.semibold};
25
- color: ${({ theme }) => theme.colors.text.body};
26
- `;
27
-
28
- export const SelectTrigger = styled(Select.Trigger)<{
3
+ import { cn, type WithStringClassName } from "./cn";
4
+
5
+ /**
6
+ * v3 Select surface, rendered with CSS classes from menu.css (imported via
7
+ * @sproutsocial/seeds-react-menu/base/css) instead of styled-components. Each
8
+ * export keeps its previous name and forwards props/refs to the underlying
9
+ * Base UI component; the transient `$isInvalid` / `$fullWidth` props are
10
+ * translated to modifier classes here so call sites are unchanged.
11
+ */
12
+
13
+ type FieldProps = WithStringClassName<React.ComponentProps<"div">> & {
14
+ $fullWidth?: boolean;
15
+ };
16
+
17
+ export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
18
+ ({ $fullWidth, className, ...rest }, ref) => (
19
+ <div
20
+ ref={ref}
21
+ className={cn(
22
+ "seeds-menu-field",
23
+ { "seeds-menu-field--inline": $fullWidth === false },
24
+ className
25
+ )}
26
+ {...rest}
27
+ />
28
+ )
29
+ );
30
+ Field.displayName = "Field";
31
+
32
+ export const SelectLabel = React.forwardRef<
33
+ React.ElementRef<typeof Select.Label>,
34
+ WithStringClassName<React.ComponentProps<typeof Select.Label>>
35
+ >(({ className, ...rest }, ref) => (
36
+ <Select.Label
37
+ ref={ref}
38
+ className={cn("seeds-menu-label", className)}
39
+ {...rest}
40
+ />
41
+ ));
42
+ SelectLabel.displayName = "SelectLabel";
43
+
44
+ type SelectTriggerProps = WithStringClassName<
45
+ React.ComponentProps<typeof Select.Trigger>
46
+ > & {
29
47
  $isInvalid?: boolean;
30
- }>`
31
- display: flex;
32
- align-items: center;
33
- justify-content: space-between;
34
- gap: ${({ theme }) => theme.space[300]};
35
- padding: ${({ theme }) => `${theme.space[300]}`};
36
- min-width: 160px;
37
- border-radius: ${({ theme }) => theme.radii[500]};
38
- border: 1px solid ${({ theme }) => theme.colors.form.border.base};
39
- background: ${({ theme }) => theme.colors.form.background.base};
40
- font-size: ${({ theme }) => theme.typography[200].fontSize};
41
- color: ${({ theme }) => theme.colors.text.body};
42
- cursor: default;
43
- outline: none;
44
- transition: border-color ${({ theme }) => theme.duration.fast}
45
- ${({ theme }) => theme.easing.ease_in},
46
- box-shadow ${({ theme }) => theme.duration.fast}
47
- ${({ theme }) => theme.easing.ease_in};
48
-
49
- &:focus-visible {
50
- ${focusRing}
51
- }
52
-
53
- &[data-popup-open] {
54
- ${focusRing}
55
- }
56
-
57
- &[data-popup-open] [data-chevron] {
58
- transform: rotate(-180deg);
59
- }
60
-
61
- &[data-disabled] {
62
- opacity: 0.4;
63
- cursor: not-allowed;
64
- }
65
-
66
- ${({ $isInvalid }) => $isInvalid && invalidStyles}
67
- `;
68
-
69
- export const SelectValue = styled(Select.Value)`
70
- min-width: 0;
71
- overflow: hidden;
72
- `;
73
-
74
- export const SelectIcon = styled(Select.Icon)`
75
- display: flex;
76
- align-items: center;
77
- color: ${({ theme }) => theme.colors.icon.base};
78
- flex-shrink: 0;
79
- `;
80
-
81
- export const SelectGroup = styled(Select.Group)`
82
- display: block;
83
- `;
84
-
85
- export const SelectGroupLabel = styled(Select.GroupLabel)`
86
- padding: ${({ theme }) =>
87
- `${theme.space[300]} ${theme.space[300]} ${theme.space[200]}`};
88
- font-size: ${({ theme }) => theme.typography[100].fontSize};
89
- font-weight: ${({ theme }) => theme.fontWeights.semibold};
90
- text-transform: uppercase;
91
- letter-spacing: 0.05em;
92
- color: ${({ theme }) => theme.colors.text.subtext};
93
- `;
94
-
95
- export const SelectSeparator = styled(Select.Separator)`
96
- height: 1px;
97
- margin: ${({ theme }) => `${theme.space[200]} ${theme.space[300]}`};
98
- background: ${({ theme }) => theme.colors.container.border.base};
99
- `;
100
-
101
- export const SelectPositioner = styled(Select.Positioner)`
102
- width: var(--anchor-width);
103
- z-index: 7;
104
- `;
105
-
106
- export const SelectPopup = styled(Select.Popup)`
107
- font-family: ${({ theme }) => theme.fontFamily};
108
- background: ${({ theme }) => theme.colors.container.background.base};
109
- border: 1px solid ${({ theme }) => theme.colors.container.border.base};
110
- border-radius: ${({ theme }) => theme.radii[500]};
111
- box-shadow: ${({ theme }) => theme.shadows.low};
112
- outline: none;
113
- max-height: min(var(--available-height, 400px), 400px);
114
- overflow-y: auto;
115
- padding: ${({ theme }) => theme.space[200]};
116
-
117
- &[data-open] {
118
- animation: select-popup-in 120ms ease-out;
119
- }
120
-
121
- &[data-closed] {
122
- animation: select-popup-out 100ms ease-in;
123
- }
124
-
125
- @keyframes select-popup-in {
126
- from {
127
- opacity: 0;
128
- transform: scale(0.95);
129
- }
130
- to {
131
- opacity: 1;
132
- transform: scale(1);
133
- }
134
- }
135
-
136
- @keyframes select-popup-out {
137
- from {
138
- opacity: 1;
139
- transform: scale(1);
140
- }
141
- to {
142
- opacity: 0;
143
- transform: scale(0.95);
144
- }
145
- }
146
- `;
48
+ };
49
+
50
+ export const SelectTrigger = React.forwardRef<
51
+ React.ElementRef<typeof Select.Trigger>,
52
+ SelectTriggerProps
53
+ >(({ $isInvalid, className, ...rest }, ref) => (
54
+ <Select.Trigger
55
+ ref={ref}
56
+ className={cn(
57
+ "seeds-select-trigger",
58
+ { "seeds-select-trigger--invalid": !!$isInvalid },
59
+ className
60
+ )}
61
+ {...rest}
62
+ />
63
+ ));
64
+ SelectTrigger.displayName = "SelectTrigger";
65
+
66
+ export const SelectValue = React.forwardRef<
67
+ React.ElementRef<typeof Select.Value>,
68
+ WithStringClassName<React.ComponentProps<typeof Select.Value>>
69
+ >(({ className, ...rest }, ref) => (
70
+ <Select.Value
71
+ ref={ref}
72
+ className={cn("seeds-select-value", className)}
73
+ {...rest}
74
+ />
75
+ ));
76
+ SelectValue.displayName = "SelectValue";
77
+
78
+ export const SelectIcon = React.forwardRef<
79
+ React.ElementRef<typeof Select.Icon>,
80
+ WithStringClassName<React.ComponentProps<typeof Select.Icon>>
81
+ >(({ className, ...rest }, ref) => (
82
+ <Select.Icon
83
+ ref={ref}
84
+ className={cn("seeds-select-icon", className)}
85
+ {...rest}
86
+ />
87
+ ));
88
+ SelectIcon.displayName = "SelectIcon";
89
+
90
+ export const SelectGroup = React.forwardRef<
91
+ React.ElementRef<typeof Select.Group>,
92
+ WithStringClassName<React.ComponentProps<typeof Select.Group>>
93
+ >(({ className, ...rest }, ref) => (
94
+ <Select.Group
95
+ ref={ref}
96
+ className={cn("seeds-select-group", className)}
97
+ {...rest}
98
+ />
99
+ ));
100
+ SelectGroup.displayName = "SelectGroup";
101
+
102
+ export const SelectGroupLabel = React.forwardRef<
103
+ React.ElementRef<typeof Select.GroupLabel>,
104
+ WithStringClassName<React.ComponentProps<typeof Select.GroupLabel>>
105
+ >(({ className, ...rest }, ref) => (
106
+ <Select.GroupLabel
107
+ ref={ref}
108
+ className={cn("seeds-select-group-label", className)}
109
+ {...rest}
110
+ />
111
+ ));
112
+ SelectGroupLabel.displayName = "SelectGroupLabel";
113
+
114
+ export const SelectSeparator = React.forwardRef<
115
+ React.ElementRef<typeof Select.Separator>,
116
+ WithStringClassName<React.ComponentProps<typeof Select.Separator>>
117
+ >(({ className, ...rest }, ref) => (
118
+ <Select.Separator
119
+ ref={ref}
120
+ className={cn("seeds-select-separator", className)}
121
+ {...rest}
122
+ />
123
+ ));
124
+ SelectSeparator.displayName = "SelectSeparator";
125
+
126
+ export const SelectPositioner = React.forwardRef<
127
+ React.ElementRef<typeof Select.Positioner>,
128
+ WithStringClassName<React.ComponentProps<typeof Select.Positioner>>
129
+ >(({ className, ...rest }, ref) => (
130
+ <Select.Positioner
131
+ ref={ref}
132
+ className={cn("seeds-select-positioner", className)}
133
+ {...rest}
134
+ />
135
+ ));
136
+ SelectPositioner.displayName = "SelectPositioner";
137
+
138
+ export const SelectPopup = React.forwardRef<
139
+ React.ElementRef<typeof Select.Popup>,
140
+ WithStringClassName<React.ComponentProps<typeof Select.Popup>>
141
+ >(({ className, ...rest }, ref) => (
142
+ <Select.Popup
143
+ ref={ref}
144
+ className={cn("seeds-select-popup", className)}
145
+ {...rest}
146
+ />
147
+ ));
148
+ SelectPopup.displayName = "SelectPopup";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Local class-name merge helper for the v3 menu CSS-class components.
3
+ *
4
+ * `cn` is not exported from @sproutsocial/seeds-react-utilities, so — mirroring
5
+ * ButtonTailwind/MessageTailwind — the menu package keeps a local copy. Accepts
6
+ * strings and { className: boolean } records; falsy entries are dropped.
7
+ */
8
+ /**
9
+ * Narrow a Base UI component's props so `className` is a plain string. Base UI
10
+ * types `className` as `string | ((state) => string)`; the v3 CSS-class
11
+ * wrappers only ever forward a string (no consumer uses the function form), and
12
+ * a string is what `cn` accepts.
13
+ */
14
+ export type WithStringClassName<P> = Omit<P, "className"> & {
15
+ className?: string;
16
+ };
17
+
18
+ export function cn(
19
+ ...inputs: (string | undefined | null | false | Record<string, boolean>)[]
20
+ ): string {
21
+ const classes: string[] = [];
22
+
23
+ for (const input of inputs) {
24
+ if (!input) continue;
25
+
26
+ if (typeof input === "string") {
27
+ classes.push(input);
28
+ } else if (typeof input === "object") {
29
+ for (const [key, value] of Object.entries(input)) {
30
+ if (value) {
31
+ classes.push(key);
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+ return classes.join(" ");
38
+ }
@@ -6,7 +6,7 @@ import { MenuButton } from "./MenuButton";
6
6
  import { MenuItem } from "./ActionMenu";
7
7
 
8
8
  const meta: Meta<typeof MenuButton> = {
9
- title: "Really Under Development/V3/MenuButton",
9
+ title: "Components/Menus/v3/MenuButton",
10
10
  component: MenuButton,
11
11
  decorators: [
12
12
  (Story) => (
@@ -1,38 +1,59 @@
1
- import styled from "styled-components";
2
- import { Button } from "@sproutsocial/seeds-react-button";
1
+ import * as React from "react";
2
+ import { Button, type TypeButtonProps } from "@sproutsocial/seeds-react-button";
3
+ import { cn } from "./Common/cn";
3
4
 
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
- `;
5
+ /**
6
+ * v3 MenuButton (split button) surface, rendered with CSS classes from menu.css
7
+ * (imported via @sproutsocial/seeds-react-menu/base/css) instead of
8
+ * styled-components. Each export keeps its previous name.
9
+ *
10
+ * MainButton / TriggerButton square off the inner corners so the two halves
11
+ * read as one control. The class rules use doubled selectors to reproduce the
12
+ * old `&&` specificity bump; Button forwards `className` and spreads `data-*`
13
+ * (including Base UI's `data-popup-open`) onto its root DOM node, so the chevron
14
+ * rotation selector `.seeds-menu-button-trigger[data-popup-open] .Icon-svg`
15
+ * still resolves.
16
+ *
17
+ * These forward refs and spread all props so Base UI's `Menu.Trigger render`
18
+ * can inject its ref, `data-popup-open`, and event handlers onto the trigger —
19
+ * matching what `styled(Button)` did previously.
20
+ */
10
21
 
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
- `;
22
+ export const MenuButtonContainer = React.forwardRef<
23
+ HTMLDivElement,
24
+ React.ComponentProps<"div">
25
+ >(({ className, ...rest }, ref) => (
26
+ <div
27
+ ref={ref}
28
+ className={cn("seeds-menu-button-container", className)}
29
+ {...rest}
30
+ />
31
+ ));
32
+ MenuButtonContainer.displayName = "MenuButtonContainer";
20
33
 
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
+ // Square off the inner (right) corners of the main button.
35
+ export const MainButton = React.forwardRef<HTMLButtonElement, TypeButtonProps>(
36
+ ({ className, ...rest }, ref) => (
37
+ <Button
38
+ ref={ref}
39
+ className={cn("seeds-menu-button-main", className)}
40
+ {...rest}
41
+ />
42
+ )
43
+ );
44
+ MainButton.displayName = "MainButton";
34
45
 
35
- &[data-popup-open] .Icon-svg {
36
- transform: rotate(180deg);
37
- }
38
- `;
46
+ // Square off the inner (left) corners of the chevron trigger button. The
47
+ // chevron rotates 180° while the menu is open: Base UI's Menu.Trigger sets
48
+ // `data-popup-open` on this element (Button spreads it onto its root DOM node).
49
+ export const TriggerButton = React.forwardRef<
50
+ HTMLButtonElement,
51
+ TypeButtonProps
52
+ >(({ className, ...rest }, ref) => (
53
+ <Button
54
+ ref={ref}
55
+ className={cn("seeds-menu-button-trigger", className)}
56
+ {...rest}
57
+ />
58
+ ));
59
+ TriggerButton.displayName = "TriggerButton";
@@ -138,7 +138,7 @@ function AllComponents() {
138
138
  // ─── Meta ─────────────────────────────────────────────────────────────────────
139
139
 
140
140
  const meta: Meta = {
141
- title: "Really Under Development/V3/In Modal",
141
+ title: "Components/Menus/v3/In Modal",
142
142
  };
143
143
 
144
144
  export default meta;
@@ -18,7 +18,7 @@ import {
18
18
  } from "./storyData";
19
19
 
20
20
  const meta: Meta<typeof MultiCombobox> = {
21
- title: "Really Under Development/V3/Multi Combobox",
21
+ title: "Components/Menus/v3/Multi Combobox",
22
22
  component: MultiCombobox,
23
23
  };
24
24
 
@@ -74,6 +74,9 @@ interface MultiComboboxBaseProps {
74
74
  filter?: ((item: unknown, query: string) => boolean) | null;
75
75
  inputValue?: string;
76
76
  onInputValueChange?: (value: string) => void;
77
+ "aria-label"?: string;
78
+ /** Id of a visible label. Preferred over `aria-label`. */
79
+ "aria-labelledby"?: string;
77
80
  "aria-describedby"?: string;
78
81
  isInvalid?: boolean;
79
82
  required?: boolean;
@@ -150,6 +153,8 @@ export function MultiCombobox<T extends DataWithId>(
150
153
  fullWidth = true,
151
154
  } = props;
152
155
  const ariaDescribedBy = props["aria-describedby"];
156
+ const ariaLabel = props["aria-label"];
157
+ const ariaLabelledBy = props["aria-labelledby"];
153
158
  const isInvalid = props.isInvalid;
154
159
 
155
160
  const isChildrenMode = "children" in props && props.children != null;
@@ -674,6 +679,8 @@ export function MultiCombobox<T extends DataWithId>(
674
679
  <ComboboxStatus>{isLoading ? loadingText : null}</ComboboxStatus>
675
680
  <ComboboxEmpty>{isLoading ? null : emptyText}</ComboboxEmpty>
676
681
  <ComboboxList
682
+ aria-label={ariaLabel}
683
+ aria-labelledby={ariaLabelledBy}
677
684
  style={
678
685
  isVirtualized
679
686
  ? {
@@ -11,7 +11,7 @@ import {
11
11
  } from "./storyData";
12
12
 
13
13
  const meta: Meta<typeof MultiSearchableSelect> = {
14
- title: "Really Under Development/V3/Multi Searchable Select",
14
+ title: "Components/Menus/v3/Multi Searchable Select",
15
15
  component: MultiSearchableSelect,
16
16
  };
17
17