@majoo-ui/react 2.1.3 → 2.2.1

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.
@@ -47,9 +47,7 @@ const slideLeftAndFade = keyframes({
47
47
  transform: 'translateX(0)'
48
48
  }
49
49
  });
50
- const StyledContent = styled(DropdownMenuPrimitive.Content, {
51
- width: 143,
52
- maxWidth: 243,
50
+ const contentStyles = {
53
51
  backgroundColor: '$bgWhite',
54
52
  borderRadius: 4,
55
53
  padding: 8,
@@ -75,6 +73,19 @@ const StyledContent = styled(DropdownMenuPrimitive.Content, {
75
73
  }
76
74
  }
77
75
  }
76
+ };
77
+ const StyledContent = styled(DropdownMenuPrimitive.Content, {
78
+ width: 143,
79
+ maxWidth: 243,
80
+ ...contentStyles
81
+ });
82
+
83
+ // Submenus size to their own content instead of inheriting the trigger-sized width above.
84
+ const StyledSubContent = styled(DropdownMenuPrimitive.SubContent, {
85
+ width: 'max-content',
86
+ minWidth: 180,
87
+ maxWidth: 320,
88
+ ...contentStyles
78
89
  });
79
90
  const itemStyles = {
80
91
  all: 'unset',
@@ -97,24 +108,105 @@ const itemStyles = {
97
108
  '&:active': {
98
109
  backgroundColor: '$gray150'
99
110
  },
100
- '&:focus': {
111
+ // Radix moves focus to the highlighted item, but data-highlighted is the attribute it
112
+ // guarantees — keep both so hover and keyboard land on the same style.
113
+ '&:focus, &[data-highlighted]': {
101
114
  backgroundColor: '$gray100'
102
115
  }
103
116
  };
117
+ const selectedItemStyles = {
118
+ backgroundColor: '$bgSelectedLight',
119
+ color: '$textBrand',
120
+ fontWeight: 600
121
+ };
122
+
123
+ // Space reserved for the indicator drawn in the item's left gutter.
124
+ const INDICATOR_GUTTER = 28;
104
125
  const StyledItem = styled(DropdownMenuPrimitive.Item, {
105
- ...itemStyles
126
+ ...itemStyles,
127
+ variants: {
128
+ selected: {
129
+ true: selectedItemStyles
130
+ }
131
+ }
106
132
  });
107
133
  StyledItem.propTypes = {
108
134
  asChild: PropTypes.bool,
109
135
  disabled: PropTypes.bool,
110
- onSelect: PropTypes.func
136
+ onSelect: PropTypes.func,
137
+ selected: PropTypes.bool
111
138
  };
112
139
  const StyledCheckboxItem = styled(DropdownMenuPrimitive.CheckboxItem, {
113
- ...itemStyles
140
+ ...itemStyles,
141
+ paddingLeft: INDICATOR_GUTTER,
142
+ '&[data-state="checked"]': {
143
+ color: '$textPrimary',
144
+ fontWeight: 600
145
+ }
114
146
  });
147
+
148
+ // The ring and dot are drawn here rather than in ItemIndicator because Radix only mounts
149
+ // ItemIndicator while checked — the unchecked ring has to come from the item itself.
115
150
  const StyledRadioItem = styled(DropdownMenuPrimitive.RadioItem, {
116
- ...itemStyles
151
+ ...itemStyles,
152
+ paddingLeft: INDICATOR_GUTTER,
153
+ '&::before': {
154
+ content: '',
155
+ position: 'absolute',
156
+ left: 0,
157
+ top: '50%',
158
+ boxSizing: 'border-box',
159
+ width: 18,
160
+ height: 18,
161
+ borderRadius: '50%',
162
+ border: '1.5px solid $colors$formDefault',
163
+ transform: 'translateY(-50%)',
164
+ transition: 'border-color 120ms ease'
165
+ },
166
+ '&::after': {
167
+ content: '',
168
+ position: 'absolute',
169
+ left: 5,
170
+ top: '50%',
171
+ width: 8,
172
+ height: 8,
173
+ borderRadius: '50%',
174
+ backgroundColor: '$iconBrand',
175
+ transform: 'translateY(-50%) scale(0)',
176
+ transition: 'transform 120ms ease'
177
+ },
178
+ '&[data-state="checked"]': {
179
+ color: '$textPrimary',
180
+ fontWeight: 600,
181
+ '&::before': {
182
+ borderColor: '$formActive'
183
+ },
184
+ '&::after': {
185
+ transform: 'translateY(-50%) scale(1)'
186
+ }
187
+ },
188
+ '&[data-disabled]': {
189
+ '&::before': {
190
+ borderColor: '$formDisable'
191
+ },
192
+ '&::after': {
193
+ backgroundColor: '$iconDisable'
194
+ }
195
+ }
196
+ });
197
+ const StyledSubTrigger = styled(DropdownMenuPrimitive.SubTrigger, {
198
+ ...itemStyles,
199
+ '&[data-state="open"]': selectedItemStyles,
200
+ variants: {
201
+ selected: {
202
+ true: selectedItemStyles
203
+ }
204
+ }
117
205
  });
206
+ StyledSubTrigger.propTypes = {
207
+ disabled: PropTypes.bool,
208
+ selected: PropTypes.bool
209
+ };
118
210
  const StyledLabel = styled(DropdownMenuPrimitive.Label, {
119
211
  padding: '8px 0',
120
212
  fontSize: 12,
@@ -129,7 +221,10 @@ const StyledSeparator = styled(DropdownMenuPrimitive.Separator, {
129
221
  const StyledItemIndicator = styled(DropdownMenuPrimitive.ItemIndicator, {
130
222
  position: 'absolute',
131
223
  left: 0,
132
- width: 25,
224
+ top: '50%',
225
+ width: 18,
226
+ height: 18,
227
+ transform: 'translateY(-50%)',
133
228
  display: 'inline-flex',
134
229
  alignItems: 'center',
135
230
  justifyContent: 'center'
@@ -159,7 +254,7 @@ DropdownMenuContext.propTypes = {
159
254
  align: PropTypes.oneOf(['start', 'center', 'end'])
160
255
  };
161
256
  const DropdownMenuContentContext = ({
162
- theme,
257
+ theme = 'default',
163
258
  ...props
164
259
  }) => {
165
260
  const {
@@ -191,21 +286,43 @@ DropdownMenuContentContext.propTypes = {
191
286
  alignOffset: PropTypes.number,
192
287
  asChild: PropTypes.bool
193
288
  };
194
- const DropdownTrigger = /*#__PURE__*/React__default.forwardRef(({
289
+
290
+ // Radix positions submenus itself, so this only carries the theme class across the portal.
291
+ const DropdownMenuSubContentContext = ({
292
+ theme = 'default',
195
293
  children,
196
294
  ...props
295
+ }) => {
296
+ const themeProvider = useThemeProvider(theme);
297
+ return /*#__PURE__*/jsx(StyledSubContent, {
298
+ className: `dropdown-content ${themeProvider?.theme || ''}`,
299
+ ...props,
300
+ children: children
301
+ });
302
+ };
303
+ DropdownMenuSubContentContext.propTypes = {
304
+ sideOffset: PropTypes.number,
305
+ alignOffset: PropTypes.number,
306
+ asChild: PropTypes.bool
307
+ };
308
+
309
+ /**
310
+ * @type {React.ForwardRefExoticComponent<
311
+ * React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger> & React.RefAttributes<HTMLButtonElement>
312
+ * >}
313
+ */
314
+ const DropdownTrigger = /*#__PURE__*/React__default.forwardRef(({
315
+ children,
316
+ asChild,
317
+ ...rest
197
318
  }, ref) => {
198
- const {
199
- asChild,
200
- ...rest
201
- } = props;
202
319
  return /*#__PURE__*/jsx(DropdownMenuPrimitive.Trigger, {
203
320
  ...rest,
204
321
  asChild: asChild,
205
322
  ref: ref,
206
- children: children && /*#__PURE__*/React__default.cloneElement(children, {
323
+ children: /*#__PURE__*/React__default.isValidElement(children) ? /*#__PURE__*/React__default.cloneElement(children, {
207
324
  ...rest
208
- })
325
+ }) : children
209
326
  });
210
327
  });
211
328
  DropdownTrigger.propTypes = {
@@ -213,11 +330,20 @@ DropdownTrigger.propTypes = {
213
330
  };
214
331
  const RightSlot = styled('div', {
215
332
  marginLeft: 'auto',
216
- ':focus > &': {
217
- color: 'white'
218
- },
333
+ paddingLeft: 16,
334
+ display: 'inline-flex',
335
+ alignItems: 'center',
219
336
  '[data-disabled] &': {
220
337
  color: '$gray400'
338
+ },
339
+ variants: {
340
+ // Secondary text (a hint or shortcut) rather than an icon.
341
+ muted: {
342
+ true: {
343
+ color: '$textDisable',
344
+ fontSize: 12
345
+ }
346
+ }
221
347
  }
222
348
  });
223
349
  const StyledDropdown = styled(Button, {
@@ -261,7 +387,9 @@ const DropdownMenuCheckboxItem = StyledCheckboxItem;
261
387
  const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
262
388
  const DropdownMenuRadioItem = StyledRadioItem;
263
389
  const DropdownMenuItemIndicator = StyledItemIndicator;
264
- // export const DropdownMenuTriggerItem = StyledTriggerItem;
390
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
391
+ const DropdownMenuSubTrigger = StyledSubTrigger;
392
+ const DropdownMenuSubContent = DropdownMenuSubContentContext;
265
393
  const DropdownMenuLabel = StyledLabel;
266
394
  const DropdownMenuSeparator = StyledSeparator;
267
395
  const DropdownMenuArrow = StyledArrow;
@@ -273,9 +401,11 @@ DropdownMenuCheckboxItem.displayName = 'DropdownMenuCheckboxItem';
273
401
  DropdownMenuRadioGroup.displayName = 'DropdownMenuRadioGroup';
274
402
  DropdownMenuRadioItem.displayName = 'DropdownMenuRadioItem';
275
403
  DropdownMenuItemIndicator.displayName = 'DropdownMenuItemIndicator';
276
- // DropdownMenuTriggerItem.displayName = 'DropdownMenuTriggerItem';
404
+ DropdownMenuSub.displayName = 'DropdownMenuSub';
405
+ DropdownMenuSubTrigger.displayName = 'DropdownMenuSubTrigger';
406
+ DropdownMenuSubContent.displayName = 'DropdownMenuSubContent';
277
407
  DropdownMenuLabel.displayName = 'DropdownMenuLabel';
278
408
  DropdownMenuSeparator.displayName = 'DropdownMenuSeparator';
279
409
  DropdownMenuArrow.displayName = 'DropdownMenuArrow';
280
410
 
281
- export { ButtonDropdown, DropdownMenu, DropdownMenuArrow, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuItemIndicator, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, RightSlot, StyledDropdown };
411
+ export { ButtonDropdown, DropdownMenu, DropdownMenuArrow, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuItemIndicator, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, RightSlot, StyledDropdown };
@@ -219,23 +219,17 @@ const OptionWrapper = styled('div', {
219
219
  backgroundColor: '$bgWhite',
220
220
  boxShadow: '0px 2px 12px #00000014',
221
221
  width: '100%',
222
- maxHeight: '300px',
223
- position: 'absolute',
224
- float: 'left',
225
- top: '100%',
226
- left: '0',
222
+ // In-flow on purpose: Popover's flip/shift/size measure the floating element,
223
+ // which is only as tall as this wrapper. An absolute wrapper measures 0px and
224
+ // the dropdown never flips. The cap shrinks to the space Popover reports.
225
+ maxHeight: 'min(300px, var(--majoo-popover-available-height, 300px))',
226
+ position: 'relative',
227
227
  variants: {
228
+ // Kept for API compatibility only — Popover (floating-ui) owns placement now,
229
+ // treating it as a preference that flips when the preferred side lacks room.
228
230
  placement: {
229
- bottom: {
230
- top: '100%',
231
- left: '0',
232
- transform: 'none'
233
- },
234
- top: {
235
- top: '0',
236
- left: '0',
237
- transform: 'translate(0, -100%)'
238
- }
231
+ bottom: {},
232
+ top: {}
239
233
  },
240
234
  withFooter: {
241
235
  true: {
@@ -683,13 +677,8 @@ const InputSelect = /*#__PURE__*/React.forwardRef((_a, ref) => {
683
677
  position: 'absolute',
684
678
  inset: 0,
685
679
  zIndex: '$tooltip',
686
- top: item.length > 0 ? '0px' : placement === 'top' ? '-20px' : '20px',
687
- // bottom: '80px',
688
- width: '100%',
689
- height: scrollerRef.current ? scrollerRef.current.clientHeight : '40px',
690
680
  backgroundColor: '$bgWhite',
691
- opacity: 0.8,
692
- transform: placement === 'top' ? 'translate(0, -100%)' : 'none'
681
+ opacity: 0.8
693
682
  },
694
683
  children: jsx(Text, {
695
684
  variant: "caption",
@@ -699,9 +688,10 @@ const InputSelect = /*#__PURE__*/React.forwardRef((_a, ref) => {
699
688
  condition: renderFooter && typeof renderFooter === 'function',
700
689
  wrapper: child => jsxs(Box, {
701
690
  css: {
702
- position: 'absolute',
703
- float: 'left',
704
- display: isOpen ? 'block' : 'none',
691
+ position: 'relative',
692
+ display: isOpen ? 'flex' : 'none',
693
+ flexDirection: 'column',
694
+ maxHeight: 'var(--majoo-popover-available-height, calc(100vh - 160px))',
705
695
  zIndex: '$popover',
706
696
  width: '100%'
707
697
  },
@@ -714,11 +704,16 @@ const InputSelect = /*#__PURE__*/React.forwardRef((_a, ref) => {
714
704
  }, {
715
705
  suppressRefError: true
716
706
  }), {
717
- css: Object.assign({
707
+ css: Object.assign(Object.assign({
718
708
  display: isOpen ? 'block' : 'none',
719
709
  zIndex: '$popover',
720
- pointerEvents: 'auto'
721
- }, optionCss),
710
+ pointerEvents: 'auto',
711
+ minHeight: isLoading ? '40px' : undefined
712
+ }, renderFooter && typeof renderFooter === 'function' ? {
713
+ flex: 1,
714
+ minHeight: 0,
715
+ maxHeight: 'none'
716
+ } : {}), optionCss),
722
717
  withFooter: renderFooter && typeof renderFooter === 'function',
723
718
  placement: placement,
724
719
  children: [!search && embedSearch && isOpen && jsx(SelectContainer, {