@12-apps/ui 1.4.0 → 1.4.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.
Files changed (53) hide show
  1. package/package.json +4 -4
  2. package/src/components/data-display/Carousel/Carousel.chrome.tsx +83 -0
  3. package/src/components/data-display/Carousel/Carousel.hooks.ts +88 -0
  4. package/src/components/data-display/Carousel/Carousel.parts.tsx +346 -0
  5. package/src/components/data-display/Carousel/Carousel.slide.tsx +190 -0
  6. package/src/components/data-display/Carousel/Carousel.styles.ts +86 -0
  7. package/src/components/data-display/Carousel/Carousel.tsx +84 -732
  8. package/src/components/data-display/HoverCard/HoverCard.animations.ts +73 -0
  9. package/src/components/data-display/HoverCard/HoverCard.content.tsx +143 -0
  10. package/src/components/data-display/HoverCard/HoverCard.hooks.ts +180 -0
  11. package/src/components/data-display/HoverCard/HoverCard.styles.ts +178 -0
  12. package/src/components/data-display/HoverCard/HoverCard.tsx +117 -631
  13. package/src/components/data-display/Sheet/Sheet.animations.ts +44 -0
  14. package/src/components/data-display/Sheet/Sheet.body.tsx +124 -0
  15. package/src/components/data-display/Sheet/Sheet.drag.ts +203 -0
  16. package/src/components/data-display/Sheet/Sheet.hooks.ts +318 -0
  17. package/src/components/data-display/Sheet/Sheet.parts.tsx +224 -0
  18. package/src/components/data-display/Sheet/Sheet.styles.ts +257 -0
  19. package/src/components/data-display/Sheet/Sheet.tsx +125 -948
  20. package/src/components/feedback/Modal/Modal.styles.ts +126 -0
  21. package/src/components/feedback/Modal/Modal.transitions.tsx +40 -0
  22. package/src/components/feedback/Modal/Modal.tsx +37 -242
  23. package/src/components/form/InputOTP/InputOTP.hooks.ts +104 -0
  24. package/src/components/form/InputOTP/InputOTP.styles.ts +97 -0
  25. package/src/components/form/InputOTP/InputOTP.tsx +74 -221
  26. package/src/components/form/Label/Label.parts.tsx +75 -0
  27. package/src/components/form/Label/Label.styles.ts +206 -0
  28. package/src/components/form/Label/Label.tsx +73 -325
  29. package/src/components/form/Menubar/Menubar.hooks.ts +41 -0
  30. package/src/components/form/Menubar/Menubar.item.tsx +121 -0
  31. package/src/components/form/Menubar/Menubar.separator.tsx +25 -0
  32. package/src/components/form/Menubar/Menubar.styles.ts +110 -0
  33. package/src/components/form/Menubar/Menubar.toolbar.tsx +103 -0
  34. package/src/components/form/Menubar/Menubar.tsx +64 -347
  35. package/src/components/form/RadioGroup/RadioGroup.animations.ts +48 -0
  36. package/src/components/form/RadioGroup/RadioGroup.styles.ts +305 -0
  37. package/src/components/form/RadioGroup/RadioGroup.tsx +81 -651
  38. package/src/components/form/RadioGroup/RadioGroup.variants.tsx +291 -0
  39. package/src/components/form/Slider/Slider.animations.ts +40 -0
  40. package/src/components/form/Slider/Slider.styles.ts +292 -0
  41. package/src/components/form/Slider/Slider.tsx +106 -357
  42. package/src/components/form/Switch/Switch.animations.ts +62 -0
  43. package/src/components/form/Switch/Switch.parts.tsx +213 -0
  44. package/src/components/form/Switch/Switch.styles.ts +355 -0
  45. package/src/components/form/Switch/Switch.tsx +87 -566
  46. package/src/components/typography/Blockquote/Blockquote.test.stories.tsx +7 -7
  47. package/src/components/typography/Heading/Heading.styles.ts +101 -0
  48. package/src/components/typography/Heading/Heading.tsx +48 -164
  49. package/src/components/utility/AnimatedIcon/AnimatedIcon.animations.ts +274 -0
  50. package/src/components/utility/AnimatedIcon/AnimatedIcon.styles.ts +231 -0
  51. package/src/components/utility/AnimatedIcon/AnimatedIcon.tsx +39 -559
  52. package/src/utils/index.ts +1 -0
  53. package/src/utils/withDefaults.ts +20 -0
@@ -1,296 +1,51 @@
1
- import {
2
- alpha,
3
- Box,
4
- CircularProgress,
5
- Tooltip,
6
- Typography,
7
- useTheme,
8
- } from '@mui/material';
1
+ import { Box, Tooltip, Typography, useTheme } from '@mui/material';
9
2
  import React, { forwardRef } from 'react';
10
3
 
11
- import type { LabelProps } from './Label.types';
12
-
13
- export const Label = forwardRef<globalThis.HTMLLabelElement, LabelProps>(({
14
- children,
15
- htmlFor,
16
- required = false,
17
- disabled = false,
18
- error = false,
19
- variant = 'default',
20
- size = 'md',
21
- color = 'default',
22
- glow = false,
23
- pulse = false,
24
- ripple = false,
25
- loading = false,
26
- className,
27
- style,
28
- tooltip,
29
- helperText,
30
- asteriskPlacement = 'end',
31
- icon,
32
- iconPosition = 'start',
33
- onClick,
34
- onFocus,
35
- onBlur,
36
- srOnly = false,
37
- weight = 'regular',
38
- transform = 'none',
39
- align = 'left',
40
- nowrap = false,
41
- truncate = false,
42
- dataTestId,
43
- ...props
44
- }, ref) => {
45
- const theme = useTheme();
46
-
47
- const getSizeStyles = () => {
48
- switch (size) {
49
- case 'xs':
50
- return {
51
- fontSize: '0.75rem',
52
- lineHeight: 1.2,
53
- padding: theme.spacing(0.25, 0.5),
54
- };
55
- case 'sm':
56
- return {
57
- fontSize: '0.875rem',
58
- lineHeight: 1.3,
59
- padding: theme.spacing(0.5, 0.75),
60
- };
61
- case 'md':
62
- return {
63
- fontSize: '1rem',
64
- lineHeight: 1.5,
65
- padding: theme.spacing(0.75, 1),
66
- };
67
- case 'lg':
68
- return {
69
- fontSize: '1.125rem',
70
- lineHeight: 1.6,
71
- padding: theme.spacing(1, 1.25),
72
- };
73
- case 'xl':
74
- return {
75
- fontSize: '1.25rem',
76
- lineHeight: 1.7,
77
- padding: theme.spacing(1.25, 1.5),
78
- };
79
- default:
80
- return {
81
- fontSize: '1rem',
82
- lineHeight: 1.5,
83
- padding: theme.spacing(0.75, 1),
84
- };
85
- }
86
- };
87
-
88
- const getColorStyles = () => {
89
- if (error) {
90
- return { color: theme.palette.error.main };
91
- }
92
- if (disabled) {
93
- return { color: theme.palette.text.disabled };
94
- }
95
- if (color === 'default') {
96
- return { color: theme.palette.text.primary };
97
- }
98
- return { color: theme.palette[color].main };
99
- };
100
-
101
- const getVariantStyles = () => {
102
- const baseStyles = {
103
- display: srOnly ? 'none' : 'inline-flex',
104
- alignItems: 'center',
105
- gap: theme.spacing(0.5),
106
- transition: theme.transitions.create(['all'], {
107
- duration: theme.transitions.duration.standard,
108
- }),
109
- cursor: onClick ? 'pointer' : 'default',
110
- textAlign: align,
111
- fontWeight: getFontWeight(),
112
- textTransform: transform,
113
- whiteSpace: nowrap ? 'nowrap' : 'normal',
114
- overflow: truncate ? 'hidden' : 'visible',
115
- textOverflow: truncate ? 'ellipsis' : 'clip',
116
- position: 'relative' as const,
117
- ...getSizeStyles(),
118
- ...getColorStyles(),
119
- };
120
-
121
- const glowStyles = glow ? {
122
- textShadow: `0 0 10px ${alpha(getColorStyles().color || theme.palette.primary.main, 0.5)}`,
123
- } : {};
124
-
125
- const pulseStyles = pulse ? {
126
- animation: 'pulse 2s infinite',
127
- '@keyframes pulse': {
128
- '0%': { opacity: 1 },
129
- '50%': { opacity: 0.6 },
130
- '100%': { opacity: 1 },
131
- },
132
- } : {};
133
-
134
- const rippleStyles = ripple && onClick ? {
135
- overflow: 'hidden',
136
- '&::after': {
137
- content: '""',
138
- position: 'absolute',
139
- top: '50%',
140
- left: '50%',
141
- width: 0,
142
- height: 0,
143
- borderRadius: '50%',
144
- backgroundColor: alpha(theme.palette.primary.main, 0.3),
145
- transform: 'translate(-50%, -50%)',
146
- transition: 'width 0.6s, height 0.6s',
147
- },
148
- '&:active::after': {
149
- width: '300%',
150
- height: '300%',
151
- },
152
- } : {};
4
+ import { withDefaults } from '../../../utils/withDefaults';
153
5
 
154
- switch (variant) {
155
- case 'filled':
156
- return {
157
- ...baseStyles,
158
- ...glowStyles,
159
- ...pulseStyles,
160
- ...rippleStyles,
161
- backgroundColor: alpha(getColorStyles().color || theme.palette.primary.main, 0.1),
162
- borderRadius: theme.spacing(0.5),
163
- '&:hover': onClick ? {
164
- backgroundColor: alpha(getColorStyles().color || theme.palette.primary.main, 0.15),
165
- } : {},
166
- };
167
-
168
- case 'outlined':
169
- return {
170
- ...baseStyles,
171
- ...glowStyles,
172
- ...pulseStyles,
173
- ...rippleStyles,
174
- border: `1px solid ${alpha(getColorStyles().color || theme.palette.primary.main, 0.5)}`,
175
- borderRadius: theme.spacing(0.5),
176
- '&:hover': onClick ? {
177
- borderColor: getColorStyles().color || theme.palette.primary.main,
178
- backgroundColor: alpha(getColorStyles().color || theme.palette.primary.main, 0.05),
179
- } : {},
180
- };
181
-
182
- case 'glass':
183
- return {
184
- ...baseStyles,
185
- ...glowStyles,
186
- ...pulseStyles,
187
- ...rippleStyles,
188
- backgroundColor: alpha(theme.palette.background.paper, 0.1),
189
- backdropFilter: 'blur(10px)',
190
- border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
191
- borderRadius: theme.spacing(0.5),
192
- '&:hover': onClick ? {
193
- backgroundColor: alpha(theme.palette.background.paper, 0.15),
194
- borderColor: alpha(theme.palette.primary.main, 0.3),
195
- } : {},
196
- };
197
-
198
- case 'gradient': {
199
- const gradientColor = color === 'default' ? 'primary' : color;
200
- return {
201
- ...baseStyles,
202
- ...glowStyles,
203
- ...pulseStyles,
204
- ...rippleStyles,
205
- background: `linear-gradient(135deg, ${theme.palette[gradientColor].light}, ${theme.palette[gradientColor].main})`,
206
- WebkitBackgroundClip: 'text',
207
- WebkitTextFillColor: 'transparent',
208
- backgroundClip: 'text',
209
- '&:hover': onClick ? {
210
- background: `linear-gradient(135deg, ${theme.palette[gradientColor].main}, ${theme.palette[gradientColor].dark})`,
211
- WebkitBackgroundClip: 'text',
212
- WebkitTextFillColor: 'transparent',
213
- backgroundClip: 'text',
214
- } : {},
215
- };
216
- }
217
-
218
- case 'minimal':
219
- return {
220
- ...baseStyles,
221
- ...glowStyles,
222
- ...pulseStyles,
223
- ...rippleStyles,
224
- padding: 0,
225
- '&:hover': onClick ? {
226
- color: theme.palette.primary.main,
227
- } : {},
228
- };
229
-
230
- default:
231
- return {
232
- ...baseStyles,
233
- ...glowStyles,
234
- ...pulseStyles,
235
- ...rippleStyles,
236
- };
237
- }
238
- };
6
+ import { LabelBody } from './Label.parts';
7
+ import { labelStyles, sizeStyles, SR_ONLY_SX, textColor } from './Label.styles';
8
+ import type { LabelStyleFlags } from './Label.styles';
9
+ import type { LabelProps } from './Label.types';
239
10
 
240
- const getFontWeight = () => {
241
- switch (weight) {
242
- case 'light': return 300;
243
- case 'regular': return 400;
244
- case 'medium': return 500;
245
- case 'semibold': return 600;
246
- case 'bold': return 700;
247
- default: return 400;
248
- }
249
- };
11
+ const DEFAULTS = {
12
+ required: false,
13
+ disabled: false,
14
+ error: false,
15
+ variant: 'default',
16
+ size: 'md',
17
+ color: 'default',
18
+ glow: false,
19
+ pulse: false,
20
+ ripple: false,
21
+ loading: false,
22
+ asteriskPlacement: 'end',
23
+ iconPosition: 'start',
24
+ srOnly: false,
25
+ weight: 'regular',
26
+ transform: 'none',
27
+ align: 'left',
28
+ nowrap: false,
29
+ truncate: false,
30
+ } satisfies Partial<LabelProps>;
31
+
32
+ type ResolvedProps = LabelProps & Required<Pick<LabelProps, keyof typeof DEFAULTS>>;
33
+
34
+ export const Label = forwardRef<globalThis.HTMLLabelElement, LabelProps>((props, ref) => {
35
+ const {
36
+ children, htmlFor, required, disabled, error, variant, size, color,
37
+ glow, pulse, ripple, loading, className, style, tooltip, helperText,
38
+ asteriskPlacement, icon, iconPosition, onClick, onFocus, onBlur,
39
+ srOnly, weight, transform, align, nowrap, truncate, dataTestId,
40
+ ...rest
41
+ } = withDefaults(props, DEFAULTS) as ResolvedProps;
250
42
 
251
- const renderAsterisk = () => {
252
- if (!required) return null;
253
- return (
254
- <Box
255
- component="span"
256
- sx={{
257
- color: theme.palette.error.main,
258
- marginLeft: asteriskPlacement === 'end' ? theme.spacing(0.5) : 0,
259
- marginRight: asteriskPlacement === 'start' ? theme.spacing(0.5) : 0,
260
- }}
261
- >
262
- *
263
- </Box>
264
- );
265
- };
266
-
267
- const renderIcon = () => {
268
- if (!icon) return null;
269
- return (
270
- <Box
271
- component="span"
272
- sx={{
273
- display: 'inline-flex',
274
- alignItems: 'center',
275
- fontSize: 'inherit',
276
- }}
277
- >
278
- {icon}
279
- </Box>
280
- );
281
- };
43
+ const theme = useTheme();
282
44
 
283
- const renderLoading = () => {
284
- if (!loading) return null;
285
- return (
286
- <CircularProgress
287
- size={getSizeStyles().fontSize}
288
- sx={{
289
- color: getColorStyles().color,
290
- marginLeft: theme.spacing(0.5),
291
- }}
292
- />
293
- );
45
+ const flags: LabelStyleFlags = {
46
+ variant, size, color, weight, transform, align,
47
+ error, disabled, glow, pulse, ripple, srOnly, nowrap, truncate,
48
+ clickable: Boolean(onClick),
294
49
  };
295
50
 
296
51
  const labelContent = (
@@ -303,51 +58,44 @@ export const Label = forwardRef<globalThis.HTMLLabelElement, LabelProps>(({
303
58
  onBlur={onBlur}
304
59
  className={className}
305
60
  data-testid={dataTestId}
306
- sx={{
307
- ...getVariantStyles(),
308
- ...style,
309
- }}
310
- {...props}
61
+ sx={{ ...labelStyles(theme, flags), ...style }}
62
+ {...rest}
311
63
  >
312
- {asteriskPlacement === 'start' && renderAsterisk()}
313
- {iconPosition === 'start' && renderIcon()}
314
- {children}
315
- {iconPosition === 'end' && renderIcon()}
316
- {asteriskPlacement === 'end' && renderAsterisk()}
317
- {renderLoading()}
64
+ <LabelBody
65
+ required={required}
66
+ asteriskPlacement={asteriskPlacement}
67
+ icon={icon}
68
+ iconPosition={iconPosition}
69
+ loading={loading}
70
+ fontSize={sizeStyles(theme, size).fontSize as string}
71
+ color={textColor(theme, flags)}
72
+ >
73
+ {children}
74
+ </LabelBody>
318
75
  </Box>
319
76
  );
320
77
 
321
- const wrappedContent = tooltip ? (
322
- <Tooltip title={tooltip} arrow>
323
- {labelContent}
324
- </Tooltip>
325
- ) : labelContent;
78
+ // A screen-reader-only label is rendered on its own: the tooltip, the styling
79
+ // and the helper text all describe something nothing can see.
80
+ if (srOnly) {
81
+ return (
82
+ <Box component="label" htmlFor={htmlFor} data-testid={dataTestId} sx={SR_ONLY_SX}>
83
+ {children}
84
+ </Box>
85
+ );
86
+ }
326
87
 
327
88
  return (
328
89
  <>
329
- {srOnly && (
330
- <Box
331
- component="label"
332
- htmlFor={htmlFor}
333
- data-testid={dataTestId}
334
- sx={{
335
- position: 'absolute',
336
- width: '1px',
337
- height: '1px',
338
- padding: 0,
339
- margin: '-1px',
340
- overflow: 'hidden',
341
- clip: 'rect(0, 0, 0, 0)',
342
- whiteSpace: 'nowrap',
343
- border: 0,
344
- }}
345
- >
346
- {children}
347
- </Box>
90
+ {tooltip ? (
91
+ <Tooltip title={tooltip} arrow>
92
+ {labelContent}
93
+ </Tooltip>
94
+ ) : (
95
+ labelContent
348
96
  )}
349
- {!srOnly && wrappedContent}
350
- {helperText && !srOnly && (
97
+
98
+ {helperText && (
351
99
  <Typography
352
100
  variant="caption"
353
101
  sx={{
@@ -364,4 +112,4 @@ export const Label = forwardRef<globalThis.HTMLLabelElement, LabelProps>(({
364
112
  );
365
113
  });
366
114
 
367
- Label.displayName = 'Label';
115
+ Label.displayName = 'Label';
@@ -0,0 +1,41 @@
1
+ import { useCallback, useRef, useState } from 'react';
2
+
3
+ import type { MenubarItem, MenubarProps } from './Menubar.types';
4
+
5
+ /**
6
+ * Which dropdown is open, and the buttons that opened them.
7
+ *
8
+ * The anchors are kept in a ref rather than state because closing needs to
9
+ * return focus to the button that opened the menu, and that lookup must not
10
+ * itself cause a render.
11
+ */
12
+ export const useMenubarMenus = (onClick?: MenubarProps['onClick']) => {
13
+ const [activeMenu, setActiveMenu] = useState<string | null>(null);
14
+ const menuRefs = useRef<{ [key: string]: HTMLElement | null }>({});
15
+
16
+ const handleMenuOpen = useCallback((itemId: string, anchorEl: HTMLElement) => {
17
+ setActiveMenu(itemId);
18
+ menuRefs.current[itemId] = anchorEl;
19
+ }, []);
20
+
21
+ const handleMenuClose = useCallback(() => {
22
+ setActiveMenu((previous) => {
23
+ // Return focus to the button that opened the menu.
24
+ if (previous && menuRefs.current[previous]) {
25
+ menuRefs.current[previous].focus();
26
+ }
27
+ return null;
28
+ });
29
+ }, []);
30
+
31
+ const handleItemClick = useCallback(
32
+ (item: MenubarItem) => {
33
+ item.action?.();
34
+ onClick?.(item);
35
+ handleMenuClose();
36
+ },
37
+ [onClick, handleMenuClose],
38
+ );
39
+
40
+ return { activeMenu, menuRefs, handleMenuOpen, handleMenuClose, handleItemClick };
41
+ };
@@ -0,0 +1,121 @@
1
+ import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
2
+ import {
3
+ Box,
4
+ Button,
5
+ Chip,
6
+ Divider,
7
+ ListItemIcon,
8
+ ListItemText,
9
+ Menu,
10
+ MenuItem,
11
+ useTheme,
12
+ } from '@mui/material';
13
+ import React from 'react';
14
+
15
+ import { sizeStyles } from './Menubar.styles';
16
+ import type { MenubarItem, MenubarProps } from './Menubar.types';
17
+
18
+ export interface MenubarItemViewProps {
19
+ item: MenubarItem;
20
+ size: NonNullable<MenubarProps['size']>;
21
+ disabled: boolean;
22
+ isOpen: boolean;
23
+ anchorEl: HTMLElement | null;
24
+ onOpen: (itemId: string, anchorEl: HTMLElement) => void;
25
+ onActivate: (item: MenubarItem) => void;
26
+ onClose: () => void;
27
+ onFocus?: React.FocusEventHandler;
28
+ onBlur?: React.FocusEventHandler;
29
+ }
30
+
31
+ /** One row of an open menu: its icon, its label, and its shortcut hint. */
32
+ const DropdownItem: React.FC<{ child: MenubarItem; onActivate: (item: MenubarItem) => void }> = ({
33
+ child,
34
+ onActivate,
35
+ }) =>
36
+ child.divider ? (
37
+ <Divider />
38
+ ) : (
39
+ <MenuItem onClick={() => onActivate(child)} disabled={child.disabled}>
40
+ {child.icon && <ListItemIcon>{child.icon}</ListItemIcon>}
41
+ <ListItemText>{child.label}</ListItemText>
42
+ {child.shortcut && (
43
+ <Chip
44
+ label={child.shortcut}
45
+ size="small"
46
+ variant="outlined"
47
+ sx={{ ml: 2, height: 20, fontSize: '0.7rem' }}
48
+ />
49
+ )}
50
+ </MenuItem>
51
+ );
52
+
53
+ /**
54
+ * A top-level menubar entry. An entry with children opens its dropdown; one
55
+ * without acts immediately — a decision the click and keyboard paths both need,
56
+ * so it is made once here rather than in each handler.
57
+ */
58
+ export const MenubarItemView: React.FC<MenubarItemViewProps> = ({
59
+ item,
60
+ size,
61
+ disabled,
62
+ isOpen,
63
+ anchorEl,
64
+ onOpen,
65
+ onActivate,
66
+ onClose,
67
+ onFocus,
68
+ onBlur,
69
+ }) => {
70
+ const theme = useTheme();
71
+ const hasChildren = Boolean(item.children && item.children.length > 0);
72
+
73
+ const activate = (target: HTMLElement) => {
74
+ if (hasChildren) {
75
+ onOpen(item.id, target);
76
+ } else {
77
+ onActivate(item);
78
+ }
79
+ };
80
+
81
+ return (
82
+ <Box>
83
+ <Button
84
+ data-testid={`menubar-button-${item.id}`}
85
+ onClick={(e) => activate(e.currentTarget)}
86
+ onKeyDown={(e) => {
87
+ if (e.key === 'Enter' || e.key === ' ') {
88
+ e.preventDefault();
89
+ activate(e.currentTarget);
90
+ }
91
+ }}
92
+ // Keeping focus on mousedown is what lets handleMenuClose return it here.
93
+ onMouseDown={(e) => e.preventDefault()}
94
+ onFocus={onFocus}
95
+ onBlur={onBlur}
96
+ disabled={item.disabled || disabled}
97
+ startIcon={item.icon}
98
+ endIcon={hasChildren ? <KeyboardArrowDownIcon /> : null}
99
+ aria-haspopup={hasChildren ? 'menu' : undefined}
100
+ aria-expanded={hasChildren ? isOpen : undefined}
101
+ sx={{ color: 'inherit', textTransform: 'none', ...sizeStyles(theme, size) }}
102
+ >
103
+ {item.label}
104
+ </Button>
105
+
106
+ {hasChildren && (
107
+ <Menu
108
+ anchorEl={anchorEl}
109
+ open={isOpen}
110
+ onClose={onClose}
111
+ autoFocus
112
+ PaperProps={{ sx: { minWidth: 200, mt: 1 } }}
113
+ >
114
+ {item.children?.map((child) => (
115
+ <DropdownItem key={child.id} child={child} onActivate={onActivate} />
116
+ ))}
117
+ </Menu>
118
+ )}
119
+ </Box>
120
+ );
121
+ };
@@ -0,0 +1,25 @@
1
+ import { Divider } from '@mui/material';
2
+ import React from 'react';
3
+
4
+ import type { MenubarSeparatorProps } from './Menubar.types';
5
+
6
+ /**
7
+ * The rule between two entries. Its own orientation is the bar's turned ninety
8
+ * degrees: a horizontal bar is divided by vertical rules.
9
+ */
10
+ export const MenubarSeparator: React.FC<MenubarSeparatorProps> = ({
11
+ orientation = 'horizontal',
12
+ className,
13
+ style,
14
+ }) => (
15
+ <Divider
16
+ orientation={orientation === 'horizontal' ? 'vertical' : 'horizontal'}
17
+ flexItem
18
+ className={className}
19
+ sx={{
20
+ mx: orientation === 'horizontal' ? 1 : 0,
21
+ my: orientation === 'vertical' ? 1 : 0,
22
+ ...style,
23
+ }}
24
+ />
25
+ );