UNPKG

13.3 kBJavaScriptView Raw
1'use client';
2
3import * as React from 'react';
4import PropTypes from 'prop-types';
5import clsx from 'clsx';
6import refType from '@mui/utils/refType';
7import elementTypeAcceptingRef from '@mui/utils/elementTypeAcceptingRef';
8import composeClasses from '@mui/utils/composeClasses';
9import isFocusVisible from '@mui/utils/isFocusVisible';
10import { styled } from "../zero-styled/index.js";
11import { useDefaultProps } from "../DefaultPropsProvider/index.js";
12import useForkRef from "../utils/useForkRef.js";
13import useEventCallback from "../utils/useEventCallback.js";
14import useLazyRipple from "../useLazyRipple/index.js";
15import TouchRipple from "./TouchRipple.js";
16import buttonBaseClasses, { getButtonBaseUtilityClass } from "./buttonBaseClasses.js";
17import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18const useUtilityClasses = ownerState => {
19 const {
20 disabled,
21 focusVisible,
22 focusVisibleClassName,
23 classes
24 } = ownerState;
25 const slots = {
26 root: ['root', disabled && 'disabled', focusVisible && 'focusVisible']
27 };
28 const composedClasses = composeClasses(slots, getButtonBaseUtilityClass, classes);
29 if (focusVisible && focusVisibleClassName) {
30 composedClasses.root += ` ${focusVisibleClassName}`;
31 }
32 return composedClasses;
33};
34export const ButtonBaseRoot = styled('button', {
35 name: 'MuiButtonBase',
36 slot: 'Root',
37 overridesResolver: (props, styles) => styles.root
38})({
39 display: 'inline-flex',
40 alignItems: 'center',
41 justifyContent: 'center',
42 position: 'relative',
43 boxSizing: 'border-box',
44 WebkitTapHighlightColor: 'transparent',
45 backgroundColor: 'transparent',
46 // Reset default value
47 // We disable the focus ring for mouse, touch and keyboard users.
48 outline: 0,
49 border: 0,
50 margin: 0,
51 // Remove the margin in Safari
52 borderRadius: 0,
53 padding: 0,
54 // Remove the padding in Firefox
55 cursor: 'pointer',
56 userSelect: 'none',
57 verticalAlign: 'middle',
58 MozAppearance: 'none',
59 // Reset
60 WebkitAppearance: 'none',
61 // Reset
62 textDecoration: 'none',
63 // So we take precedent over the style of a native <a /> element.
64 color: 'inherit',
65 '&::-moz-focus-inner': {
66 borderStyle: 'none' // Remove Firefox dotted outline.
67 },
68 [`&.${buttonBaseClasses.disabled}`]: {
69 pointerEvents: 'none',
70 // Disable link interactions
71 cursor: 'default'
72 },
73 '@media print': {
74 colorAdjust: 'exact'
75 }
76});
77
78/**
79 * `ButtonBase` contains as few styles as possible.
80 * It aims to be a simple building block for creating a button.
81 * It contains a load of style reset and some focus/ripple logic.
82 */
83const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, ref) {
84 const props = useDefaultProps({
85 props: inProps,
86 name: 'MuiButtonBase'
87 });
88 const {
89 action,
90 centerRipple = false,
91 children,
92 className,
93 component = 'button',
94 disabled = false,
95 disableRipple = false,
96 disableTouchRipple = false,
97 focusRipple = false,
98 focusVisibleClassName,
99 LinkComponent = 'a',
100 onBlur,
101 onClick,
102 onContextMenu,
103 onDragLeave,
104 onFocus,
105 onFocusVisible,
106 onKeyDown,
107 onKeyUp,
108 onMouseDown,
109 onMouseLeave,
110 onMouseUp,
111 onTouchEnd,
112 onTouchMove,
113 onTouchStart,
114 tabIndex = 0,
115 TouchRippleProps,
116 touchRippleRef,
117 type,
118 ...other
119 } = props;
120 const buttonRef = React.useRef(null);
121 const ripple = useLazyRipple();
122 const handleRippleRef = useForkRef(ripple.ref, touchRippleRef);
123 const [focusVisible, setFocusVisible] = React.useState(false);
124 if (disabled && focusVisible) {
125 setFocusVisible(false);
126 }
127 React.useImperativeHandle(action, () => ({
128 focusVisible: () => {
129 setFocusVisible(true);
130 buttonRef.current.focus();
131 }
132 }), []);
133 const enableTouchRipple = ripple.shouldMount && !disableRipple && !disabled;
134 React.useEffect(() => {
135 if (focusVisible && focusRipple && !disableRipple) {
136 ripple.pulsate();
137 }
138 }, [disableRipple, focusRipple, focusVisible, ripple]);
139 function useRippleHandler(rippleAction, eventCallback, skipRippleAction = disableTouchRipple) {
140 return useEventCallback(event => {
141 if (eventCallback) {
142 eventCallback(event);
143 }
144 const ignore = skipRippleAction;
145 if (!ignore) {
146 ripple[rippleAction](event);
147 }
148 return true;
149 });
150 }
151 const handleMouseDown = useRippleHandler('start', onMouseDown);
152 const handleContextMenu = useRippleHandler('stop', onContextMenu);
153 const handleDragLeave = useRippleHandler('stop', onDragLeave);
154 const handleMouseUp = useRippleHandler('stop', onMouseUp);
155 const handleMouseLeave = useRippleHandler('stop', event => {
156 if (focusVisible) {
157 event.preventDefault();
158 }
159 if (onMouseLeave) {
160 onMouseLeave(event);
161 }
162 });
163 const handleTouchStart = useRippleHandler('start', onTouchStart);
164 const handleTouchEnd = useRippleHandler('stop', onTouchEnd);
165 const handleTouchMove = useRippleHandler('stop', onTouchMove);
166 const handleBlur = useRippleHandler('stop', event => {
167 if (!isFocusVisible(event.target)) {
168 setFocusVisible(false);
169 }
170 if (onBlur) {
171 onBlur(event);
172 }
173 }, false);
174 const handleFocus = useEventCallback(event => {
175 // Fix for https://github.com/facebook/react/issues/7769
176 if (!buttonRef.current) {
177 buttonRef.current = event.currentTarget;
178 }
179 if (isFocusVisible(event.target)) {
180 setFocusVisible(true);
181 if (onFocusVisible) {
182 onFocusVisible(event);
183 }
184 }
185 if (onFocus) {
186 onFocus(event);
187 }
188 });
189 const isNonNativeButton = () => {
190 const button = buttonRef.current;
191 return component && component !== 'button' && !(button.tagName === 'A' && button.href);
192 };
193 const handleKeyDown = useEventCallback(event => {
194 // Check if key is already down to avoid repeats being counted as multiple activations
195 if (focusRipple && !event.repeat && focusVisible && event.key === ' ') {
196 ripple.stop(event, () => {
197 ripple.start(event);
198 });
199 }
200 if (event.target === event.currentTarget && isNonNativeButton() && event.key === ' ') {
201 event.preventDefault();
202 }
203 if (onKeyDown) {
204 onKeyDown(event);
205 }
206
207 // Keyboard accessibility for non interactive elements
208 if (event.target === event.currentTarget && isNonNativeButton() && event.key === 'Enter' && !disabled) {
209 event.preventDefault();
210 if (onClick) {
211 onClick(event);
212 }
213 }
214 });
215 const handleKeyUp = useEventCallback(event => {
216 // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
217 // https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
218 if (focusRipple && event.key === ' ' && focusVisible && !event.defaultPrevented) {
219 ripple.stop(event, () => {
220 ripple.pulsate(event);
221 });
222 }
223 if (onKeyUp) {
224 onKeyUp(event);
225 }
226
227 // Keyboard accessibility for non interactive elements
228 if (onClick && event.target === event.currentTarget && isNonNativeButton() && event.key === ' ' && !event.defaultPrevented) {
229 onClick(event);
230 }
231 });
232 let ComponentProp = component;
233 if (ComponentProp === 'button' && (other.href || other.to)) {
234 ComponentProp = LinkComponent;
235 }
236 const buttonProps = {};
237 if (ComponentProp === 'button') {
238 buttonProps.type = type === undefined ? 'button' : type;
239 buttonProps.disabled = disabled;
240 } else {
241 if (!other.href && !other.to) {
242 buttonProps.role = 'button';
243 }
244 if (disabled) {
245 buttonProps['aria-disabled'] = disabled;
246 }
247 }
248 const handleRef = useForkRef(ref, buttonRef);
249 const ownerState = {
250 ...props,
251 centerRipple,
252 component,
253 disabled,
254 disableRipple,
255 disableTouchRipple,
256 focusRipple,
257 tabIndex,
258 focusVisible
259 };
260 const classes = useUtilityClasses(ownerState);
261 return /*#__PURE__*/_jsxs(ButtonBaseRoot, {
262 as: ComponentProp,
263 className: clsx(classes.root, className),
264 ownerState: ownerState,
265 onBlur: handleBlur,
266 onClick: onClick,
267 onContextMenu: handleContextMenu,
268 onFocus: handleFocus,
269 onKeyDown: handleKeyDown,
270 onKeyUp: handleKeyUp,
271 onMouseDown: handleMouseDown,
272 onMouseLeave: handleMouseLeave,
273 onMouseUp: handleMouseUp,
274 onDragLeave: handleDragLeave,
275 onTouchEnd: handleTouchEnd,
276 onTouchMove: handleTouchMove,
277 onTouchStart: handleTouchStart,
278 ref: handleRef,
279 tabIndex: disabled ? -1 : tabIndex,
280 type: type,
281 ...buttonProps,
282 ...other,
283 children: [children, enableTouchRipple ? /*#__PURE__*/_jsx(TouchRipple, {
284 ref: handleRippleRef,
285 center: centerRipple,
286 ...TouchRippleProps
287 }) : null]
288 });
289});
290process.env.NODE_ENV !== "production" ? ButtonBase.propTypes /* remove-proptypes */ = {
291 // ┌────────────────────────────── Warning ──────────────────────────────┐
292 // │ These PropTypes are generated from the TypeScript type definitions. │
293 // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
294 // └─────────────────────────────────────────────────────────────────────┘
295 /**
296 * A ref for imperative actions.
297 * It currently only supports `focusVisible()` action.
298 */
299 action: refType,
300 /**
301 * If `true`, the ripples are centered.
302 * They won't start at the cursor interaction position.
303 * @default false
304 */
305 centerRipple: PropTypes.bool,
306 /**
307 * The content of the component.
308 */
309 children: PropTypes.node,
310 /**
311 * Override or extend the styles applied to the component.
312 */
313 classes: PropTypes.object,
314 /**
315 * @ignore
316 */
317 className: PropTypes.string,
318 /**
319 * The component used for the root node.
320 * Either a string to use a HTML element or a component.
321 */
322 component: elementTypeAcceptingRef,
323 /**
324 * If `true`, the component is disabled.
325 * @default false
326 */
327 disabled: PropTypes.bool,
328 /**
329 * If `true`, the ripple effect is disabled.
330 *
331 * ⚠️ Without a ripple there is no styling for :focus-visible by default. Be sure
332 * to highlight the element by applying separate styles with the `.Mui-focusVisible` class.
333 * @default false
334 */
335 disableRipple: PropTypes.bool,
336 /**
337 * If `true`, the touch ripple effect is disabled.
338 * @default false
339 */
340 disableTouchRipple: PropTypes.bool,
341 /**
342 * If `true`, the base button will have a keyboard focus ripple.
343 * @default false
344 */
345 focusRipple: PropTypes.bool,
346 /**
347 * This prop can help identify which element has keyboard focus.
348 * The class name will be applied when the element gains the focus through keyboard interaction.
349 * It's a polyfill for the [CSS :focus-visible selector](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo).
350 * The rationale for using this feature [is explained here](https://github.com/WICG/focus-visible/blob/HEAD/explainer.md).
351 * A [polyfill can be used](https://github.com/WICG/focus-visible) to apply a `focus-visible` class to other components
352 * if needed.
353 */
354 focusVisibleClassName: PropTypes.string,
355 /**
356 * @ignore
357 */
358 href: PropTypes /* @typescript-to-proptypes-ignore */.any,
359 /**
360 * The component used to render a link when the `href` prop is provided.
361 * @default 'a'
362 */
363 LinkComponent: PropTypes.elementType,
364 /**
365 * @ignore
366 */
367 onBlur: PropTypes.func,
368 /**
369 * @ignore
370 */
371 onClick: PropTypes.func,
372 /**
373 * @ignore
374 */
375 onContextMenu: PropTypes.func,
376 /**
377 * @ignore
378 */
379 onDragLeave: PropTypes.func,
380 /**
381 * @ignore
382 */
383 onFocus: PropTypes.func,
384 /**
385 * Callback fired when the component is focused with a keyboard.
386 * We trigger a `onFocus` callback too.
387 */
388 onFocusVisible: PropTypes.func,
389 /**
390 * @ignore
391 */
392 onKeyDown: PropTypes.func,
393 /**
394 * @ignore
395 */
396 onKeyUp: PropTypes.func,
397 /**
398 * @ignore
399 */
400 onMouseDown: PropTypes.func,
401 /**
402 * @ignore
403 */
404 onMouseLeave: PropTypes.func,
405 /**
406 * @ignore
407 */
408 onMouseUp: PropTypes.func,
409 /**
410 * @ignore
411 */
412 onTouchEnd: PropTypes.func,
413 /**
414 * @ignore
415 */
416 onTouchMove: PropTypes.func,
417 /**
418 * @ignore
419 */
420 onTouchStart: PropTypes.func,
421 /**
422 * The system prop that allows defining system overrides as well as additional CSS styles.
423 */
424 sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
425 /**
426 * @default 0
427 */
428 tabIndex: PropTypes.number,
429 /**
430 * Props applied to the `TouchRipple` element.
431 */
432 TouchRippleProps: PropTypes.object,
433 /**
434 * A ref that points to the `TouchRipple` element.
435 */
436 touchRippleRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({
437 current: PropTypes.shape({
438 pulsate: PropTypes.func.isRequired,
439 start: PropTypes.func.isRequired,
440 stop: PropTypes.func.isRequired
441 })
442 })]),
443 /**
444 * @ignore
445 */
446 type: PropTypes.oneOfType([PropTypes.oneOf(['button', 'reset', 'submit']), PropTypes.string])
447} : void 0;
448export default ButtonBase;
\No newline at end of file