@box/blueprint-web 9.2.1 → 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.
@@ -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--b843f{
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--b843f{
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--b843f{
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--b843f{
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--b843f[data-disabled]{
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--b843f:not([data-disabled]):focus-visible{
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--b843f:not([data-disabled]):hover{
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--b843f[data-state=on]{
6881
+ .bp_toolbar_module_toolbarToggle--eeb80[data-state=on]{
6855
6882
  background:var(--surface-toggle-surface-pressed);
6856
6883
  }
6857
- .bp_toolbar_module_toolbarToggle--b843f[data-state=on] svg *{
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--b843f[data-state=on]:not([data-disabled]):hover{
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--b843f.bp_toolbar_module_invertCaret--b843f{
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--b843f{
6896
+ .bp_toolbar_module_triggerButtonSelectedWithColor--eeb80{
6870
6897
  --trigger-button-hover-opacity:0.7;
6871
6898
  }
6872
- .bp_toolbar_module_triggerButtonSelectedWithColor--b843f[data-state=on] .bp_toolbar_module_dropdownIndicator--b843f path{
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--b843f[data-state=on]:hover{
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--b843f{
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--b843f{
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--b843f:hover{
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--b843f:active{
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--b843f[aria-checked=true]{
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--b843f[aria-checked=true]:hover{
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--b843f[aria-checked=true]:active{
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
  }
@@ -43,7 +43,7 @@ const SelectMenuGridOption = /*#__PURE__*/forwardRef((props, forwardedRef) => {
43
43
  const isButtonDragged = isWithLabel && !isButtonDisabled && props.draggable && props.dragging;
44
44
  const shouldShowTooltip = !isButtonDisabled && !isWithLabel;
45
45
  const ariaLabel = !isWithLabel ? props['aria-label'] : props.label;
46
- const buttonProps = isWithLabel ? omit(restProps, ['colorVariant', 'icon']) : restProps;
46
+ const buttonProps = isWithLabel ? omit(restProps, ['colorVariant', 'icon', 'dragging', 'label']) : restProps;
47
47
  // TODO: [DSYS-764] use IconButton instead of AriakitButton
48
48
  const button = jsx(CompositeItem, {
49
49
  render: jsxs(Button, {
@@ -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("@radix-ui/react-toolbar").ToolbarProps & import("react").RefAttributes<HTMLDivElement>>;
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 * as ToolbarPrimitive from '@radix-ui/react-toolbar';
2
- export declare const ToolbarRoot: import("react").ForwardRefExoticComponent<ToolbarPrimitive.ToolbarProps & import("react").RefAttributes<HTMLDivElement>>;
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--b843f","separator":"bp_toolbar_module_separator--b843f","toggleGroup":"bp_toolbar_module_toggleGroup--b843f","toolbarItem":"bp_toolbar_module_toolbarItem--b843f","toolbarToggle":"bp_toolbar_module_toolbarToggle--b843f","dropdownIndicator":"bp_toolbar_module_dropdownIndicator--b843f","invertCaret":"bp_toolbar_module_invertCaret--b843f","triggerButtonSelectedWithColor":"bp_toolbar_module_triggerButtonSelectedWithColor--b843f","toolbarIcon":"bp_toolbar_module_toolbarIcon--b843f","toolbarTextToggleItem":"bp_toolbar_module_toolbarTextToggleItem--b843f"};
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
- export type ToolbarProps = ToolbarPrimitiveProps;
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.2.1",
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.0",
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.7.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": "94a36651838769cd08fd62a9ab1b41e0b4cb5280",
66
+ "gitHead": "d9e9463682eab8c3cf54e738849001a2095220e8",
67
67
  "module": "lib-esm/index.js",
68
68
  "main": "lib-esm/index.js",
69
69
  "exports": {