@box/blueprint-web 9.2.2 → 9.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/lib-esm/combobox/combobox.js +5 -7
- package/lib-esm/index.css +49 -22
- package/lib-esm/toolbar/index.d.ts +1 -1
- package/lib-esm/toolbar/toolbar-root.d.ts +2 -2
- package/lib-esm/toolbar/toolbar-root.js +27 -0
- package/lib-esm/toolbar/toolbar-scrollable-container.d.ts +7 -0
- package/lib-esm/toolbar/toolbar-scrollable-container.js +64 -0
- package/lib-esm/toolbar/toolbar.module.js +1 -1
- package/lib-esm/toolbar/types.d.ts +19 -1
- package/lib-esm/toolbar/utils/horizontal-scroll-context.d.ts +14 -0
- package/lib-esm/toolbar/utils/horizontal-scroll-context.js +107 -0
- package/package.json +4 -4
|
@@ -22,6 +22,10 @@ const getDisplayValueFromOptionValue = (optionValue, options, displayValue) => {
|
|
|
22
22
|
const option = getOptionFromValue(optionValue, options);
|
|
23
23
|
return displayValue && option ? displayValue(option) : optionValue;
|
|
24
24
|
};
|
|
25
|
+
const getDisplayAvatarFromOptionValue = (optionValue, options, displayAvatar) => {
|
|
26
|
+
const option = getOptionFromValue(optionValue, options);
|
|
27
|
+
return displayAvatar && option ? displayAvatar(option) : undefined;
|
|
28
|
+
};
|
|
25
29
|
const getTooltipValueFromOptionValue = (optionValue, options, displayTooltip) => {
|
|
26
30
|
const option = getOptionFromValue(optionValue, options);
|
|
27
31
|
return displayTooltip && option ? displayTooltip(option) : undefined;
|
|
@@ -155,12 +159,6 @@ const RootInner = ({
|
|
|
155
159
|
}
|
|
156
160
|
return visibleOptions.filter(option => !selectedValue.includes(getOptionValue(option)));
|
|
157
161
|
}, [filterFn, options, hideSelectedOptions, inputValue, selectedValue]);
|
|
158
|
-
const getDisplayAvatarFromOptionValue = useCallback(optionValue => {
|
|
159
|
-
const option = getOptionFromValue(optionValue, options);
|
|
160
|
-
return displayAvatar && option ? displayAvatar(option) : undefined;
|
|
161
|
-
},
|
|
162
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
163
|
-
[displayAvatar]);
|
|
164
162
|
const focusInput = useCallback(() => {
|
|
165
163
|
inputRef.current?.focus();
|
|
166
164
|
}, []);
|
|
@@ -265,7 +263,7 @@ const RootInner = ({
|
|
|
265
263
|
}),
|
|
266
264
|
children: [showChipsGroup && jsx(ChipsGroup, {
|
|
267
265
|
children: selectedValue.map(selected => jsx(InputChip, {
|
|
268
|
-
avatar: getDisplayAvatarFromOptionValue(selected),
|
|
266
|
+
avatar: getDisplayAvatarFromOptionValue(selected, options, displayAvatar),
|
|
269
267
|
label: getDisplayValueFromOptionValue(selected, options, displayValue),
|
|
270
268
|
onDelete: () => removeMultiSelectInputChip(selected),
|
|
271
269
|
tooltip: getTooltipValueFromOptionValue(selected, options, displayTooltip)
|
package/lib-esm/index.css
CHANGED
|
@@ -6797,30 +6797,57 @@ table.bp_inline_table_module_inlineTable--b023b tr:not(:last-child) td{
|
|
|
6797
6797
|
--z-index-card-tooltip:2147483647;
|
|
6798
6798
|
}
|
|
6799
6799
|
|
|
6800
|
-
.bp_toolbar_module_toolbarRoot--
|
|
6801
|
-
align-items:center;
|
|
6800
|
+
.bp_toolbar_module_toolbarRoot--eeb80{
|
|
6802
6801
|
background:var(--surface-surface);
|
|
6803
6802
|
border:var(--border-1) solid var(--border-card-border);
|
|
6804
6803
|
border-radius:var(--radius-4);
|
|
6805
6804
|
box-shadow:var(--dropshadow-3);
|
|
6805
|
+
padding:calc(var(--space-1) - var(--border-1));
|
|
6806
|
+
}
|
|
6807
|
+
|
|
6808
|
+
.bp_toolbar_module_scrollButtonWrapper--eeb80,.bp_toolbar_module_toolbarRoot--eeb80{
|
|
6809
|
+
align-items:center;
|
|
6806
6810
|
display:flex;
|
|
6807
6811
|
gap:var(--space-1);
|
|
6812
|
+
}
|
|
6813
|
+
.bp_toolbar_module_scrollButtonWrapper--eeb80.bp_toolbar_module_hidden--eeb80{
|
|
6814
|
+
display:none;
|
|
6815
|
+
}
|
|
6816
|
+
|
|
6817
|
+
.bp_toolbar_module_scrollableChildrenWrapper--eeb80{
|
|
6818
|
+
-ms-overflow-style:none;
|
|
6819
|
+
align-items:center;
|
|
6820
|
+
display:flex;
|
|
6821
|
+
flex-grow:1;
|
|
6822
|
+
gap:var(--space-1);
|
|
6823
|
+
overflow-x:auto;
|
|
6808
6824
|
padding:calc(var(--space-1) - var(--border-1));
|
|
6825
|
+
scroll-behavior:smooth;
|
|
6826
|
+
scroll-padding:calc(var(--space-1) - var(--border-1));
|
|
6827
|
+
scrollbar-width:none;
|
|
6828
|
+
white-space:nowrap;
|
|
6829
|
+
}
|
|
6830
|
+
.bp_toolbar_module_scrollableChildrenWrapper--eeb80::-webkit-scrollbar{
|
|
6831
|
+
display:none;
|
|
6832
|
+
}
|
|
6833
|
+
.bp_toolbar_module_scrollableChildrenWrapper--eeb80 > *{
|
|
6834
|
+
flex-grow:0;
|
|
6835
|
+
flex-shrink:0;
|
|
6809
6836
|
}
|
|
6810
6837
|
|
|
6811
|
-
.bp_toolbar_module_separator--
|
|
6838
|
+
.bp_toolbar_module_separator--eeb80{
|
|
6812
6839
|
background-color:var(--border-divider-border);
|
|
6813
6840
|
border-radius:var(--radius-2);
|
|
6814
6841
|
height:var(--size-6);
|
|
6815
6842
|
width:1px;
|
|
6816
6843
|
}
|
|
6817
6844
|
|
|
6818
|
-
.bp_toolbar_module_toggleGroup--
|
|
6845
|
+
.bp_toolbar_module_toggleGroup--eeb80{
|
|
6819
6846
|
display:flex;
|
|
6820
6847
|
gap:var(--space-1);
|
|
6821
6848
|
}
|
|
6822
6849
|
|
|
6823
|
-
.bp_toolbar_module_toolbarItem--
|
|
6850
|
+
.bp_toolbar_module_toolbarItem--eeb80{
|
|
6824
6851
|
--toolbar-item-hover-opacity:0.3;
|
|
6825
6852
|
align-items:center;
|
|
6826
6853
|
background:var(--toolbar-button-color, var(--surface-toggle-surface));
|
|
@@ -6838,45 +6865,45 @@ table.bp_inline_table_module_inlineTable--b023b tr:not(:last-child) td{
|
|
|
6838
6865
|
-webkit-user-select:none;
|
|
6839
6866
|
user-select:none;
|
|
6840
6867
|
}
|
|
6841
|
-
.bp_toolbar_module_toolbarItem--
|
|
6868
|
+
.bp_toolbar_module_toolbarItem--eeb80[data-disabled]{
|
|
6842
6869
|
background:var(--surface-toggle-surface);
|
|
6843
6870
|
opacity:var(--toolbar-item-hover-opacity);
|
|
6844
6871
|
pointer-events:none;
|
|
6845
6872
|
}
|
|
6846
|
-
.bp_toolbar_module_toolbarItem--
|
|
6873
|
+
.bp_toolbar_module_toolbarItem--eeb80:not([data-disabled]):focus-visible{
|
|
6847
6874
|
box-shadow:0 0 0 var(--border-1, 1px) var(--background-background), 0 0 0 var(--border-3) #2486fc;
|
|
6848
6875
|
}
|
|
6849
|
-
.bp_toolbar_module_toolbarItem--
|
|
6876
|
+
.bp_toolbar_module_toolbarItem--eeb80:not([data-disabled]):hover{
|
|
6850
6877
|
background:var(--toolbar-button-color, var(--surface-toggle-surface-hover));
|
|
6851
6878
|
border:var(--border-1) solid var(--toolbar-button-color, var(--surface-toggle-surface-hover));
|
|
6852
6879
|
}
|
|
6853
6880
|
|
|
6854
|
-
.bp_toolbar_module_toolbarToggle--
|
|
6881
|
+
.bp_toolbar_module_toolbarToggle--eeb80[data-state=on]{
|
|
6855
6882
|
background:var(--surface-toggle-surface-pressed);
|
|
6856
6883
|
}
|
|
6857
|
-
.bp_toolbar_module_toolbarToggle--
|
|
6884
|
+
.bp_toolbar_module_toolbarToggle--eeb80[data-state=on] svg *{
|
|
6858
6885
|
fill:var(--icon-icon-on-dark);
|
|
6859
6886
|
}
|
|
6860
|
-
.bp_toolbar_module_toolbarToggle--
|
|
6887
|
+
.bp_toolbar_module_toolbarToggle--eeb80[data-state=on]:not([data-disabled]):hover{
|
|
6861
6888
|
background:var(--surface-toggle-surface-on-hover);
|
|
6862
6889
|
border:var(--border-1) solid var(--surface-toggle-surface-on-hover);
|
|
6863
6890
|
}
|
|
6864
6891
|
|
|
6865
|
-
.bp_toolbar_module_dropdownIndicator--
|
|
6892
|
+
.bp_toolbar_module_dropdownIndicator--eeb80.bp_toolbar_module_invertCaret--eeb80{
|
|
6866
6893
|
transform:rotate(.5turn);
|
|
6867
6894
|
}
|
|
6868
6895
|
|
|
6869
|
-
.bp_toolbar_module_triggerButtonSelectedWithColor--
|
|
6896
|
+
.bp_toolbar_module_triggerButtonSelectedWithColor--eeb80{
|
|
6870
6897
|
--trigger-button-hover-opacity:0.7;
|
|
6871
6898
|
}
|
|
6872
|
-
.bp_toolbar_module_triggerButtonSelectedWithColor--
|
|
6899
|
+
.bp_toolbar_module_triggerButtonSelectedWithColor--eeb80[data-state=on] .bp_toolbar_module_dropdownIndicator--eeb80 path{
|
|
6873
6900
|
fill:var(--icon-icon-on-light);
|
|
6874
6901
|
}
|
|
6875
|
-
.bp_toolbar_module_triggerButtonSelectedWithColor--
|
|
6902
|
+
.bp_toolbar_module_triggerButtonSelectedWithColor--eeb80[data-state=on]:hover{
|
|
6876
6903
|
opacity:var(--trigger-button-hover-opacity);
|
|
6877
6904
|
}
|
|
6878
6905
|
|
|
6879
|
-
.bp_toolbar_module_toolbarIcon--
|
|
6906
|
+
.bp_toolbar_module_toolbarIcon--eeb80{
|
|
6880
6907
|
align-items:center;
|
|
6881
6908
|
display:flex;
|
|
6882
6909
|
height:var(--size-5);
|
|
@@ -6884,7 +6911,7 @@ table.bp_inline_table_module_inlineTable--b023b tr:not(:last-child) td{
|
|
|
6884
6911
|
width:var(--size-5);
|
|
6885
6912
|
}
|
|
6886
6913
|
|
|
6887
|
-
.bp_toolbar_module_toolbarTextToggleItem--
|
|
6914
|
+
.bp_toolbar_module_toolbarTextToggleItem--eeb80{
|
|
6888
6915
|
border:var(--border-1) solid var(--border-toggletext-border-off);
|
|
6889
6916
|
color:var(--text-text-on-light);
|
|
6890
6917
|
font-family:Lato, -apple-system, BlinkMacSystemFont, "San Francisco", "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
|
|
@@ -6898,24 +6925,24 @@ table.bp_inline_table_module_inlineTable--b023b tr:not(:last-child) td{
|
|
|
6898
6925
|
text-transform:none;
|
|
6899
6926
|
white-space:nowrap;
|
|
6900
6927
|
}
|
|
6901
|
-
.bp_toolbar_module_toolbarTextToggleItem--
|
|
6928
|
+
.bp_toolbar_module_toolbarTextToggleItem--eeb80:hover{
|
|
6902
6929
|
background:var(--surface-toggle-surface-off-hover);
|
|
6903
6930
|
border-color:var(--border-toggletext-border-off-hover);
|
|
6904
6931
|
}
|
|
6905
|
-
.bp_toolbar_module_toolbarTextToggleItem--
|
|
6932
|
+
.bp_toolbar_module_toolbarTextToggleItem--eeb80:active{
|
|
6906
6933
|
background:var(--surface-toggle-surface-off-pressed);
|
|
6907
6934
|
border-color:var(--border-toggletext-border-off-pressed);
|
|
6908
6935
|
}
|
|
6909
|
-
.bp_toolbar_module_toolbarTextToggleItem--
|
|
6936
|
+
.bp_toolbar_module_toolbarTextToggleItem--eeb80[aria-checked=true]{
|
|
6910
6937
|
background:var(--surface-toggletext-surface-on);
|
|
6911
6938
|
border:var(--border-1) solid var(--border-toggletext-border-on);
|
|
6912
6939
|
color:var(--text-toggletext-text);
|
|
6913
6940
|
}
|
|
6914
|
-
.bp_toolbar_module_toolbarTextToggleItem--
|
|
6941
|
+
.bp_toolbar_module_toolbarTextToggleItem--eeb80[aria-checked=true]:hover{
|
|
6915
6942
|
background:var(--surface-toggletext-surface-on-hover);
|
|
6916
6943
|
border-color:var(--border-toggletext-border-on-hover);
|
|
6917
6944
|
}
|
|
6918
|
-
.bp_toolbar_module_toolbarTextToggleItem--
|
|
6945
|
+
.bp_toolbar_module_toolbarTextToggleItem--eeb80[aria-checked=true]:active{
|
|
6919
6946
|
background:var(--surface-toggletext-surface-on-pressed);
|
|
6920
6947
|
border-color:var(--border-toggletext-border-on-pressed);
|
|
6921
6948
|
}
|
|
@@ -23,7 +23,7 @@ export declare const Toolbar: {
|
|
|
23
23
|
/**
|
|
24
24
|
* Contains all parts of the toolbar component.
|
|
25
25
|
*/
|
|
26
|
-
Root: import("react").ForwardRefExoticComponent<import("
|
|
26
|
+
Root: import("react").ForwardRefExoticComponent<import("./types").ToolbarProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
27
27
|
/**
|
|
28
28
|
* Two-state button that can be toggled on or off.
|
|
29
29
|
*/
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
export declare const ToolbarRoot: import("react").ForwardRefExoticComponent<
|
|
1
|
+
import { type ToolbarProps } from './types';
|
|
2
|
+
export declare const ToolbarRoot: import("react").ForwardRefExoticComponent<ToolbarProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -2,14 +2,41 @@ import { jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import * as ToolbarPrimitive from '@radix-ui/react-toolbar';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
4
|
import { forwardRef } from 'react';
|
|
5
|
+
import { ToolbarScrollableContainer } from './toolbar-scrollable-container.js';
|
|
5
6
|
import styles from './toolbar.module.js';
|
|
7
|
+
import { HorizontalScrollProvider } from './utils/horizontal-scroll-context.js';
|
|
6
8
|
|
|
7
9
|
const ToolbarRoot = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
8
10
|
const {
|
|
9
11
|
children,
|
|
10
12
|
className,
|
|
13
|
+
scrollable,
|
|
11
14
|
...rest
|
|
12
15
|
} = props;
|
|
16
|
+
if (scrollable) {
|
|
17
|
+
const {
|
|
18
|
+
ariaLabelNextButton,
|
|
19
|
+
ariaLabelPrevButton,
|
|
20
|
+
maxWidth,
|
|
21
|
+
scrollStep = 50
|
|
22
|
+
} = props;
|
|
23
|
+
return jsx(ToolbarPrimitive.Root, {
|
|
24
|
+
...rest,
|
|
25
|
+
ref: forwardedRef,
|
|
26
|
+
className: clsx(styles.toolbarRoot, className),
|
|
27
|
+
style: {
|
|
28
|
+
maxWidth
|
|
29
|
+
},
|
|
30
|
+
children: jsx(HorizontalScrollProvider, {
|
|
31
|
+
scrollStep: scrollStep,
|
|
32
|
+
children: jsx(ToolbarScrollableContainer, {
|
|
33
|
+
ariaLabelNextButton: ariaLabelNextButton,
|
|
34
|
+
ariaLabelPrevButton: ariaLabelPrevButton,
|
|
35
|
+
children: children
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
}
|
|
13
40
|
return jsx(ToolbarPrimitive.Root, {
|
|
14
41
|
...rest,
|
|
15
42
|
ref: forwardedRef,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
interface ScrollableContainerProps {
|
|
2
|
+
children: React.ReactNode;
|
|
3
|
+
ariaLabelPrevButton: string;
|
|
4
|
+
ariaLabelNextButton: string;
|
|
5
|
+
}
|
|
6
|
+
export declare const ToolbarScrollableContainer: import("react").ForwardRefExoticComponent<ScrollableContainerProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { PointerChevronLeft, PointerChevronRight } from '@box/blueprint-web-assets/icons/Fill';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { forwardRef } from 'react';
|
|
5
|
+
import { IconButton } from '../primitives/icon-button/icon-button.js';
|
|
6
|
+
import { useRefWithEffect as useRefWithEffectMemoized } from '../utils/useRefWithEffect.js';
|
|
7
|
+
import { ToolbarSeparator } from './toolbar-separator.js';
|
|
8
|
+
import styles from './toolbar.module.js';
|
|
9
|
+
import { useScrollContext } from './utils/horizontal-scroll-context.js';
|
|
10
|
+
|
|
11
|
+
const ScrollButton = ({
|
|
12
|
+
hidden,
|
|
13
|
+
ariaLabel,
|
|
14
|
+
onClick,
|
|
15
|
+
icon: Icon,
|
|
16
|
+
separatorPosition
|
|
17
|
+
}) => jsxs("div", {
|
|
18
|
+
className: clsx(styles.scrollButtonWrapper, {
|
|
19
|
+
[styles.hidden]: hidden
|
|
20
|
+
}),
|
|
21
|
+
children: [separatorPosition === 'before' && jsx(ToolbarSeparator, {}), jsx(IconButton, {
|
|
22
|
+
"aria-label": ariaLabel,
|
|
23
|
+
icon: Icon,
|
|
24
|
+
onClick: onClick
|
|
25
|
+
}), separatorPosition === 'after' && jsx(ToolbarSeparator, {})]
|
|
26
|
+
});
|
|
27
|
+
const ToolbarScrollableContainer = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
28
|
+
const {
|
|
29
|
+
children,
|
|
30
|
+
ariaLabelPrevButton,
|
|
31
|
+
ariaLabelNextButton
|
|
32
|
+
} = props;
|
|
33
|
+
const {
|
|
34
|
+
scrollPosition,
|
|
35
|
+
scrollLeft,
|
|
36
|
+
scrollRight,
|
|
37
|
+
onAttach
|
|
38
|
+
} = useScrollContext();
|
|
39
|
+
const onMountRef = useRefWithEffectMemoized(forwardedRef, node => {
|
|
40
|
+
onAttach(node);
|
|
41
|
+
});
|
|
42
|
+
return jsxs(Fragment, {
|
|
43
|
+
children: [jsx(ScrollButton, {
|
|
44
|
+
ariaLabel: ariaLabelPrevButton,
|
|
45
|
+
hidden: scrollPosition === 'start' || scrollPosition === 'none',
|
|
46
|
+
icon: PointerChevronLeft,
|
|
47
|
+
onClick: scrollLeft,
|
|
48
|
+
separatorPosition: "after"
|
|
49
|
+
}), jsx("div", {
|
|
50
|
+
ref: onMountRef,
|
|
51
|
+
className: clsx(styles.scrollableChildrenWrapper),
|
|
52
|
+
"data-testid": "toolbar-scroll-container",
|
|
53
|
+
children: children
|
|
54
|
+
}), jsx(ScrollButton, {
|
|
55
|
+
ariaLabel: ariaLabelNextButton,
|
|
56
|
+
hidden: scrollPosition === 'end' || scrollPosition === 'none',
|
|
57
|
+
icon: PointerChevronRight,
|
|
58
|
+
onClick: scrollRight,
|
|
59
|
+
separatorPosition: "before"
|
|
60
|
+
})]
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export { ToolbarScrollableContainer };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import '../index.css';
|
|
2
|
-
var styles = {"toolbarRoot":"bp_toolbar_module_toolbarRoot--
|
|
2
|
+
var styles = {"toolbarRoot":"bp_toolbar_module_toolbarRoot--eeb80","scrollButtonWrapper":"bp_toolbar_module_scrollButtonWrapper--eeb80","hidden":"bp_toolbar_module_hidden--eeb80","scrollableChildrenWrapper":"bp_toolbar_module_scrollableChildrenWrapper--eeb80","separator":"bp_toolbar_module_separator--eeb80","toggleGroup":"bp_toolbar_module_toggleGroup--eeb80","toolbarItem":"bp_toolbar_module_toolbarItem--eeb80","toolbarToggle":"bp_toolbar_module_toolbarToggle--eeb80","dropdownIndicator":"bp_toolbar_module_dropdownIndicator--eeb80","invertCaret":"bp_toolbar_module_invertCaret--eeb80","triggerButtonSelectedWithColor":"bp_toolbar_module_triggerButtonSelectedWithColor--eeb80","toolbarIcon":"bp_toolbar_module_toolbarIcon--eeb80","toolbarTextToggleItem":"bp_toolbar_module_toolbarTextToggleItem--eeb80"};
|
|
3
3
|
|
|
4
4
|
export { styles as default };
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { type ToolbarButtonProps as ToolbarPrimitiveButtonProps, type ToolbarProps as ToolbarPrimitiveProps, type ToolbarSeparatorProps as ToolbarPrimitiveSeparatorProps, type ToolbarToggleItemProps as ToolbarPrimitiveToggleItemProps, type ToggleGroupProps as ToolbarToggleGroupPrimitiveProps } from '@radix-ui/react-toolbar';
|
|
2
2
|
import { type CSSProperties, type FunctionComponent, type HTMLAttributes, type PropsWithChildren, type SVGProps } from 'react';
|
|
3
|
-
|
|
3
|
+
interface ToolbarWithoutScrollProps {
|
|
4
|
+
scrollable?: false;
|
|
5
|
+
}
|
|
6
|
+
interface ToolbarWithScrollProps {
|
|
7
|
+
/** Prop to enable horizontal scrolling with control buttons. */
|
|
8
|
+
scrollable: true;
|
|
9
|
+
/** The maximum width of the toolbar. When this width is reached, horizontal scrolling with control buttons will be enabled. */
|
|
10
|
+
maxWidth: string;
|
|
11
|
+
/** Aria label for the previous control button. */
|
|
12
|
+
ariaLabelPrevButton: string;
|
|
13
|
+
/** Aria label for the next control button. */
|
|
14
|
+
ariaLabelNextButton: string;
|
|
15
|
+
/** Prop to configure scroll distance when control button is clicked.
|
|
16
|
+
* @default '50'
|
|
17
|
+
*/
|
|
18
|
+
scrollStep?: number;
|
|
19
|
+
}
|
|
20
|
+
type ScrollProps = ToolbarWithoutScrollProps | ToolbarWithScrollProps;
|
|
21
|
+
export type ToolbarProps = ToolbarPrimitiveProps & ScrollProps;
|
|
4
22
|
export type ToolbarSeparatorProps = ToolbarPrimitiveSeparatorProps;
|
|
5
23
|
export type ToolbarToggleGroupProps = ToolbarToggleGroupPrimitiveProps;
|
|
6
24
|
interface ToolbarButtonCSS extends CSSProperties {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface HorizontalScrollContextType {
|
|
3
|
+
scrollPosition: 'start' | 'end' | 'intermediate' | 'none';
|
|
4
|
+
scrollLeft: () => void;
|
|
5
|
+
scrollRight: () => void;
|
|
6
|
+
onAttach: (node: HTMLDivElement | null) => void;
|
|
7
|
+
}
|
|
8
|
+
interface HorizontalScrollProviderProps {
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
scrollStep?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const HorizontalScrollProvider: ({ children, scrollStep, }: HorizontalScrollProviderProps) => React.ReactElement;
|
|
13
|
+
export declare const useScrollContext: () => HorizontalScrollContextType;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import noop from 'lodash/noop';
|
|
3
|
+
import { useState, useRef, useCallback, useEffect, useMemo, createContext, useContext } from 'react';
|
|
4
|
+
|
|
5
|
+
const HorizontalScrollContext = /*#__PURE__*/createContext({
|
|
6
|
+
scrollPosition: 'none',
|
|
7
|
+
scrollLeft: noop,
|
|
8
|
+
scrollRight: noop,
|
|
9
|
+
onAttach: noop
|
|
10
|
+
});
|
|
11
|
+
const HorizontalScrollProvider = ({
|
|
12
|
+
children,
|
|
13
|
+
scrollStep = 50
|
|
14
|
+
}) => {
|
|
15
|
+
const [scrollPosition, setScrollPosition] = useState('none');
|
|
16
|
+
const [scrollableElement, setScrollableElement] = useState(null);
|
|
17
|
+
const scrollAnimationFrame = useRef(null);
|
|
18
|
+
const updateScrollState = useCallback(() => {
|
|
19
|
+
if (scrollableElement) {
|
|
20
|
+
const {
|
|
21
|
+
scrollLeft,
|
|
22
|
+
scrollWidth,
|
|
23
|
+
clientWidth
|
|
24
|
+
} = scrollableElement;
|
|
25
|
+
if (scrollWidth <= clientWidth) {
|
|
26
|
+
setScrollPosition('none');
|
|
27
|
+
} else if (scrollLeft === 0) {
|
|
28
|
+
setScrollPosition('start');
|
|
29
|
+
} else if (scrollLeft + clientWidth >= scrollWidth) {
|
|
30
|
+
setScrollPosition('end');
|
|
31
|
+
} else {
|
|
32
|
+
setScrollPosition('intermediate');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}, [scrollableElement]);
|
|
36
|
+
const handleScroll = useCallback(() => {
|
|
37
|
+
if (scrollAnimationFrame.current !== null) {
|
|
38
|
+
cancelAnimationFrame(scrollAnimationFrame.current);
|
|
39
|
+
}
|
|
40
|
+
scrollAnimationFrame.current = requestAnimationFrame(updateScrollState);
|
|
41
|
+
}, [updateScrollState]);
|
|
42
|
+
const scrollLeft = useCallback(() => {
|
|
43
|
+
if (scrollableElement) {
|
|
44
|
+
scrollableElement.scrollBy({
|
|
45
|
+
left: -scrollStep,
|
|
46
|
+
behavior: 'smooth'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}, [scrollStep, scrollableElement]);
|
|
50
|
+
const scrollRight = useCallback(() => {
|
|
51
|
+
if (scrollableElement) {
|
|
52
|
+
scrollableElement.scrollBy({
|
|
53
|
+
left: scrollStep,
|
|
54
|
+
behavior: 'smooth'
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}, [scrollStep, scrollableElement]);
|
|
58
|
+
const onAttach = useCallback(node => {
|
|
59
|
+
setScrollableElement(node);
|
|
60
|
+
if (node) {
|
|
61
|
+
updateScrollState();
|
|
62
|
+
}
|
|
63
|
+
}, [updateScrollState]);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!scrollableElement) {
|
|
66
|
+
return noop;
|
|
67
|
+
}
|
|
68
|
+
const handleFocusIn = event => {
|
|
69
|
+
const target = event.target;
|
|
70
|
+
if (scrollableElement.contains(target)) {
|
|
71
|
+
target.scrollIntoView({
|
|
72
|
+
behavior: 'smooth',
|
|
73
|
+
inline: 'nearest'
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
scrollableElement.addEventListener('focusin', handleFocusIn);
|
|
78
|
+
scrollableElement.addEventListener('scroll', handleScroll);
|
|
79
|
+
updateScrollState();
|
|
80
|
+
return () => {
|
|
81
|
+
scrollableElement.removeEventListener('focusin', handleFocusIn);
|
|
82
|
+
scrollableElement.removeEventListener('scroll', handleScroll);
|
|
83
|
+
if (scrollAnimationFrame.current !== null) {
|
|
84
|
+
cancelAnimationFrame(scrollAnimationFrame.current);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}, [scrollableElement, handleScroll, updateScrollState]);
|
|
88
|
+
const contextValue = useMemo(() => ({
|
|
89
|
+
scrollPosition,
|
|
90
|
+
scrollLeft,
|
|
91
|
+
scrollRight,
|
|
92
|
+
onAttach
|
|
93
|
+
}), [scrollPosition, scrollLeft, scrollRight, onAttach]);
|
|
94
|
+
return jsx(HorizontalScrollContext.Provider, {
|
|
95
|
+
value: contextValue,
|
|
96
|
+
children: children
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
const useScrollContext = () => {
|
|
100
|
+
const context = useContext(HorizontalScrollContext);
|
|
101
|
+
if (!context) {
|
|
102
|
+
throw new Error('useScrollContext must be used within a HorizontalScrollProvider');
|
|
103
|
+
}
|
|
104
|
+
return context;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export { HorizontalScrollProvider, useScrollContext };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@box/blueprint-web",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"publishConfig": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@ariakit/react": "0.4.14",
|
|
26
26
|
"@ariakit/react-core": "0.4.14",
|
|
27
|
-
"@box/blueprint-web-assets": "^4.30.
|
|
27
|
+
"@box/blueprint-web-assets": "^4.30.1",
|
|
28
28
|
"@internationalized/date": "^3.5.4",
|
|
29
29
|
"@radix-ui/react-accordion": "1.1.2",
|
|
30
30
|
"@radix-ui/react-checkbox": "1.0.4",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"type-fest": "^3.2.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@box/storybook-utils": "^0.
|
|
58
|
+
"@box/storybook-utils": "^0.8.0",
|
|
59
59
|
"@types/react": "^18.0.0",
|
|
60
60
|
"@types/react-dom": "^18.0.0",
|
|
61
61
|
"react": "^18.3.0",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"react-stately": "^3.31.1",
|
|
64
64
|
"tsx": "^4.16.5"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "d9e9463682eab8c3cf54e738849001a2095220e8",
|
|
67
67
|
"module": "lib-esm/index.js",
|
|
68
68
|
"main": "lib-esm/index.js",
|
|
69
69
|
"exports": {
|