@box/blueprint-web 15.2.0 → 15.3.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/blueprint-configuration-context/consts.d.ts +1 -1
- package/dist/lib-esm/blueprint-configuration-context/consts.js +1 -1
- package/dist/lib-esm/index.css +39 -5
- package/dist/lib-esm/primitives/inline-error/inline-error.js +60 -22
- package/dist/lib-esm/primitives/inline-error/inline-error.module.js +1 -1
- package/dist/lib-esm/primitives/inline-error/utils/use-inline-error-presence.d.ts +13 -0
- package/dist/lib-esm/primitives/inline-error/utils/use-inline-error-presence.js +54 -0
- package/package.json +3 -3
|
@@ -38,7 +38,7 @@ export declare const BLUEPRINT_CONFIGURATION_SPLITS: {
|
|
|
38
38
|
*/
|
|
39
39
|
export declare const ANIMATED_COMPONENTS_BY_PHASE: {
|
|
40
40
|
readonly phase1: readonly ["Button", "IconButton", "DropdownMenu", "Tooltip", "Popover", "SplitButton", "CardTooltip", "NavigationMenu", "Modal", "TextButton", "ContextMenu"];
|
|
41
|
-
readonly phase2: readonly ["DropdownTrigger", "Switch", "Checkbox", "TextInput", "Select", "Datepicker", "TextArea", "Combobox", "PasswordInput", "ComboboxGroup"];
|
|
41
|
+
readonly phase2: readonly ["DropdownTrigger", "Switch", "Checkbox", "TextInput", "Select", "Datepicker", "TextArea", "Combobox", "PasswordInput", "ComboboxGroup", "InlineError"];
|
|
42
42
|
readonly phase3: readonly [];
|
|
43
43
|
};
|
|
44
44
|
/**
|
|
@@ -40,7 +40,7 @@ const BLUEPRINT_CONFIGURATION_SPLITS = deepFreeze({
|
|
|
40
40
|
*/
|
|
41
41
|
const ANIMATED_COMPONENTS_BY_PHASE = deepFreeze({
|
|
42
42
|
phase1: ['Button', 'IconButton', 'DropdownMenu', 'Tooltip', 'Popover', 'SplitButton', 'CardTooltip', 'NavigationMenu', 'Modal', 'TextButton', 'ContextMenu'],
|
|
43
|
-
phase2: ['DropdownTrigger', 'Switch', 'Checkbox', 'TextInput', 'Select', 'Datepicker', 'TextArea', 'Combobox', 'PasswordInput', 'ComboboxGroup'],
|
|
43
|
+
phase2: ['DropdownTrigger', 'Switch', 'Checkbox', 'TextInput', 'Select', 'Datepicker', 'TextArea', 'Combobox', 'PasswordInput', 'ComboboxGroup', 'InlineError'],
|
|
44
44
|
phase3: []
|
|
45
45
|
});
|
|
46
46
|
/**
|
package/dist/lib-esm/index.css
CHANGED
|
@@ -4546,7 +4546,7 @@
|
|
|
4546
4546
|
.bp_input_chip_module_container--8ac7b .bp_input_chip_module_avatar--8ac7b.bp_input_chip_module_modern--8ac7b *{
|
|
4547
4547
|
border-radius:unset;
|
|
4548
4548
|
}
|
|
4549
|
-
.bp_inline_error_module_inlineError--
|
|
4549
|
+
.bp_inline_error_module_inlineError--e70b6[data-modern=false]{
|
|
4550
4550
|
--inline-error-gap:var(--size-1);
|
|
4551
4551
|
--inline-error-height:var(--size-4);
|
|
4552
4552
|
--inline-error-text-color:var(--text-text-error-on-light);
|
|
@@ -4559,10 +4559,11 @@
|
|
|
4559
4559
|
text-transform:var(--body-default-bold-text-case);
|
|
4560
4560
|
}
|
|
4561
4561
|
|
|
4562
|
-
.bp_inline_error_module_inlineError--
|
|
4562
|
+
.bp_inline_error_module_inlineError--e70b6[data-modern=true]{
|
|
4563
4563
|
--inline-error-gap:var(--bp-space-010);
|
|
4564
4564
|
--inline-error-height:var(--bp-size-040);
|
|
4565
4565
|
--inline-error-text-color:var(--bp-text-text-error-on-light);
|
|
4566
|
+
--inline-error-slide-offset:calc(var(--bp-font-size-02, 0.625rem)*-1);
|
|
4566
4567
|
font-family:var(--bp-font-font-family), -apple-system, BlinkMacSystemFont, "San Francisco", "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
|
|
4567
4568
|
font-size:var(--bp-font-size-05);
|
|
4568
4569
|
font-style:normal;
|
|
@@ -4571,21 +4572,54 @@
|
|
|
4571
4572
|
line-height:var(--bp-font-line-height-04);
|
|
4572
4573
|
}
|
|
4573
4574
|
|
|
4574
|
-
.bp_inline_error_module_inlineError--
|
|
4575
|
+
.bp_inline_error_module_inlineError--e70b6.bp_inline_error_module_inlineError--e70b6{
|
|
4575
4576
|
align-items:center;
|
|
4576
4577
|
color:var(--inline-error-text-color);
|
|
4577
4578
|
display:flex;
|
|
4578
4579
|
gap:var(--inline-error-gap);
|
|
4579
4580
|
line-height:var(--inline-error-height);
|
|
4580
4581
|
}
|
|
4581
|
-
.bp_inline_error_module_inlineError--
|
|
4582
|
+
.bp_inline_error_module_inlineError--e70b6.bp_inline_error_module_inlineError--e70b6.bp_inline_error_module_empty--e70b6{
|
|
4582
4583
|
display:none;
|
|
4583
4584
|
}
|
|
4584
|
-
.bp_inline_error_module_inlineError--
|
|
4585
|
+
.bp_inline_error_module_inlineError--e70b6.bp_inline_error_module_inlineError--e70b6 .bp_inline_error_module_errorIcon--e70b6{
|
|
4585
4586
|
align-self:flex-start;
|
|
4586
4587
|
flex-grow:0;
|
|
4587
4588
|
flex-shrink:0;
|
|
4588
4589
|
}
|
|
4590
|
+
|
|
4591
|
+
.bp_inline_error_module_inlineError--e70b6.bp_inline_error_module_inlineError--e70b6.bp_inline_error_module_animated--e70b6[data-state=open]{
|
|
4592
|
+
animation-duration:var(--bp-duration-short);
|
|
4593
|
+
animation-fill-mode:both;
|
|
4594
|
+
animation-name:bp_inline_error_module_bpInlineErrorSlideEnter--e70b6;
|
|
4595
|
+
animation-timing-function:var(--bp-curve-small-on);
|
|
4596
|
+
}
|
|
4597
|
+
|
|
4598
|
+
.bp_inline_error_module_inlineError--e70b6.bp_inline_error_module_inlineError--e70b6.bp_inline_error_module_animated--e70b6[data-state=closed]{
|
|
4599
|
+
animation-duration:var(--bp-duration-short);
|
|
4600
|
+
animation-fill-mode:forwards;
|
|
4601
|
+
animation-name:bp_inline_error_module_bpInlineErrorFadeExit--e70b6;
|
|
4602
|
+
animation-timing-function:var(--bp-curve-small-off);
|
|
4603
|
+
}
|
|
4604
|
+
|
|
4605
|
+
@keyframes bp_inline_error_module_bpInlineErrorSlideEnter--e70b6{
|
|
4606
|
+
from{
|
|
4607
|
+
opacity:var(--bp-opacity-hidden);
|
|
4608
|
+
transform:translateY(var(--inline-error-slide-offset));
|
|
4609
|
+
}
|
|
4610
|
+
to{
|
|
4611
|
+
opacity:var(--bp-opacity-visible);
|
|
4612
|
+
transform:translateY(0);
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
4615
|
+
@keyframes bp_inline_error_module_bpInlineErrorFadeExit--e70b6{
|
|
4616
|
+
from{
|
|
4617
|
+
opacity:var(--bp-opacity-visible);
|
|
4618
|
+
}
|
|
4619
|
+
to{
|
|
4620
|
+
opacity:var(--bp-opacity-hidden);
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4589
4623
|
.bp_labelable_module_required--25dbc{
|
|
4590
4624
|
align-items:center;
|
|
4591
4625
|
display:inline-flex;
|
|
@@ -3,44 +3,82 @@ import { AlertBadge } from '@box/blueprint-web-assets/icons/Fill';
|
|
|
3
3
|
import { AlertCircle } from '@box/blueprint-web-assets/icons/Medium';
|
|
4
4
|
import { Size4, IconIconErrorOnLight, bpSize050 } from '@box/blueprint-web-assets/tokens/tokens';
|
|
5
5
|
import clsx from 'clsx';
|
|
6
|
-
import { forwardRef } from 'react';
|
|
6
|
+
import { forwardRef, useCallback } from 'react';
|
|
7
|
+
import '../../blueprint-configuration-context/blueprint-configuration-context.js';
|
|
8
|
+
import '../../blueprint-configuration-context/consts.js';
|
|
9
|
+
import { useBlueprintConfiguration } from '../../blueprint-configuration-context/useBlueprintConfiguration.js';
|
|
7
10
|
import { useBlueprintModernization } from '../../blueprint-modernization-context/useBlueprintModernization.js';
|
|
11
|
+
import { useInlineErrorPresence } from './utils/use-inline-error-presence.js';
|
|
8
12
|
import styles from './inline-error.module.js';
|
|
9
13
|
|
|
14
|
+
const renderErrorContent = (content, enableModernizedComponents, errorIconClassName) => content && (!enableModernizedComponents ? jsxs(Fragment, {
|
|
15
|
+
children: [jsx(AlertBadge, {
|
|
16
|
+
className: errorIconClassName,
|
|
17
|
+
color: IconIconErrorOnLight,
|
|
18
|
+
height: Size4,
|
|
19
|
+
role: "presentation",
|
|
20
|
+
width: Size4
|
|
21
|
+
}), content]
|
|
22
|
+
}) : jsxs(Fragment, {
|
|
23
|
+
children: [jsx(AlertCircle, {
|
|
24
|
+
className: errorIconClassName,
|
|
25
|
+
color: IconIconErrorOnLight,
|
|
26
|
+
height: bpSize050,
|
|
27
|
+
role: "presentation",
|
|
28
|
+
width: bpSize050
|
|
29
|
+
}), content]
|
|
30
|
+
}));
|
|
10
31
|
/** Renders an inline error message and icon, used to show error state in form elements. */
|
|
11
32
|
const InlineError = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
12
33
|
const {
|
|
13
34
|
children,
|
|
14
35
|
className,
|
|
36
|
+
onAnimationEnd: onAnimationEndProp,
|
|
15
37
|
...rest
|
|
16
38
|
} = props;
|
|
17
39
|
const {
|
|
18
40
|
enableModernizedComponents
|
|
19
41
|
} = useBlueprintModernization();
|
|
42
|
+
const {
|
|
43
|
+
componentsWithAnimationEnabled
|
|
44
|
+
} = useBlueprintConfiguration();
|
|
45
|
+
const isAnimationEnabled = componentsWithAnimationEnabled.includes('InlineError');
|
|
46
|
+
const useModernizedAnimation = isAnimationEnabled && enableModernizedComponents;
|
|
47
|
+
const presence = useInlineErrorPresence(children, useModernizedAnimation);
|
|
48
|
+
const {
|
|
49
|
+
onAnimationEnd: onPresenceAnimationEnd
|
|
50
|
+
} = presence;
|
|
51
|
+
const handleAnimationEnd = useCallback(event => {
|
|
52
|
+
onPresenceAnimationEnd(event);
|
|
53
|
+
onAnimationEndProp?.(event);
|
|
54
|
+
}, [onPresenceAnimationEnd, onAnimationEndProp]);
|
|
55
|
+
/** Legacy path: identical DOM/CSS to pre-animation InlineError (no mount/unmount, `.empty` + `display: none`). */
|
|
56
|
+
if (!useModernizedAnimation) {
|
|
57
|
+
return jsx("span", {
|
|
58
|
+
...rest,
|
|
59
|
+
ref: forwardedRef,
|
|
60
|
+
className: clsx([className, styles.inlineError, {
|
|
61
|
+
[styles.empty]: !children
|
|
62
|
+
}]),
|
|
63
|
+
"data-modern": enableModernizedComponents ? 'true' : 'false',
|
|
64
|
+
onAnimationEnd: onAnimationEndProp,
|
|
65
|
+
children: renderErrorContent(children, enableModernizedComponents, styles.errorIcon)
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** `return null` when there is no error (and we are not in the exit phase): no visible slot for the
|
|
69
|
+
message — layout matches legacy (legacy hides the same slot via `.empty` + `display: none`). */
|
|
70
|
+
if (!presence.isMounted) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
20
73
|
return jsx("span", {
|
|
21
74
|
...rest,
|
|
22
75
|
ref: forwardedRef,
|
|
23
|
-
className: clsx([className, styles.inlineError,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"data-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
className: styles.errorIcon,
|
|
30
|
-
color: IconIconErrorOnLight,
|
|
31
|
-
height: Size4,
|
|
32
|
-
role: "presentation",
|
|
33
|
-
width: Size4
|
|
34
|
-
}), children]
|
|
35
|
-
}) : jsxs(Fragment, {
|
|
36
|
-
children: [jsx(AlertCircle, {
|
|
37
|
-
className: styles.errorIcon,
|
|
38
|
-
color: IconIconErrorOnLight,
|
|
39
|
-
height: bpSize050,
|
|
40
|
-
role: "presentation",
|
|
41
|
-
width: bpSize050
|
|
42
|
-
}), children]
|
|
43
|
-
}))
|
|
76
|
+
className: clsx([className, styles.inlineError, styles.animated]),
|
|
77
|
+
"data-bp-animated": "true",
|
|
78
|
+
"data-modern": "true",
|
|
79
|
+
"data-state": presence.presenceState,
|
|
80
|
+
onAnimationEnd: handleAnimationEnd,
|
|
81
|
+
children: renderErrorContent(presence.content, enableModernizedComponents, styles.errorIcon)
|
|
44
82
|
});
|
|
45
83
|
});
|
|
46
84
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import '../../index.css';
|
|
2
|
-
var styles = {"inlineError":"bp_inline_error_module_inlineError--
|
|
2
|
+
var styles = {"inlineError":"bp_inline_error_module_inlineError--e70b6","empty":"bp_inline_error_module_empty--e70b6","errorIcon":"bp_inline_error_module_errorIcon--e70b6","animated":"bp_inline_error_module_animated--e70b6"};
|
|
3
3
|
|
|
4
4
|
export { styles as default };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AnimationEvent } from 'react';
|
|
2
|
+
type PresenceState = 'open' | 'closed';
|
|
3
|
+
/**
|
|
4
|
+
* Exit-animation presence for `InlineError`. Pattern inspired by Radix UI `usePresence`:
|
|
5
|
+
* keep the node mounted while exit CSS runs, then unmount on `animationend`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useInlineErrorPresence(children: React.ReactNode, isAnimationEnabled: boolean): {
|
|
8
|
+
content: string | number | boolean | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | Iterable<import("react").ReactNode> | import("react").ReactPortal | null | undefined;
|
|
9
|
+
isMounted: boolean;
|
|
10
|
+
presenceState: PresenceState | undefined;
|
|
11
|
+
onAnimationEnd: (e: AnimationEvent<HTMLSpanElement>) => void;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import noop from 'lodash/noop';
|
|
2
|
+
import { useState, useCallback } from 'react';
|
|
3
|
+
import { useEnhancedEffect } from '../../../utils/useEnhancedEffect.js';
|
|
4
|
+
|
|
5
|
+
const animationEndNoop = noop;
|
|
6
|
+
/**
|
|
7
|
+
* Exit-animation presence for `InlineError`. Pattern inspired by Radix UI `usePresence`:
|
|
8
|
+
* keep the node mounted while exit CSS runs, then unmount on `animationend`.
|
|
9
|
+
*/
|
|
10
|
+
function useInlineErrorPresence(children, isAnimationEnabled) {
|
|
11
|
+
const hasContent = Boolean(children);
|
|
12
|
+
const [renderedChildren, setRenderedChildren] = useState(children);
|
|
13
|
+
const [presenceState, setPresenceState] = useState(() => isAnimationEnabled && hasContent ? 'open' : undefined);
|
|
14
|
+
const isMounted = isAnimationEnabled && (presenceState === 'open' || presenceState === 'closed');
|
|
15
|
+
const unmountAfterExit = useCallback(() => {
|
|
16
|
+
setPresenceState(undefined);
|
|
17
|
+
setRenderedChildren(undefined);
|
|
18
|
+
}, []);
|
|
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 (hasContent) {
|
|
30
|
+
setRenderedChildren(children);
|
|
31
|
+
setPresenceState('open');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
setPresenceState(current => current === 'open' ? 'closed' : current);
|
|
35
|
+
}, [children, isAnimationEnabled, hasContent]);
|
|
36
|
+
// When `isAnimationEnabled` is false (legacy path), return a no-op result: no mount/unmount, no exit animation.
|
|
37
|
+
// `inline-error.tsx` ignores `presence` and renders the legacy `<span>` + `.empty` instead.
|
|
38
|
+
if (!isAnimationEnabled) {
|
|
39
|
+
return {
|
|
40
|
+
content: children,
|
|
41
|
+
isMounted: false,
|
|
42
|
+
presenceState: undefined,
|
|
43
|
+
onAnimationEnd: animationEndNoop
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
content: renderedChildren,
|
|
48
|
+
isMounted,
|
|
49
|
+
presenceState,
|
|
50
|
+
onAnimationEnd
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { useInlineErrorPresence };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@box/blueprint-web",
|
|
3
|
-
"version": "15.
|
|
3
|
+
"version": "15.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"publishConfig": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@ariakit/react": "0.4.21",
|
|
49
49
|
"@ariakit/react-core": "0.4.21",
|
|
50
|
-
"@box/blueprint-web-assets": "^4.121.
|
|
50
|
+
"@box/blueprint-web-assets": "^4.121.6",
|
|
51
51
|
"@internationalized/date": "^3.12.0",
|
|
52
52
|
"@radix-ui/react-accordion": "1.1.2",
|
|
53
53
|
"@radix-ui/react-checkbox": "1.0.4",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"type-fest": "^3.2.0"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
-
"@box/storybook-utils": "^0.20.
|
|
80
|
+
"@box/storybook-utils": "^0.20.6",
|
|
81
81
|
"@figma/code-connect": "1.4.4",
|
|
82
82
|
"@types/react": "^18.0.0",
|
|
83
83
|
"@types/react-dom": "^18.0.0",
|