@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.
@@ -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--b843f{
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--b843f{
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--b843f{
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--b843f{
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--b843f[data-disabled]{
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--b843f:not([data-disabled]):focus-visible{
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--b843f:not([data-disabled]):hover{
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--b843f[data-state=on]{
6880
+ .bp_toolbar_module_toolbarToggle--b6b06[data-state=on]{
6855
6881
  background:var(--surface-toggle-surface-pressed);
6856
6882
  }
6857
- .bp_toolbar_module_toolbarToggle--b843f[data-state=on] svg *{
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--b843f[data-state=on]:not([data-disabled]):hover{
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--b843f.bp_toolbar_module_invertCaret--b843f{
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--b843f{
6895
+ .bp_toolbar_module_triggerButtonSelectedWithColor--b6b06{
6870
6896
  --trigger-button-hover-opacity:0.7;
6871
6897
  }
6872
- .bp_toolbar_module_triggerButtonSelectedWithColor--b843f[data-state=on] .bp_toolbar_module_dropdownIndicator--b843f path{
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--b843f[data-state=on]:hover{
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--b843f{
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--b843f{
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--b843f:hover{
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--b843f:active{
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--b843f[aria-checked=true]{
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--b843f[aria-checked=true]:hover{
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--b843f[aria-checked=true]:active{
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("@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--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
- 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,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,5 @@
1
+ export declare const useScrollState: (scrollableElement: HTMLDivElement | null, scrollStep?: number) => {
2
+ scrollPosition: "start" | "end" | "intermediate" | "none";
3
+ scrollLeft: () => void;
4
+ scrollRight: () => void;
5
+ };
@@ -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.2.2",
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.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": "5bfcdbead36b50a11388503a8c0decf508b91878",
66
+ "gitHead": "3ca9db9ebc750eda29f3a1c71326fedd318dde5c",
67
67
  "module": "lib-esm/index.js",
68
68
  "main": "lib-esm/index.js",
69
69
  "exports": {