@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
@@ -0,0 +1,110 @@
1
+ import { alpha } from '@mui/material';
2
+ import type { CSSObject, Theme } from '@mui/material';
3
+
4
+ import type { MenubarProps } from './Menubar.types';
5
+
6
+ type Size = NonNullable<MenubarProps['size']>;
7
+ type Variant = NonNullable<MenubarProps['variant']>;
8
+ type Color = NonNullable<MenubarProps['color']>;
9
+
10
+ const SIZES: Record<Size, (theme: Theme) => CSSObject> = {
11
+ xs: (theme) => ({ minHeight: 40, fontSize: '0.75rem', padding: theme.spacing(0.5, 1) }),
12
+ sm: (theme) => ({ minHeight: 48, fontSize: '0.875rem', padding: theme.spacing(0.75, 1.5) }),
13
+ md: (theme) => ({ minHeight: 56, fontSize: '1rem', padding: theme.spacing(1, 2) }),
14
+ lg: (theme) => ({ minHeight: 64, fontSize: '1.125rem', padding: theme.spacing(1.25, 2.5) }),
15
+ xl: (theme) => ({ minHeight: 72, fontSize: '1.25rem', padding: theme.spacing(1.5, 3) }),
16
+ };
17
+
18
+ export const sizeStyles = (theme: Theme, size: Size): CSSObject =>
19
+ (SIZES[size] ?? SIZES.md)(theme);
20
+
21
+ /** `default` is not a palette entry — it means the paper surface and its text. */
22
+ const accentOf = (theme: Theme, color: Color) =>
23
+ color === 'default' ? theme.palette.primary.main : theme.palette[color].main;
24
+
25
+ const colorStyles = (theme: Theme, color: Color, transparent: boolean): CSSObject => {
26
+ const surface =
27
+ color === 'default'
28
+ ? { backgroundColor: theme.palette.background.paper, color: theme.palette.text.primary }
29
+ : { backgroundColor: theme.palette[color].main, color: theme.palette[color].contrastText };
30
+
31
+ return transparent ? { ...surface, backgroundColor: 'transparent' } : surface;
32
+ };
33
+
34
+ interface MenubarStyleFlags {
35
+ variant: Variant;
36
+ size: Size;
37
+ color: Color;
38
+ glow: boolean;
39
+ pulse: boolean;
40
+ glass: boolean;
41
+ gradient: boolean;
42
+ blur: boolean;
43
+ transparent: boolean;
44
+ elevation: number;
45
+ }
46
+
47
+ /**
48
+ * Each variant's own additions to the shared bar. The `glass` and `gradient`
49
+ * arms deliberately do not take the blur spread: glass sets its own
50
+ * backdropFilter, and a blurred gradient loses the gradient.
51
+ */
52
+ const SURFACES: Record<Variant, (theme: Theme, flags: MenubarStyleFlags) => CSSObject> = {
53
+ glass: (theme, { glass }) => ({
54
+ backgroundColor: alpha(theme.palette.background.paper, glass ? 0.1 : 0.8),
55
+ backdropFilter: 'blur(20px)',
56
+ borderBottom: `1px solid ${alpha(theme.palette.divider, 0.1)}`,
57
+ }),
58
+
59
+ gradient: (theme, { color, gradient }) => {
60
+ const key = color === 'default' ? 'primary' : color;
61
+ return {
62
+ background: gradient
63
+ ? `linear-gradient(135deg, ${theme.palette[key].light}, ${theme.palette[key].dark})`
64
+ : theme.palette[key].main,
65
+ color: theme.palette[key].contrastText,
66
+ };
67
+ },
68
+
69
+ elevated: (theme, { blur, elevation }) => ({
70
+ ...(blur && { backdropFilter: 'blur(10px)' }),
71
+ boxShadow: theme.shadows[elevation],
72
+ }),
73
+
74
+ minimal: (_theme, { blur }) => ({
75
+ ...(blur && { backdropFilter: 'blur(10px)' }),
76
+ boxShadow: 'none',
77
+ borderBottom: 'none',
78
+ }),
79
+
80
+ bordered: (theme, { blur, color }) => ({
81
+ ...(blur && { backdropFilter: 'blur(10px)' }),
82
+ borderBottom: `2px solid ${
83
+ color === 'default' ? theme.palette.divider : theme.palette[color].main
84
+ }`,
85
+ }),
86
+
87
+ default: (_theme, { blur }) => ({ ...(blur && { backdropFilter: 'blur(10px)' }) }),
88
+ };
89
+
90
+ export const barStyles = (theme: Theme, flags: MenubarStyleFlags): CSSObject => {
91
+ const { variant, size, color, glow, pulse, transparent } = flags;
92
+
93
+ return {
94
+ transition: theme.transitions.create(['all'], {
95
+ duration: theme.transitions.duration.standard,
96
+ }),
97
+ ...sizeStyles(theme, size),
98
+ ...colorStyles(theme, color, transparent),
99
+ ...(glow && { boxShadow: `0 0 20px ${alpha(accentOf(theme, color), 0.4)}` }),
100
+ ...(pulse && {
101
+ animation: 'pulse 2s infinite',
102
+ '@keyframes pulse': {
103
+ '0%': { opacity: 1 },
104
+ '50%': { opacity: 0.8 },
105
+ '100%': { opacity: 1 },
106
+ },
107
+ }),
108
+ ...(SURFACES[variant] ?? SURFACES.default)(theme, flags),
109
+ };
110
+ };
@@ -0,0 +1,103 @@
1
+ import { Box, CircularProgress, Toolbar } from '@mui/material';
2
+ import type { CSSObject } from '@mui/material';
3
+ import React from 'react';
4
+
5
+ import { MenubarItemView } from './Menubar.item';
6
+ import { MenubarSeparator } from './Menubar.separator';
7
+ import type { MenubarItem, MenubarProps } from './Menubar.types';
8
+
9
+ export interface MenubarToolbarProps {
10
+ barSx: CSSObject;
11
+ items: MenubarItem[];
12
+ size: NonNullable<MenubarProps['size']>;
13
+ orientation: NonNullable<MenubarProps['orientation']>;
14
+ disabled: boolean;
15
+ loading: boolean;
16
+ logo?: React.ReactNode;
17
+ endContent?: React.ReactNode;
18
+ fullWidth: boolean;
19
+ activeMenu: string | null;
20
+ menuRefs: React.MutableRefObject<{ [key: string]: HTMLElement | null }>;
21
+ onOpen: (itemId: string, anchorEl: HTMLElement) => void;
22
+ onActivate: (item: MenubarItem) => void;
23
+ onClose: () => void;
24
+ onFocus?: React.FocusEventHandler;
25
+ onBlur?: React.FocusEventHandler;
26
+ }
27
+
28
+ /**
29
+ * The bar's contents: an optional logo, the entries themselves, and whatever the
30
+ * caller hangs off the end. Shared by both orientations — only the element
31
+ * wrapping it differs.
32
+ */
33
+ export const MenubarToolbar: React.FC<MenubarToolbarProps> = ({
34
+ barSx,
35
+ items,
36
+ size,
37
+ orientation,
38
+ disabled,
39
+ loading,
40
+ logo,
41
+ endContent,
42
+ fullWidth,
43
+ activeMenu,
44
+ menuRefs,
45
+ onOpen,
46
+ onActivate,
47
+ onClose,
48
+ onFocus,
49
+ onBlur,
50
+ }) => {
51
+ const isVertical = orientation === 'vertical';
52
+
53
+ return (
54
+ <Toolbar
55
+ role="toolbar"
56
+ aria-label="Main navigation"
57
+ sx={{
58
+ ...barSx,
59
+ width: fullWidth ? '100%' : 'auto',
60
+ flexDirection: isVertical ? 'column' : 'row',
61
+ alignItems: isVertical ? 'flex-start' : 'center',
62
+ gap: 1,
63
+ }}
64
+ >
65
+ {logo && <Box sx={{ mr: 2, display: 'flex', alignItems: 'center' }}>{logo}</Box>}
66
+
67
+ {loading ? (
68
+ <CircularProgress size={20} color="inherit" />
69
+ ) : (
70
+ <Box
71
+ sx={{
72
+ display: 'flex',
73
+ flexDirection: isVertical ? 'column' : 'row',
74
+ gap: isVertical ? 0.5 : 1,
75
+ flex: 1,
76
+ }}
77
+ >
78
+ {items.map((item) =>
79
+ item.divider ? (
80
+ <MenubarSeparator key={item.id} orientation={orientation} />
81
+ ) : (
82
+ <MenubarItemView
83
+ key={item.id}
84
+ item={item}
85
+ size={size}
86
+ disabled={disabled}
87
+ isOpen={activeMenu === item.id}
88
+ anchorEl={menuRefs.current[item.id] ?? null}
89
+ onOpen={onOpen}
90
+ onActivate={onActivate}
91
+ onClose={onClose}
92
+ onFocus={onFocus}
93
+ onBlur={onBlur}
94
+ />
95
+ ),
96
+ )}
97
+ </Box>
98
+ )}
99
+
100
+ {endContent && <Box sx={{ ml: 'auto' }}>{endContent}</Box>}
101
+ </Toolbar>
102
+ );
103
+ };
@@ -1,357 +1,91 @@
1
1
  import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
2
2
  import {
3
- alpha,
4
3
  AppBar,
5
4
  Box,
6
5
  Button,
7
- Chip,
8
- CircularProgress,
9
6
  Divider,
10
7
  ListItemIcon,
11
8
  ListItemText,
12
9
  Menu,
13
10
  MenuItem,
14
- Toolbar,
15
11
  Typography,
16
12
  useTheme,
17
13
  } from '@mui/material';
18
- import React, { useRef,useState } from 'react';
14
+ import React, { useState } from 'react';
19
15
 
16
+ import { withDefaults } from '../../../utils/withDefaults';
17
+
18
+ import { useMenubarMenus } from './Menubar.hooks';
19
+ import { barStyles } from './Menubar.styles';
20
+ import { MenubarToolbar } from './Menubar.toolbar';
20
21
  import type {
21
22
  MenubarGroupProps,
22
23
  MenubarItem,
23
24
  MenubarProps,
24
- MenubarSeparatorProps,
25
25
  } from './Menubar.types';
26
26
 
27
- export const Menubar: React.FC<MenubarProps> = ({
28
- items,
29
- variant = 'default',
30
- size = 'md',
31
- color = 'default',
32
- orientation = 'horizontal',
33
- glow = false,
34
- pulse = false,
35
- glass = false,
36
- gradient = false,
37
- loading = false,
38
- disabled = false,
39
- className,
40
- style,
41
- logo,
42
- endContent,
43
- sticky = false,
44
- transparent = false,
45
- blur = false,
46
- elevation = 4,
47
- fullWidth = true,
48
- onClick,
49
- onFocus,
50
- onBlur,
51
- 'data-testid': dataTestId,
52
- }) => {
53
- const theme = useTheme();
54
- const [activeMenu, setActiveMenu] = useState<string | null>(null);
55
- const menuRefs = useRef<{ [key: string]: HTMLElement | null }>({});
56
-
57
- const handleMenuOpen = (itemId: string, anchorEl: HTMLElement) => {
58
- setActiveMenu(itemId);
59
- menuRefs.current[itemId] = anchorEl;
60
- };
61
-
62
- const handleMenuClose = () => {
63
- const previousMenu = activeMenu;
64
- setActiveMenu(null);
65
- // Return focus to the button that opened the menu
66
- if (previousMenu && menuRefs.current[previousMenu]) {
67
- menuRefs.current[previousMenu].focus();
68
- }
69
- };
70
-
71
- const handleItemClick = (item: MenubarItem) => {
72
- if (item.action) {
73
- item.action();
74
- }
75
- onClick?.(item);
76
- handleMenuClose();
77
- };
78
-
79
- const getSizeStyles = () => {
80
- switch (size) {
81
- case 'xs':
82
- return {
83
- minHeight: 40,
84
- fontSize: '0.75rem',
85
- padding: theme.spacing(0.5, 1),
86
- };
87
- case 'sm':
88
- return {
89
- minHeight: 48,
90
- fontSize: '0.875rem',
91
- padding: theme.spacing(0.75, 1.5),
92
- };
93
- case 'md':
94
- return {
95
- minHeight: 56,
96
- fontSize: '1rem',
97
- padding: theme.spacing(1, 2),
98
- };
99
- case 'lg':
100
- return {
101
- minHeight: 64,
102
- fontSize: '1.125rem',
103
- padding: theme.spacing(1.25, 2.5),
104
- };
105
- case 'xl':
106
- return {
107
- minHeight: 72,
108
- fontSize: '1.25rem',
109
- padding: theme.spacing(1.5, 3),
110
- };
111
- default:
112
- return {
113
- minHeight: 56,
114
- fontSize: '1rem',
115
- padding: theme.spacing(1, 2),
116
- };
117
- }
118
- };
119
-
120
- const getColorStyles = () => {
121
- if (color === 'default') {
122
- return {
123
- backgroundColor: transparent ? 'transparent' : theme.palette.background.paper,
124
- color: theme.palette.text.primary,
125
- };
126
- }
127
- return {
128
- backgroundColor: transparent ? 'transparent' : theme.palette[color].main,
129
- color: theme.palette[color].contrastText,
130
- };
131
- };
132
-
133
- const getVariantStyles = () => {
134
- const baseStyles = {
135
- transition: theme.transitions.create(['all'], {
136
- duration: theme.transitions.duration.standard,
137
- }),
138
- ...getSizeStyles(),
139
- ...getColorStyles(),
140
- };
141
-
142
- const glowStyles = glow
143
- ? {
144
- boxShadow: `0 0 20px ${alpha(
145
- color === 'default' ? theme.palette.primary.main : theme.palette[color].main,
146
- 0.4,
147
- )}`,
148
- }
149
- : {};
150
-
151
- const pulseStyles = pulse
152
- ? {
153
- animation: 'pulse 2s infinite',
154
- '@keyframes pulse': {
155
- '0%': { opacity: 1 },
156
- '50%': { opacity: 0.8 },
157
- '100%': { opacity: 1 },
158
- },
159
- }
160
- : {};
27
+ const DEFAULTS = {
28
+ variant: 'default',
29
+ size: 'md',
30
+ color: 'default',
31
+ orientation: 'horizontal',
32
+ glow: false,
33
+ pulse: false,
34
+ glass: false,
35
+ gradient: false,
36
+ loading: false,
37
+ disabled: false,
38
+ sticky: false,
39
+ transparent: false,
40
+ blur: false,
41
+ elevation: 4,
42
+ fullWidth: true,
43
+ } satisfies Partial<MenubarProps>;
44
+
45
+ type ResolvedProps = MenubarProps & Required<Pick<MenubarProps, keyof typeof DEFAULTS>>;
46
+
47
+ export const Menubar: React.FC<MenubarProps> = (props) => {
48
+ const {
49
+ items, variant, size, color, orientation, glow, pulse, glass, gradient,
50
+ loading, disabled, className, style, logo, endContent, sticky, transparent,
51
+ blur, elevation, fullWidth, onClick, onFocus, onBlur,
52
+ 'data-testid': dataTestId,
53
+ } = withDefaults(props, DEFAULTS) as ResolvedProps;
161
54
 
162
- const blurStyles = blur
163
- ? {
164
- backdropFilter: 'blur(10px)',
165
- }
166
- : {};
167
-
168
- switch (variant) {
169
- case 'glass':
170
- return {
171
- ...baseStyles,
172
- ...glowStyles,
173
- ...pulseStyles,
174
- backgroundColor: alpha(theme.palette.background.paper, glass ? 0.1 : 0.8),
175
- backdropFilter: 'blur(20px)',
176
- borderBottom: `1px solid ${alpha(theme.palette.divider, 0.1)}`,
177
- };
178
-
179
- case 'gradient': {
180
- const gradientColor = color === 'default' ? 'primary' : color;
181
- return {
182
- ...baseStyles,
183
- ...glowStyles,
184
- ...pulseStyles,
185
- background: gradient
186
- ? `linear-gradient(135deg, ${theme.palette[gradientColor].light}, ${theme.palette[gradientColor].dark})`
187
- : theme.palette[gradientColor].main,
188
- color: theme.palette[gradientColor].contrastText,
189
- };
190
- }
191
-
192
- case 'elevated':
193
- return {
194
- ...baseStyles,
195
- ...glowStyles,
196
- ...pulseStyles,
197
- ...blurStyles,
198
- boxShadow: theme.shadows[elevation],
199
- };
200
-
201
- case 'minimal':
202
- return {
203
- ...baseStyles,
204
- ...glowStyles,
205
- ...pulseStyles,
206
- ...blurStyles,
207
- boxShadow: 'none',
208
- borderBottom: 'none',
209
- };
210
-
211
- case 'bordered':
212
- return {
213
- ...baseStyles,
214
- ...glowStyles,
215
- ...pulseStyles,
216
- ...blurStyles,
217
- borderBottom: `2px solid ${
218
- color === 'default' ? theme.palette.divider : theme.palette[color].main
219
- }`,
220
- };
221
-
222
- default:
223
- return {
224
- ...baseStyles,
225
- ...glowStyles,
226
- ...pulseStyles,
227
- ...blurStyles,
228
- };
229
- }
230
- };
55
+ const theme = useTheme();
56
+ const { activeMenu, menuRefs, handleMenuOpen, handleMenuClose, handleItemClick } =
57
+ useMenubarMenus(onClick);
231
58
 
232
- const renderMenubarItem = (item: MenubarItem) => {
233
- if (item.divider) {
234
- return <MenubarSeparator key={item.id} orientation={orientation} />;
235
- }
59
+ const isVertical = orientation === 'vertical';
236
60
 
237
- const hasChildren = item.children && item.children.length > 0;
238
-
239
- return (
240
- <Box key={item.id}>
241
- <Button
242
- data-testid={`menubar-button-${item.id}`}
243
- onClick={(e) => {
244
- if (hasChildren) {
245
- handleMenuOpen(item.id, e.currentTarget);
246
- } else {
247
- handleItemClick(item);
248
- }
249
- }}
250
- onKeyDown={(e) => {
251
- if (e.key === 'Enter' || e.key === ' ') {
252
- e.preventDefault();
253
- if (hasChildren) {
254
- handleMenuOpen(item.id, e.currentTarget);
255
- } else {
256
- handleItemClick(item);
257
- }
258
- }
259
- }}
260
- onMouseDown={(e) => {
261
- // Prevent default to maintain focus
262
- e.preventDefault();
263
- }}
264
- onFocus={onFocus}
265
- onBlur={onBlur}
266
- disabled={item.disabled || disabled}
267
- startIcon={item.icon}
268
- endIcon={hasChildren ? <KeyboardArrowDownIcon /> : null}
269
- aria-haspopup={hasChildren ? 'menu' : undefined}
270
- aria-expanded={hasChildren ? activeMenu === item.id : undefined}
271
- sx={{
272
- color: 'inherit',
273
- textTransform: 'none',
274
- ...getSizeStyles(),
275
- }}
276
- >
277
- {item.label}
278
- </Button>
279
-
280
- {hasChildren && (
281
- <Menu
282
- anchorEl={menuRefs.current[item.id]}
283
- open={activeMenu === item.id}
284
- onClose={handleMenuClose}
285
- autoFocus
286
- PaperProps={{
287
- sx: {
288
- minWidth: 200,
289
- mt: 1,
290
- },
291
- }}
292
- >
293
- {item.children?.map((child) =>
294
- child.divider ? (
295
- <Divider key={child.id} />
296
- ) : (
297
- <MenuItem
298
- key={child.id}
299
- onClick={() => handleItemClick(child)}
300
- disabled={child.disabled}
301
- >
302
- {child.icon && <ListItemIcon>{child.icon}</ListItemIcon>}
303
- <ListItemText>{child.label}</ListItemText>
304
- {child.shortcut && (
305
- <Chip
306
- label={child.shortcut}
307
- size="small"
308
- variant="outlined"
309
- sx={{ ml: 2, height: 20, fontSize: '0.7rem' }}
310
- />
311
- )}
312
- </MenuItem>
313
- ),
314
- )}
315
- </Menu>
316
- )}
317
- </Box>
318
- );
319
- };
61
+ const barSx = barStyles(theme, {
62
+ variant, size, color, glow, pulse, glass, gradient, blur, transparent, elevation,
63
+ });
320
64
 
321
65
  const content = (
322
- <Toolbar
323
- role="toolbar"
324
- aria-label="Main navigation"
325
- sx={{
326
- ...getVariantStyles(),
327
- width: fullWidth ? '100%' : 'auto',
328
- flexDirection: orientation === 'vertical' ? 'column' : 'row',
329
- alignItems: orientation === 'vertical' ? 'flex-start' : 'center',
330
- gap: 1,
331
- }}
332
- >
333
- {logo && <Box sx={{ mr: 2, display: 'flex', alignItems: 'center' }}>{logo}</Box>}
334
-
335
- {loading ? (
336
- <CircularProgress size={20} color="inherit" />
337
- ) : (
338
- <Box
339
- sx={{
340
- display: 'flex',
341
- flexDirection: orientation === 'vertical' ? 'column' : 'row',
342
- gap: orientation === 'vertical' ? 0.5 : 1,
343
- flex: 1,
344
- }}
345
- >
346
- {items.map(renderMenubarItem)}
347
- </Box>
348
- )}
349
-
350
- {endContent && <Box sx={{ ml: 'auto' }}>{endContent}</Box>}
351
- </Toolbar>
66
+ <MenubarToolbar
67
+ barSx={barSx}
68
+ items={items}
69
+ size={size}
70
+ orientation={orientation}
71
+ disabled={disabled}
72
+ loading={loading}
73
+ logo={logo}
74
+ endContent={endContent}
75
+ fullWidth={fullWidth}
76
+ activeMenu={activeMenu}
77
+ menuRefs={menuRefs}
78
+ onOpen={handleMenuOpen}
79
+ onActivate={handleItemClick}
80
+ onClose={handleMenuClose}
81
+ onFocus={onFocus}
82
+ onBlur={onBlur}
83
+ />
352
84
  );
353
85
 
354
- if (orientation === 'vertical') {
86
+ // A vertical bar is not an app bar: AppBar's fixed/sticky positioning assumes
87
+ // a horizontal strip at the top of the page.
88
+ if (isVertical) {
355
89
  return (
356
90
  <Box
357
91
  className={className}
@@ -372,10 +106,7 @@ export const Menubar: React.FC<MenubarProps> = ({
372
106
  position={sticky ? 'sticky' : 'static'}
373
107
  className={className}
374
108
  data-testid={dataTestId}
375
- sx={{
376
- ...getVariantStyles(),
377
- ...style,
378
- }}
109
+ sx={{ ...barSx, ...style }}
379
110
  elevation={0}
380
111
  component="header"
381
112
  >
@@ -384,6 +115,7 @@ export const Menubar: React.FC<MenubarProps> = ({
384
115
  );
385
116
  };
386
117
 
118
+
387
119
  export const MenubarGroup: React.FC<MenubarGroupProps> = ({
388
120
  label,
389
121
  items,
@@ -449,19 +181,4 @@ export const MenubarGroup: React.FC<MenubarGroupProps> = ({
449
181
  );
450
182
  };
451
183
 
452
- export const MenubarSeparator: React.FC<MenubarSeparatorProps> = ({
453
- orientation = 'horizontal',
454
- className,
455
- style,
456
- }) => (
457
- <Divider
458
- orientation={orientation === 'horizontal' ? 'vertical' : 'horizontal'}
459
- flexItem
460
- className={className}
461
- sx={{
462
- mx: orientation === 'horizontal' ? 1 : 0,
463
- my: orientation === 'vertical' ? 1 : 0,
464
- ...style,
465
- }}
466
- />
467
- );
184
+ export { MenubarSeparator } from './Menubar.separator';