UNPKG

19.9 kBJavaScriptView Raw
1'use client';
2
3import * as React from 'react';
4import PropTypes from 'prop-types';
5import clsx from 'clsx';
6import composeClasses from '@mui/utils/composeClasses';
7import { alpha } from '@mui/system/colorManipulator';
8import CancelIcon from "../internal/svg-icons/Cancel.js";
9import useForkRef from "../utils/useForkRef.js";
10import unsupportedProp from "../utils/unsupportedProp.js";
11import capitalize from "../utils/capitalize.js";
12import ButtonBase from "../ButtonBase/index.js";
13import { styled } from "../zero-styled/index.js";
14import memoTheme from "../utils/memoTheme.js";
15import createSimplePaletteValueFilter from "../utils/createSimplePaletteValueFilter.js";
16import { useDefaultProps } from "../DefaultPropsProvider/index.js";
17import chipClasses, { getChipUtilityClass } from "./chipClasses.js";
18import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19const useUtilityClasses = ownerState => {
20 const {
21 classes,
22 disabled,
23 size,
24 color,
25 iconColor,
26 onDelete,
27 clickable,
28 variant
29 } = ownerState;
30 const slots = {
31 root: ['root', variant, disabled && 'disabled', `size${capitalize(size)}`, `color${capitalize(color)}`, clickable && 'clickable', clickable && `clickableColor${capitalize(color)}`, onDelete && 'deletable', onDelete && `deletableColor${capitalize(color)}`, `${variant}${capitalize(color)}`],
32 label: ['label', `label${capitalize(size)}`],
33 avatar: ['avatar', `avatar${capitalize(size)}`, `avatarColor${capitalize(color)}`],
34 icon: ['icon', `icon${capitalize(size)}`, `iconColor${capitalize(iconColor)}`],
35 deleteIcon: ['deleteIcon', `deleteIcon${capitalize(size)}`, `deleteIconColor${capitalize(color)}`, `deleteIcon${capitalize(variant)}Color${capitalize(color)}`]
36 };
37 return composeClasses(slots, getChipUtilityClass, classes);
38};
39const ChipRoot = styled('div', {
40 name: 'MuiChip',
41 slot: 'Root',
42 overridesResolver: (props, styles) => {
43 const {
44 ownerState
45 } = props;
46 const {
47 color,
48 iconColor,
49 clickable,
50 onDelete,
51 size,
52 variant
53 } = ownerState;
54 return [{
55 [`& .${chipClasses.avatar}`]: styles.avatar
56 }, {
57 [`& .${chipClasses.avatar}`]: styles[`avatar${capitalize(size)}`]
58 }, {
59 [`& .${chipClasses.avatar}`]: styles[`avatarColor${capitalize(color)}`]
60 }, {
61 [`& .${chipClasses.icon}`]: styles.icon
62 }, {
63 [`& .${chipClasses.icon}`]: styles[`icon${capitalize(size)}`]
64 }, {
65 [`& .${chipClasses.icon}`]: styles[`iconColor${capitalize(iconColor)}`]
66 }, {
67 [`& .${chipClasses.deleteIcon}`]: styles.deleteIcon
68 }, {
69 [`& .${chipClasses.deleteIcon}`]: styles[`deleteIcon${capitalize(size)}`]
70 }, {
71 [`& .${chipClasses.deleteIcon}`]: styles[`deleteIconColor${capitalize(color)}`]
72 }, {
73 [`& .${chipClasses.deleteIcon}`]: styles[`deleteIcon${capitalize(variant)}Color${capitalize(color)}`]
74 }, styles.root, styles[`size${capitalize(size)}`], styles[`color${capitalize(color)}`], clickable && styles.clickable, clickable && color !== 'default' && styles[`clickableColor${capitalize(color)})`], onDelete && styles.deletable, onDelete && color !== 'default' && styles[`deletableColor${capitalize(color)}`], styles[variant], styles[`${variant}${capitalize(color)}`]];
75 }
76})(memoTheme(({
77 theme
78}) => {
79 const textColor = theme.palette.mode === 'light' ? theme.palette.grey[700] : theme.palette.grey[300];
80 return {
81 maxWidth: '100%',
82 fontFamily: theme.typography.fontFamily,
83 fontSize: theme.typography.pxToRem(13),
84 display: 'inline-flex',
85 alignItems: 'center',
86 justifyContent: 'center',
87 height: 32,
88 color: (theme.vars || theme).palette.text.primary,
89 backgroundColor: (theme.vars || theme).palette.action.selected,
90 borderRadius: 32 / 2,
91 whiteSpace: 'nowrap',
92 transition: theme.transitions.create(['background-color', 'box-shadow']),
93 // reset cursor explicitly in case ButtonBase is used
94 cursor: 'unset',
95 // We disable the focus ring for mouse, touch and keyboard users.
96 outline: 0,
97 textDecoration: 'none',
98 border: 0,
99 // Remove `button` border
100 padding: 0,
101 // Remove `button` padding
102 verticalAlign: 'middle',
103 boxSizing: 'border-box',
104 [`&.${chipClasses.disabled}`]: {
105 opacity: (theme.vars || theme).palette.action.disabledOpacity,
106 pointerEvents: 'none'
107 },
108 [`& .${chipClasses.avatar}`]: {
109 marginLeft: 5,
110 marginRight: -6,
111 width: 24,
112 height: 24,
113 color: theme.vars ? theme.vars.palette.Chip.defaultAvatarColor : textColor,
114 fontSize: theme.typography.pxToRem(12)
115 },
116 [`& .${chipClasses.avatarColorPrimary}`]: {
117 color: (theme.vars || theme).palette.primary.contrastText,
118 backgroundColor: (theme.vars || theme).palette.primary.dark
119 },
120 [`& .${chipClasses.avatarColorSecondary}`]: {
121 color: (theme.vars || theme).palette.secondary.contrastText,
122 backgroundColor: (theme.vars || theme).palette.secondary.dark
123 },
124 [`& .${chipClasses.avatarSmall}`]: {
125 marginLeft: 4,
126 marginRight: -4,
127 width: 18,
128 height: 18,
129 fontSize: theme.typography.pxToRem(10)
130 },
131 [`& .${chipClasses.icon}`]: {
132 marginLeft: 5,
133 marginRight: -6
134 },
135 [`& .${chipClasses.deleteIcon}`]: {
136 WebkitTapHighlightColor: 'transparent',
137 color: theme.vars ? `rgba(${theme.vars.palette.text.primaryChannel} / 0.26)` : alpha(theme.palette.text.primary, 0.26),
138 fontSize: 22,
139 cursor: 'pointer',
140 margin: '0 5px 0 -6px',
141 '&:hover': {
142 color: theme.vars ? `rgba(${theme.vars.palette.text.primaryChannel} / 0.4)` : alpha(theme.palette.text.primary, 0.4)
143 }
144 },
145 variants: [{
146 props: {
147 size: 'small'
148 },
149 style: {
150 height: 24,
151 [`& .${chipClasses.icon}`]: {
152 fontSize: 18,
153 marginLeft: 4,
154 marginRight: -4
155 },
156 [`& .${chipClasses.deleteIcon}`]: {
157 fontSize: 16,
158 marginRight: 4,
159 marginLeft: -4
160 }
161 }
162 }, ...Object.entries(theme.palette).filter(createSimplePaletteValueFilter(['contrastText'])).map(([color]) => {
163 return {
164 props: {
165 color
166 },
167 style: {
168 backgroundColor: (theme.vars || theme).palette[color].main,
169 color: (theme.vars || theme).palette[color].contrastText,
170 [`& .${chipClasses.deleteIcon}`]: {
171 color: theme.vars ? `rgba(${theme.vars.palette[color].contrastTextChannel} / 0.7)` : alpha(theme.palette[color].contrastText, 0.7),
172 '&:hover, &:active': {
173 color: (theme.vars || theme).palette[color].contrastText
174 }
175 }
176 }
177 };
178 }), {
179 props: props => props.iconColor === props.color,
180 style: {
181 [`& .${chipClasses.icon}`]: {
182 color: theme.vars ? theme.vars.palette.Chip.defaultIconColor : textColor
183 }
184 }
185 }, {
186 props: props => props.iconColor === props.color && props.color !== 'default',
187 style: {
188 [`& .${chipClasses.icon}`]: {
189 color: 'inherit'
190 }
191 }
192 }, {
193 props: {
194 onDelete: true
195 },
196 style: {
197 [`&.${chipClasses.focusVisible}`]: {
198 backgroundColor: theme.vars ? `rgba(${theme.vars.palette.action.selectedChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.focusOpacity}))` : alpha(theme.palette.action.selected, theme.palette.action.selectedOpacity + theme.palette.action.focusOpacity)
199 }
200 }
201 }, ...Object.entries(theme.palette).filter(createSimplePaletteValueFilter(['dark'])).map(([color]) => {
202 return {
203 props: {
204 color,
205 onDelete: true
206 },
207 style: {
208 [`&.${chipClasses.focusVisible}`]: {
209 background: (theme.vars || theme).palette[color].dark
210 }
211 }
212 };
213 }), {
214 props: {
215 clickable: true
216 },
217 style: {
218 userSelect: 'none',
219 WebkitTapHighlightColor: 'transparent',
220 cursor: 'pointer',
221 '&:hover': {
222 backgroundColor: theme.vars ? `rgba(${theme.vars.palette.action.selectedChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.hoverOpacity}))` : alpha(theme.palette.action.selected, theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity)
223 },
224 [`&.${chipClasses.focusVisible}`]: {
225 backgroundColor: theme.vars ? `rgba(${theme.vars.palette.action.selectedChannel} / calc(${theme.vars.palette.action.selectedOpacity} + ${theme.vars.palette.action.focusOpacity}))` : alpha(theme.palette.action.selected, theme.palette.action.selectedOpacity + theme.palette.action.focusOpacity)
226 },
227 '&:active': {
228 boxShadow: (theme.vars || theme).shadows[1]
229 }
230 }
231 }, ...Object.entries(theme.palette).filter(createSimplePaletteValueFilter(['dark'])).map(([color]) => ({
232 props: {
233 color,
234 clickable: true
235 },
236 style: {
237 [`&:hover, &.${chipClasses.focusVisible}`]: {
238 backgroundColor: (theme.vars || theme).palette[color].dark
239 }
240 }
241 })), {
242 props: {
243 variant: 'outlined'
244 },
245 style: {
246 backgroundColor: 'transparent',
247 border: theme.vars ? `1px solid ${theme.vars.palette.Chip.defaultBorder}` : `1px solid ${theme.palette.mode === 'light' ? theme.palette.grey[400] : theme.palette.grey[700]}`,
248 [`&.${chipClasses.clickable}:hover`]: {
249 backgroundColor: (theme.vars || theme).palette.action.hover
250 },
251 [`&.${chipClasses.focusVisible}`]: {
252 backgroundColor: (theme.vars || theme).palette.action.focus
253 },
254 [`& .${chipClasses.avatar}`]: {
255 marginLeft: 4
256 },
257 [`& .${chipClasses.avatarSmall}`]: {
258 marginLeft: 2
259 },
260 [`& .${chipClasses.icon}`]: {
261 marginLeft: 4
262 },
263 [`& .${chipClasses.iconSmall}`]: {
264 marginLeft: 2
265 },
266 [`& .${chipClasses.deleteIcon}`]: {
267 marginRight: 5
268 },
269 [`& .${chipClasses.deleteIconSmall}`]: {
270 marginRight: 3
271 }
272 }
273 }, ...Object.entries(theme.palette).filter(createSimplePaletteValueFilter()) // no need to check for mainChannel as it's calculated from main
274 .map(([color]) => ({
275 props: {
276 variant: 'outlined',
277 color
278 },
279 style: {
280 color: (theme.vars || theme).palette[color].main,
281 border: `1px solid ${theme.vars ? `rgba(${theme.vars.palette[color].mainChannel} / 0.7)` : alpha(theme.palette[color].main, 0.7)}`,
282 [`&.${chipClasses.clickable}:hover`]: {
283 backgroundColor: theme.vars ? `rgba(${theme.vars.palette[color].mainChannel} / ${theme.vars.palette.action.hoverOpacity})` : alpha(theme.palette[color].main, theme.palette.action.hoverOpacity)
284 },
285 [`&.${chipClasses.focusVisible}`]: {
286 backgroundColor: theme.vars ? `rgba(${theme.vars.palette[color].mainChannel} / ${theme.vars.palette.action.focusOpacity})` : alpha(theme.palette[color].main, theme.palette.action.focusOpacity)
287 },
288 [`& .${chipClasses.deleteIcon}`]: {
289 color: theme.vars ? `rgba(${theme.vars.palette[color].mainChannel} / 0.7)` : alpha(theme.palette[color].main, 0.7),
290 '&:hover, &:active': {
291 color: (theme.vars || theme).palette[color].main
292 }
293 }
294 }
295 }))]
296 };
297}));
298const ChipLabel = styled('span', {
299 name: 'MuiChip',
300 slot: 'Label',
301 overridesResolver: (props, styles) => {
302 const {
303 ownerState
304 } = props;
305 const {
306 size
307 } = ownerState;
308 return [styles.label, styles[`label${capitalize(size)}`]];
309 }
310})({
311 overflow: 'hidden',
312 textOverflow: 'ellipsis',
313 paddingLeft: 12,
314 paddingRight: 12,
315 whiteSpace: 'nowrap',
316 variants: [{
317 props: {
318 variant: 'outlined'
319 },
320 style: {
321 paddingLeft: 11,
322 paddingRight: 11
323 }
324 }, {
325 props: {
326 size: 'small'
327 },
328 style: {
329 paddingLeft: 8,
330 paddingRight: 8
331 }
332 }, {
333 props: {
334 size: 'small',
335 variant: 'outlined'
336 },
337 style: {
338 paddingLeft: 7,
339 paddingRight: 7
340 }
341 }]
342});
343function isDeleteKeyboardEvent(keyboardEvent) {
344 return keyboardEvent.key === 'Backspace' || keyboardEvent.key === 'Delete';
345}
346
347/**
348 * Chips represent complex entities in small blocks, such as a contact.
349 */
350const Chip = /*#__PURE__*/React.forwardRef(function Chip(inProps, ref) {
351 const props = useDefaultProps({
352 props: inProps,
353 name: 'MuiChip'
354 });
355 const {
356 avatar: avatarProp,
357 className,
358 clickable: clickableProp,
359 color = 'default',
360 component: ComponentProp,
361 deleteIcon: deleteIconProp,
362 disabled = false,
363 icon: iconProp,
364 label,
365 onClick,
366 onDelete,
367 onKeyDown,
368 onKeyUp,
369 size = 'medium',
370 variant = 'filled',
371 tabIndex,
372 skipFocusWhenDisabled = false,
373 // TODO v6: Rename to `focusableWhenDisabled`.
374 ...other
375 } = props;
376 const chipRef = React.useRef(null);
377 const handleRef = useForkRef(chipRef, ref);
378 const handleDeleteIconClick = event => {
379 // Stop the event from bubbling up to the `Chip`
380 event.stopPropagation();
381 if (onDelete) {
382 onDelete(event);
383 }
384 };
385 const handleKeyDown = event => {
386 // Ignore events from children of `Chip`.
387 if (event.currentTarget === event.target && isDeleteKeyboardEvent(event)) {
388 // Will be handled in keyUp, otherwise some browsers
389 // might init navigation
390 event.preventDefault();
391 }
392 if (onKeyDown) {
393 onKeyDown(event);
394 }
395 };
396 const handleKeyUp = event => {
397 // Ignore events from children of `Chip`.
398 if (event.currentTarget === event.target) {
399 if (onDelete && isDeleteKeyboardEvent(event)) {
400 onDelete(event);
401 }
402 }
403 if (onKeyUp) {
404 onKeyUp(event);
405 }
406 };
407 const clickable = clickableProp !== false && onClick ? true : clickableProp;
408 const component = clickable || onDelete ? ButtonBase : ComponentProp || 'div';
409 const ownerState = {
410 ...props,
411 component,
412 disabled,
413 size,
414 color,
415 iconColor: /*#__PURE__*/React.isValidElement(iconProp) ? iconProp.props.color || color : color,
416 onDelete: !!onDelete,
417 clickable,
418 variant
419 };
420 const classes = useUtilityClasses(ownerState);
421 const moreProps = component === ButtonBase ? {
422 component: ComponentProp || 'div',
423 focusVisibleClassName: classes.focusVisible,
424 ...(onDelete && {
425 disableRipple: true
426 })
427 } : {};
428 let deleteIcon = null;
429 if (onDelete) {
430 deleteIcon = deleteIconProp && /*#__PURE__*/React.isValidElement(deleteIconProp) ? (/*#__PURE__*/React.cloneElement(deleteIconProp, {
431 className: clsx(deleteIconProp.props.className, classes.deleteIcon),
432 onClick: handleDeleteIconClick
433 })) : /*#__PURE__*/_jsx(CancelIcon, {
434 className: clsx(classes.deleteIcon),
435 onClick: handleDeleteIconClick
436 });
437 }
438 let avatar = null;
439 if (avatarProp && /*#__PURE__*/React.isValidElement(avatarProp)) {
440 avatar = /*#__PURE__*/React.cloneElement(avatarProp, {
441 className: clsx(classes.avatar, avatarProp.props.className)
442 });
443 }
444 let icon = null;
445 if (iconProp && /*#__PURE__*/React.isValidElement(iconProp)) {
446 icon = /*#__PURE__*/React.cloneElement(iconProp, {
447 className: clsx(classes.icon, iconProp.props.className)
448 });
449 }
450 if (process.env.NODE_ENV !== 'production') {
451 if (avatar && icon) {
452 console.error('MUI: The Chip component can not handle the avatar ' + 'and the icon prop at the same time. Pick one.');
453 }
454 }
455 return /*#__PURE__*/_jsxs(ChipRoot, {
456 as: component,
457 className: clsx(classes.root, className),
458 disabled: clickable && disabled ? true : undefined,
459 onClick: onClick,
460 onKeyDown: handleKeyDown,
461 onKeyUp: handleKeyUp,
462 ref: handleRef,
463 tabIndex: skipFocusWhenDisabled && disabled ? -1 : tabIndex,
464 ownerState: ownerState,
465 ...moreProps,
466 ...other,
467 children: [avatar || icon, /*#__PURE__*/_jsx(ChipLabel, {
468 className: clsx(classes.label),
469 ownerState: ownerState,
470 children: label
471 }), deleteIcon]
472 });
473});
474process.env.NODE_ENV !== "production" ? Chip.propTypes /* remove-proptypes */ = {
475 // ┌────────────────────────────── Warning ──────────────────────────────┐
476 // │ These PropTypes are generated from the TypeScript type definitions. │
477 // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
478 // └─────────────────────────────────────────────────────────────────────┘
479 /**
480 * The Avatar element to display.
481 */
482 avatar: PropTypes.element,
483 /**
484 * This prop isn't supported.
485 * Use the `component` prop if you need to change the children structure.
486 */
487 children: unsupportedProp,
488 /**
489 * Override or extend the styles applied to the component.
490 */
491 classes: PropTypes.object,
492 /**
493 * @ignore
494 */
495 className: PropTypes.string,
496 /**
497 * If `true`, the chip will appear clickable, and will raise when pressed,
498 * even if the onClick prop is not defined.
499 * If `false`, the chip will not appear clickable, even if onClick prop is defined.
500 * This can be used, for example,
501 * along with the component prop to indicate an anchor Chip is clickable.
502 * Note: this controls the UI and does not affect the onClick event.
503 */
504 clickable: PropTypes.bool,
505 /**
506 * The color of the component.
507 * It supports both default and custom theme colors, which can be added as shown in the
508 * [palette customization guide](https://mui.com/material-ui/customization/palette/#custom-colors).
509 * @default 'default'
510 */
511 color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['default', 'primary', 'secondary', 'error', 'info', 'success', 'warning']), PropTypes.string]),
512 /**
513 * The component used for the root node.
514 * Either a string to use a HTML element or a component.
515 */
516 component: PropTypes.elementType,
517 /**
518 * Override the default delete icon element. Shown only if `onDelete` is set.
519 */
520 deleteIcon: PropTypes.element,
521 /**
522 * If `true`, the component is disabled.
523 * @default false
524 */
525 disabled: PropTypes.bool,
526 /**
527 * Icon element.
528 */
529 icon: PropTypes.element,
530 /**
531 * The content of the component.
532 */
533 label: PropTypes.node,
534 /**
535 * @ignore
536 */
537 onClick: PropTypes.func,
538 /**
539 * Callback fired when the delete icon is clicked.
540 * If set, the delete icon will be shown.
541 */
542 onDelete: PropTypes.func,
543 /**
544 * @ignore
545 */
546 onKeyDown: PropTypes.func,
547 /**
548 * @ignore
549 */
550 onKeyUp: PropTypes.func,
551 /**
552 * The size of the component.
553 * @default 'medium'
554 */
555 size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['medium', 'small']), PropTypes.string]),
556 /**
557 * If `true`, allows the disabled chip to escape focus.
558 * If `false`, allows the disabled chip to receive focus.
559 * @default false
560 */
561 skipFocusWhenDisabled: PropTypes.bool,
562 /**
563 * The system prop that allows defining system overrides as well as additional CSS styles.
564 */
565 sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
566 /**
567 * @ignore
568 */
569 tabIndex: PropTypes.number,
570 /**
571 * The variant to use.
572 * @default 'filled'
573 */
574 variant: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.oneOf(['filled', 'outlined']), PropTypes.string])
575} : void 0;
576export default Chip;
\No newline at end of file