UNPKG

13.5 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 const handleMouseDown = useRippleHandler(ripple, 'start', onMouseDown, disableTouchRipple);
140 const handleContextMenu = useRippleHandler(ripple, 'stop', onContextMenu, disableTouchRipple);
141 const handleDragLeave = useRippleHandler(ripple, 'stop', onDragLeave, disableTouchRipple);
142 const handleMouseUp = useRippleHandler(ripple, 'stop', onMouseUp, disableTouchRipple);
143 const handleMouseLeave = useRippleHandler(ripple, 'stop', event => {
144 if (focusVisible) {
145 event.preventDefault();
146 }
147 if (onMouseLeave) {
148 onMouseLeave(event);
149 }
150 }, disableTouchRipple);
151 const handleTouchStart = useRippleHandler(ripple, 'start', onTouchStart, disableTouchRipple);
152 const handleTouchEnd = useRippleHandler(ripple, 'stop', onTouchEnd, disableTouchRipple);
153 const handleTouchMove = useRippleHandler(ripple, 'stop', onTouchMove, disableTouchRipple);
154 const handleBlur = useRippleHandler(ripple, 'stop', event => {
155 if (!isFocusVisible(event.target)) {
156 setFocusVisible(false);
157 }
158 if (onBlur) {
159 onBlur(event);
160 }
161 }, false);
162 const handleFocus = useEventCallback(event => {
163 // Fix for https://github.com/facebook/react/issues/7769
164 if (!buttonRef.current) {
165 buttonRef.current = event.currentTarget;
166 }
167 if (isFocusVisible(event.target)) {
168 setFocusVisible(true);
169 if (onFocusVisible) {
170 onFocusVisible(event);
171 }
172 }
173 if (onFocus) {
174 onFocus(event);
175 }
176 });
177 const isNonNativeButton = () => {
178 const button = buttonRef.current;
179 return component && component !== 'button' && !(button.tagName === 'A' && button.href);
180 };
181 const handleKeyDown = useEventCallback(event => {
182 // Check if key is already down to avoid repeats being counted as multiple activations
183 if (focusRipple && !event.repeat && focusVisible && event.key === ' ') {
184 ripple.stop(event, () => {
185 ripple.start(event);
186 });
187 }
188 if (event.target === event.currentTarget && isNonNativeButton() && event.key === ' ') {
189 event.preventDefault();
190 }
191 if (onKeyDown) {
192 onKeyDown(event);
193 }
194
195 // Keyboard accessibility for non interactive elements
196 if (event.target === event.currentTarget && isNonNativeButton() && event.key === 'Enter' && !disabled) {
197 event.preventDefault();
198 if (onClick) {
199 onClick(event);
200 }
201 }
202 });
203 const handleKeyUp = useEventCallback(event => {
204 // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
205 // https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
206 if (focusRipple && event.key === ' ' && focusVisible && !event.defaultPrevented) {
207 ripple.stop(event, () => {
208 ripple.pulsate(event);
209 });
210 }
211 if (onKeyUp) {
212 onKeyUp(event);
213 }
214
215 // Keyboard accessibility for non interactive elements
216 if (onClick && event.target === event.currentTarget && isNonNativeButton() && event.key === ' ' && !event.defaultPrevented) {
217 onClick(event);
218 }
219 });
220 let ComponentProp = component;
221 if (ComponentProp === 'button' && (other.href || other.to)) {
222 ComponentProp = LinkComponent;
223 }
224 const buttonProps = {};
225 if (ComponentProp === 'button') {
226 buttonProps.type = type === undefined ? 'button' : type;
227 buttonProps.disabled = disabled;
228 } else {
229 if (!other.href && !other.to) {
230 buttonProps.role = 'button';
231 }
232 if (disabled) {
233 buttonProps['aria-disabled'] = disabled;
234 }
235 }
236 const handleRef = useForkRef(ref, buttonRef);
237 const ownerState = {
238 ...props,
239 centerRipple,
240 component,
241 disabled,
242 disableRipple,
243 disableTouchRipple,
244 focusRipple,
245 tabIndex,
246 focusVisible
247 };
248 const classes = useUtilityClasses(ownerState);
249 return /*#__PURE__*/_jsxs(ButtonBaseRoot, {
250 as: ComponentProp,
251 className: clsx(classes.root, className),
252 ownerState: ownerState,
253 onBlur: handleBlur,
254 onClick: onClick,
255 onContextMenu: handleContextMenu,
256 onFocus: handleFocus,
257 onKeyDown: handleKeyDown,
258 onKeyUp: handleKeyUp,
259 onMouseDown: handleMouseDown,
260 onMouseLeave: handleMouseLeave,
261 onMouseUp: handleMouseUp,
262 onDragLeave: handleDragLeave,
263 onTouchEnd: handleTouchEnd,
264 onTouchMove: handleTouchMove,
265 onTouchStart: handleTouchStart,
266 ref: handleRef,
267 tabIndex: disabled ? -1 : tabIndex,
268 type: type,
269 ...buttonProps,
270 ...other,
271 children: [children, enableTouchRipple ? /*#__PURE__*/_jsx(TouchRipple, {
272 ref: handleRippleRef,
273 center: centerRipple,
274 ...TouchRippleProps
275 }) : null]
276 });
277});
278function useRippleHandler(ripple, rippleAction, eventCallback, skipRippleAction = false) {
279 return useEventCallback(event => {
280 if (eventCallback) {
281 eventCallback(event);
282 }
283 if (!skipRippleAction) {
284 ripple[rippleAction](event);
285 }
286 return true;
287 });
288}
289process.env.NODE_ENV !== "production" ? ButtonBase.propTypes /* remove-proptypes */ = {
290 // ┌────────────────────────────── Warning ──────────────────────────────┐
291 // │ These PropTypes are generated from the TypeScript type definitions. │
292 // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
293 // └─────────────────────────────────────────────────────────────────────┘
294 /**
295 * A ref for imperative actions.
296 * It currently only supports `focusVisible()` action.
297 */
298 action: refType,
299 /**
300 * If `true`, the ripples are centered.
301 * They won't start at the cursor interaction position.
302 * @default false
303 */
304 centerRipple: PropTypes.bool,
305 /**
306 * The content of the component.
307 */
308 children: PropTypes.node,
309 /**
310 * Override or extend the styles applied to the component.
311 */
312 classes: PropTypes.object,
313 /**
314 * @ignore
315 */
316 className: PropTypes.string,
317 /**
318 * The component used for the root node.
319 * Either a string to use a HTML element or a component.
320 */
321 component: elementTypeAcceptingRef,
322 /**
323 * If `true`, the component is disabled.
324 * @default false
325 */
326 disabled: PropTypes.bool,
327 /**
328 * If `true`, the ripple effect is disabled.
329 *
330 * ⚠️ Without a ripple there is no styling for :focus-visible by default. Be sure
331 * to highlight the element by applying separate styles with the `.Mui-focusVisible` class.
332 * @default false
333 */
334 disableRipple: PropTypes.bool,
335 /**
336 * If `true`, the touch ripple effect is disabled.
337 * @default false
338 */
339 disableTouchRipple: PropTypes.bool,
340 /**
341 * If `true`, the base button will have a keyboard focus ripple.
342 * @default false
343 */
344 focusRipple: PropTypes.bool,
345 /**
346 * This prop can help identify which element has keyboard focus.
347 * The class name will be applied when the element gains the focus through keyboard interaction.
348 * It's a polyfill for the [CSS :focus-visible selector](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo).
349 * The rationale for using this feature [is explained here](https://github.com/WICG/focus-visible/blob/HEAD/explainer.md).
350 * A [polyfill can be used](https://github.com/WICG/focus-visible) to apply a `focus-visible` class to other components
351 * if needed.
352 */
353 focusVisibleClassName: PropTypes.string,
354 /**
355 * @ignore
356 */
357 href: PropTypes /* @typescript-to-proptypes-ignore */.any,
358 /**
359 * The component used to render a link when the `href` prop is provided.
360 * @default 'a'
361 */
362 LinkComponent: PropTypes.elementType,
363 /**
364 * @ignore
365 */
366 onBlur: PropTypes.func,
367 /**
368 * @ignore
369 */
370 onClick: PropTypes.func,
371 /**
372 * @ignore
373 */
374 onContextMenu: PropTypes.func,
375 /**
376 * @ignore
377 */
378 onDragLeave: PropTypes.func,
379 /**
380 * @ignore
381 */
382 onFocus: PropTypes.func,
383 /**
384 * Callback fired when the component is focused with a keyboard.
385 * We trigger a `onFocus` callback too.
386 */
387 onFocusVisible: PropTypes.func,
388 /**
389 * @ignore
390 */
391 onKeyDown: PropTypes.func,
392 /**
393 * @ignore
394 */
395 onKeyUp: PropTypes.func,
396 /**
397 * @ignore
398 */
399 onMouseDown: PropTypes.func,
400 /**
401 * @ignore
402 */
403 onMouseLeave: PropTypes.func,
404 /**
405 * @ignore
406 */
407 onMouseUp: PropTypes.func,
408 /**
409 * @ignore
410 */
411 onTouchEnd: PropTypes.func,
412 /**
413 * @ignore
414 */
415 onTouchMove: PropTypes.func,
416 /**
417 * @ignore
418 */
419 onTouchStart: PropTypes.func,
420 /**
421 * The system prop that allows defining system overrides as well as additional CSS styles.
422 */
423 sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
424 /**
425 * @default 0
426 */
427 tabIndex: PropTypes.number,
428 /**
429 * Props applied to the `TouchRipple` element.
430 */
431 TouchRippleProps: PropTypes.object,
432 /**
433 * A ref that points to the `TouchRipple` element.
434 */
435 touchRippleRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({
436 current: PropTypes.shape({
437 pulsate: PropTypes.func.isRequired,
438 start: PropTypes.func.isRequired,
439 stop: PropTypes.func.isRequired
440 })
441 })]),
442 /**
443 * @ignore
444 */
445 type: PropTypes.oneOfType([PropTypes.oneOf(['button', 'reset', 'submit']), PropTypes.string])
446} : void 0;
447export default ButtonBase;
\No newline at end of file