UNPKG

20.6 kBJavaScriptView Raw
1'use client';
2
3import * as React from 'react';
4import PropTypes from 'prop-types';
5import clsx from 'clsx';
6import resolveProps from '@mui/utils/resolveProps';
7import composeClasses from '@mui/utils/composeClasses';
8import { alpha } from '@mui/system/colorManipulator';
9import { unstable_useId as useId } from '@mui/material/utils';
10import rootShouldForwardProp from "../styles/rootShouldForwardProp.js";
11import { styled } from "../zero-styled/index.js";
12import memoTheme from "../utils/memoTheme.js";
13import { useDefaultProps } from "../DefaultPropsProvider/index.js";
14import ButtonBase from "../ButtonBase/index.js";
15import CircularProgress from "../CircularProgress/index.js";
16import capitalize from "../utils/capitalize.js";
17import createSimplePaletteValueFilter from "../utils/createSimplePaletteValueFilter.js";
18import buttonClasses, { getButtonUtilityClass } from "./buttonClasses.js";
19import ButtonGroupContext from "../ButtonGroup/ButtonGroupContext.js";
20import ButtonGroupButtonContext from "../ButtonGroup/ButtonGroupButtonContext.js";
21import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
22const useUtilityClasses = ownerState => {
23 const {
24 color,
25 disableElevation,
26 fullWidth,
27 size,
28 variant,
29 loading,
30 loadingPosition,
31 classes
32 } = ownerState;
33 const slots = {
34 root: ['root', loading && 'loading', variant, `${variant}${capitalize(color)}`, `size${capitalize(size)}`, `${variant}Size${capitalize(size)}`, `color${capitalize(color)}`, disableElevation && 'disableElevation', fullWidth && 'fullWidth', loading && `loadingPosition${capitalize(loadingPosition)}`],
35 startIcon: ['icon', 'startIcon', `iconSize${capitalize(size)}`],
36 endIcon: ['icon', 'endIcon', `iconSize${capitalize(size)}`],
37 loadingIndicator: ['loadingIndicator'],
38 loadingWrapper: ['loadingWrapper']
39 };
40 const composedClasses = composeClasses(slots, getButtonUtilityClass, classes);
41 return {
42 ...classes,
43 // forward the focused, disabled, etc. classes to the ButtonBase
44 ...composedClasses
45 };
46};
47const commonIconStyles = [{
48 props: {
49 size: 'small'
50 },
51 style: {
52 '& > *:nth-of-type(1)': {
53 fontSize: 18
54 }
55 }
56}, {
57 props: {
58 size: 'medium'
59 },
60 style: {
61 '& > *:nth-of-type(1)': {
62 fontSize: 20
63 }
64 }
65}, {
66 props: {
67 size: 'large'
68 },
69 style: {
70 '& > *:nth-of-type(1)': {
71 fontSize: 22
72 }
73 }
74}];
75const ButtonRoot = styled(ButtonBase, {
76 shouldForwardProp: prop => rootShouldForwardProp(prop) || prop === 'classes',
77 name: 'MuiButton',
78 slot: 'Root',
79 overridesResolver: (props, styles) => {
80 const {
81 ownerState
82 } = props;
83 return [styles.root, styles[ownerState.variant], styles[`${ownerState.variant}${capitalize(ownerState.color)}`], styles[`size${capitalize(ownerState.size)}`], styles[`${ownerState.variant}Size${capitalize(ownerState.size)}`], ownerState.color === 'inherit' && styles.colorInherit, ownerState.disableElevation && styles.disableElevation, ownerState.fullWidth && styles.fullWidth, ownerState.loading && styles.loading];
84 }
85})(memoTheme(({
86 theme
87}) => {
88 const inheritContainedBackgroundColor = theme.palette.mode === 'light' ? theme.palette.grey[300] : theme.palette.grey[800];
89 const inheritContainedHoverBackgroundColor = theme.palette.mode === 'light' ? theme.palette.grey.A100 : theme.palette.grey[700];
90 return {
91 ...theme.typography.button,
92 minWidth: 64,
93 padding: '6px 16px',
94 border: 0,
95 borderRadius: (theme.vars || theme).shape.borderRadius,
96 transition: theme.transitions.create(['background-color', 'box-shadow', 'border-color', 'color'], {
97 duration: theme.transitions.duration.short
98 }),
99 '&:hover': {
100 textDecoration: 'none'
101 },
102 [`&.${buttonClasses.disabled}`]: {
103 color: (theme.vars || theme).palette.action.disabled
104 },
105 variants: [{
106 props: {
107 variant: 'contained'
108 },
109 style: {
110 color: `var(--variant-containedColor)`,
111 backgroundColor: `var(--variant-containedBg)`,
112 boxShadow: (theme.vars || theme).shadows[2],
113 '&:hover': {
114 boxShadow: (theme.vars || theme).shadows[4],
115 // Reset on touch devices, it doesn't add specificity
116 '@media (hover: none)': {
117 boxShadow: (theme.vars || theme).shadows[2]
118 }
119 },
120 '&:active': {
121 boxShadow: (theme.vars || theme).shadows[8]
122 },
123 [`&.${buttonClasses.focusVisible}`]: {
124 boxShadow: (theme.vars || theme).shadows[6]
125 },
126 [`&.${buttonClasses.disabled}`]: {
127 color: (theme.vars || theme).palette.action.disabled,
128 boxShadow: (theme.vars || theme).shadows[0],
129 backgroundColor: (theme.vars || theme).palette.action.disabledBackground
130 }
131 }
132 }, {
133 props: {
134 variant: 'outlined'
135 },
136 style: {
137 padding: '5px 15px',
138 border: '1px solid currentColor',
139 borderColor: `var(--variant-outlinedBorder, currentColor)`,
140 backgroundColor: `var(--variant-outlinedBg)`,
141 color: `var(--variant-outlinedColor)`,
142 [`&.${buttonClasses.disabled}`]: {
143 border: `1px solid ${(theme.vars || theme).palette.action.disabledBackground}`
144 }
145 }
146 }, {
147 props: {
148 variant: 'text'
149 },
150 style: {
151 padding: '6px 8px',
152 color: `var(--variant-textColor)`,
153 backgroundColor: `var(--variant-textBg)`
154 }
155 }, ...Object.entries(theme.palette).filter(createSimplePaletteValueFilter()).map(([color]) => ({
156 props: {
157 color
158 },
159 style: {
160 '--variant-textColor': (theme.vars || theme).palette[color].main,
161 '--variant-outlinedColor': (theme.vars || theme).palette[color].main,
162 '--variant-outlinedBorder': theme.vars ? `rgba(${theme.vars.palette[color].mainChannel} / 0.5)` : alpha(theme.palette[color].main, 0.5),
163 '--variant-containedColor': (theme.vars || theme).palette[color].contrastText,
164 '--variant-containedBg': (theme.vars || theme).palette[color].main,
165 '@media (hover: hover)': {
166 '&:hover': {
167 '--variant-containedBg': (theme.vars || theme).palette[color].dark,
168 '--variant-textBg': theme.vars ? `rgba(${theme.vars.palette[color].mainChannel} / ${theme.vars.palette.action.hoverOpacity})` : alpha(theme.palette[color].main, theme.palette.action.hoverOpacity),
169 '--variant-outlinedBorder': (theme.vars || theme).palette[color].main,
170 '--variant-outlinedBg': theme.vars ? `rgba(${theme.vars.palette[color].mainChannel} / ${theme.vars.palette.action.hoverOpacity})` : alpha(theme.palette[color].main, theme.palette.action.hoverOpacity)
171 }
172 }
173 }
174 })), {
175 props: {
176 color: 'inherit'
177 },
178 style: {
179 color: 'inherit',
180 borderColor: 'currentColor',
181 '--variant-containedBg': theme.vars ? theme.vars.palette.Button.inheritContainedBg : inheritContainedBackgroundColor,
182 '@media (hover: hover)': {
183 '&:hover': {
184 '--variant-containedBg': theme.vars ? theme.vars.palette.Button.inheritContainedHoverBg : inheritContainedHoverBackgroundColor,
185 '--variant-textBg': theme.vars ? `rgba(${theme.vars.palette.text.primaryChannel} / ${theme.vars.palette.action.hoverOpacity})` : alpha(theme.palette.text.primary, theme.palette.action.hoverOpacity),
186 '--variant-outlinedBg': theme.vars ? `rgba(${theme.vars.palette.text.primaryChannel} / ${theme.vars.palette.action.hoverOpacity})` : alpha(theme.palette.text.primary, theme.palette.action.hoverOpacity)
187 }
188 }
189 }
190 }, {
191 props: {
192 size: 'small',
193 variant: 'text'
194 },
195 style: {
196 padding: '4px 5px',
197 fontSize: theme.typography.pxToRem(13)
198 }
199 }, {
200 props: {
201 size: 'large',
202 variant: 'text'
203 },
204 style: {
205 padding: '8px 11px',
206 fontSize: theme.typography.pxToRem(15)
207 }
208 }, {
209 props: {
210 size: 'small',
211 variant: 'outlined'
212 },
213 style: {
214 padding: '3px 9px',
215 fontSize: theme.typography.pxToRem(13)
216 }
217 }, {
218 props: {
219 size: 'large',
220 variant: 'outlined'
221 },
222 style: {
223 padding: '7px 21px',
224 fontSize: theme.typography.pxToRem(15)
225 }
226 }, {
227 props: {
228 size: 'small',
229 variant: 'contained'
230 },
231 style: {
232 padding: '4px 10px',
233 fontSize: theme.typography.pxToRem(13)
234 }
235 }, {
236 props: {
237 size: 'large',
238 variant: 'contained'
239 },
240 style: {
241 padding: '8px 22px',
242 fontSize: theme.typography.pxToRem(15)
243 }
244 }, {
245 props: {
246 disableElevation: true
247 },
248 style: {
249 boxShadow: 'none',
250 '&:hover': {
251 boxShadow: 'none'
252 },
253 [`&.${buttonClasses.focusVisible}`]: {
254 boxShadow: 'none'
255 },
256 '&:active': {
257 boxShadow: 'none'
258 },
259 [`&.${buttonClasses.disabled}`]: {
260 boxShadow: 'none'
261 }
262 }
263 }, {
264 props: {
265 fullWidth: true
266 },
267 style: {
268 width: '100%'
269 }
270 }, {
271 props: {
272 loadingPosition: 'center'
273 },
274 style: {
275 transition: theme.transitions.create(['background-color', 'box-shadow', 'border-color'], {
276 duration: theme.transitions.duration.short
277 }),
278 [`&.${buttonClasses.loading}`]: {
279 color: 'transparent'
280 }
281 }
282 }]
283 };
284}));
285const ButtonStartIcon = styled('span', {
286 name: 'MuiButton',
287 slot: 'StartIcon',
288 overridesResolver: (props, styles) => {
289 const {
290 ownerState
291 } = props;
292 return [styles.startIcon, ownerState.loading && styles.startIconLoadingStart, styles[`iconSize${capitalize(ownerState.size)}`]];
293 }
294})(({
295 theme
296}) => ({
297 display: 'inherit',
298 marginRight: 8,
299 marginLeft: -4,
300 variants: [{
301 props: {
302 size: 'small'
303 },
304 style: {
305 marginLeft: -2
306 }
307 }, {
308 props: {
309 loadingPosition: 'start',
310 loading: true
311 },
312 style: {
313 transition: theme.transitions.create(['opacity'], {
314 duration: theme.transitions.duration.short
315 }),
316 opacity: 0
317 }
318 }, {
319 props: {
320 loadingPosition: 'start',
321 loading: true,
322 fullWidth: true
323 },
324 style: {
325 marginRight: -8
326 }
327 }, ...commonIconStyles]
328}));
329const ButtonEndIcon = styled('span', {
330 name: 'MuiButton',
331 slot: 'EndIcon',
332 overridesResolver: (props, styles) => {
333 const {
334 ownerState
335 } = props;
336 return [styles.endIcon, ownerState.loading && styles.endIconLoadingEnd, styles[`iconSize${capitalize(ownerState.size)}`]];
337 }
338})(({
339 theme
340}) => ({
341 display: 'inherit',
342 marginRight: -4,
343 marginLeft: 8,
344 variants: [{
345 props: {
346 size: 'small'
347 },
348 style: {
349 marginRight: -2
350 }
351 }, {
352 props: {
353 loadingPosition: 'end',
354 loading: true
355 },
356 style: {
357 transition: theme.transitions.create(['opacity'], {
358 duration: theme.transitions.duration.short
359 }),
360 opacity: 0
361 }
362 }, {
363 props: {
364 loadingPosition: 'end',
365 loading: true,
366 fullWidth: true
367 },
368 style: {
369 marginLeft: -8
370 }
371 }, ...commonIconStyles]
372}));
373const ButtonLoadingIndicator = styled('span', {
374 name: 'MuiButton',
375 slot: 'LoadingIndicator',
376 overridesResolver: (props, styles) => styles.loadingIndicator
377})(({
378 theme
379}) => ({
380 display: 'none',
381 position: 'absolute',
382 visibility: 'visible',
383 variants: [{
384 props: {
385 loading: true
386 },
387 style: {
388 display: 'flex'
389 }
390 }, {
391 props: {
392 loadingPosition: 'start'
393 },
394 style: {
395 left: 14
396 }
397 }, {
398 props: {
399 loadingPosition: 'start',
400 size: 'small'
401 },
402 style: {
403 left: 10
404 }
405 }, {
406 props: {
407 variant: 'text',
408 loadingPosition: 'start'
409 },
410 style: {
411 left: 6
412 }
413 }, {
414 props: {
415 loadingPosition: 'center'
416 },
417 style: {
418 left: '50%',
419 transform: 'translate(-50%)',
420 color: (theme.vars || theme).palette.action.disabled
421 }
422 }, {
423 props: {
424 loadingPosition: 'end'
425 },
426 style: {
427 right: 14
428 }
429 }, {
430 props: {
431 loadingPosition: 'end',
432 size: 'small'
433 },
434 style: {
435 right: 10
436 }
437 }, {
438 props: {
439 variant: 'text',
440 loadingPosition: 'end'
441 },
442 style: {
443 right: 6
444 }
445 }, {
446 props: {
447 loadingPosition: 'start',
448 fullWidth: true
449 },
450 style: {
451 position: 'relative',
452 left: -10
453 }
454 }, {
455 props: {
456 loadingPosition: 'end',
457 fullWidth: true
458 },
459 style: {
460 position: 'relative',
461 right: -10
462 }
463 }]
464}));
465const ButtonLoadingIconPlaceholder = styled('span', {
466 name: 'MuiButton',
467 slot: 'LoadingIconPlaceholder',
468 overridesResolver: (props, styles) => styles.loadingIconPlaceholder
469})({
470 display: 'inline-block',
471 width: '1em',
472 height: '1em'
473});
474const Button = /*#__PURE__*/React.forwardRef(function Button(inProps, ref) {
475 // props priority: `inProps` > `contextProps` > `themeDefaultProps`
476 const contextProps = React.useContext(ButtonGroupContext);
477 const buttonGroupButtonContextPositionClassName = React.useContext(ButtonGroupButtonContext);
478 const resolvedProps = resolveProps(contextProps, inProps);
479 const props = useDefaultProps({
480 props: resolvedProps,
481 name: 'MuiButton'
482 });
483 const {
484 children,
485 color = 'primary',
486 component = 'button',
487 className,
488 disabled = false,
489 disableElevation = false,
490 disableFocusRipple = false,
491 endIcon: endIconProp,
492 focusVisibleClassName,
493 fullWidth = false,
494 id: idProp,
495 loading = null,
496 loadingIndicator: loadingIndicatorProp,
497 loadingPosition = 'center',
498 size = 'medium',
499 startIcon: startIconProp,
500 type,
501 variant = 'text',
502 ...other
503 } = props;
504 const id = useId(idProp);
505 const loadingIndicator = loadingIndicatorProp ?? /*#__PURE__*/_jsx(CircularProgress, {
506 "aria-labelledby": id,
507 color: "inherit",
508 size: 16
509 });
510 const ownerState = {
511 ...props,
512 color,
513 component,
514 disabled,
515 disableElevation,
516 disableFocusRipple,
517 fullWidth,
518 loading,
519 loadingIndicator,
520 loadingPosition,
521 size,
522 type,
523 variant
524 };
525 const classes = useUtilityClasses(ownerState);
526 const startIcon = (startIconProp || loading && loadingPosition === 'start') && /*#__PURE__*/_jsx(ButtonStartIcon, {
527 className: classes.startIcon,
528 ownerState: ownerState,
529 children: startIconProp || /*#__PURE__*/_jsx(ButtonLoadingIconPlaceholder, {
530 className: classes.loadingIconPlaceholder,
531 ownerState: ownerState
532 })
533 });
534 const endIcon = (endIconProp || loading && loadingPosition === 'end') && /*#__PURE__*/_jsx(ButtonEndIcon, {
535 className: classes.endIcon,
536 ownerState: ownerState,
537 children: endIconProp || /*#__PURE__*/_jsx(ButtonLoadingIconPlaceholder, {
538 className: classes.loadingIconPlaceholder,
539 ownerState: ownerState
540 })
541 });
542 const positionClassName = buttonGroupButtonContextPositionClassName || '';
543 const loader = typeof loading === 'boolean' ?
544 /*#__PURE__*/
545 // use plain HTML span to minimize the runtime overhead
546 _jsx("span", {
547 className: classes.loadingWrapper,
548 style: {
549 display: 'contents'
550 },
551 children: loading && /*#__PURE__*/_jsx(ButtonLoadingIndicator, {
552 className: classes.loadingIndicator,
553 ownerState: ownerState,
554 children: loadingIndicator
555 })
556 }) : null;
557 return /*#__PURE__*/_jsxs(ButtonRoot, {
558 ownerState: ownerState,
559 className: clsx(contextProps.className, classes.root, className, positionClassName),
560 component: component,
561 disabled: disabled || loading,
562 focusRipple: !disableFocusRipple,
563 focusVisibleClassName: clsx(classes.focusVisible, focusVisibleClassName),
564 ref: ref,
565 type: type,
566 id: id,
567 ...other,
568 classes: classes,
569 children: [startIcon, loadingPosition !== 'end' && loader, children, loadingPosition === 'end' && loader, endIcon]
570 });
571});
572process.env.NODE_ENV !== "production" ? Button.propTypes /* remove-proptypes */ = {
573 // ┌────────────────────────────── Warning ──────────────────────────────┐
574 // │ These PropTypes are generated from the TypeScript type definitions. │
575 // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
576 // └─────────────────────────────────────────────────────────────────────┘
577 /**
578 * The content of the component.
579 */
580 children: PropTypes.node,
581 /**
582 * Override or extend the styles applied to the component.
583 */
584 classes: PropTypes.object,
585 /**
586 * @ignore
587 */
588 className: PropTypes.string,
589 /**
590 * The color of the component.
591 * It supports both default and custom theme colors, which can be added as shown in the
592 * [palette customization guide](https://mui.com/material-ui/customization/palette/#custom-colors).
593 * @default 'primary'
594 */
595 color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['inherit', 'primary', 'secondary', 'success', 'error', 'info', 'warning']), PropTypes.string]),
596 /**
597 * The component used for the root node.
598 * Either a string to use a HTML element or a component.
599 */
600 component: PropTypes.elementType,
601 /**
602 * If `true`, the component is disabled.
603 * @default false
604 */
605 disabled: PropTypes.bool,
606 /**
607 * If `true`, no elevation is used.
608 * @default false
609 */
610 disableElevation: PropTypes.bool,
611 /**
612 * If `true`, the keyboard focus ripple is disabled.
613 * @default false
614 */
615 disableFocusRipple: PropTypes.bool,
616 /**
617 * If `true`, the ripple effect is disabled.
618 *
619 * ⚠️ Without a ripple there is no styling for :focus-visible by default. Be sure
620 * to highlight the element by applying separate styles with the `.Mui-focusVisible` class.
621 * @default false
622 */
623 disableRipple: PropTypes.bool,
624 /**
625 * Element placed after the children.
626 */
627 endIcon: PropTypes.node,
628 /**
629 * @ignore
630 */
631 focusVisibleClassName: PropTypes.string,
632 /**
633 * If `true`, the button will take up the full width of its container.
634 * @default false
635 */
636 fullWidth: PropTypes.bool,
637 /**
638 * The URL to link to when the button is clicked.
639 * If defined, an `a` element will be used as the root node.
640 */
641 href: PropTypes.string,
642 /**
643 * @ignore
644 */
645 id: PropTypes.string,
646 /**
647 * If `true`, the loading indicator is visible and the button is disabled.
648 * If `true | false`, the loading wrapper is always rendered before the children to prevent [Google Translation Crash](https://github.com/mui/material-ui/issues/27853).
649 * @default null
650 */
651 loading: PropTypes.bool,
652 /**
653 * Element placed before the children if the button is in loading state.
654 * The node should contain an element with `role="progressbar"` with an accessible name.
655 * By default, it renders a `CircularProgress` that is labeled by the button itself.
656 * @default <CircularProgress color="inherit" size={16} />
657 */
658 loadingIndicator: PropTypes.node,
659 /**
660 * The loading indicator can be positioned on the start, end, or the center of the button.
661 * @default 'center'
662 */
663 loadingPosition: PropTypes.oneOf(['center', 'end', 'start']),
664 /**
665 * The size of the component.
666 * `small` is equivalent to the dense button styling.
667 * @default 'medium'
668 */
669 size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['small', 'medium', 'large']), PropTypes.string]),
670 /**
671 * Element placed before the children.
672 */
673 startIcon: PropTypes.node,
674 /**
675 * The system prop that allows defining system overrides as well as additional CSS styles.
676 */
677 sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
678 /**
679 * @ignore
680 */
681 type: PropTypes.oneOfType([PropTypes.oneOf(['button', 'reset', 'submit']), PropTypes.string]),
682 /**
683 * The variant to use.
684 * @default 'text'
685 */
686 variant: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['contained', 'outlined', 'text']), PropTypes.string])
687} : void 0;
688export default Button;
\No newline at end of file