@box/blueprint-web 17.3.0 → 17.4.0
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.
- package/dist/lib-esm/combobox/chips-group.js +40 -23
- package/dist/lib-esm/combobox/combobox.js +56 -15
- package/dist/lib-esm/combobox/combobox.module.js +1 -1
- package/dist/lib-esm/combobox/utils/use-exiting-chip-ids.d.ts +10 -0
- package/dist/lib-esm/combobox/utils/use-exiting-chip-ids.js +33 -0
- package/dist/lib-esm/components.css +151 -77
- package/dist/lib-esm/index.css +151 -77
- package/dist/lib-esm/input-chip/input-chip.js +45 -9
- package/dist/lib-esm/input-chip/input-chip.module.js +1 -1
- package/dist/lib-esm/input-chip/types.d.ts +16 -0
- package/dist/lib-esm/input-chip/utils/use-input-chip-presence.d.ts +24 -0
- package/dist/lib-esm/input-chip/utils/use-input-chip-presence.js +54 -0
- package/package.json +3 -3
|
@@ -2,9 +2,10 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import { XMark } from '@box/blueprint-web-assets/icons/Fill';
|
|
3
3
|
import { Size4 } from '@box/blueprint-web-assets/tokens/tokens';
|
|
4
4
|
import clsx from 'clsx';
|
|
5
|
-
import React__default, { forwardRef } from 'react';
|
|
5
|
+
import React__default, { forwardRef, useMemo, useCallback } from 'react';
|
|
6
6
|
import { Text } from '../text/text.js';
|
|
7
7
|
import styles from './input-chip.module.js';
|
|
8
|
+
import { useInputChipPresence } from './utils/use-input-chip-presence.js';
|
|
8
9
|
|
|
9
10
|
const getVariantClass = variant => {
|
|
10
11
|
if (variant === 'error') {
|
|
@@ -20,12 +21,32 @@ const InputChip = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
20
21
|
const {
|
|
21
22
|
avatar: avatarProp,
|
|
22
23
|
label,
|
|
24
|
+
tooltip,
|
|
23
25
|
variant,
|
|
24
26
|
className,
|
|
25
27
|
onDelete,
|
|
28
|
+
present = true,
|
|
29
|
+
isAnimationEnabled = false,
|
|
30
|
+
onAnimationEnd: onAnimationEndProp,
|
|
31
|
+
'aria-hidden': ariaHiddenProp,
|
|
32
|
+
tabIndex: tabIndexProp,
|
|
26
33
|
...buttonProps
|
|
27
34
|
} = props;
|
|
28
35
|
const enableModernizedComponents = true;
|
|
36
|
+
const content = useMemo(() => ({
|
|
37
|
+
avatar: avatarProp,
|
|
38
|
+
label,
|
|
39
|
+
tooltip,
|
|
40
|
+
variant
|
|
41
|
+
}), [avatarProp, label, tooltip, variant]);
|
|
42
|
+
const presence = useInputChipPresence(present, isAnimationEnabled, content);
|
|
43
|
+
const {
|
|
44
|
+
onAnimationEnd: onPresenceAnimationEnd
|
|
45
|
+
} = presence;
|
|
46
|
+
const handleAnimationEnd = useCallback(event => {
|
|
47
|
+
onPresenceAnimationEnd(event);
|
|
48
|
+
onAnimationEndProp?.(event);
|
|
49
|
+
}, [onPresenceAnimationEnd, onAnimationEndProp]);
|
|
29
50
|
// Clone avatar element to override its props
|
|
30
51
|
const withSmallAvatarSize = element => {
|
|
31
52
|
const cloneAvatarWithSmallSize = el => /*#__PURE__*/React__default.cloneElement(el, {
|
|
@@ -48,29 +69,44 @@ const InputChip = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
48
69
|
// Avatar without an IconBadge, apply size directly to the Avatar
|
|
49
70
|
return cloneAvatarWithSmallSize(element);
|
|
50
71
|
};
|
|
51
|
-
|
|
72
|
+
if (!presence.isMounted) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const {
|
|
76
|
+
avatar: chipAvatar,
|
|
77
|
+
label: chipLabel,
|
|
78
|
+
variant: chipVariant
|
|
79
|
+
} = presence.content;
|
|
80
|
+
const isExiting = presence.presenceState === 'closed';
|
|
81
|
+
const canDelete = Boolean(onDelete) && !isExiting;
|
|
82
|
+
const avatar = chipAvatar ? withSmallAvatarSize(chipAvatar) : null;
|
|
52
83
|
const handleKeyDown = event => {
|
|
53
|
-
if (
|
|
54
|
-
onDelete();
|
|
84
|
+
if (canDelete && DELETE_TRIGGER_KEYS.includes(event.code)) {
|
|
85
|
+
onDelete?.();
|
|
55
86
|
}
|
|
56
87
|
buttonProps.onKeyDown?.(event);
|
|
57
88
|
};
|
|
58
|
-
const hasError =
|
|
59
|
-
const variantClass = getVariantClass(
|
|
60
|
-
const containerClassName = clsx(styles.container, variantClass, avatar && styles.avatarContainer,
|
|
89
|
+
const hasError = chipVariant === 'error';
|
|
90
|
+
const variantClass = getVariantClass(chipVariant);
|
|
91
|
+
const containerClassName = clsx(styles.container, variantClass, avatar && styles.avatarContainer, canDelete && styles.deleteButton, !canDelete && enableModernizedComponents && styles.noDeleteButton, className);
|
|
61
92
|
return /* eslint-disable-next-line jsx-a11y/role-supports-aria-props */jsxs("button", {
|
|
62
93
|
...buttonProps,
|
|
63
94
|
ref: ref,
|
|
95
|
+
"aria-hidden": isExiting ? true : ariaHiddenProp,
|
|
64
96
|
"aria-invalid": hasError,
|
|
65
97
|
className: containerClassName,
|
|
98
|
+
"data-bp-animated": isAnimationEnabled ? 'true' : 'false',
|
|
66
99
|
"data-modern": 'true' ,
|
|
100
|
+
"data-state": presence.presenceState,
|
|
101
|
+
onAnimationEnd: handleAnimationEnd,
|
|
67
102
|
onKeyDown: handleKeyDown,
|
|
103
|
+
tabIndex: isExiting ? -1 : tabIndexProp,
|
|
68
104
|
type: "button",
|
|
69
105
|
children: [avatar, jsx(Text, {
|
|
70
106
|
as: "span",
|
|
71
107
|
className: styles.label,
|
|
72
|
-
children:
|
|
73
|
-
}),
|
|
108
|
+
children: chipLabel
|
|
109
|
+
}), canDelete && (/* Don't use `onClick` handler on icon due to BUIE's "pointer-events: none" style that would cause click event to not trigger */
|
|
74
110
|
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
|
|
75
111
|
jsx("span", {
|
|
76
112
|
className: styles.deleteIcon,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import '../index.css';
|
|
2
|
-
var styles = {"container":"bp_input_chip_module_container--
|
|
2
|
+
var styles = {"container":"bp_input_chip_module_container--d98d0","avatarContainer":"bp_input_chip_module_avatarContainer--d98d0","deleteButton":"bp_input_chip_module_deleteButton--d98d0","noDeleteButton":"bp_input_chip_module_noDeleteButton--d98d0","error":"bp_input_chip_module_error--d98d0","deleteIcon":"bp_input_chip_module_deleteIcon--d98d0","label":"bp_input_chip_module_label--d98d0","variable":"bp_input_chip_module_variable--d98d0","avatar":"bp_input_chip_module_avatar--d98d0","modern":"bp_input_chip_module_modern--d98d0"};
|
|
3
3
|
|
|
4
4
|
export { styles as default };
|
|
@@ -22,4 +22,20 @@ export interface InputChipProps extends HTMLAttributes<HTMLElement> {
|
|
|
22
22
|
* Callback fired when chip is deleted with X button click or keyboard interaction.
|
|
23
23
|
*/
|
|
24
24
|
onDelete?: () => void;
|
|
25
|
+
/**
|
|
26
|
+
* Whether the chip should be shown. Set to `false` to trigger the exit animation (when
|
|
27
|
+
* `isAnimationEnabled` is `true`); the chip keeps rendering (non-interactively) until the exit
|
|
28
|
+
* animation completes, then unmounts itself. When `isAnimationEnabled` is `false`,
|
|
29
|
+
* `present={false}` unmounts immediately.
|
|
30
|
+
*
|
|
31
|
+
* @default true
|
|
32
|
+
*/
|
|
33
|
+
present?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Opts the chip's mount/unmount transition into the Blueprint animation system. When `false`
|
|
36
|
+
* (default), `present` changes take effect immediately with no animation.
|
|
37
|
+
*
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
isAnimationEnabled?: boolean;
|
|
25
41
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type AnimationEvent } from 'react';
|
|
2
|
+
import { type InputChipProps } from '../types';
|
|
3
|
+
export interface InputChipContentSnapshot {
|
|
4
|
+
avatar?: InputChipProps['avatar'];
|
|
5
|
+
label: string;
|
|
6
|
+
tooltip?: InputChipProps['tooltip'];
|
|
7
|
+
variant?: InputChipProps['variant'];
|
|
8
|
+
}
|
|
9
|
+
type PresenceState = 'open' | 'closed';
|
|
10
|
+
/**
|
|
11
|
+
* Exit-animation presence for `InputChip`. Pattern mirrors `useInlineErrorPresence`: keep the
|
|
12
|
+
* chip mounted (non-interactively) while its exit CSS animation runs, then unmount on
|
|
13
|
+
* `animationend`. Also freezes `content` the moment `present` becomes `false`, so a caller (e.g.
|
|
14
|
+
* Combobox) doesn't need its own cache of a chip's display data once it no longer has a source of
|
|
15
|
+
* truth for it — as long as this InputChip element stays mounted at a stable tree position across
|
|
16
|
+
* the transition (see ChipsGroup).
|
|
17
|
+
*/
|
|
18
|
+
export declare function useInputChipPresence(present: boolean, isAnimationEnabled: boolean, content: InputChipContentSnapshot): {
|
|
19
|
+
content: InputChipContentSnapshot;
|
|
20
|
+
isMounted: boolean;
|
|
21
|
+
presenceState: PresenceState | undefined;
|
|
22
|
+
onAnimationEnd: (e: AnimationEvent<HTMLButtonElement>) => void;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
import { noop } from '../../utils/noop.js';
|
|
3
|
+
import { useEnhancedEffect } from '../../utils/useEnhancedEffect.js';
|
|
4
|
+
|
|
5
|
+
const animationEndNoop = noop;
|
|
6
|
+
/**
|
|
7
|
+
* Exit-animation presence for `InputChip`. Pattern mirrors `useInlineErrorPresence`: keep the
|
|
8
|
+
* chip mounted (non-interactively) while its exit CSS animation runs, then unmount on
|
|
9
|
+
* `animationend`. Also freezes `content` the moment `present` becomes `false`, so a caller (e.g.
|
|
10
|
+
* Combobox) doesn't need its own cache of a chip's display data once it no longer has a source of
|
|
11
|
+
* truth for it — as long as this InputChip element stays mounted at a stable tree position across
|
|
12
|
+
* the transition (see ChipsGroup).
|
|
13
|
+
*/
|
|
14
|
+
function useInputChipPresence(present, isAnimationEnabled, content) {
|
|
15
|
+
const [renderedContent, setRenderedContent] = useState(content);
|
|
16
|
+
const [presenceState, setPresenceState] = useState(() => isAnimationEnabled && present ? 'open' : undefined);
|
|
17
|
+
const isMounted = isAnimationEnabled && (presenceState === 'open' || presenceState === 'closed');
|
|
18
|
+
const unmountAfterExit = useCallback(() => setPresenceState(undefined), []);
|
|
19
|
+
const onAnimationEnd = useCallback(e => {
|
|
20
|
+
if (e.target !== e.currentTarget || presenceState !== 'closed') {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
unmountAfterExit();
|
|
24
|
+
}, [presenceState, unmountAfterExit]);
|
|
25
|
+
useEnhancedEffect(() => {
|
|
26
|
+
if (!isAnimationEnabled) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (present) {
|
|
30
|
+
setRenderedContent(content);
|
|
31
|
+
setPresenceState('open');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Don't reassign renderedContent here — freezes it at its last "present" value.
|
|
35
|
+
setPresenceState(current => current === 'open' ? 'closed' : current);
|
|
36
|
+
}, [content, isAnimationEnabled, present]);
|
|
37
|
+
if (!isAnimationEnabled) {
|
|
38
|
+
// Legacy / animation-disabled path: no bookkeeping, no lingering DOM node.
|
|
39
|
+
return {
|
|
40
|
+
content,
|
|
41
|
+
isMounted: present,
|
|
42
|
+
presenceState: undefined,
|
|
43
|
+
onAnimationEnd: animationEndNoop
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
content: renderedContent,
|
|
48
|
+
isMounted,
|
|
49
|
+
presenceState,
|
|
50
|
+
onAnimationEnd
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { useInputChipPresence };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@box/blueprint-web",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"publishConfig": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@ariakit/react": "0.4.21",
|
|
57
57
|
"@ariakit/react-core": "0.4.21",
|
|
58
|
-
"@box/blueprint-web-assets": "^5.9.
|
|
58
|
+
"@box/blueprint-web-assets": "^5.9.3",
|
|
59
59
|
"@internationalized/date": "^3.12.0",
|
|
60
60
|
"@radix-ui/react-accordion": "1.1.2",
|
|
61
61
|
"@radix-ui/react-checkbox": "1.0.4",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@box/box-test-client": "^3.2.7",
|
|
88
88
|
"@box/playwright-utils": "^2.8.5",
|
|
89
|
-
"@box/storybook-utils": "^1.2.
|
|
89
|
+
"@box/storybook-utils": "^1.2.34",
|
|
90
90
|
"@figma/code-connect": "1.4.4",
|
|
91
91
|
"@playwright/test": "1.46.1",
|
|
92
92
|
"@types/js-yaml": "^4.0.9",
|