@box/blueprint-web 9.2.2 → 9.3.1
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 +48 -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 +44 -0
- package/lib-esm/toolbar/utils/use-scroll-state.d.ts +5 -0
- package/lib-esm/toolbar/utils/use-scroll-state.js +84 -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,56 @@ 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--b6b06{
|
|
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--b6b06,.bp_toolbar_module_toolbarRoot--b6b06{
|
|
6809
|
+
align-items:center;
|
|
6810
|
+
display:flex;
|
|
6811
|
+
gap:var(--space-1);
|
|
6812
|
+
}
|
|
6813
|
+
.bp_toolbar_module_scrollButtonWrapper--b6b06.bp_toolbar_module_hidden--b6b06{
|
|
6814
|
+
display:none;
|
|
6815
|
+
}
|
|
6816
|
+
|
|
6817
|
+
.bp_toolbar_module_scrollableChildrenWrapper--b6b06{
|
|
6818
|
+
-ms-overflow-style:none;
|
|
6819
|
+
align-items:center;
|
|
6806
6820
|
display:flex;
|
|
6821
|
+
flex-grow:1;
|
|
6807
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--b6b06::-webkit-scrollbar{
|
|
6831
|
+
display:none;
|
|
6832
|
+
}
|
|
6833
|
+
.bp_toolbar_module_scrollableChildrenWrapper--b6b06 > *{
|
|
6834
|
+
flex:none;
|
|
6809
6835
|
}
|
|
6810
6836
|
|
|
6811
|
-
.bp_toolbar_module_separator--
|
|
6837
|
+
.bp_toolbar_module_separator--b6b06{
|
|
6812
6838
|
background-color:var(--border-divider-border);
|
|
6813
6839
|
border-radius:var(--radius-2);
|
|
6814
6840
|
height:var(--size-6);
|
|
6815
6841
|
width:1px;
|
|
6816
6842
|
}
|
|
6817
6843
|
|
|
6818
|
-
.bp_toolbar_module_toggleGroup--
|
|
6844
|
+
.bp_toolbar_module_toggleGroup--b6b06{
|
|
6819
6845
|
display:flex;
|
|
6820
6846
|
gap:var(--space-1);
|
|
6821
6847
|
}
|
|
6822
6848
|
|
|
6823
|
-
.bp_toolbar_module_toolbarItem--
|
|
6849
|
+
.bp_toolbar_module_toolbarItem--b6b06{
|
|
6824
6850
|
--toolbar-item-hover-opacity:0.3;
|
|
6825
6851
|
align-items:center;
|
|
6826
6852
|
background:var(--toolbar-button-color, var(--surface-toggle-surface));
|
|
@@ -6838,45 +6864,45 @@ table.bp_inline_table_module_inlineTable--b023b tr:not(:last-child) td{
|
|
|
6838
6864
|
-webkit-user-select:none;
|
|
6839
6865
|
user-select:none;
|
|
6840
6866
|
}
|
|
6841
|
-
.bp_toolbar_module_toolbarItem--
|
|
6867
|
+
.bp_toolbar_module_toolbarItem--b6b06[data-disabled]{
|
|
6842
6868
|
background:var(--surface-toggle-surface);
|
|
6843
6869
|
opacity:var(--toolbar-item-hover-opacity);
|
|
6844
6870
|
pointer-events:none;
|
|
6845
6871
|
}
|
|
6846
|
-
.bp_toolbar_module_toolbarItem--
|
|
6872
|
+
.bp_toolbar_module_toolbarItem--b6b06:not([data-disabled]):focus-visible{
|
|
6847
6873
|
box-shadow:0 0 0 var(--border-1, 1px) var(--background-background), 0 0 0 var(--border-3) #2486fc;
|
|
6848
6874
|
}
|
|
6849
|
-
.bp_toolbar_module_toolbarItem--
|
|
6875
|
+
.bp_toolbar_module_toolbarItem--b6b06:not([data-disabled]):hover{
|
|
6850
6876
|
background:var(--toolbar-button-color, var(--surface-toggle-surface-hover));
|
|
6851
6877
|
border:var(--border-1) solid var(--toolbar-button-color, var(--surface-toggle-surface-hover));
|
|
6852
6878
|
}
|
|
6853
6879
|
|
|
6854
|
-
.bp_toolbar_module_toolbarToggle--
|
|
6880
|
+
.bp_toolbar_module_toolbarToggle--b6b06[data-state=on]{
|
|
6855
6881
|
background:var(--surface-toggle-surface-pressed);
|
|
6856
6882
|
}
|
|
6857
|
-
.bp_toolbar_module_toolbarToggle--
|
|
6883
|
+
.bp_toolbar_module_toolbarToggle--b6b06[data-state=on] svg *{
|
|
6858
6884
|
fill:var(--icon-icon-on-dark);
|
|
6859
6885
|
}
|
|
6860
|
-
.bp_toolbar_module_toolbarToggle--
|
|
6886
|
+
.bp_toolbar_module_toolbarToggle--b6b06[data-state=on]:not([data-disabled]):hover{
|
|
6861
6887
|
background:var(--surface-toggle-surface-on-hover);
|
|
6862
6888
|
border:var(--border-1) solid var(--surface-toggle-surface-on-hover);
|
|
6863
6889
|
}
|
|
6864
6890
|
|
|
6865
|
-
.bp_toolbar_module_dropdownIndicator--
|
|
6891
|
+
.bp_toolbar_module_dropdownIndicator--b6b06.bp_toolbar_module_invertCaret--b6b06{
|
|
6866
6892
|
transform:rotate(.5turn);
|
|
6867
6893
|
}
|
|
6868
6894
|
|
|
6869
|
-
.bp_toolbar_module_triggerButtonSelectedWithColor--
|
|
6895
|
+
.bp_toolbar_module_triggerButtonSelectedWithColor--b6b06{
|
|
6870
6896
|
--trigger-button-hover-opacity:0.7;
|
|
6871
6897
|
}
|
|
6872
|
-
.bp_toolbar_module_triggerButtonSelectedWithColor--
|
|
6898
|
+
.bp_toolbar_module_triggerButtonSelectedWithColor--b6b06[data-state=on] .bp_toolbar_module_dropdownIndicator--b6b06 path{
|
|
6873
6899
|
fill:var(--icon-icon-on-light);
|
|
6874
6900
|
}
|
|
6875
|
-
.bp_toolbar_module_triggerButtonSelectedWithColor--
|
|
6901
|
+
.bp_toolbar_module_triggerButtonSelectedWithColor--b6b06[data-state=on]:hover{
|
|
6876
6902
|
opacity:var(--trigger-button-hover-opacity);
|
|
6877
6903
|
}
|
|
6878
6904
|
|
|
6879
|
-
.bp_toolbar_module_toolbarIcon--
|
|
6905
|
+
.bp_toolbar_module_toolbarIcon--b6b06{
|
|
6880
6906
|
align-items:center;
|
|
6881
6907
|
display:flex;
|
|
6882
6908
|
height:var(--size-5);
|
|
@@ -6884,7 +6910,7 @@ table.bp_inline_table_module_inlineTable--b023b tr:not(:last-child) td{
|
|
|
6884
6910
|
width:var(--size-5);
|
|
6885
6911
|
}
|
|
6886
6912
|
|
|
6887
|
-
.bp_toolbar_module_toolbarTextToggleItem--
|
|
6913
|
+
.bp_toolbar_module_toolbarTextToggleItem--b6b06{
|
|
6888
6914
|
border:var(--border-1) solid var(--border-toggletext-border-off);
|
|
6889
6915
|
color:var(--text-text-on-light);
|
|
6890
6916
|
font-family:Lato, -apple-system, BlinkMacSystemFont, "San Francisco", "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
|
|
@@ -6898,24 +6924,24 @@ table.bp_inline_table_module_inlineTable--b023b tr:not(:last-child) td{
|
|
|
6898
6924
|
text-transform:none;
|
|
6899
6925
|
white-space:nowrap;
|
|
6900
6926
|
}
|
|
6901
|
-
.bp_toolbar_module_toolbarTextToggleItem--
|
|
6927
|
+
.bp_toolbar_module_toolbarTextToggleItem--b6b06:hover{
|
|
6902
6928
|
background:var(--surface-toggle-surface-off-hover);
|
|
6903
6929
|
border-color:var(--border-toggletext-border-off-hover);
|
|
6904
6930
|
}
|
|
6905
|
-
.bp_toolbar_module_toolbarTextToggleItem--
|
|
6931
|
+
.bp_toolbar_module_toolbarTextToggleItem--b6b06:active{
|
|
6906
6932
|
background:var(--surface-toggle-surface-off-pressed);
|
|
6907
6933
|
border-color:var(--border-toggletext-border-off-pressed);
|
|
6908
6934
|
}
|
|
6909
|
-
.bp_toolbar_module_toolbarTextToggleItem--
|
|
6935
|
+
.bp_toolbar_module_toolbarTextToggleItem--b6b06[aria-checked=true]{
|
|
6910
6936
|
background:var(--surface-toggletext-surface-on);
|
|
6911
6937
|
border:var(--border-1) solid var(--border-toggletext-border-on);
|
|
6912
6938
|
color:var(--text-toggletext-text);
|
|
6913
6939
|
}
|
|
6914
|
-
.bp_toolbar_module_toolbarTextToggleItem--
|
|
6940
|
+
.bp_toolbar_module_toolbarTextToggleItem--b6b06[aria-checked=true]:hover{
|
|
6915
6941
|
background:var(--surface-toggletext-surface-on-hover);
|
|
6916
6942
|
border-color:var(--border-toggletext-border-on-hover);
|
|
6917
6943
|
}
|
|
6918
|
-
.bp_toolbar_module_toolbarTextToggleItem--
|
|
6944
|
+
.bp_toolbar_module_toolbarTextToggleItem--b6b06[aria-checked=true]:active{
|
|
6919
6945
|
background:var(--surface-toggletext-surface-on-pressed);
|
|
6920
6946
|
border-color:var(--border-toggletext-border-on-pressed);
|
|
6921
6947
|
}
|
|
@@ -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--b6b06","scrollButtonWrapper":"bp_toolbar_module_scrollButtonWrapper--b6b06","hidden":"bp_toolbar_module_hidden--b6b06","scrollableChildrenWrapper":"bp_toolbar_module_scrollableChildrenWrapper--b6b06","separator":"bp_toolbar_module_separator--b6b06","toggleGroup":"bp_toolbar_module_toggleGroup--b6b06","toolbarItem":"bp_toolbar_module_toolbarItem--b6b06","toolbarToggle":"bp_toolbar_module_toolbarToggle--b6b06","dropdownIndicator":"bp_toolbar_module_dropdownIndicator--b6b06","invertCaret":"bp_toolbar_module_invertCaret--b6b06","triggerButtonSelectedWithColor":"bp_toolbar_module_triggerButtonSelectedWithColor--b6b06","toolbarIcon":"bp_toolbar_module_toolbarIcon--b6b06","toolbarTextToggleItem":"bp_toolbar_module_toolbarTextToggleItem--b6b06"};
|
|
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,44 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import noop from 'lodash/noop';
|
|
3
|
+
import { useState, useCallback, useMemo, createContext, useContext } from 'react';
|
|
4
|
+
import { useScrollState } from './use-scroll-state.js';
|
|
5
|
+
|
|
6
|
+
const HorizontalScrollContext = /*#__PURE__*/createContext({
|
|
7
|
+
scrollPosition: 'none',
|
|
8
|
+
scrollLeft: noop,
|
|
9
|
+
scrollRight: noop,
|
|
10
|
+
onAttach: noop
|
|
11
|
+
});
|
|
12
|
+
const HorizontalScrollProvider = ({
|
|
13
|
+
children,
|
|
14
|
+
scrollStep = 50
|
|
15
|
+
}) => {
|
|
16
|
+
const [scrollableElement, setScrollableElement] = useState(null);
|
|
17
|
+
const {
|
|
18
|
+
scrollPosition,
|
|
19
|
+
scrollLeft,
|
|
20
|
+
scrollRight
|
|
21
|
+
} = useScrollState(scrollableElement, scrollStep);
|
|
22
|
+
const onAttach = useCallback(node => {
|
|
23
|
+
setScrollableElement(node);
|
|
24
|
+
}, []);
|
|
25
|
+
const contextValue = useMemo(() => ({
|
|
26
|
+
scrollPosition,
|
|
27
|
+
scrollLeft,
|
|
28
|
+
scrollRight,
|
|
29
|
+
onAttach
|
|
30
|
+
}), [scrollPosition, scrollLeft, scrollRight, onAttach]);
|
|
31
|
+
return jsx(HorizontalScrollContext.Provider, {
|
|
32
|
+
value: contextValue,
|
|
33
|
+
children: children
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
const useScrollContext = () => {
|
|
37
|
+
const context = useContext(HorizontalScrollContext);
|
|
38
|
+
if (!context) {
|
|
39
|
+
throw new Error('useScrollContext must be used within a HorizontalScrollProvider');
|
|
40
|
+
}
|
|
41
|
+
return context;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export { HorizontalScrollProvider, useScrollContext };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import noop from 'lodash/noop';
|
|
2
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
// The hook that manages the scroll position logic and provides scroll control functions
|
|
5
|
+
const useScrollState = (scrollableElement, scrollStep = 50) => {
|
|
6
|
+
const [scrollPosition, setScrollPosition] = useState('none');
|
|
7
|
+
const scrollAnimationFrame = useRef(null);
|
|
8
|
+
const updateScrollState = useCallback(() => {
|
|
9
|
+
if (!scrollableElement) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const {
|
|
13
|
+
scrollLeft,
|
|
14
|
+
scrollWidth,
|
|
15
|
+
clientWidth
|
|
16
|
+
} = scrollableElement;
|
|
17
|
+
if (scrollWidth <= clientWidth) {
|
|
18
|
+
setScrollPosition('none');
|
|
19
|
+
} else if (scrollLeft === 0) {
|
|
20
|
+
setScrollPosition('start');
|
|
21
|
+
} else if (scrollLeft + clientWidth >= scrollWidth) {
|
|
22
|
+
setScrollPosition('end');
|
|
23
|
+
} else {
|
|
24
|
+
setScrollPosition('intermediate');
|
|
25
|
+
}
|
|
26
|
+
}, [scrollableElement]);
|
|
27
|
+
const handleScroll = useCallback(() => {
|
|
28
|
+
if (scrollAnimationFrame.current !== null) {
|
|
29
|
+
cancelAnimationFrame(scrollAnimationFrame.current);
|
|
30
|
+
}
|
|
31
|
+
scrollAnimationFrame.current = requestAnimationFrame(updateScrollState);
|
|
32
|
+
}, [updateScrollState]);
|
|
33
|
+
const scrollLeft = useCallback(() => {
|
|
34
|
+
if (!scrollableElement) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
scrollableElement.scrollBy({
|
|
38
|
+
left: -scrollStep,
|
|
39
|
+
behavior: 'smooth'
|
|
40
|
+
});
|
|
41
|
+
}, [scrollStep, scrollableElement]);
|
|
42
|
+
const scrollRight = useCallback(() => {
|
|
43
|
+
if (!scrollableElement) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
scrollableElement.scrollBy({
|
|
47
|
+
left: scrollStep,
|
|
48
|
+
behavior: 'smooth'
|
|
49
|
+
});
|
|
50
|
+
}, [scrollStep, scrollableElement]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!scrollableElement) {
|
|
53
|
+
return noop;
|
|
54
|
+
}
|
|
55
|
+
const handleFocusIn = event => {
|
|
56
|
+
const {
|
|
57
|
+
target
|
|
58
|
+
} = event;
|
|
59
|
+
if (target instanceof HTMLElement && scrollableElement.contains(target)) {
|
|
60
|
+
target.scrollIntoView({
|
|
61
|
+
behavior: 'smooth',
|
|
62
|
+
inline: 'nearest'
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
scrollableElement.addEventListener('focusin', handleFocusIn);
|
|
67
|
+
scrollableElement.addEventListener('scroll', handleScroll);
|
|
68
|
+
updateScrollState();
|
|
69
|
+
return () => {
|
|
70
|
+
scrollableElement.removeEventListener('focusin', handleFocusIn);
|
|
71
|
+
scrollableElement.removeEventListener('scroll', handleScroll);
|
|
72
|
+
if (scrollAnimationFrame.current !== null) {
|
|
73
|
+
cancelAnimationFrame(scrollAnimationFrame.current);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}, [scrollableElement, handleScroll, updateScrollState]);
|
|
77
|
+
return {
|
|
78
|
+
scrollPosition,
|
|
79
|
+
scrollLeft,
|
|
80
|
+
scrollRight
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export { useScrollState };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@box/blueprint-web",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.3.1",
|
|
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": "3ca9db9ebc750eda29f3a1c71326fedd318dde5c",
|
|
67
67
|
"module": "lib-esm/index.js",
|
|
68
68
|
"main": "lib-esm/index.js",
|
|
69
69
|
"exports": {
|